载入中...
搜索中...
未找到
ScriptError.cpp
浏览该文件的文档.
2
3#include <simplesquirrel/simplesquirrel.hpp>
4
5#include <cctype>
6#include <sstream>
7#include <unordered_map>
8#include <utility>
9
10namespace eve::script {
11namespace {
12
13thread_local std::unordered_map<HSQUIRRELVM, ScriptErrorContext> g_last_errors;
14
15const char* typeName(SQObjectType type) {
16 switch (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";
34 }
35}
36
37// Formats the error value at stack slot 2 (the argument Squirrel passes to the
38// runtime error handler). Containers are named by type instead of being
39// stringified: sq_tostring can invoke _tostring metamethods, which could raise
40// again and recurse into the error handler.
41std::string errorValueString(HSQUIRRELVM vm) {
42 if (!vm) return "unknown error";
43 switch (sq_gettype(vm, 2)) {
44 case OT_NULL:
45 return "null";
46 case OT_BOOL: {
47 SQBool value = SQFalse;
48 if (SQ_SUCCEEDED(sq_getbool(vm, 2, &value)))
49 return value ? "true" : "false";
50 break;
51 }
52 case OT_INTEGER: {
53 SQInteger value = 0;
54 if (SQ_SUCCEEDED(sq_getinteger(vm, 2, &value)))
55 return std::to_string(static_cast<long long>(value));
56 break;
57 }
58 case OT_FLOAT: {
59 SQFloat value = 0;
60 if (SQ_SUCCEEDED(sq_getfloat(vm, 2, &value))) {
61 std::ostringstream out;
62 out << value;
63 return out.str();
64 }
65 break;
66 }
67 case OT_STRING: {
68 const SQChar* value = nullptr;
69 if (SQ_SUCCEEDED(sq_getstring(vm, 2, &value)) && value) return value;
70 break;
71 }
72 default:
73 break;
74 }
75 return std::string("error value of type ") + typeName(sq_gettype(vm, 2));
76}
77
78} // namespace
79
82 if (!vm) return ctx;
83 ctx.message = errorValueString(vm);
84 // Level 0 is the error-handler closure itself; level 1 is the throwing
85 // script frame. Walk outward until Squirrel runs out of frames.
86 for (int level = 1; level <= 64; ++level) {
87 SQStackInfos si;
88 if (SQ_FAILED(sq_stackinfos(vm, level, &si))) break;
89 ScriptFrame frame;
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));
94 }
95 if (!ctx.stack.empty()) {
96 ctx.source = ctx.stack.front().source;
97 ctx.function = ctx.stack.front().function;
98 ctx.line = ctx.stack.front().line;
99 }
100 return ctx;
101}
102
105 if (!vm) return ctx;
106 // ssq::VM stores the last compile error on the VM (foreign pointer) and
107 // exposes it through getLastCompileException(). Only call this after a
108 // failed compile, otherwise the stored exception may not exist yet.
109 auto* machine = reinterpret_cast<ssq::VM*>(sq_getforeignptr(vm));
110 if (!machine) return ctx;
111 try {
112 ctx.message = machine->getLastCompileException().what();
113 } catch (...) {
114 return ctx;
115 }
116 parseCompileError(ctx.message, &ctx.source, &ctx.line, &ctx.column, &ctx.message);
117 return ctx;
118}
119
120bool parseCompileError(const std::string& text, std::string* source, int* line,
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);
125
126 // Layout: <source>:<line>:<column> <description>. The source may itself
127 // contain ':' (Windows drive letters), so scan for the first ':' after
128 // which "<digits>:<digits> " follows — that marks the line/column pair.
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])))
134 ++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])))
139 ++columnEnd;
140 if (columnEnd == lineEnd + 1 || columnEnd >= rest.size() ||
141 rest[columnEnd] != ' ')
142 continue;
143
144 try {
145 if (source) *source = rest.substr(0, i);
146 if (line)
147 *line = std::stoi(rest.substr(i + 1, lineEnd - i - 1));
148 if (column)
149 *column = std::stoi(rest.substr(lineEnd + 1, columnEnd - lineEnd - 1));
150 if (message) *message = rest.substr(columnEnd + 1);
151 } catch (...) {
152 return false;
153 }
154 return true;
155 }
156 return false;
157}
158
159std::string sourceLineText(const std::string& sourceText, int line) {
160 if (line <= 0 || sourceText.empty()) return {};
161 size_t start = 0;
162 for (int current = 1; current < line; ++current) {
163 const size_t newline = sourceText.find('\n', start);
164 if (newline == std::string::npos) return {};
165 start = newline + 1;
166 }
167 size_t end = sourceText.find('\n', start);
168 std::string text =
169 sourceText.substr(start, end == std::string::npos ? std::string::npos
170 : end - start);
171 if (!text.empty() && text.back() == '\r') text.pop_back();
172 return text;
173}
174
175std::string formatStackTrace(const std::vector<ScriptFrame>& frames) {
176 std::ostringstream out;
177 for (const ScriptFrame& frame : frames) {
178 out << (frame.source.empty() ? "<unknown>" : frame.source) << ':';
179 if (frame.line > 0)
180 out << frame.line;
181 else
182 out << '?';
183 if (!frame.function.empty()) out << " in " << frame.function;
184 out << '\n';
185 }
186 return out.str();
187}
188
189std::string formatScriptError(const ScriptErrorContext& ctx) {
190 std::ostringstream out;
191 if (!ctx.source.empty()) {
192 out << ctx.source;
193 if (ctx.line > 0) out << ':' << ctx.line;
194 out << " (" << (ctx.function.empty() ? "<anonymous>" : ctx.function)
195 << "): " << ctx.message;
196 } else {
197 out << ctx.message;
198 }
199 if (!ctx.hint.empty()) out << "\n " << ctx.hint;
200 if (!ctx.stack.empty()) out << "\nStack:\n" << formatStackTrace(ctx.stack);
201 std::string result = out.str();
202 if (!result.empty() && result.back() == '\n') result.pop_back();
203 return result;
204}
205
207 if (!vm) return;
208 g_last_errors[vm] = std::move(ctx);
209}
210
212 if (!vm) return {};
213 auto found = g_last_errors.find(vm);
214 if (found == g_last_errors.end()) return {};
215 ScriptErrorContext ctx = std::move(found->second);
216 g_last_errors.erase(found);
217 return ctx;
218}
219
221 if (!vm) return nullptr;
222 auto found = g_last_errors.find(vm);
223 return found == g_last_errors.end() ? nullptr : &found->second;
224}
225
227 if (!vm) return;
228 g_last_errors.erase(vm);
229}
230
231} // namespace eve::script
struct SQVM * HSQUIRRELVM
int line
std::string value
HSQUIRRELVM vm
Definition ECS.cpp:20
std::string type
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.
Definition ScriptError.h:25
int line
1-based throw-site line; -1 when unknown.
Definition ScriptError.h:29
std::string function
Function of the throwing frame.
Definition ScriptError.h:28
int column
1-based column (compile errors only).
Definition ScriptError.h:30
std::string hint
Optional source snippet shown under the message.
Definition ScriptError.h:31
std::string message
Raw error value ("kaboom", "42", ...).
Definition ScriptError.h:26
std::vector< ScriptFrame > stack
Call stack, innermost frame first.
Definition ScriptError.h:32
std::string source
Source of the throwing frame.
Definition ScriptError.h:27
One call-stack frame captured while the script error is still live.
Definition ScriptError.h:13
int line
1-based source line; -1 when unknown.
Definition ScriptError.h:16
std::string function
Function name, when Squirrel debug info knows it.
Definition ScriptError.h:15
std::string source
Source name of the frame ("main.nut", "buffer", ...).
Definition ScriptError.h:14