diff --git a/src/engine/cmdline/Run.cpp b/src/engine/cmdline/Run.cpp index fb181fba..e1a20789 100644 --- a/src/engine/cmdline/Run.cpp +++ b/src/engine/cmdline/Run.cpp @@ -2,6 +2,7 @@ #include "scripts.h" #include "common/Module.h" #include "common/Runtime.h" +#include "common/ScriptCompiler.h" #include "common/config.h" #include "common/ECS.h" #include "filesystem/Filesystem.h" @@ -128,12 +129,12 @@ const char* playgroundEval(const char* source) { const std::string wrapped = std::string("return (") + source + ");"; const char* evalSource = wrapped.c_str(); SQInteger evalLen = static_cast(wrapped.size()); - bool compileOk = - SQ_SUCCEEDED(sq_compilebuffer(vm, evalSource, evalLen, _SC("playground_eval"), SQTrue)); + bool compileOk = SQ_SUCCEEDED(eve::script::ScriptCompiler::compileBuffer( + vm, evalSource, evalLen, _SC("playground_eval"), SQTrue)); if (!compileOk) { sq_settop(vm, top); - compileOk = SQ_SUCCEEDED(sq_compilebuffer(vm, source, std::strlen(source), - _SC("playground_eval"), SQTrue)); + compileOk = SQ_SUCCEEDED(eve::script::ScriptCompiler::compileBuffer( + vm, source, static_cast(std::strlen(source)), _SC("playground_eval"), SQTrue)); } if (!compileOk) { const SQChar* msg = nullptr; diff --git a/src/engine/common/ScriptCompiler.cpp b/src/engine/common/ScriptCompiler.cpp index c09077ad..1f5fefc1 100644 --- a/src/engine/common/ScriptCompiler.cpp +++ b/src/engine/common/ScriptCompiler.cpp @@ -10,12 +10,16 @@ #include #include #include +#include #include #include namespace eve::script { namespace { +std::mutex compilerRegistryMutex; +std::unordered_map compilerRegistry; + std::string hashSource(std::string_view source) { uint64_t hash = 1469598103934665603ull; for (const unsigned char c : source) { @@ -184,6 +188,10 @@ std::vector BindingContractRegistry::snapshot() const { } ScriptCompiler::ScriptCompiler(ssq::VM& vm, ScriptModuleResolver& modules) : vm_(&vm), modules_(&modules) { + { + std::lock_guard lock(compilerRegistryMutex); + compilerRegistry[vm_->getHandle()] = this; + } sq_setnamedargresolver( vm_->getHandle(), [](HSQUIRRELVM, const SQChar* callee, SQInteger index, const SQChar** unit, const SQChar** choices, @@ -217,6 +225,10 @@ ScriptCompiler::ScriptCompiler(ssq::VM& vm, ScriptModuleResolver& modules) : vm_ } ScriptCompiler::~ScriptCompiler() { + { + std::lock_guard lock(compilerRegistryMutex); + compilerRegistry.erase(vm_->getHandle()); + } sq_setannotationresolver(vm_->getHandle(), nullptr, nullptr); sq_setnamedargresolver(vm_->getHandle(), nullptr, nullptr); } @@ -351,6 +363,46 @@ std::vector ScriptCompiler::diagnostics(std::string_view canon return unit == nullptr ? std::vector{} : unit->diagnostics; } +SQRESULT ScriptCompiler::compileBuffer(HSQUIRRELVM vm, const SQChar* source, SQInteger size, const SQChar* sourceName, + SQBool raiseError) { + ScriptCompiler* compiler = nullptr; + { + std::lock_guard lock(compilerRegistryMutex); + const auto found = compilerRegistry.find(vm); + if (found != compilerRegistry.end()) compiler = found->second; + } + if (compiler == nullptr) return sq_compilebuffer(vm, source, size, sourceName, raiseError); + + const std::string_view text(source, static_cast(size)); + const std::string uri = sourceName == nullptr ? "buffer" : sourceName; + ScriptMetadata next = analyze(text, uri); + compiler->modules_->beginCompilation(uri); + const SQInteger top = sq_gettop(vm); + const SQRESULT result = sq_compilebuffer(vm, source, size, sourceName, raiseError); + if (SQ_FAILED(result)) { + const ScriptErrorContext context = captureCompileError(vm); + ScriptDiagnostic diagnostic; + diagnostic.code = diagnosticCode(context.message); + diagnostic.message = context.message.empty() ? "script compilation failed" : context.message; + diagnostic.canonicalUri = context.source.empty() ? uri : context.source; + diagnostic.position.line = static_cast(std::max(context.line, 1)); + diagnostic.position.column = static_cast(std::max(context.column, 1)); + next.diagnostics.push_back(std::move(diagnostic)); + compiler->metadata_[uri] = std::move(next); + return result; + } + try { + compiler->modules_->prepareDependencies(uri); + } catch (const std::exception& error) { + sq_settop(vm, top); + next.diagnostics.push_back(makeDiagnostic(error.what(), uri)); + compiler->metadata_[uri] = std::move(next); + return sq_throwerror(vm, error.what()); + } + compiler->metadata_[uri] = std::move(next); + return SQ_OK; +} + ScriptMetadata ScriptCompiler::analyze(std::string_view source, std::string_view canonicalUri) { ScriptMetadata result; result.sourceHash = hashSource(source); diff --git a/src/engine/common/ScriptCompiler.h b/src/engine/common/ScriptCompiler.h index 43d8d6a8..3fd036e3 100644 --- a/src/engine/common/ScriptCompiler.h +++ b/src/engine/common/ScriptCompiler.h @@ -182,6 +182,9 @@ public: std::optional hover(std::string_view canonicalUri, std::string_view symbol) const; /** @brief Returns the latest diagnostics for one source unit. */ std::vector diagnostics(std::string_view canonicalUri) const; + /** @brief Unified raw-VM entry used by REPL, debugger, MCP, and editor adapters. */ + static SQRESULT compileBuffer(HSQUIRRELVM vm, const SQChar* source, SQInteger size, const SQChar* sourceName, + SQBool raiseError); /** @brief Performs source-only metadata extraction without executing code. */ static ScriptMetadata analyze(std::string_view source, std::string_view canonicalUri); diff --git a/src/engine/devtools/ConsolePanel.cpp b/src/engine/devtools/ConsolePanel.cpp index 673143b1..dc617f4d 100644 --- a/src/engine/devtools/ConsolePanel.cpp +++ b/src/engine/devtools/ConsolePanel.cpp @@ -2,6 +2,7 @@ #include "devtools/Immortal.hpp" #include "common/ScriptError.h" +#include "common/ScriptCompiler.h" #include #include @@ -223,8 +224,8 @@ std::string ConsolePanel::eval(const std::string& expression) { const SQInteger top = sq_gettop(vm); // Compile `return (expr);` so the evaluation result lands on the stack. const std::string source = "return (" + expression + ");"; - if (SQ_FAILED(sq_compilebuffer(vm, source.c_str(), static_cast(source.size()), - _SC("console_repl.nut"), SQTrue))) { + if (SQ_FAILED(eve::script::ScriptCompiler::compileBuffer( + vm, source.c_str(), static_cast(source.size()), _SC("console_repl.nut"), SQTrue))) { sq_settop(vm, top); const eve::script::ScriptErrorContext ctx = eve::script::captureCompileError(vm); const std::string err = "error: " + diff --git a/src/engine/devtools/Debugger.cpp b/src/engine/devtools/Debugger.cpp index 48b34bc3..35fcde55 100644 --- a/src/engine/devtools/Debugger.cpp +++ b/src/engine/devtools/Debugger.cpp @@ -3,6 +3,7 @@ #include "devtools/RenderVision.hpp" #include "common/ScriptError.h" +#include "common/ScriptCompiler.h" #include #include @@ -920,8 +921,8 @@ VariableInfo Debugger::evaluate(const std::string& expression, int frameLevel) c } const std::string src = "return (" + expression + ");"; - if (SQ_FAILED(sq_compilebuffer(vm, src.c_str(), static_cast(src.size()), - _SC("eval"), SQTrue))) { + if (SQ_FAILED(eve::script::ScriptCompiler::compileBuffer( + vm, src.c_str(), static_cast(src.size()), _SC("eval"), SQTrue))) { sq_settop(vm, top); eve::script::ScriptErrorContext ctx = eve::script::captureCompileError(vm); VariableInfo info; diff --git a/src/engine/devtools/McpServer.cpp b/src/engine/devtools/McpServer.cpp index 9b594a17..7d3bdb36 100644 --- a/src/engine/devtools/McpServer.cpp +++ b/src/engine/devtools/McpServer.cpp @@ -1,6 +1,7 @@ #include "devtools/McpServer.hpp" #include "common/EditorAutomation.h" +#include "common/ScriptCompiler.h" #include "devtools/Immortal.hpp" #include "devtools/AiPanel.hpp" @@ -283,8 +284,8 @@ std::string snippetErrorText(HSQUIRRELVM vm, bool compile) { // Compile + run a snippet against the live VM (no return value captured). bool runVmSnippet(HSQUIRRELVM vm, const std::string& source, std::string* err) { const SQInteger top = sq_gettop(vm); - if (SQ_FAILED(sq_compilebuffer(vm, source.c_str(), static_cast(source.size()), _SC("mcp_snippet.nut"), - SQTrue))) { + if (SQ_FAILED(eve::script::ScriptCompiler::compileBuffer( + vm, source.c_str(), static_cast(source.size()), _SC("mcp_snippet.nut"), SQTrue))) { sq_settop(vm, top); if (err) *err = snippetErrorText(vm, true); return false; @@ -364,8 +365,8 @@ std::string sqValueToJson(HSQUIRRELVM vm, SQInteger idx) { // return value (e.g. `return ::scene_director.info();`). std::string callSceneDirectorReturn(HSQUIRRELVM vm, const std::string& snippet, std::string* err) { const SQInteger top = sq_gettop(vm); - if (SQ_FAILED(sq_compilebuffer(vm, snippet.c_str(), static_cast(snippet.size()), - _SC("mcp_scene_director.nut"), SQTrue))) { + if (SQ_FAILED(eve::script::ScriptCompiler::compileBuffer( + vm, snippet.c_str(), static_cast(snippet.size()), _SC("mcp_scene_director.nut"), SQTrue))) { sq_settop(vm, top); if (err) *err = snippetErrorText(vm, true); return {}; @@ -990,8 +991,8 @@ std::string callTool(McpServer& mcp, const std::string& name, Poco::JSON::Object const std::string source = argString(args, "source"); if (source.empty()) return "error: missing source"; const SQInteger top = sq_gettop(vm); - if (SQ_FAILED(sq_compilebuffer(vm, source.c_str(), static_cast(source.size()), - _SC("mcp_snippet.nut"), SQTrue))) { + if (SQ_FAILED(eve::script::ScriptCompiler::compileBuffer( + vm, source.c_str(), static_cast(source.size()), _SC("mcp_snippet.nut"), SQTrue))) { sq_settop(vm, top); return "error: " + snippetErrorText(vm, true); } diff --git a/test/script_compiler.cpp b/test/script_compiler.cpp index 2ab4da54..b0731f4f 100644 --- a/test/script_compiler.cpp +++ b/test/script_compiler.cpp @@ -4,6 +4,7 @@ #include "zeroerr/assert.h" #include "zeroerr/unittest.h" +#include #include #include #include @@ -201,3 +202,17 @@ TEST_CASE("scriptCompiler.servesLanguageToolingQueries") { CHECK(hover->markdown.find("physics.newWorld") != std::string::npos); CHECK(runtime.scriptCompiler().diagnostics("game:/tooling.nut").empty()); } + +TEST_CASE("scriptCompiler.rawVmBridgeRecordsToolingMetadata") { + Runtime runtime(512, ssq::Libs::ALL); + const char* source = "local replValue: int = 7\n"; + const SQInteger top = sq_gettop(runtime.handle()); + const SQRESULT result = script::ScriptCompiler::compileBuffer( + runtime.handle(), source, static_cast(std::strlen(source)), "console_repl.nut", SQTrue); + CHECK(SQ_SUCCEEDED(result)); + sq_settop(runtime.handle(), top); + const script::ScriptMetadata* metadata = runtime.scriptCompiler().metadata("console_repl.nut"); + CHECK(metadata != nullptr); + CHECK_EQ(metadata->symbols.size(), size_t(1)); + CHECK_EQ(metadata->symbols[0].name, std::string("replValue")); +}