载入中...
搜索中...
未找到
SquirrelBinding.cpp
浏览该文件的文档.
2
3#include "common/Assert.h"
4
5#include <algorithm>
6#include <cctype>
7#include <cmath>
8#include <cstdint>
9#include <limits>
10#include <string_view>
11#include <vector>
12
13namespace eve::script {
14namespace {
15
16struct ConversionContext {
17 const SquirrelValueOptions& options;
18 std::size_t elements = 0;
19 std::vector<const void*> activeContainers;
20};
21
22Diagnostic conversionError(const ConversionContext& context, DiagnosticCode code, std::string message,
23 std::string path) {
24 return Diagnostic::error(code, message, path, {}, context.options.source);
25}
26
27std::string childPath(std::string_view parent, std::size_t index) {
28 return std::string(parent) + "[" + std::to_string(index) + "]";
29}
30
31std::string memberPath(std::string_view parent, std::string_view key) {
32 if (!key.empty() && (std::isalpha(static_cast<unsigned char>(key.front())) != 0 || key.front() == '_') &&
33 std::all_of(key.begin() + 1, key.end(),
34 [](unsigned char value) { return std::isalnum(value) != 0 || value == '_'; })) {
35 return std::string(parent) + "." + std::string(key);
36 }
37 return std::string(parent) + "[\"" + std::string(key) + "\"]";
38}
39
40bool takeObject(HSQUIRRELVM vm, SQInteger index, HSQOBJECT& object, Diagnostic& diagnostic,
41 const ConversionContext& context, const std::string& path) {
42 if (!vm || SQ_FAILED(sq_getstackobj(vm, index, &object))) {
43 diagnostic =
44 conversionError(context, DiagnosticCode::InvalidArgument, "Squirrel stack value is unavailable", path);
45 return false;
46 }
47 return true;
48}
49
50bool enterContainer(const HSQOBJECT& object, ConversionContext& context, Diagnostic& diagnostic,
51 const std::string& path) {
52 if (context.activeContainers.size() >= context.options.maxDepth) {
53 diagnostic = conversionError(context, DiagnosticCode::InvalidArgument,
54 "Squirrel value exceeds maximum conversion depth", path);
55 return false;
56 }
57 const void* identity = nullptr;
58 if (object._type == OT_ARRAY)
59 identity = static_cast<const void*>(object._unVal.pArray);
60 else if (object._type == OT_TABLE)
61 identity = static_cast<const void*>(object._unVal.pTable);
62 if (identity && std::find(context.activeContainers.begin(), context.activeContainers.end(), identity) !=
63 context.activeContainers.end()) {
64 diagnostic = conversionError(context, DiagnosticCode::InvalidArgument,
65 "cyclic Squirrel container cannot become an owning Value", path);
66 return false;
67 }
68 if (identity) context.activeContainers.push_back(identity);
69 return true;
70}
71
72void leaveContainer(const HSQOBJECT& object, ConversionContext& context) {
73 if ((object._type != OT_ARRAY && object._type != OT_TABLE) || context.activeContainers.empty()) return;
74 context.activeContainers.pop_back();
75}
76
77bool countElement(ConversionContext& context, Diagnostic& diagnostic, const std::string& path) {
78 if (context.elements >= context.options.maxElements) {
79 diagnostic = conversionError(context, DiagnosticCode::InvalidArgument,
80 "Squirrel value exceeds maximum element count", path);
81 return false;
82 }
83 ++context.elements;
84 return true;
85}
86
87bool convertAt(HSQUIRRELVM vm, SQInteger index, ConversionContext& context, const std::string& path, Value& output,
88 Diagnostic& diagnostic) {
89 if (!countElement(context, diagnostic, path)) return false;
90
91 const SQObjectType type = sq_gettype(vm, index);
92 switch (type) {
93 case OT_NULL: output = Value(); return true;
94 case OT_BOOL: {
95 SQBool value = SQFalse;
96 if (SQ_FAILED(sq_getbool(vm, index, &value))) {
97 diagnostic =
98 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel boolean", path);
99 return false;
100 }
101 output = Value(value != SQFalse);
102 return true;
103 }
104 case OT_INTEGER: {
105 SQInteger value = 0;
106 if (SQ_FAILED(sq_getinteger(vm, index, &value))) {
107 diagnostic =
108 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel integer", path);
109 return false;
110 }
111 output = Value(static_cast<std::int64_t>(value));
112 return true;
113 }
114 case OT_FLOAT: {
115 SQFloat value = 0;
116 if (SQ_FAILED(sq_getfloat(vm, index, &value))) {
117 diagnostic =
118 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel number", path);
119 return false;
120 }
121 const double converted = static_cast<double>(value);
122 if (!std::isfinite(converted)) {
123 diagnostic = conversionError(context, DiagnosticCode::InvalidArgument,
124 "non-finite Squirrel number is not representable", path);
125 return false;
126 }
127 output = Value(converted);
128 return true;
129 }
130 case OT_STRING: {
131 const SQChar* value = nullptr;
132 SQInteger size = 0;
133 if (SQ_FAILED(sq_getstringandsize(vm, index, &value, &size)) || !value || size < 0) {
134 diagnostic =
135 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel string", path);
136 return false;
137 }
138 output = Value(std::string(value, static_cast<std::size_t>(size)));
139 return true;
140 }
141 case OT_ARRAY:
142 case OT_TABLE: break;
143 default:
144 diagnostic = conversionError(context, DiagnosticCode::Unsupported,
145 "Squirrel value kind is not part of eve::Value", path);
146 return false;
147 }
148
149 HSQOBJECT object{};
150 if (!takeObject(vm, index, object, diagnostic, context, path)) return false;
151 if (!enterContainer(object, context, diagnostic, path)) return false;
152
153 bool converted = true;
154 if (type == OT_ARRAY) {
155 const SQInteger size = sq_getsize(vm, index);
156 if (size < 0) {
157 diagnostic =
158 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel array size", path);
159 converted = false;
160 } else {
162 values.reserve(static_cast<std::size_t>(size));
163 const SQInteger absolute = index > 0 ? index : sq_gettop(vm) + index + 1;
164 for (SQInteger i = 0; converted && i < size; ++i) {
165 sq_pushinteger(vm, i);
166 if (SQ_FAILED(sq_get(vm, absolute))) {
167 diagnostic =
168 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel array element",
169 childPath(path, static_cast<std::size_t>(i)));
170 converted = false;
171 break;
172 }
174 converted =
175 convertAt(vm, -1, context, childPath(path, static_cast<std::size_t>(i)), element, diagnostic);
176 sq_pop(vm, 1);
177 if (converted) values.push_back(std::move(element));
178 }
179 if (converted) output = Value(std::move(values));
180 }
181 } else {
183 const SQInteger absolute = index > 0 ? index : sq_gettop(vm) + index + 1;
184 sq_pushnull(vm);
185 while (converted && SQ_SUCCEEDED(sq_next(vm, absolute))) {
186 if (sq_gettype(vm, -2) != OT_STRING) {
187 diagnostic = conversionError(context, DiagnosticCode::Unsupported,
188 "Squirrel table keys must be strings", path + "[<non-string-key>]");
189 converted = false;
190 sq_pop(vm, 2);
191 break;
192 }
193 const SQChar* key = nullptr;
194 SQInteger keySize = 0;
195 if (SQ_FAILED(sq_getstringandsize(vm, -2, &key, &keySize)) || !key || keySize < 0) {
196 diagnostic =
197 conversionError(context, DiagnosticCode::ParseError, "failed to read Squirrel table key", path);
198 converted = false;
199 sq_pop(vm, 2);
200 break;
201 }
202 const std::string keyText(key, static_cast<std::size_t>(keySize));
203 const std::string field = memberPath(path, keyText);
204 Value value;
205 converted = convertAt(vm, -1, context, field, value, diagnostic);
206 sq_pop(vm, 2);
207 if (converted) fields.emplace(keyText, std::move(value));
208 }
209 sq_pop(vm, 1); // iterator
210 if (converted) output = Value(std::move(fields));
211 }
212 leaveContainer(object, context);
213 return converted;
214}
215
216bool validateValue(const Value& value, const SquirrelValueOptions& options, std::size_t depth, std::size_t& elements,
217 const std::string& path, Diagnostic& diagnostic) {
219 diagnostic = Diagnostic::error(DiagnosticCode::InvalidArgument, "Value exceeds maximum Squirrel element count",
220 path, {}, options.source);
221 return false;
222 }
223 ++elements;
224 if (value.isDouble() && !std::isfinite(value.asDouble())) {
225 diagnostic = Diagnostic::error(DiagnosticCode::InvalidArgument, "non-finite Value cannot be pushed to Squirrel",
226 path, {}, options.source);
227 return false;
228 }
229 if (!value.isArray() && !value.isObject()) return true;
230 if (depth >= options.maxDepth) {
232 "Value exceeds maximum Squirrel conversion depth", path, {}, options.source);
233 return false;
234 }
235 if (value.isArray()) {
236 for (std::size_t index = 0; index < value.arraySize(); ++index) {
237 if (!validateValue(value.at(index), options, depth + 1, elements, childPath(path, index), diagnostic))
238 return false;
239 }
240 return true;
241 }
242 for (const std::string& key : value.keys()) {
243 const Value* member = value.find(key);
244 if (!member) continue;
245 if (!validateValue(*member, options, depth + 1, elements, memberPath(path, key), diagnostic)) return false;
246 }
247 return true;
248}
249
250bool pushImpl(HSQUIRRELVM vm, const Value& value, const SquirrelValueOptions& options, std::size_t depth,
251 const std::string& path, Diagnostic& diagnostic) {
252 if (value.isNull()) {
253 sq_pushnull(vm);
254 return true;
255 }
256 if (value.isBool()) {
257 sq_pushbool(vm, value.asBool() ? SQTrue : SQFalse);
258 return true;
259 }
260 if (value.isInt64()) {
261 const auto integer = value.asInt();
262 if (integer < static_cast<std::int64_t>(std::numeric_limits<SQInteger>::min()) ||
263 integer > static_cast<std::int64_t>(std::numeric_limits<SQInteger>::max())) {
264 diagnostic = Diagnostic::error(DiagnosticCode::InvalidArgument, "Int64 value does not fit Squirrel integer",
265 path, {}, options.source);
266 return false;
267 }
268 sq_pushinteger(vm, static_cast<SQInteger>(integer));
269 return true;
270 }
271 if (value.isDouble()) {
272 sq_pushfloat(vm, static_cast<SQFloat>(value.asDouble()));
273 return true;
274 }
275 if (value.isString()) {
276 const std::string& string = value.asString();
277 sq_pushstring(vm, string.c_str(), static_cast<SQInteger>(string.size()));
278 return true;
279 }
280 if (depth >= options.maxDepth) {
282 "Value exceeds maximum Squirrel conversion depth", path, {}, options.source);
283 return false;
284 }
285 if (value.isArray()) {
286 sq_newarray(vm, 0);
287 for (std::size_t index = 0; index < value.arraySize(); ++index) {
288 if (!pushImpl(vm, value.at(index), options, depth + 1, childPath(path, index), diagnostic)) return false;
289 if (SQ_FAILED(sq_arrayappend(vm, -2))) {
290 diagnostic =
291 Diagnostic::error(DiagnosticCode::Failed, "failed to append Value array element to Squirrel",
292 childPath(path, index), {}, options.source);
293 return false;
294 }
295 }
296 return true;
297 }
298 sq_newtable(vm);
299 for (const std::string& key : value.keys()) {
300 const Value* member = value.find(key);
301 if (!member) continue;
302 sq_pushstring(vm, key.c_str(), static_cast<SQInteger>(key.size()));
303 if (!pushImpl(vm, *member, options, depth + 1, memberPath(path, key), diagnostic)) return false;
304 if (SQ_FAILED(sq_newslot(vm, -3, SQFalse))) {
305 diagnostic = Diagnostic::error(DiagnosticCode::Failed, "failed to add Value object member to Squirrel",
306 memberPath(path, key), {}, options.source);
307 return false;
308 }
309 }
310 return true;
311}
312
313ssq::Object objectFromTop(HSQUIRRELVM vm, SQInteger top) {
314 ssq::Object output(vm);
315 if (SQ_SUCCEEDED(sq_getstackobj(vm, -1, &output.getRaw()))) {
316 sq_addref(vm, &output.getRaw());
317 }
318 sq_settop(vm, top);
319 return output;
320}
321
322ssq::Object projectValueObject(HSQUIRRELVM vm, const Value& value) {
323 if (!vm) return ssq::Object();
324 const SQInteger top = sq_gettop(vm);
325 auto pushed = pushValue(vm, value);
326 if (!pushed.ok()) {
327 pushed.ignore("Result projection could not push its value payload");
328 sq_settop(vm, top);
329 return ssq::Object(vm);
330 }
331 return objectFromTop(vm, top);
332}
333
334bool readBoolField(const ssq::Object& object, const char* name) {
335 if (object.getType() != ssq::Type::TABLE) return false;
336 const ssq::Object field = object.find(name);
337 return field.getType() == ssq::Type::BOOL && field.toBool();
338}
339
340} // namespace
341
343 if (!vm)
344 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Squirrel VM must not be null",
345 "$", {}, options.source));
346 const SQInteger top = sq_gettop(vm);
347 ConversionContext context{options, 0, {}};
349 Diagnostic diagnostic;
350 const bool converted = convertAt(vm, index, context, "$", output, diagnostic);
351 sq_settop(vm, top);
352 if (!converted) return Result<Value>::failure(std::move(diagnostic));
353 return Result<Value>::success(std::move(output));
354}
355
357 const HSQUIRRELVM vm = object.getHandle();
358 if (!vm || object.isEmpty())
360 Diagnostic::error(DiagnosticCode::InvalidArgument, "Squirrel object is empty", "$", {}, options.source));
361 const SQInteger top = sq_gettop(vm);
362 sq_pushobject(vm, object.getRaw());
363 auto result = valueFromSquirrel(vm, -1, options);
364 // valueFromSquirrel restores to the height observed after this push.
365 sq_settop(vm, top);
366 return result;
367}
368
370 if (!vm)
371 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Squirrel VM must not be null",
372 "$", {}, options.source));
373 std::size_t elements = 0;
374 Diagnostic diagnostic;
375 if (!validateValue(value, options, 0, elements, "$", diagnostic))
376 return Result<void>::failure(std::move(diagnostic));
377
378 const SQInteger top = sq_gettop(vm);
379 if (!pushImpl(vm, value, options, 0, "$", diagnostic)) {
380 sq_settop(vm, top);
381 return Result<void>::failure(std::move(diagnostic));
382 }
383 return Result<void>::success();
384}
385
386ssq::Table projectDiagnostic(HSQUIRRELVM vm, const Diagnostic& diagnostic) {
387 ssq::Table result(vm);
388 result.set("code", std::string(diagnosticCodeName(diagnostic.code())));
389 result.set("severity", [&diagnostic] {
390 switch (diagnostic.severity()) {
391 case Severity::Info: return std::string("info");
392 case Severity::Warning: return std::string("warning");
393 case Severity::Error: return std::string("error");
394 case Severity::Fatal: return std::string("fatal");
395 }
396 return std::string("unknown");
397 }());
398 result.set("message", diagnostic.message());
399 result.set("path", diagnostic.path());
400 result.set("source", diagnostic.source());
401 ssq::Table details(vm);
402 for (const auto& [key, value] : diagnostic.details()) details.set(key.c_str(), value);
403 result.set("details", details);
404 return result;
405}
406
407namespace {
408
410ssq::Array projectDiagnostics(HSQUIRRELVM vm, const Status& status) {
411 ssq::Array diagnostics(vm);
412 for (const Diagnostic& diagnostic : status.diagnostics()) diagnostics.push(projectDiagnostic(vm, diagnostic));
413 return diagnostics;
414}
415
417ssq::Table buildStatusTable(HSQUIRRELVM vm, const Status& status, const ssq::Array& diagnostics) {
418 ssq::Table result(vm);
419 result.set("ok", status.isSuccess());
420 result.set("code", std::string(statusCodeName(status.code())));
421 result.set("summary", status.describe());
422 result.set("diagnostics", diagnostics);
423 result.set("diagnosticCount", static_cast<std::int64_t>(status.diagnostics().size()));
424 return result;
425}
426
433ssq::Table buildResultTable(HSQUIRRELVM vm, const Status& status, bool hasValue) {
434 ssq::Table result(vm);
435 result.set("ok", status.isSuccess());
436 result.set("code", std::string(statusCodeName(status.code())));
437 result.set("hasValue", hasValue);
438 result.set("checked", true);
439 result.set("ignored", false);
440 result.set("ignoreReason", std::string{});
441 ssq::Array diagnostics = projectDiagnostics(vm, status);
442 result.set("status", buildStatusTable(vm, status, diagnostics));
443 result.set("diagnostics", diagnostics);
444 if (!hasValue) result.set("value", ssq::Object(vm));
445 return result;
446}
447
448} // namespace
449
451 return buildStatusTable(vm, status, projectDiagnostics(vm, status));
452}
453
454ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status& status) { return buildResultTable(vm, status, false); }
455
457 ssq::Table result = buildResultTable(vm, status, true);
458 result.set("value", projectValueObject(vm, value));
459 return result;
460}
461
462ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status& status, ssq::Object value) {
463 ssq::Table result = buildResultTable(vm, status, true);
464 result.set("value", std::move(value));
465 return result;
466}
467
468void markResultHasValue(ssq::Table& result) { result.set("hasValue", true); }
469
471 const Status status = result.status();
473}
474
475bool ignoreResult(const ssq::Object& result, const std::string& reason) {
476 if (result.getType() != ssq::Type::TABLE || reason.empty()) return false;
477 HSQUIRRELVM vm = result.getHandle();
478 if (!vm) return false;
479 const SQInteger top = sq_gettop(vm);
480 sq_pushobject(vm, result.getRaw());
481 sq_pushstring(vm, "ignored", -1);
482 sq_pushbool(vm, SQTrue);
483 if (SQ_FAILED(sq_newslot(vm, -3, SQFalse))) {
484 sq_settop(vm, top);
485 return false;
486 }
487 sq_pushstring(vm, "ignoreReason", -1);
488 sq_pushstring(vm, reason.c_str(), static_cast<SQInteger>(reason.size()));
489 if (SQ_FAILED(sq_newslot(vm, -3, SQFalse))) {
490 sq_settop(vm, top);
491 return false;
492 }
493 // A result table is already consumed by C++; this flag makes the script
494 // intent explicit without pretending that ignored means successful.
495 sq_pushstring(vm, "checked", -1);
496 sq_pushbool(vm, SQTrue);
497 if (SQ_FAILED(sq_newslot(vm, -3, SQFalse))) {
498 sq_settop(vm, top);
499 return false;
500 }
501 sq_settop(vm, top);
502 return true;
503}
504
505void exposeResultBindings(ssq::Table& eveTable) {
506 ssq::Table result = eveTable.addTable("result");
507 result.addFunc("ignore", [](ssq::Object value, const std::string& reason) { return ignoreResult(value, reason); });
508 result.addFunc("isChecked", [](ssq::Object value) { return readBoolField(value, "checked"); });
509 result.addFunc("isIgnored", [](ssq::Object value) { return readBoolField(value, "ignored"); });
510}
511
512} // namespace eve::script
double value
SQInteger top
EVEngine assertion entry point, backed by zeroerr.
std::string output
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
std::map< std::string, Var > values
std::string message
DiagnosticCode code
std::uint32_t key
wgpu::PopErrorScopeStatus status
std::int32_t parent
std::string name
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string element
std::vector< const void * > activeContainers
std::size_t elements
const SquirrelValueOptions & options
The single Squirrel projection for common Result, Status and Value.
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
uint32_t index
std::uint32_t depth
const VegetationPresetContext & context
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
DiagnosticCode code() const noexcept
Stable machine-readable code.
Definition Diagnostic.h:143
const std::string & path() const noexcept
Optional field, property, URI, or logical path.
Definition Diagnostic.h:149
Severity severity() const noexcept
Severity for logging and presentation.
Definition Diagnostic.h:145
const std::string & message() const noexcept
Human-readable explanation.
Definition Diagnostic.h:147
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
const DiagnosticDetails & details() const noexcept
Structured context in insertion order.
Definition Diagnostic.h:151
const std::string & source() const noexcept
Optional producer or subsystem that emitted the diagnostic.
Definition Diagnostic.h:153
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
eve::Diagnostic Diagnostic
const EditorValue * field(const EditorValue &value, const char *name)
bool ignoreResult(const ssq::Object &result, const std::string &reason)
Mark a projected Result table as intentionally ignored by script.
ssq::Table projectDiagnostic(HSQUIRRELVM vm, const Diagnostic &diagnostic)
Project one Diagnostic using the common script table schema.
Result< void > pushValue(HSQUIRRELVM vm, const Value &value, const SquirrelValueOptions &options)
Push a canonical Value into the active Squirrel VM.
void markResultHasValue(ssq::Table &result)
Declare that a projected table received a payload the caller attached itself.
ssq::Table projectStatus(HSQUIRRELVM vm, const Status &status)
Project one Status using the common script table schema.
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
void exposeResultBindings(ssq::Table &eveTable)
Expose the common script helpers under eve.result.
Result< Value > valueFromSquirrel(HSQUIRRELVM vm, SQInteger index, const SquirrelValueOptions &options)
Convert one Squirrel value into the canonical owning Value tree.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
constexpr std::string_view statusCodeName(StatusCode code) noexcept
Return the stable protocol spelling of a status code.
Definition Status.h:45
constexpr std::string_view diagnosticCodeName(DiagnosticCode code) noexcept
Return a stable, compact spelling useful for logs and diagnostics.
Definition Diagnostic.h:176
Limits and provenance used by the Squirrel ↔ Value adapter.