7#include <simplesquirrel/vm.hpp>
22std::mutex compilerRegistryMutex;
23std::unordered_map<HSQUIRRELVM, ScriptCompiler*> compilerRegistry;
25std::string hashSource(std::string_view
source) {
26 uint64_t
hash = 1469598103934665603ull;
27 for (
const unsigned char c :
source) {
29 hash *= 1099511628211ull;
31 std::ostringstream out;
32 out << std::hex <<
hash;
36ScriptSourcePosition positionAt(std::string_view
source,
size_t offset) {
38 for (
size_t i = 0; i <
offset && i <
source.size(); ++i) {
49template <
typename Callback>
53 callback(*it,
static_cast<size_t>(it->position()));
56void appendUnique(std::vector<std::string>&
values, std::string
value) {
60std::string maskComments(std::string_view
source) {
61 std::string result(
source);
63 bool lineComment =
false;
64 bool blockComment =
false;
65 for (
size_t i = 0; i < result.size(); ++i) {
66 const char c = result[i];
67 const char next = i + 1 < result.size() ? result[i + 1] : 0;
73 }
else if (blockComment) {
74 if (
c ==
'*' && next ==
'/') {
75 result[i] = result[i + 1] =
' ';
78 }
else if (
c !=
'\n') {
81 }
else if (
quote != 0) {
86 }
else if (
c ==
'"' ||
c ==
'\'') {
88 }
else if (
c ==
'/' && next ==
'/') {
89 result[i] = result[i + 1] =
' ';
92 }
else if (
c ==
'/' && next ==
'*') {
93 result[i] = result[i + 1] =
' ';
101std::string trim(std::string_view
value) {
102 const size_t begin =
value.find_first_not_of(
" \t\r\n");
103 if (
begin == std::string_view::npos)
return {};
104 const size_t end =
value.find_last_not_of(
" \t\r\n");
108std::string attributeValue(std::string_view
value) {
109 std::string result = trim(
value);
110 if (result.size() >= 2 &&
111 ((result.front() ==
'"' && result.back() ==
'"') || (result.front() ==
'\'' && result.back() ==
'\'')))
112 return result.substr(1, result.size() - 2);
116std::vector<std::string> splitArguments(std::string_view arguments) {
117 std::vector<std::string> result;
120 for (
size_t i = 0; i <= arguments.size(); ++i) {
121 const char c = i == arguments.size() ?
',' : arguments[i];
122 if ((
c ==
'"' ||
c ==
'\'') && (i == 0 || arguments[i - 1] !=
'\\'))
124 if (
c ==
',' &&
quote == 0) {
125 result.push_back(trim(arguments.substr(
begin, i -
begin)));
129 if (result.size() == 1 && result.front().empty()) result.clear();
133std::vector<std::string> typeChoices(std::string_view
type) {
134 std::vector<std::string> result;
135 static const std::regex choicePattern(R
"([\"']([^\"']+)[\"'])");
136 forMatches(type, choicePattern, [&](const std::smatch& match,
size_t) { appendUnique(result, match[1].str()); });
140std::string diagnosticCode(std::string_view
message) {
141 if (
message.find(
"not assignable") != std::string_view::npos)
return "EVE2002";
142 if (
message.find(
"outside the allowed choices") != std::string_view::npos)
return "EVE2101";
143 if (
message.find(
"named argument") != std::string_view::npos)
return "EVE2201";
144 if (
message.find(
"Binding Contract") != std::string_view::npos)
return "EVE2202";
145 if (
message.find(
"incompatible unit") != std::string_view::npos)
return "EVE2301";
146 if (
message.find(
"non-exhaustive match") != std::string_view::npos)
return "EVE2401";
147 if (
message.find(
"persist is only allowed") != std::string_view::npos)
return "EVE2501";
148 if (
message.find(
"await is only allowed") != std::string_view::npos)
return "EVE2601";
149 if (
message.find(
"cannot resolve") != std::string_view::npos)
return "EVE1101";
150 if (
message.find(
"escapes") != std::string_view::npos)
return "EVE1102";
151 if (
message.find(
"cyclic import") != std::string_view::npos)
return "EVE1103";
152 if (
message.find(
"export") != std::string_view::npos)
return "EVE1104";
153 if (
message.find(
"identity") != std::string_view::npos)
return "EVE1105";
157ScriptDiagnostic makeDiagnostic(std::string_view
error, std::string_view
uri) {
163 ScriptDiagnostic diagnostic;
164 diagnostic.code = diagnosticCode(
message);
165 diagnostic.message = std::move(
message);
167 diagnostic.position.line =
static_cast<uint32_t
>(std::max(
line, 1));
168 diagnostic.position.column =
static_cast<uint32_t
>(std::max(
column, 1));
169 if (diagnostic.code ==
"EVE2201") diagnostic.fix =
"check the parameter names and required arguments";
170 if (diagnostic.code ==
"EVE2401") diagnostic.fix =
"add the missing cases or an else arm";
171 if (diagnostic.code ==
"EVE2601") diagnostic.fix =
"mark the containing function async";
180 if (entry.generated.line > generated.line ||
181 (entry.generated.line == generated.line && entry.generated.column > generated.column))
183 result.
line = entry.original.line + (generated.line - entry.generated.line);
184 result.
column = generated.line == entry.generated.line
185 ? entry.original.column + (generated.column - entry.generated.column)
194 if (entry.original.line > original.line ||
195 (entry.original.line == original.line && entry.original.column > original.column))
197 result.
line = entry.generated.line + (original.line - entry.original.line);
198 result.
column = original.line == entry.original.line
199 ? entry.generated.column + (original.column - entry.original.column)
207bool sourceIdentityMatches(std::string_view registered, std::string_view requested) {
208 std::string
left(registered);
209 std::string
right(requested);
210 std::replace(
left.begin(),
left.end(),
'\\',
'/');
211 std::replace(
right.begin(),
right.end(),
'\\',
'/');
213 const size_t scheme =
left.find(
":/");
214 if (scheme != std::string::npos)
left.erase(0, scheme + 2);
220template <
typename Mapper>
221ScriptSourcePosition mapRegisteredPosition(std::string_view
source, ScriptSourcePosition
position, Mapper mapper) {
222 std::lock_guard lock(compilerRegistryMutex);
223 for (
const auto& [_,
compiler] : compilerRegistry) {
224 for (
const ScriptMetadata& metadata :
compiler->metadataSnapshot()) {
225 if (sourceIdentityMatches(metadata.canonicalUri,
source))
return mapper(metadata.sourceMap,
position);
234 std::string result =
module;
246std::string jsonEscape(std::string_view
text) {
248 out.reserve(
text.size() + 8);
249 for (
unsigned char c :
text) {
251 case '"': out +=
"\\\"";
break;
252 case '\\': out +=
"\\\\";
break;
253 case '\n': out +=
"\\n";
break;
254 case '\r': out +=
"\\r";
break;
255 case '\t': out +=
"\\t";
break;
259 std::snprintf(buf,
sizeof(buf),
"\\u%04x",
c);
262 out.push_back(
static_cast<char>(
c));
283std::string asciiLower(std::string_view
text) {
284 std::string out(
text);
285 for (
char&
c : out)
c = static_cast<char>(
std::tolower(static_cast<unsigned char>(
c)));
289std::vector<std::string> splitTokens(
const std::string& query) {
290 std::vector<std::string>
tokens;
293 if (std::isspace(
static_cast<unsigned char>(
c))) {
306int scoreContract(
const BindingContract& contract,
const std::string& queryLower,
307 const std::vector<std::string>&
tokens) {
308 const std::string
key = asciiLower(contract.key());
309 const std::string
method = asciiLower(contract.method);
310 const std::string
cls = asciiLower(contract.scriptClass);
311 const std::string
module = asciiLower(contract.module);
312 const std::string docs = asciiLower(contract.documentationId);
313 const std::string hay =
key +
" " +
module + " " + cls + " " + method + " " + docs;
314 if (queryLower.empty())
return 1;
316 if (hay.find(
token) == std::string::npos)
return 0;
318 if (
key == queryLower)
return 200;
319 if (
cls +
"." +
method == queryLower)
return 180;
320 if (
method == queryLower)
return 150;
321 if (
method.rfind(queryLower, 0) == 0)
return 120;
322 if (
method.find(queryLower) != std::string::npos)
return 90;
323 if (
cls.find(queryLower) != std::string::npos)
return 75;
324 if (
key.find(queryLower) != std::string::npos)
return 70;
325 if (docs.find(queryLower) != std::string::npos)
return 50;
332 std::ostringstream json;
333 json <<
"{\"key\":\"" << jsonEscape(
key()) <<
"\",\"module\":\"" << jsonEscape(module) <<
"\",\"scriptClass\":\""
334 << jsonEscape(
scriptClass) <<
"\",\"method\":\"" << jsonEscape(
method) <<
"\",\"parameters\":[";
335 for (
size_t i = 0; i <
parameters.size(); ++i) {
337 if (i != 0) json <<
',';
338 json <<
"{\"name\":\"" << jsonEscape(parameter.
name) <<
"\",\"type\":\"" << jsonEscape(parameter.
type)
339 <<
"\",\"nullable\":" << (parameter.
nullable ?
"true" :
"false");
341 json <<
",\"default\":\"" << jsonEscape(*parameter.
scriptDefault) <<
'"';
344 json <<
",\"unit\":\"" << scriptUnitName(parameter.
unit) <<
'"';
346 if (!parameter.
choices.empty()) {
347 json <<
",\"choices\":[";
348 for (
size_t c = 0;
c < parameter.
choices.size(); ++
c) {
349 if (
c != 0) json <<
',';
350 json <<
'"' << jsonEscape(parameter.
choices[
c]) <<
'"';
356 json <<
"],\"returnType\":\"" << jsonEscape(
returnType)
357 <<
"\",\"returnNullable\":" << (
returnNullable ?
"true" :
"false") <<
",\"ownership\":\""
359 for (
size_t i = 0; i <
platforms.size(); ++i) {
360 if (i != 0) json <<
',';
361 json <<
'"' << jsonEscape(
platforms[i]) <<
'"';
363 json <<
"],\"documentationId\":\"" << jsonEscape(
documentationId) <<
"\"}";
369 storage_->contracts[contract.
key()] = std::move(contract);
374 return storage_->contracts.erase(std::string(
key)) != 0;
378 const auto found = storage_->contracts.find(std::string(
key));
379 return found == storage_->contracts.end() ? nullptr : &
found->second;
384 for (
const auto& [_, contract] : storage_->contracts) {
385 if (contract.method !=
method)
continue;
386 if (result !=
nullptr)
return nullptr;
393 std::string_view
method)
const noexcept {
395 for (
const auto& [_, contract] : storage_->contracts) {
396 if (contract.scriptClass != scriptClass || contract.method !=
method)
continue;
397 if (result !=
nullptr)
return nullptr;
404 for (
const auto& [_, contract] : storage_->contracts) {
405 if (contract.scriptClass == scriptClass)
return true;
411 std::vector<BindingContract> result;
412 result.reserve(storage_->contracts.size());
413 for (
const auto& [_, contract] : storage_->contracts) result.push_back(contract);
414 std::sort(result.begin(), result.end(),
420 std::string_view classFilter,
size_t limit)
const {
423 const std::string queryLower = asciiLower(query);
424 const std::string moduleLower = asciiLower(moduleFilter);
425 const std::string classLower = asciiLower(classFilter);
426 const auto tokens = splitTokens(queryLower);
427 std::vector<BindingSearchHit> hits;
428 for (
const auto& [_, contract] : storage_->contracts) {
429 if (!moduleLower.empty() && asciiLower(contract.module) != moduleLower)
continue;
430 if (!classLower.empty() && asciiLower(contract.scriptClass) != classLower)
continue;
431 const int score = scoreContract(contract, queryLower,
tokens);
432 if (
score <= 0)
continue;
433 hits.push_back({contract,
score});
436 if (a.score != b.score) return a.score > b.score;
437 return a.contract.key() < b.contract.key();
443void BindingContractRegistry::ensureUnique() {
444 if (storage_.use_count() != 1) storage_ = std::make_shared<Storage>(*storage_);
458 const SQInteger
top = sq_gettop(vm_->getHandle());
459 sq_pushroottable(vm_->getHandle());
460 sq_pushnull(vm_->getHandle());
461 while (SQ_SUCCEEDED(sq_next(vm_->getHandle(), -2))) {
462 if (sq_gettype(vm_->getHandle(), -2) == OT_STRING) {
463 const SQChar*
name =
nullptr;
464 if (SQ_SUCCEEDED(sq_getstring(vm_->getHandle(), -2, &
name)) &&
name !=
nullptr) nativeGlobals_.insert(
name);
466 sq_pop(vm_->getHandle(), 2);
468 sq_settop(vm_->getHandle(),
top);
470 std::lock_guard lock(compilerRegistryMutex);
471 compilerRegistry[vm_->getHandle()] =
this;
473 sq_setnamedargresolver(
476 const SQChar**
unit,
const SQChar**
choices, SQUserPointer user) ->
const SQChar* {
477 const auto& self = *static_cast<ScriptCompiler*>(user);
478 if (self.nativeGlobals_.find(callee) != self.nativeGlobals_.end()) return nullptr;
479 const BindingContract* contract = self.bindings_.findMethod(callee);
480 if (contract == nullptr || index < 0 || static_cast<size_t>(index) >= contract->parameters.size())
482 const BindingParameterContract& parameter = contract->parameters[static_cast<size_t>(index)];
483 if (type != nullptr) *type = parameter.type.c_str();
484 if (nullable != nullptr) *nullable = parameter.nullable ? SQTrue : SQFalse;
485 static const char* units[] = {nullptr,
"seconds",
"milliseconds",
"radians",
"degrees",
"pixels",
"meters"};
486 if (
unit !=
nullptr) *
unit =
units[
static_cast<size_t>(parameter.unit)];
488 static thread_local std::string choiceBuffer;
489 choiceBuffer.clear();
490 for (
size_t i = 0; i < parameter.choices.size(); ++i) {
491 if (i != 0) choiceBuffer +=
',';
492 choiceBuffer += parameter.choices[i];
494 *
choices = choiceBuffer.empty() ? nullptr : choiceBuffer.c_str();
496 return parameter.name.c_str();
499 sq_setannotationresolver(
501 [](
HSQUIRRELVM,
const SQChar* annotation, SQUserPointer user) -> SQBool {
502 const auto& names = static_cast<ScriptCompiler*>(user)->annotations_;
503 return names.find(annotation) == names.end() ? SQFalse : SQTrue;
508ScriptCompiler::~ScriptCompiler() {
510 std::lock_guard lock(compilerRegistryMutex);
511 compilerRegistry.erase(vm_->getHandle());
513 sq_setannotationresolver(vm_->getHandle(),
nullptr,
nullptr);
514 sq_setnamedargresolver(vm_->getHandle(),
nullptr,
nullptr);
517ssq::Script ScriptCompiler::compileSource(std::string_view
source, std::string_view sourceName) {
518 const std::string
uri(sourceName);
519 modules_->beginCompilation(
uri);
522 ssq::Script compiled = vm_->compileSource(std::string(
source).c_str(),
uri.c_str());
523 modules_->prepareDependencies(
uri);
524 next.dependencies = modules_->dependencies(
uri);
525 metadata_[
uri] = std::move(next);
526 refreshDependencyMetadata();
528 }
catch (
const std::exception&
error) {
529 const auto failure = modules_->takeCompilationFailure();
530 next.diagnostics.push_back(makeDiagnostic(failure ? *failure :
error.what(),
uri));
532 metadata_[
uri] = std::move(next);
533 throw std::runtime_error(*failure);
535 metadata_[
uri] = std::move(next);
540ssq::Script ScriptCompiler::compileFile(std::string_view
path) {
543 std::ifstream
input(
uri, std::ios::binary);
545 const std::string
source(std::istreambuf_iterator<char>(
input), {});
548 next.canonicalUri =
uri;
549 next.sourceMap.canonicalUri =
uri;
551 modules_->beginCompilation(
uri);
553 ssq::Script compiled = vm_->compileFile(
uri.c_str());
554 modules_->prepareDependencies(
uri);
555 next.dependencies = modules_->dependencies(
uri);
556 metadata_[
uri] = std::move(next);
557 refreshDependencyMetadata();
559 }
catch (
const std::exception&
error) {
560 const auto failure = modules_->takeCompilationFailure();
561 next.diagnostics.push_back(makeDiagnostic(failure ? *failure :
error.what(),
uri));
563 metadata_[
uri] = std::move(next);
564 throw std::runtime_error(*failure);
566 metadata_[
uri] = std::move(next);
572 const auto found = metadata_.find(std::string(canonicalUri));
577std::vector<ScriptMetadata> ScriptCompiler::metadataSnapshot()
const {
578 std::vector<ScriptMetadata> result;
579 result.reserve(metadata_.size());
580 for (
const auto& [_, metadata] : metadata_) result.push_back(metadata);
581 std::sort(result.begin(), result.end(),
586void ScriptCompiler::refreshDependencyMetadata() {
587 for (
auto& [_, metadata] : metadata_) metadata.reverseDependencies.clear();
588 for (
const auto& [importer, metadata] : metadata_) {
589 for (
const std::string& dependency : metadata.dependencies) {
590 const auto found = metadata_.find(dependency);
591 if (
found != metadata_.end()) appendUnique(
found->second.reverseDependencies, importer);
594 for (
auto& [_, metadata] : metadata_)
595 std::sort(metadata.reverseDependencies.
begin(), metadata.reverseDependencies.
end());
601void ScriptCompiler::registerAnnotation(std::string
name) {
602 if (!
name.empty()) annotations_.insert(std::move(
name));
605bool ScriptCompiler::unregisterAnnotation(std::string_view
name) {
return annotations_.erase(std::string(
name)) != 0; }
607std::vector<ScriptCompletion> ScriptCompiler::completions(std::string_view canonicalUri,
608 std::string_view prefix)
const {
609 std::vector<ScriptCompletion> result;
610 const auto matches = [prefix](std::string_view
value) {
return value.rfind(prefix, 0) == 0; };
611 if (
const auto unit = metadata(canonicalUri)) {
613 if (matches(symbol.name)) result.push_back({symbol.name, symbol.kind, symbol.erasedType, symbol.name});
615 for (
const std::string&
name :
unit->get().exports) {
616 if (matches(
name)) result.push_back({
name,
"export",
"module export",
name});
619 for (
const BindingContract& contract : bindings_.snapshot()) {
620 if (!matches(contract.method))
continue;
621 std::string signature = contract.method +
"(";
622 std::string insertion = contract.method +
"(";
623 for (
size_t i = 0; i < contract.parameters.size(); ++i) {
628 signature += contract.parameters[i].name +
": " + contract.parameters[i].type;
629 insertion += contract.parameters[i].name +
": ";
631 signature +=
") -> " + contract.returnType;
633 result.push_back({contract.method,
"function", std::move(signature), std::move(insertion)});
635 std::sort(result.begin(), result.end(), [](
const ScriptCompletion&
a,
const ScriptCompletion&
b) {
636 if (a.label != b.label) return a.label < b.label;
637 return a.kind < b.kind;
639 result.erase(std::unique(result.begin(), result.end(),
640 [](
const ScriptCompletion&
a,
const ScriptCompletion&
b) {
641 return a.label == b.label && a.kind == b.kind;
647std::optional<ScriptHover> ScriptCompiler::hover(std::string_view canonicalUri, std::string_view symbol)
const {
648 if (
const auto unit = metadata(canonicalUri)) {
650 if (candidate.name == symbol)
652 "`" + candidate.kind +
" " + candidate.name +
": " + candidate.erasedType +
"`",
657 std::string markdown =
"`" + contract->method +
"(";
658 for (
size_t i = 0; i < contract->parameters.size(); ++i) {
659 if (i != 0) markdown +=
", ";
660 markdown += contract->parameters[i].name +
": " + contract->parameters[i].type;
662 markdown +=
") -> " + contract->returnType +
"`";
663 if (!contract->documentationId.empty()) markdown +=
"\n\n" + contract->documentationId;
664 return ScriptHover{contract->method, std::move(markdown), {1, 1}};
669std::vector<ScriptDiagnostic> ScriptCompiler::diagnostics(std::string_view canonicalUri)
const {
670 const auto unit = metadata(canonicalUri);
671 return unit ?
unit->get().diagnostics : std::vector<ScriptDiagnostic>{};
674bool ScriptCompiler::setSourceMap(std::string_view canonicalUri,
ScriptSourceMap sourceMap) {
675 const auto found = metadata_.find(std::string(canonicalUri));
676 if (
found == metadata_.end())
return false;
678 found->second.sourceMap = std::move(sourceMap);
698 std::lock_guard lock(compilerRegistryMutex);
699 const auto found = compilerRegistry.find(
vm);
705 const std::string
uri = sourceName ==
nullptr ?
"buffer" : sourceName;
708 const SQInteger
top = sq_gettop(
vm);
709 const SQRESULT result = sq_compilebuffer(
vm,
source,
size, sourceName, raiseError);
710 if (SQ_FAILED(result)) {
718 next.diagnostics.push_back(std::move(diagnostic));
724 }
catch (
const std::exception&
error) {
726 next.diagnostics.push_back(makeDiagnostic(
error.what(),
uri));
728 return sq_throwerror(
vm,
error.what());
730 next.dependencies =
compiler->modules_->dependencies(
uri);
732 compiler->refreshDependencyMetadata();
736std::vector<ScriptDiagnostic> ScriptCompiler::validateProjectConfig(std::string_view
source,
737 std::string_view canonicalUri,
738 const std::vector<std::string>& knownModules,
739 const std::vector<std::string>& activeModules) {
740 std::vector<ScriptDiagnostic> result;
741 const std::string scanned = maskComments(
source);
742 static const std::regex listPattern(R
"(\b(modules|optionalModules)\s*=\s*\[([^\]]*)\])");
743 static const std::regex valuePattern(R
"([\"']([^\"']+)[\"'])");
744 forMatches(scanned, listPattern, [&](const std::smatch& list,
size_t listOffset) {
745 const bool required = list[1].str() ==
"modules";
746 const std::string
values = list[2].str();
747 forMatches(
values, valuePattern, [&](
const std::smatch&
value,
size_t valueOffset) {
749 const bool known = std::find(knownModules.begin(), knownModules.end(),
name) != knownModules.end();
750 const bool active = std::find(activeModules.begin(), activeModules.end(),
name) != activeModules.end();
753 diagnostic.
code = known ?
"EVE1002" :
"EVE1001";
754 diagnostic.
message = known ?
"required module is absent from the selected profile: " +
name
755 :
"config contains an unknown module root slot: " +
name;
757 diagnostic.
position = positionAt(
source, listOffset +
static_cast<size_t>(list.position(2)) + valueOffset);
759 diagnostic.
fix = known ?
"enable the module in the selected build profile"
760 :
"use a root slot from the generated Module Contract";
761 result.push_back(std::move(diagnostic));
774 const std::string scanned = maskComments(
source);
776 static const std::regex importPattern(
777 R
"(\bimport\s+(?:\*\s+as\s+[A-Za-z_]\w*|\{[^}]*\})\s+from\s+[\"']([^\"']+)[\"'])");
778 static const std::regex exportPattern(R
"(\bexport\s+(?:function|class|const)\s+([A-Za-z_]\w*))");
779 static const std::regex persistPattern(R
"(\bpersist\s+([A-Za-z_]\w*)\s*(?::[^=]+)?=)");
780 static const std::regex symbolPattern(R
"(\b(local|function|class)\s+([A-Za-z_]\w*)\s*(?::\s*([^=,\)\{\n]+))?)");
781 static const std::regex asyncPattern(R
"(\basync\s+function\s+([A-Za-z_]\w*))");
782 static const std::regex awaitPattern(R
"(\bawait\b)");
783 static const std::regex propertyPattern(
784 R
"(((?:[ \t]*@[A-Za-z_]\w*\([^\r\n]*\)[ \t]*\r?\n)+)[ \t]*([A-Za-z_]\w*)[ \t]*:[ \t]*([^=\r\n]+)[ \t]*=)");
785 static const std::regex annotationPattern(R
"(@([A-Za-z_]\w*)\(([^)]*)\))");
787 forMatches(scanned, importPattern, [&](const std::smatch& match,
size_t) {
788 appendUnique(result.
imports, match[1].str());
791 forMatches(scanned, exportPattern,
792 [&](
const std::smatch& match,
size_t) { appendUnique(result.
exports, match[1].str()); });
793 forMatches(scanned, persistPattern, [&](
const std::smatch& match,
size_t offset) {
795 result.
symbols.push_back({match[1].str(),
"persist",
"dynamic", positionAt(
source,
offset)});
797 forMatches(scanned, symbolPattern, [&](
const std::smatch& match,
size_t offset) {
798 result.
symbols.push_back({match[2].str(), match[1].str(), match[3].matched ? match[3].str() :
"dynamic",
801 forMatches(scanned, asyncPattern,
802 [&](
const std::smatch& match,
size_t) { appendUnique(result.
asyncFunctions, match[1].str()); });
803 forMatches(scanned, awaitPattern,
805 forMatches(scanned, propertyPattern, [&](
const std::smatch& match,
size_t offset) {
807 property.
name = match[2].str();
808 property.erasedType = trim(match[3].str());
809 property.position = positionAt(
source,
offset +
static_cast<size_t>(match.position(2)));
810 const std::string annotations = match[1].str();
811 forMatches(annotations, annotationPattern, [&](
const std::smatch& annotation,
size_t) {
812 const std::string
name = annotation[1].str();
813 const std::vector<std::string> args = splitArguments(annotation[2].str());
814 for (
size_t i = 0; i < args.size(); ++i) {
815 const size_t colon = args[i].find(
':');
816 if (i == 0 && colon == std::string::npos)
817 property.attributes[
name] = attributeValue(args[i]);
818 else if (colon != std::string::npos)
819 property.attributes[trim(std::string_view(args[i]).substr(0, colon))] =
820 attributeValue(std::string_view(args[i]).substr(colon + 1));
823 property.choices = typeChoices(property.
erasedType);
824 const auto editor =
property.attributes.find(
"editor");
825 if (editor != property.
attributes.end() && editor->second ==
"combo" && !property.
choices.empty()) {
827 for (
size_t i = 0; i <
property.choices.size(); ++i) {
829 options +=
property.choices[i];
831 property.attributes[
"options"] = std::move(
options);
833 result.
properties.push_back(std::move(property));
std::unordered_map< std::string, QuestRuntime > entries
struct SQVM * HSQUIRRELVM
std::map< std::string, Var > values
std::array< std::uint8_t, 32 > hash
std::unordered_map< const Graphics *, std::shared_ptr< Lifetime > > tokens
std::array< float, 3 > position
std::set< std::string > choices
onnx_detail::CompilerQueue compiler
const SquirrelValueOptions & options
std::vector< UnitCandidate > units
const UnitySourceAsset & source
const VegetationPresetContext & context
Registry consumed by compiler checks and tooling protocol adapters.
void registerContract(BindingContract contract)
Adds or atomically replaces a binding contract by key.
const BindingContract * find(std::string_view key) const noexcept
Finds a binding contract.
std::vector< BindingSearchHit > search(std::string_view query, std::string_view moduleFilter={}, std::string_view classFilter={}, size_t limit=20) const
Ranked substring/token search over keys, classes, methods and documentation ids.
bool hasScriptClass(std::string_view scriptClass) const noexcept
True when generated contracts include this script class.
std::vector< BindingContract > snapshot() const
Returns a stable snapshot sorted by contract key.
const BindingContract * findMethod(std::string_view method) const noexcept
Finds a uniquely named method.
bool unregisterContract(std::string_view key)
Removes a binding contract.
Unified Runtime compiler facade for metadata, diagnostics, and module preparation.
ScriptCompiler(ssq::VM &vm, ScriptModuleResolver &modules)
Script compiler.
Owns providers, the static dependency graph, compiled closures, and module instances.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
ScriptUnit
Unit attached to a binding parameter or return value.
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
const BindingContractRegistry & generatedBindingContracts()
Process-wide generated Binding Contracts for this linked engine build.
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".
EVENGINE_API_FOUNDATION void registerEngineBindingContracts(BindingContractRegistry ®istry)
Registers the build-generated contracts for every SimpleSquirrel binding.
std::optional< std::reference_wrapper< T > > OptionalRef
Optional borrowed reference; it does not extend T's lifetime.
Complete script-facing contract for a native function or method.
std::string key() const
Stable lookup key in the form module/class.method.
std::string documentationId
std::vector< BindingParameterContract > parameters
std::vector< std::string > platforms
std::string toJson() const
Compact JSON object for one contract (eve.binding-api item).
std::string threadAffinity
std::string std::string scriptClass
One native binding parameter visible to EveScript tools and named arguments.
std::vector< std::string > choices
std::optional< std::string > scriptDefault
Ranked Binding Contract hit from text search.
Stable structured diagnostic shared by Runtime, LSP, MCP, and editor tools.
ScriptSourcePosition position
Structured snapshot of a script error: message, throw site and stack.
Tool-neutral hover result for one script or binding symbol.
ScriptSourcePosition position
One generated-to-original mapping segment. Native syntax uses identity mappings.
Source map for a compiled script.
ScriptSourcePosition generatedPosition(ScriptSourcePosition original) const noexcept
Maps an original location to the nearest generated location.
std::vector< ScriptSourceMapEntry > entries
ScriptSourcePosition originalPosition(ScriptSourcePosition generated) const noexcept
Maps a generated location back to the nearest original location.
Original EveScript source position. Lines and columns are one-based.