3#include <simplesquirrel/simplesquirrel.hpp>
7#include <unordered_map>
13thread_local std::unordered_map<HSQUIRRELVM, ScriptErrorContext> g_last_errors;
15const char* typeName(SQObjectType
type) {
17 case OT_NULL:
return "null";
18 case OT_INTEGER:
return "integer";
19 case OT_FLOAT:
return "float";
20 case OT_BOOL:
return "bool";
21 case OT_STRING:
return "string";
22 case OT_TABLE:
return "table";
23 case OT_ARRAY:
return "array";
24 case OT_USERDATA:
return "userdata";
25 case OT_CLOSURE:
return "closure";
26 case OT_NATIVECLOSURE:
return "native";
27 case OT_GENERATOR:
return "generator";
28 case OT_USERPOINTER:
return "userpointer";
29 case OT_THREAD:
return "thread";
30 case OT_CLASS:
return "class";
31 case OT_INSTANCE:
return "instance";
32 case OT_WEAKREF:
return "weakref";
33 default:
return "other";
42 if (!
vm)
return "unknown error";
43 switch (sq_gettype(
vm, 2)) {
47 SQBool
value = SQFalse;
48 if (SQ_SUCCEEDED(sq_getbool(
vm, 2, &
value)))
49 return value ?
"true" :
"false";
54 if (SQ_SUCCEEDED(sq_getinteger(
vm, 2, &
value)))
55 return std::to_string(
static_cast<long long>(
value));
60 if (SQ_SUCCEEDED(sq_getfloat(
vm, 2, &
value))) {
61 std::ostringstream out;
68 const SQChar*
value =
nullptr;
75 return std::string(
"error value of type ") + typeName(sq_gettype(
vm, 2));
86 for (
int level = 1; level <= 64; ++level) {
88 if (SQ_FAILED(sq_stackinfos(
vm, level, &si)))
break;
90 frame.
source = si.source ? si.source :
"";
91 frame.
function = si.funcname ? si.funcname :
"";
92 frame.
line =
static_cast<int>(si.line);
93 ctx.
stack.push_back(std::move(frame));
95 if (!ctx.
stack.empty()) {
109 auto* machine =
reinterpret_cast<ssq::VM*
>(sq_getforeignptr(
vm));
110 if (!machine)
return ctx;
112 ctx.
message = machine->getLastCompileException().what();
121 int* column, std::string* message) {
122 constexpr const char* kPrefix =
"Compile error at ";
123 if (text.rfind(kPrefix, 0) != 0)
return false;
124 const std::string rest = text.substr(
sizeof(kPrefix) - 1);
129 for (
size_t i = 0; i < rest.size(); ++i) {
130 if (rest[i] !=
':')
continue;
131 size_t lineEnd = i + 1;
132 while (lineEnd < rest.size() &&
133 std::isdigit(
static_cast<unsigned char>(rest[lineEnd])))
135 if (lineEnd == i + 1 || lineEnd >= rest.size() || rest[lineEnd] !=
':')
continue;
136 size_t columnEnd = lineEnd + 1;
137 while (columnEnd < rest.size() &&
138 std::isdigit(
static_cast<unsigned char>(rest[columnEnd])))
140 if (columnEnd == lineEnd + 1 || columnEnd >= rest.size() ||
141 rest[columnEnd] !=
' ')
145 if (source) *source = rest.substr(0, i);
147 *
line = std::stoi(rest.substr(i + 1, lineEnd - i - 1));
149 *column = std::stoi(rest.substr(lineEnd + 1, columnEnd - lineEnd - 1));
150 if (message) *message = rest.substr(columnEnd + 1);
160 if (
line <= 0 || sourceText.empty())
return {};
162 for (
int current = 1; current <
line; ++current) {
163 const size_t newline = sourceText.find(
'\n', start);
164 if (newline == std::string::npos)
return {};
167 size_t end = sourceText.find(
'\n', start);
169 sourceText.substr(start, end == std::string::npos ? std::string::npos
171 if (!text.empty() && text.back() ==
'\r') text.pop_back();
176 std::ostringstream out;
178 out << (frame.source.empty() ?
"<unknown>" : frame.source) <<
':';
183 if (!frame.function.empty()) out <<
" in " << frame.function;
190 std::ostringstream out;
191 if (!ctx.
source.empty()) {
193 if (ctx.
line > 0) out <<
':' << ctx.
line;
199 if (!ctx.
hint.empty()) out <<
"\n " << ctx.
hint;
201 std::string result = out.str();
202 if (!result.empty() && result.back() ==
'\n') result.pop_back();
208 g_last_errors[
vm] = std::move(ctx);
213 auto found = g_last_errors.find(
vm);
214 if (found == g_last_errors.end())
return {};
216 g_last_errors.erase(found);
221 if (!
vm)
return nullptr;
222 auto found = g_last_errors.find(
vm);
223 return found == g_last_errors.end() ? nullptr : &found->second;
228 g_last_errors.erase(
vm);
struct SQVM * HSQUIRRELVM
void clearLastScriptError(HSQUIRRELVM vm)
Drops any recorded error for a VM.
const ScriptErrorContext * peekLastScriptError(HSQUIRRELVM vm)
Peeks at the last recorded error for a VM without clearing it.
bool parseCompileError(const std::string &text, std::string *source, int *line, int *column, std::string *message)
Parses the ssq compile message "Compile error at source:line:column msg".
std::string formatStackTrace(const std::vector< ScriptFrame > &frames)
Formats just the call-stack portion of a context.
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
std::string sourceLineText(const std::string &sourceText, int line)
Extracts one 1-based line from script source text.
ScriptErrorContext takeLastScriptError(HSQUIRRELVM vm)
Consumes and clears the last recorded error for a VM.
void setLastScriptError(HSQUIRRELVM vm, ScriptErrorContext ctx)
Records the last error for a VM (thread-local, synchronous consumers).
ScriptErrorContext captureScriptError(HSQUIRRELVM vm)
Captures the pending runtime error from inside the Squirrel error handler.
Structured snapshot of a script error: message, throw site and stack.
int line
1-based throw-site line; -1 when unknown.
std::string function
Function of the throwing frame.
int column
1-based column (compile errors only).
std::string hint
Optional source snippet shown under the message.
std::string message
Raw error value ("kaboom", "42", ...).
std::vector< ScriptFrame > stack
Call stack, innermost frame first.
std::string source
Source of the throwing frame.
One call-stack frame captured while the script error is still live.
int line
1-based source line; -1 when unknown.
std::string function
Function name, when Squirrel debug info knows it.
std::string source
Source name of the frame ("main.nut", "buffer", ...).