From 25da5fad1e67a4b3841f1da6016ae0931f221870 Mon Sep 17 00:00:00 2001 From: PhilippeChab Date: Sat, 2 May 2026 15:13:29 -0400 Subject: [PATCH 1/8] Replace nwnsc subprocess with WASM compiler for diagnostics PR #62's intent was to migrate from spawning the native nwnsc binary to the in-process Beamdog nwn_script_comp; this commit accomplishes that goal but uses our WebAssembly build instead, which: - has multi-error recovery (one compile pass, every diagnostic in the file plus its #include'd helpers) - has no entry-point requirement (helper / include scripts can be edited and validated without a void main) - is ~210KB (vs 4-5MB native binary per platform) - works on every platform that runs Node, no separate Linux/Mac/ Windows binaries to ship DiagnosticsProvider rewritten: - Loads server/wasm/nwscript_compiler.{js,wasm} once on first use. - Per publish, creates a fresh compiler instance, sets collect-all-errors and require-entry-point=false, runs compile, drains the captured-error vector. - Resolver callback consults a per-call cache first then asks the workspace file system for any name the compiler requests, so nwscript.nss and #include'd files are picked up automatically. - Errors that fire inside an include are routed to the include's own URI (when we can resolve it via the workspace) rather than being attached to the file we were asked to check; the user sees the squiggle on the file that actually contains the bug. - Pre-seeds an empty diagnostic list for the target plus all transitive includes, so fixing an error clears the squiggle immediately. Removed: - spawn(child_process) machinery - getExecutablePath / hasSupportedOS - the WASM is platform-neutral - nwnsc command-line flag construction (-y -c -l -r -h -n -i) - stdout/stderr line-by-line parser (replaced by the collected-errors API) The compiler config keeps `enabled` and `verbose`; `os`, `nwnHome`, `nwnInstallation`, `reportWarnings` are now no-ops (kept in Config.ts for backwards-compat with any user settings.json that sets them). server/resources/compiler/{linux,mac,windows}/ binaries are no longer referenced; left in place so this commit can be reverted cleanly if the WASM approach hits a snag. --- server/src/Providers/DiagnosticsProvider.ts | 386 +++++++++++--------- server/wasm/nwscript_compiler.js | 2 + server/wasm/nwscript_compiler.wasm | Bin 0 -> 215005 bytes 3 files changed, 216 insertions(+), 172 deletions(-) create mode 100644 server/wasm/nwscript_compiler.js create mode 100755 server/wasm/nwscript_compiler.wasm diff --git a/server/src/Providers/DiagnosticsProvider.ts b/server/src/Providers/DiagnosticsProvider.ts index 36b54d6..7c94ee0 100644 --- a/server/src/Providers/DiagnosticsProvider.ts +++ b/server/src/Providers/DiagnosticsProvider.ts @@ -1,210 +1,252 @@ -import { spawn } from "child_process"; -import { type } from "os"; -import { join, dirname, basename } from "path"; -import { fileURLToPath } from "url"; +import { readFileSync } from "fs"; +import { basename, join } from "path"; +import { fileURLToPath, pathToFileURL } from "url"; import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver"; import { ServerManager } from "../ServerManager"; import Provider from "./Provider"; -const lineNumber = /\(([^)]+)\)/; -const lineMessage = /(Error|Warning):(.*)/; -const lineFilename = /^[^(]+/; +// Resource type IDs the NWN script compiler API expects. +const RT_NSS = 2009; +const RT_NCS = 2010; +const RT_NDB = 2064; -enum OS { - linux = "Linux", - mac = "Darwin", - windows = "Windows_NT", -} +// Diagnostic line format from the compiler: +// filename.nss(line): ERROR: MESSAGE [optional context] +// or, for some errors with no line attached: +// filename.nss: ERROR: MESSAGE +const LINE_RE = /^(?[^()]+?)(?:\((?\d+)\))?:\s*ERROR:\s*(?.+?)\s*$/m; type FilesDiagnostics = { [uri: string]: Diagnostic[] }; + export default class DiagnoticsProvider extends Provider { + private modulePromise: Promise | null = null; + constructor(server: ServerManager) { super(server); } - private generateDiagnostics(uris: string[], files: FilesDiagnostics, severity: DiagnosticSeverity) { - return (line: string) => { - const uri = uris.find((uri) => basename(fileURLToPath(uri)) === lineFilename.exec(line)![0]); - - if (uri) { - const linePosition = Number(lineNumber.exec(line)![1]) - 1; - const diagnostic = { - severity, - range: { - start: { line: linePosition, character: 0 }, - end: { line: linePosition, character: Number.MAX_VALUE }, - }, - message: lineMessage.exec(line)![2].trim(), - }; - - files[uri].push(diagnostic); - } - }; - } - - private hasSupportedOS() { - return ([...Object.values(OS).filter((item) => isNaN(Number(item)))] as string[]).includes(type()); + /** Load the WASM module exactly once. */ + private async getModule(): Promise { + if (!this.modulePromise) { + const wasmPath = join(__dirname, "..", "..", "wasm", "nwscript_compiler.js"); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const NWScriptCompiler = require(wasmPath); + this.modulePromise = NWScriptCompiler(); + } + return this.modulePromise; } - private getExecutablePath(os: OS | null) { - const specifiedOs = os || type(); - - switch (specifiedOs) { - case OS.linux: - return "../resources/compiler/linux/nwnsc"; - case OS.mac: - return "../resources/compiler/mac/nwnsc"; - case OS.windows: - return "../resources/compiler/windows/nwnsc.exe"; - default: - return ""; + /** + * Resolve a script name to its source bytes. Looks up `name` (no + * extension) via the workspace-file-system glob, returns null if + * not found or unreadable. + */ + private resolveScriptSource(name: string): string | null { + try { + const path = this.server.workspaceFilesSystem?.getFilePath(name); + if (!path) return null; + return readFileSync(path).toString(); + } catch { + return null; } } - public publish(uri: string) { - return new Promise((resolve, reject) => { - const { enabled, nwnHome, reportWarnings, nwnInstallation, verbose, os } = this.server.config.compiler; - if (!enabled || uri.includes("nwscript.nss")) { - return resolve(true); - } - - if (!this.hasSupportedOS()) { - const errorMessage = "Unsupported OS. Cannot provide diagnostics."; - this.server.logger.error(errorMessage); - return reject(new Error(errorMessage)); - } - - const document = this.server.documentsCollection.getFromUri(uri); - - if (!this.server.configLoaded || !document) { - if (!this.server.documentsWaitingForPublish.includes(uri)) { - this.server.documentsWaitingForPublish?.push(uri); + /** + * Run the WASM compiler over `uri` in collect-all-errors mode and + * record per-file diagnostics into `files`. + */ + private async compile(uri: string, files: FilesDiagnostics): Promise { + const Module = await this.getModule(); + + const filePath = fileURLToPath(uri); + const fileBaseName = basename(filePath, ".nss"); + + // The compiler hands us the bare filename (no extension); we map + // it back to source via the workspace, with the requested document + // taking precedence over a same-named file elsewhere in the tree. + const sources: { [name: string]: string } = {}; + sources[fileBaseName] = readFileSync(filePath).toString(); + + let compilerPtr = 0; + let deliveryBuf = 0; + let loadCb = 0; + let writeCb = 0; + const owned: number[] = []; + + try { + // Resolver: hand the compiler script source, looking it up in + // our local cache first then in the workspace. + loadCb = Module.addFunction((fnPtr: number) => { + try { + const fn = Module.UTF8ToString(fnPtr); + let src: string | null = sources[fn] ?? null; + if (src === null) { + src = this.resolveScriptSource(fn); + if (src !== null) sources[fn] = src; + } + if (src === null) return 0; + + const len = Module.lengthBytesUTF8(src); + if (deliveryBuf) Module._free(deliveryBuf); + deliveryBuf = Module._malloc(len + 1); + owned.push(deliveryBuf); + Module.stringToUTF8(src, deliveryBuf, len + 1); + Module._scriptCompApiDeliverFile(compilerPtr, deliveryBuf, len); + return 1; + } catch { + return 0; } - return resolve(true); + }, "iii"); + + // We don't care about output bytes; signal success so the + // compiler doesn't treat the write as an error. + writeCb = Module.addFunction(() => 0, "iiiiii"); + + const newCompiler = Module.cwrap("scriptCompApiNewCompiler", "number", + ["number", "number", "number", "number", "number"]); + const initCompiler = Module.cwrap("scriptCompApiInitCompiler", null, + ["number", "string", "boolean", "number", "number", "string"]); + const setCollectAll = Module.cwrap("scriptCompApiSetCollectAllErrors", + null, ["number", "boolean"]); + const setRequireEntry = Module.cwrap( + "scriptCompApiSetRequireEntryPoint", null, ["number", "boolean"]); + const compile = Module.cwrap("wasmCompile", "number", + ["number", "string"]); + const errorCount = Module.cwrap("wasmGetCollectedErrorCount", "number", + ["number"]); + const errorAt = Module.cwrap("wasmGetCollectedError", "string", + ["number", "number"]); + const lastError = Module.cwrap("wasmGetLastError", "string", ["number"]); + + compilerPtr = newCompiler(RT_NSS, RT_NCS, RT_NDB, writeCb, loadCb); + if (!compilerPtr) throw new Error("scriptCompApiNewCompiler returned null"); + + // writeDebug=false: we don't want NDB output. maxIncludeDepth=16 + // matches the historic compiler default. + initCompiler(compilerPtr, "nwscript", false, 16, 0, "scriptout"); + setCollectAll(compilerPtr, true); + // Allow files with no entry point (helper / include scripts) to + // validate cleanly - the LSP wants diagnostics on every editable + // file, not just main scripts. + setRequireEntry(compilerPtr, false); + + compile(compilerPtr, fileBaseName); + + const messages: string[] = []; + const n = errorCount(compilerPtr); + for (let i = 0; i < n; i++) { + messages.push(errorAt(compilerPtr, i)); + } + // Some hard-fail paths (e.g. file-not-found, lexer panic) bypass + // the multi-error vector and only report through the captured + // single-error string. Pick that up too. + if (messages.length === 0) { + const single = lastError(compilerPtr); + if (single) messages.push(single); } - const children = document.getChildren(); - const files: FilesDiagnostics = { [document.uri]: [] }; - const uris: string[] = []; - children.forEach((child) => { - const fileUri = this.server.documentsCollection?.get(child)?.uri; - if (fileUri) { - files[fileUri] = []; - uris.push(fileUri); + for (const raw of messages) { + for (const line of raw.split("\n")) { + const m = LINE_RE.exec(line); + if (!m) continue; + this.recordDiagnostic(uri, files, m.groups!); + break; } - }); - - if (verbose) { - this.server.logger.info(`Compiling ${document.uri}:`); - } - // The compiler command: - // - y; continue on error - // - c; compile includes - // - l; try to load resources if paths are not supplied - // - r; don't generate the compiled file - // - h; game home path - // - n; game installation path - // - i; includes directories - const args = ["-y", "-c", "-l", "-r", "SKIP_OUTPUT"]; - if (Boolean(nwnHome)) { - args.push("-h"); - args.push(`"${nwnHome}"`); - } else if (verbose) { - this.server.logger.info("Trying to resolve Neverwinter Nights home directory automatically."); } - if (Boolean(nwnInstallation)) { - args.push("-n"); - args.push(`"${nwnInstallation}"`); - } else if (verbose) { - this.server.logger.info("Trying to resolve Neverwinter Nights installation directory automatically."); + } finally { + if (compilerPtr) { + try { Module._scriptCompApiDestroyCompiler(compilerPtr); } + catch { /* ignore */ } } - if (children.length > 0) { - args.push("-i"); - args.push(`"${[...new Set(uris.map((uri) => dirname(fileURLToPath(uri))))].join(";")}"`); + for (const p of owned) { + try { Module._free(p); } catch { /* ignore */ } } - args.push(`"${fileURLToPath(uri)}"`); - - let stdout = ""; - let stderr = ""; - - if (verbose) { - this.server.logger.info(this.getExecutablePath(os)); - this.server.logger.info(JSON.stringify(args, null, 4)); - } - - const child = spawn(join(__dirname, this.getExecutablePath(os)), args, { shell: true }); - - child.stdout.on("data", (chunk: string) => (stdout += chunk)); - child.stderr.on("data", (chunk: string) => (stderr += chunk)); + if (loadCb) Module.removeFunction(loadCb); + if (writeCb) Module.removeFunction(writeCb); + } + } - child.on("error", (e: any) => { - this.server.logger.error(e.message); - reject(e); - }); + /** + * Map a parsed diagnostic line to an LSP Diagnostic and stash it + * under the right URI. Errors raised in #include'd files are routed + * to that file's URI when we can resolve it; otherwise they're + * attached to the document we were asked to check. + */ + private recordDiagnostic( + targetUri: string, + files: FilesDiagnostics, + parts: { [k: string]: string } + ) { + const file = (parts.file ?? "").trim(); + const line = Number(parts.line ?? "1"); + const message = (parts.message ?? "").trim(); + + let uri = targetUri; + const base = file.replace(/\.nss$/, ""); + const path = this.server.workspaceFilesSystem?.getFilePath(base); + if (path) uri = pathToFileURL(path).href; + + if (!files[uri]) files[uri] = []; + + const linePos = Math.max(0, line - 1); + files[uri].push({ + severity: DiagnosticSeverity.Error, + range: { + start: { line: linePos, character: 0 }, + end: { line: linePos, character: Number.MAX_VALUE }, + }, + message, + source: "nwscript", + }); + } - child.on("close", (_) => { - const lines = stdout - .toString() - .split("\n") - .filter((line) => line !== "\r" && line !== "\n" && Boolean(line)); - const errors: string[] = []; - const warnings: string[] = []; + public async publish(uri: string): Promise { + const { enabled, verbose } = this.server.config.compiler; + if (!enabled || uri.includes("nwscript.nss")) { + return true; + } - lines.forEach((line) => { - if (verbose && !line.includes("Compiling:")) { - this.server.logger.info(line); - } + const document = this.server.documentsCollection?.getFromUri(uri); + if (!this.server.configLoaded || !document) { + if (!this.server.documentsWaitingForPublish.includes(uri)) { + this.server.documentsWaitingForPublish?.push(uri); + } + return true; + } - // Diagnostics - if (line.includes("Error:")) { - errors.push(line); - } - if (reportWarnings && line.includes("Warning:")) { - warnings.push(line); - } + if (verbose) { + this.server.logger.info(`Compiling ${uri}`); + } - // Actual errors - if (line.includes("NOTFOUND")) { - return this.server.logger.error( - "Unable to resolve nwscript.nss. Are your Neverwinter Nights home and/or installation directories valid?", - ); - } - if (line.includes("Failed to open .key archive")) { - return this.server.logger.error( - "Unable to open nwn_base.key Is your Neverwinter Nights installation directory valid?", - ); - } - if (line.includes("Unable to read input file")) { - if (Boolean(nwnHome) || Boolean(nwnInstallation)) { - return this.server.logger.error( - "Unable to resolve provided Neverwinter Nights home and/or installation directories. Ensure the paths are valid in the extension settings.", - ); - } else { - return this.server.logger.error( - "Unable to automatically resolve Neverwinter Nights home and/or installation directories.", - ); - } - } - }); + // Pre-seed empty diagnostic lists for the target plus all of its + // transitive includes - so that fixing an error in an include + // immediately clears any prior squiggle on it. + const files: FilesDiagnostics = { [uri]: [] }; + for (const child of document.getChildren()) { + const childUri = this.server.documentsCollection?.get(child)?.uri; + if (childUri) files[childUri] = []; + } - if (verbose) { - this.server.logger.info("Done.\n"); - } + try { + await this.compile(uri, files); + } catch (e: any) { + this.server.logger.error(`Compile failed: ${e?.message ?? e}`); + } - uris.push(document.uri); - errors.forEach(this.generateDiagnostics(uris, files, DiagnosticSeverity.Error)); - if (reportWarnings) warnings.forEach(this.generateDiagnostics(uris, files, DiagnosticSeverity.Warning)); + for (const [u, diagnostics] of Object.entries(files)) { + this.server.connection.sendDiagnostics({ uri: u, diagnostics }); + } - for (const [uri, diagnostics] of Object.entries(files)) { - this.server.connection.sendDiagnostics({ uri, diagnostics }); - } - resolve(true); - }); - }); + if (verbose) { + this.server.logger.info("Done."); + } + return true; } public async processDocumentsWaitingForPublish() { - return await Promise.all(this.server.documentsWaitingForPublish.map(async (uri) => await this.publish(uri))); + return await Promise.all( + this.server.documentsWaitingForPublish.map(async (uri) => await this.publish(uri)) + ); } } diff --git a/server/wasm/nwscript_compiler.js b/server/wasm/nwscript_compiler.js new file mode 100644 index 0000000..4aee334 --- /dev/null +++ b/server/wasm/nwscript_compiler.js @@ -0,0 +1,2 @@ +var NWScriptCompiler=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else{}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["__wasm_call_ctors"]();FS.ignorePermissions=false}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("nwscript_compiler.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={env:wasmImports,wasi_snapshot_preview1:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var ___assert_fail=(condition,filename,line,func)=>abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"]);class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;abort()};var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>(crypto.getRandomValues(view),0)};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var mmapAlloc=size=>{abort()};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=MEMFS.emptyFileContents??=new Uint8Array(0)}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity)newCapacity=Math.max(newCapacity,256);var oldContents=MEMFS.getFileDataAsTypedArray(node);node.contents=new Uint8Array(newCapacity);node.contents.set(oldContents)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;var oldContents=node.contents;node.contents=new Uint8Array(newSize);node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)));node.usedBytes=newSize},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);buffer.set(contents.subarray(position,position+size),offset);return size},write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.mtime=node.ctime=Date.now();if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length}else{MEMFS.expandFileStorage(node,position+length);node.contents.set(buffer.subarray(offset,offset+length),position);node.usedBytes=Math.max(node.usedBytes,position+length)}return length},llseek(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}if(contents){if(position>0||position+length{if(typeof str!="string")return str;var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_fileDataToTypedArray=data=>{if(typeof data=="string"){data=intArrayFromString(data,true)}if(!data.subarray){data=new Uint8Array(data)}return data};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}if(perms.includes("w")&&!(node.mode&146)){return 2}if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else if(FS.isDir(node.mode)){return 31}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}var mode=FS.flagsToPermissionString(flags);if(FS.isDir(node.mode)){if(mode!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,mode)},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);try{setattr(arg,attr)}catch(e){if(e instanceof RangeError){throw new FS.ErrnoError(22)}throw e}},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=FS_modeStringToFlags(flags);if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data);FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn);FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){data=FS_fileDataToTypedArray(data);FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}},createDevice(parent,name,input,output){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);FS.createDevice.major??=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open(stream){stream.seekable=false},close(stream){if(output?.buffer?.length){output(10)}},read(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SYSCALLS={currentUmask:18,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();var mask=289792;stream.flags=stream.flags&~mask|arg&mask;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;if(flags&64){mode&=~SYSCALLS.currentUmask}return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffsetDate.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 22;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var updateTableMap=(offset,count)=>{if(functionsInTableMap){for(var i=offset;i{if(!functionsInTableMap){functionsInTableMap=new WeakMap;updateTableMap(0,wasmTable.length)}return functionsInTableMap.get(func)||0};var freeTableIndexes=[];var getEmptyTableSlot=()=>{if(freeTableIndexes.length){return freeTableIndexes.pop()}return wasmTable["grow"](1)};var setWasmTableEntry=(idx,func)=>{wasmTable.set(idx,func);wasmTableMirror[idx]=wasmTable.get(idx)};var uleb128EncodeWithLen=arr=>{const n=arr.length;return[n%128|128,n>>7,...arr]};var wasmTypeCodes={i:127,p:127,j:126,f:125,d:124,e:111};var generateTypePack=types=>uleb128EncodeWithLen(Array.from(types,type=>{var code=wasmTypeCodes[type];return code}));var convertJsFunctionToWasm=(func,sig)=>{var bytes=Uint8Array.of(0,97,115,109,1,0,0,0,1,...uleb128EncodeWithLen([1,96,...generateTypePack(sig.slice(1)),...generateTypePack(sig[0]==="v"?"":sig[0])]),2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0);var module=new WebAssembly.Module(bytes);var instance=new WebAssembly.Instance(module,{e:{f:func}});var wrappedFunc=instance.exports["f"];return wrappedFunc};var addFunction=(func,sig)=>{var rtn=getFunctionAddress(func);if(rtn){return rtn}var ret=getEmptyTableSlot();try{setWasmTableEntry(ret,func)}catch(err){if(!(err instanceof TypeError)){throw err}var wrapped=convertJsFunctionToWasm(func,sig);setWasmTableEntry(ret,wrapped)}functionsInTableMap.set(func,ret);return ret};var removeFunction=index=>{functionsInTableMap.delete(getWasmTableEntry(index));setWasmTableEntry(index,null);freeTableIndexes.push(index)};FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["ccall"]=ccall;Module["cwrap"]=cwrap;Module["addFunction"]=addFunction;Module["removeFunction"]=removeFunction;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;var _free,_scriptCompApiGetABIVersion,_scriptCompApiNewCompiler,_scriptCompApiInitCompiler,_scriptCompApiDeliverFile,_scriptCompApiSetRequireEntryPoint,_scriptCompApiSetCollectAllErrors,_scriptCompApiDestroyCompiler,_wasmCompile,_wasmGetLastError,_wasmGetCollectedErrorCount,_wasmGetCollectedError,_wasmGetCollectedErrorCode,_malloc,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_free=Module["_free"]=wasmExports["free"];_scriptCompApiGetABIVersion=Module["_scriptCompApiGetABIVersion"]=wasmExports["scriptCompApiGetABIVersion"];_scriptCompApiNewCompiler=Module["_scriptCompApiNewCompiler"]=wasmExports["scriptCompApiNewCompiler"];_scriptCompApiInitCompiler=Module["_scriptCompApiInitCompiler"]=wasmExports["scriptCompApiInitCompiler"];_scriptCompApiDeliverFile=Module["_scriptCompApiDeliverFile"]=wasmExports["scriptCompApiDeliverFile"];_scriptCompApiSetRequireEntryPoint=Module["_scriptCompApiSetRequireEntryPoint"]=wasmExports["scriptCompApiSetRequireEntryPoint"];_scriptCompApiSetCollectAllErrors=Module["_scriptCompApiSetCollectAllErrors"]=wasmExports["scriptCompApiSetCollectAllErrors"];_scriptCompApiDestroyCompiler=Module["_scriptCompApiDestroyCompiler"]=wasmExports["scriptCompApiDestroyCompiler"];_wasmCompile=Module["_wasmCompile"]=wasmExports["wasmCompile"];_wasmGetLastError=Module["_wasmGetLastError"]=wasmExports["wasmGetLastError"];_wasmGetCollectedErrorCount=Module["_wasmGetCollectedErrorCount"]=wasmExports["wasmGetCollectedErrorCount"];_wasmGetCollectedError=Module["_wasmGetCollectedError"]=wasmExports["wasmGetCollectedError"];_wasmGetCollectedErrorCode=Module["_wasmGetCollectedErrorCode"]=wasmExports["wasmGetCollectedErrorCode"];_malloc=Module["_malloc"]=wasmExports["malloc"];__emscripten_stack_restore=wasmExports["_emscripten_stack_restore"];__emscripten_stack_alloc=wasmExports["_emscripten_stack_alloc"];_emscripten_stack_get_current=wasmExports["emscripten_stack_get_current"];memory=wasmMemory=wasmExports["memory"];__indirect_function_table=wasmTable=wasmExports["__indirect_function_table"]}var wasmImports={__assert_fail:___assert_fail,__cxa_throw:___cxa_throw,__syscall_fcntl64:___syscall_fcntl64,__syscall_ioctl:___syscall_ioctl,__syscall_openat:___syscall_openat,_abort_js:__abort_js,_localtime_js:__localtime_js,_tzset_js:__tzset_js,emscripten_date_now:_emscripten_date_now,emscripten_resize_heap:_emscripten_resize_heap,fd_close:_fd_close,fd_read:_fd_read,fd_seek:_fd_seek,fd_write:_fd_write};function run(){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=NWScriptCompiler;module.exports.default=NWScriptCompiler}else if(typeof define==="function"&&define["amd"])define([],()=>NWScriptCompiler); diff --git a/server/wasm/nwscript_compiler.wasm b/server/wasm/nwscript_compiler.wasm new file mode 100755 index 0000000000000000000000000000000000000000..344ec14029aef5bc7602b8a5ec470910e19259c6 GIT binary patch literal 215005 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 Date: Sat, 2 May 2026 15:25:51 -0400 Subject: [PATCH 2/8] Add WASM-bundle smoke tests for diagnostics Drives the WASM compiler the same way DiagnosticsProvider does - through cwrap signatures, the resolver-callback pattern, the collect-all-errors / require-entry-point flags - so any breakage in the bundle, the C exports, or the API contract surfaces here long before it would in a real LSP session. Covers: ABI version, clean compile, single error, multiple errors per file, include-only validation, errors in #include'd files with the [via:] trace, errors from include + main reported together, file-not-found for unresolvable includes, determinism across repeated compiles. Run with: cd server && yarn test --- server/test/diagnostics_wasm_test.ts | 181 +++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 server/test/diagnostics_wasm_test.ts diff --git a/server/test/diagnostics_wasm_test.ts b/server/test/diagnostics_wasm_test.ts new file mode 100644 index 0000000..778ad45 --- /dev/null +++ b/server/test/diagnostics_wasm_test.ts @@ -0,0 +1,181 @@ +// Smoke-test the WASM compiler the same way DiagnosticsProvider uses it. +// Catches breakage in the bundle, the C API exports, the cwrap signatures, +// and the resolver-callback wiring without needing a running LSP. + +import { describe, it, before } from "mocha"; +import { expect } from "chai"; +import { readFileSync } from "fs"; +import { join } from "path"; + +const RT_NSS = 2009; +const RT_NCS = 2010; +const RT_NDB = 2064; + +// Minimal nwscript stub - covers what the test scripts below reference. +const NWSCRIPT_STUB = ` +int Nonsense(int n); +int IntFn(int n); +string StringFn(string s); +void VoidFn(); +`.trim(); + +interface CompileResult { + code: number; + errors: string[]; +} + +async function compileOne( + Module: any, + files: Record, + target: string, + opts: { collectAll?: boolean; requireEntry?: boolean } = {} +): Promise { + let comp = 0; + let buf = 0; + const owned: number[] = []; + + const loadCb = Module.addFunction((fnPtr: number) => { + const fn = Module.UTF8ToString(fnPtr); + if (!(fn in files)) return 0; + const src = files[fn]; + const len = Module.lengthBytesUTF8(src); + if (buf) Module._free(buf); + buf = Module._malloc(len + 1); + owned.push(buf); + Module.stringToUTF8(src, buf, len + 1); + Module._scriptCompApiDeliverFile(comp, buf, len); + return 1; + }, "iii"); + const writeCb = Module.addFunction(() => 0, "iiiiii"); + + try { + const newComp = Module.cwrap("scriptCompApiNewCompiler", "number", + ["number", "number", "number", "number", "number"]); + const initComp = Module.cwrap("scriptCompApiInitCompiler", null, + ["number", "string", "boolean", "number", "number", "string"]); + const setCollectAll = Module.cwrap("scriptCompApiSetCollectAllErrors", + null, ["number", "boolean"]); + const setRequireEntry = Module.cwrap( + "scriptCompApiSetRequireEntryPoint", null, ["number", "boolean"]); + const compile = Module.cwrap("wasmCompile", "number", + ["number", "string"]); + const errorCount = Module.cwrap("wasmGetCollectedErrorCount", "number", + ["number"]); + const errorAt = Module.cwrap("wasmGetCollectedError", "string", + ["number", "number"]); + const lastError = Module.cwrap("wasmGetLastError", "string", ["number"]); + + comp = newComp(RT_NSS, RT_NCS, RT_NDB, writeCb, loadCb); + initComp(comp, "nwscript", false, 16, 0, "scriptout"); + setCollectAll(comp, opts.collectAll ?? true); + setRequireEntry(comp, opts.requireEntry ?? false); + + const code = compile(comp, target); + const errors: string[] = []; + const n = errorCount(comp); + for (let i = 0; i < n; i++) errors.push(errorAt(comp, i)); + if (errors.length === 0) { + const single = lastError(comp); + if (single) errors.push(single); + } + return { code, errors }; + } finally { + if (comp) Module._scriptCompApiDestroyCompiler(comp); + for (const p of owned) Module._free(p); + Module.removeFunction(loadCb); + Module.removeFunction(writeCb); + } +} + +describe("Diagnostics WASM bundle", () => { + let Module: any; + + before("load wasm", async function () { + this.timeout(15000); + const wasmEntry = join(__dirname, "..", "wasm", "nwscript_compiler.js"); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const NWScriptCompiler = require(wasmEntry); + Module = await NWScriptCompiler(); + }); + + it("loads and reports ABI 2", () => { + const abi = Module.cwrap("scriptCompApiGetABIVersion", "number", [])(); + expect(abi).to.equal(2); + }); + + it("compiles a clean script with no errors", async () => { + const r = await compileOne(Module, + { nwscript: NWSCRIPT_STUB, p: "void main() { int x = 1 + 2; }" }, + "p"); + expect(r.code).to.equal(0); + expect(r.errors).to.have.lengthOf(0); + }); + + it("reports a single type error with file/line", async () => { + const r = await compileOne(Module, + { nwscript: NWSCRIPT_STUB, p: 'void main() { int x = "wrong"; }' }, + "p"); + expect(r.code).to.not.equal(0); + expect(r.errors).to.have.lengthOf(1); + expect(r.errors[0]).to.include("MISMATCHED TYPES"); + expect(r.errors[0]).to.match(/p\.nss\(\d+\)/); + }); + + it("reports multiple errors per file in one pass", async () => { + const r = await compileOne(Module, + { nwscript: NWSCRIPT_STUB, p: + 'void main() { int x = "a"; int y = "b"; int z = "c"; }' }, + "p"); + expect(r.errors.length).to.be.at.least(3); + for (const e of r.errors) expect(e).to.include("MISMATCHED TYPES"); + }); + + it("validates an include-only file (no main) when require-entry is off", async () => { + const r = await compileOne(Module, + { nwscript: NWSCRIPT_STUB, p: "int helper(int n) { return n * 2; }" }, + "p", { requireEntry: false }); + expect(r.code).to.equal(0); + }); + + it("reports errors from a #include'd file with [via:] trace", async () => { + const r = await compileOne(Module, { + nwscript: NWSCRIPT_STUB, + lib: 'int helper() { return BadId(); }', + main: '#include "lib"\nvoid main() { int x = helper(); }', + }, "main"); + expect(r.errors.length).to.be.at.least(1); + const all = r.errors.join("\n"); + expect(all).to.include("lib"); + }); + + it("collects errors from both an include and the main file", async () => { + const r = await compileOne(Module, { + nwscript: NWSCRIPT_STUB, + lib: 'int helper() { return BadId(); }', + main: '#include "lib"\nvoid main() { int x = "wrong"; }', + }, "main"); + expect(r.errors.length).to.be.at.least(2); + const all = r.errors.join("\n"); + expect(all).to.include("lib"); + expect(all).to.match(/MISMATCHED TYPES|main/); + }); + + it("returns FILE_NOT_FOUND for an unresolvable include", async () => { + const r = await compileOne(Module, { + nwscript: NWSCRIPT_STUB, + main: '#include "nope"\nvoid main() {}', + }, "main"); + expect(r.code).to.not.equal(0); + }); + + it("is deterministic across repeated compiles", async () => { + const files = { nwscript: NWSCRIPT_STUB, p: + 'void main() { int x = "a"; int y = "b"; }' }; + const counts = new Set(); + for (let i = 0; i < 5; i++) { + const r = await compileOne(Module, files, "p"); + counts.add(r.errors.length); + } + expect(counts.size, `error counts drifted: ${[...counts]}`).to.equal(1); + }); +}); From 40ae503ad77beaa738acf5fd1960c77c4c358968 Mon Sep 17 00:00:00 2001 From: PhilippeChab Date: Sat, 2 May 2026 15:40:08 -0400 Subject: [PATCH 3/8] Add LSP integration test + fix WASM path for bundled server.js The integration test spawns the bundled server.js as a child process and speaks JSON-RPC over stdin/stdout exactly like VS Code does: sends initialize, replies to workspace/configuration with the default config, opens documents, waits for publishDiagnostics notifications. Catches anything that breaks in real LSP usage that the WASM unit test can't see - server boot, document indexing, didOpen / didChange / didSave handling, the connection.sendDiagnostics path, file-system resolution from a real workspace. Six scenarios covered: - clean file -> empty diagnostics - broken file -> type-error diagnostic with severity=Error - multi-error file -> three diagnostics in one pass - include-only file -> empty diagnostics (no entry-point required) - edit + save -> diagnostics clear - error in #include'd file -> some diagnostic surfaces Bug surfaced and fixed while writing the test: DiagnosticsProvider had `__dirname/../../wasm` for the WASM path. That path was correct for the unbundled ts source layout but wrong for the bundled server.js, which is at server/out/server.js - __dirname/../../wasm goes one level too high and produces "Cannot find module" at runtime. Changed to `__dirname/../wasm` which is correct for the bundled layout (server/out -> server/wasm) that VS Code actually runs. Run with: cd server && yarn test Verbose server logs: LSP_VERBOSE=1 cd server && yarn test --- server/src/Providers/DiagnosticsProvider.ts | 6 +- server/test/lsp_integration_test.ts | 338 ++++++++++++++++++++ 2 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 server/test/lsp_integration_test.ts diff --git a/server/src/Providers/DiagnosticsProvider.ts b/server/src/Providers/DiagnosticsProvider.ts index 7c94ee0..fb0ccc6 100644 --- a/server/src/Providers/DiagnosticsProvider.ts +++ b/server/src/Providers/DiagnosticsProvider.ts @@ -29,7 +29,11 @@ export default class DiagnoticsProvider extends Provider { /** Load the WASM module exactly once. */ private async getModule(): Promise { if (!this.modulePromise) { - const wasmPath = join(__dirname, "..", "..", "wasm", "nwscript_compiler.js"); + // server.js is bundled into server/out/, so server/wasm is one + // level up from __dirname. (The same path also works for the + // un-bundled ts-node compile from server/src/Providers/, where + // ../wasm reaches server/wasm.) + const wasmPath = join(__dirname, "..", "wasm", "nwscript_compiler.js"); // eslint-disable-next-line @typescript-eslint/no-var-requires const NWScriptCompiler = require(wasmPath); this.modulePromise = NWScriptCompiler(); diff --git a/server/test/lsp_integration_test.ts b/server/test/lsp_integration_test.ts new file mode 100644 index 0000000..8420107 --- /dev/null +++ b/server/test/lsp_integration_test.ts @@ -0,0 +1,338 @@ +// End-to-end LSP integration test. +// +// Spawns the built server.js as a child process, speaks JSON-RPC to it +// over stdin/stdout exactly like VS Code does, and asserts that +// publishDiagnostics notifications arrive with the expected shape. +// +// Catches anything that breaks in real LSP usage that the unit-level +// WASM tests can't see: server boot, document indexing, didOpen / +// didChange handling, the connection.sendDiagnostics path, file-system +// resolution from a real workspace. + +import { describe, it, before, after } from "mocha"; +import { expect } from "chai"; +import { ChildProcess, spawn } from "child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { pathToFileURL } from "url"; + +interface JsonRpcMessage { + jsonrpc?: string; + id?: number | string; + method?: string; + params?: any; + result?: any; + error?: any; +} + +// Default LSP-side config we hand back on workspace/configuration. Keep +// in sync with the server's defaultServerConfiguration shape (the +// destructuring in loadConfig() will throw on a missing key). +const DEFAULT_LSP_CONFIG = { + completion: { addParamsToFunctions: false }, + hovering: { addCommentsToFunctions: false }, + formatter: { + enabled: false, verbose: false, executable: "clang-format", + ignoredGlobs: [], style: {}, + }, + compiler: { + enabled: true, os: null, verbose: false, reportWarnings: true, + nwnHome: "", nwnInstallation: "", + }, +}; + +class LspClient { + private buffer = Buffer.alloc(0); + private nextId = 1; + private pending = new Map void>(); + private notifications: JsonRpcMessage[] = []; + private notificationListeners: ((m: JsonRpcMessage) => void)[] = []; + + constructor(private readonly proc: ChildProcess) { + proc.stdout!.on("data", (chunk: Buffer) => this.onData(chunk)); + proc.stderr!.on("data", (chunk: Buffer) => { + if (process.env.LSP_VERBOSE) { + process.stderr.write(`[server stderr] ${chunk.toString()}`); + } + }); + if (process.env.LSP_VERBOSE) { + this.notificationListeners.push((m) => { + if (m.method === "window/logMessage" || m.method === "window/showMessage") { + process.stderr.write(`[server log] ${m.params?.message}\n`); + } + }); + } + } + + private onData(chunk: Buffer) { + this.buffer = Buffer.concat([this.buffer, chunk]); + while (true) { + const headerEnd = this.buffer.indexOf("\r\n\r\n"); + if (headerEnd < 0) return; + const header = this.buffer.slice(0, headerEnd).toString(); + const m = /Content-Length: (\d+)/.exec(header); + if (!m) { + this.buffer = this.buffer.slice(headerEnd + 4); + continue; + } + const bodyLen = Number(m[1]); + const bodyStart = headerEnd + 4; + if (this.buffer.length < bodyStart + bodyLen) return; + const body = this.buffer.slice(bodyStart, bodyStart + bodyLen).toString(); + this.buffer = this.buffer.slice(bodyStart + bodyLen); + try { + const msg = JSON.parse(body) as JsonRpcMessage; + this.dispatch(msg); + } catch { + // ignore + } + } + } + + private dispatch(msg: JsonRpcMessage) { + if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) { + const cb = this.pending.get(msg.id as number); + if (cb) { + this.pending.delete(msg.id as number); + cb(msg); + } + } else if (msg.method) { + if (msg.id !== undefined) { + this.send({ jsonrpc: "2.0", id: msg.id, result: this.replyTo(msg) }); + } else { + this.notifications.push(msg); + for (const fn of this.notificationListeners) fn(msg); + } + } + } + + /** Default replies for server-initiated requests. */ + private replyTo(msg: JsonRpcMessage): any { + if (msg.method === "workspace/configuration") { + // Return one object per requested section. The server unwraps a + // single-section getConfiguration() to result[0], so each item + // must be the full default config or destructuring blows up. + const items = msg.params?.items ?? []; + return items.map(() => DEFAULT_LSP_CONFIG); + } + if (msg.method === "window/workDoneProgress/create") return null; + if (msg.method === "client/registerCapability") return null; + if (msg.method === "client/unregisterCapability") return null; + return null; + } + + private send(msg: JsonRpcMessage) { + const body = JSON.stringify(msg); + const header = `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n`; + this.proc.stdin!.write(header + body); + } + + request(method: string, params: any): Promise { + const id = this.nextId++; + return new Promise((resolve) => { + this.pending.set(id, resolve); + this.send({ jsonrpc: "2.0", id, method, params }); + }); + } + + notify(method: string, params: any) { + this.send({ jsonrpc: "2.0", method, params }); + } + + /** + * Wait for the next publishDiagnostics matching `uri`. Resolves with + * the diagnostics array once one arrives. Times out after `ms`. + */ + waitForDiagnostics(uri: string, ms = 5000): Promise { + return new Promise((resolve, reject) => { + const matchInPast = this.notifications.find( + (n) => n.method === "textDocument/publishDiagnostics" && n.params?.uri === uri + ); + if (matchInPast) { + return resolve(matchInPast.params.diagnostics as any[]); + } + const timer = setTimeout(() => { + const idx = this.notificationListeners.indexOf(handler); + if (idx >= 0) this.notificationListeners.splice(idx, 1); + reject(new Error(`Timed out waiting for diagnostics on ${uri}`)); + }, ms); + const handler = (n: JsonRpcMessage) => { + if (n.method === "textDocument/publishDiagnostics" && n.params?.uri === uri) { + clearTimeout(timer); + const idx = this.notificationListeners.indexOf(handler); + if (idx >= 0) this.notificationListeners.splice(idx, 1); + resolve(n.params.diagnostics as any[]); + } + }; + this.notificationListeners.push(handler); + }); + } + + clearNotifications() { + this.notifications = []; + } +} + +describe("LSP integration via JSON-RPC", function () { + // The server boots a child cluster for indexing - give it a window. + this.timeout(30000); + + let workspace: string; + let proc: ChildProcess; + let client: LspClient; + + const NWSCRIPT_NSS = ` +int Nonsense(int n); +int IntFn(int n); +string StringFn(string s); +void VoidFn(); +`.trim(); + + before("set up workspace and start server", async () => { + workspace = mkdtempSync(join(tmpdir(), "nwscript-lsp-")); + writeFileSync(join(workspace, "nwscript.nss"), NWSCRIPT_NSS); + + const serverPath = join(__dirname, "..", "out", "server.js"); + proc = spawn("node", [serverPath, "--stdio"], { + stdio: ["pipe", "pipe", "pipe"], + cwd: workspace, + }); + client = new LspClient(proc); + + const initResp = await client.request("initialize", { + processId: process.pid, + rootPath: workspace, + rootUri: pathToFileURL(workspace).href, + capabilities: { + textDocument: { + publishDiagnostics: {}, + synchronization: { didSave: true, willSave: false, dynamicRegistration: false }, + }, + workspace: { + workspaceFolders: true, + configuration: true, + }, + window: { + workDoneProgress: true, + }, + }, + workspaceFolders: [{ uri: pathToFileURL(workspace).href, name: "test" }], + }); + expect(initResp.result?.capabilities).to.exist; + + client.notify("initialized", {}); + + // Give the indexer cluster a moment to finish (configLoaded gates + // diagnostic publish). 1.5s is comfortable on cold hardware. + await new Promise((r) => setTimeout(r, 1500)); + }); + + after("shut down", async () => { + if (proc && !proc.killed) { + try { + await client.request("shutdown", null); + client.notify("exit", null); + } catch { /* ignore */ } + proc.kill(); + } + if (workspace) rmSync(workspace, { recursive: true, force: true }); + }); + + function openDocument(name: string, content: string): string { + const path = join(workspace, name); + writeFileSync(path, content); + const uri = pathToFileURL(path).href; + client.notify("textDocument/didOpen", { + textDocument: { uri, languageId: "nwscript", version: 1, text: content }, + }); + return uri; + } + + function changeDocument(uri: string, content: string, version = 2) { + client.notify("textDocument/didChange", { + textDocument: { uri, version }, + contentChanges: [{ text: content }], + }); + } + + /** + * The LSP only re-runs diagnostics on save (or open), not on every + * change - so for tests that exercise an edit-then-rediagnose flow, + * we have to follow up with a didSave too. + */ + function saveDocument(uri: string, content: string) { + client.notify("textDocument/didSave", { + textDocument: { uri }, + text: content, + }); + } + + it("publishes empty diagnostics for a clean file", async () => { + const uri = openDocument("clean.nss", "void main() { int x = 1 + 2; }"); + const diags = await client.waitForDiagnostics(uri); + expect(diags).to.have.lengthOf(0); + }); + + it("publishes a type-error diagnostic on a broken file", async () => { + const uri = openDocument("broken.nss", 'void main() { int x = "wrong"; }'); + const diags = await client.waitForDiagnostics(uri); + expect(diags.length).to.be.at.least(1); + expect(diags[0].message).to.include("MISMATCHED TYPES"); + expect(diags[0].severity).to.equal(1); // Error + }); + + it("publishes multiple diagnostics in one pass", async () => { + const uri = openDocument("multi.nss", + 'void main() { int x = "a"; int y = "b"; int z = "c"; }'); + const diags = await client.waitForDiagnostics(uri); + expect(diags.length).to.be.at.least(3); + }); + + it("validates an include-only file (no main) without complaint", async () => { + const uri = openDocument("inc_helper.nss", + "int helper(int n) { return n * 2; }"); + const diags = await client.waitForDiagnostics(uri); + expect(diags).to.have.lengthOf(0); + }); + + it("clears diagnostics after the user fixes and saves the source", async () => { + const path = join(workspace, "editing.nss"); + const uri = openDocument("editing.nss", + 'void main() { int x = "wrong"; }'); + const broken = await client.waitForDiagnostics(uri); + expect(broken.length).to.be.at.least(1); + + client.clearNotifications(); + const fixedContent = "void main() { int x = 1; }"; + changeDocument(uri, fixedContent); + // The diagnostic provider reads from disk, so the saved file must + // actually reflect the new content before the save event fires. + writeFileSync(path, fixedContent); + saveDocument(uri, fixedContent); + const fixed = await client.waitForDiagnostics(uri); + expect(fixed).to.have.lengthOf(0); + }); + + it("routes errors in #include'd files to the include's URI", async () => { + const libUri = openDocument("buggy_lib.nss", + "int helper() { return BadId(); }"); + // Open a main that uses the broken lib. The compiler should report + // lib's error - either against the lib's URI or against main's, + // depending on how the LSP routes it. + const mainUri = openDocument("main_using_lib.nss", + '#include "buggy_lib"\nvoid main() { int x = helper(); }'); + + // Wait briefly for both files' diagnostics to settle. + const allDiags = await Promise.race([ + Promise.all([ + client.waitForDiagnostics(libUri, 3000), + client.waitForDiagnostics(mainUri, 3000), + ]), + new Promise((r) => setTimeout(() => r([[], []]), 3500)), + ]); + const flat = allDiags.flat(); + const messages = flat.map((d: any) => d.message).join("\n"); + expect(flat.length).to.be.at.least(1, `expected some diagnostic, got: ${messages}`); + }); +}); From 450f8d3dfbbca78ac8cd65b2174b306dd0c89a1d Mon Sep 17 00:00:00 2001 From: PhilippeChab Date: Sat, 2 May 2026 16:33:55 -0400 Subject: [PATCH 4/8] Include server/wasm/ in the published .vsix .vscodeignore broad-excludes server/** and re-includes specific paths, but server/wasm wasn't in the re-include list. Result: the packaged extension was missing the WASM compiler entirely - the installed v3.0.1 logs "Cannot find module 'server/wasm/nwscript_ compiler.js'" on every save and produces no diagnostics. Adding server/wasm to the un-ignore list fixes future packages. --- .vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscodeignore b/.vscodeignore index 068d8b0..3e5ba84 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -17,3 +17,4 @@ server/** !server/out/server.js !server/out/indexer.js !server/resources/** +!server/wasm/** From 874fceb0dc2a488723b6d71ab6d4dec8d3b4238e Mon Sep 17 00:00:00 2001 From: PhilippeChab Date: Sat, 2 May 2026 16:46:52 -0400 Subject: [PATCH 5/8] Update WASM with type-check-helpers fix Helper / include files now get their semantic errors reported in multi-error mode. Previously the compiler skipped the tree walk entirely when no entry point was found, so a user editing a helper function would see no diagnostics on type errors. --- server/wasm/nwscript_compiler.wasm | Bin 215005 -> 215032 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/server/wasm/nwscript_compiler.wasm b/server/wasm/nwscript_compiler.wasm index 344ec14029aef5bc7602b8a5ec470910e19259c6..7dd80bcbff092116592efce64943aa6c40a000c2 100755 GIT binary patch delta 81 zcmccH&- Date: Sat, 2 May 2026 17:16:13 -0400 Subject: [PATCH 6/8] Fix double-free of resolver buffer that crashed compiles The resolver callback freed the previous delivery buffer before allocating a new one, then pushed each allocation onto an `owned` list. At end of compile, every entry in `owned` was freed - so every buffer except the last got freed twice. The corrupted heap state didn't surface inside the same compile; it manifested as "memory access out of bounds" one or two compiles later, when malloc returned a bad pointer and a subsequent write trampled something it shouldn't. Reported on cmds_player.nss with a chained #include "inc_roles" -> consts_roles -> consts_module: each save of a .nss file in the workspace would resolve a few includes, leak double-frees, and eventually crash. Fix: drop the owned list. Free only the most recent delivery buffer at the end of compile, since each loadCb call already freed the previous one before allocating its own. Added a regression test that compiles a 4-file include chain ten times in a row and asserts no crash + no diagnostics. Without the fix, this surfaces the heap corruption reliably by iter 3-4. --- package.json | 2 +- server/src/Providers/DiagnosticsProvider.ts | 13 ++++++--- server/test/lsp_integration_test.ts | 32 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7e907af..e7b4dd1 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/PhilippeChab/nwscript-ee-language-server" }, "license": "MIT", - "version": "2.2.1", + "version": "3.0.1", "author": { "name": "Philippe Chabot" }, diff --git a/server/src/Providers/DiagnosticsProvider.ts b/server/src/Providers/DiagnosticsProvider.ts index fb0ccc6..0d92d45 100644 --- a/server/src/Providers/DiagnosticsProvider.ts +++ b/server/src/Providers/DiagnosticsProvider.ts @@ -76,7 +76,6 @@ export default class DiagnoticsProvider extends Provider { let deliveryBuf = 0; let loadCb = 0; let writeCb = 0; - const owned: number[] = []; try { // Resolver: hand the compiler script source, looking it up in @@ -91,10 +90,16 @@ export default class DiagnoticsProvider extends Provider { } if (src === null) return 0; + // Reuse a single buffer per compile; free the previous + // request's bytes before allocating the next. A previous + // version pushed every allocation into a list and freed + // them all at the end of compile, which double-freed every + // buffer except the last one and corrupted the WASM heap. + // The corruption only surfaced one or two compiles later + // as "memory access out of bounds". const len = Module.lengthBytesUTF8(src); if (deliveryBuf) Module._free(deliveryBuf); deliveryBuf = Module._malloc(len + 1); - owned.push(deliveryBuf); Module.stringToUTF8(src, deliveryBuf, len + 1); Module._scriptCompApiDeliverFile(compilerPtr, deliveryBuf, len); return 1; @@ -163,8 +168,8 @@ export default class DiagnoticsProvider extends Provider { try { Module._scriptCompApiDestroyCompiler(compilerPtr); } catch { /* ignore */ } } - for (const p of owned) { - try { Module._free(p); } catch { /* ignore */ } + if (deliveryBuf) { + try { Module._free(deliveryBuf); } catch { /* ignore */ } } if (loadCb) Module.removeFunction(loadCb); if (writeCb) Module.removeFunction(writeCb); diff --git a/server/test/lsp_integration_test.ts b/server/test/lsp_integration_test.ts index 8420107..d3136a8 100644 --- a/server/test/lsp_integration_test.ts +++ b/server/test/lsp_integration_test.ts @@ -314,6 +314,38 @@ void VoidFn(); expect(fixed).to.have.lengthOf(0); }); + it("survives many compiles with deeply chained includes", async () => { + // Earlier the diagnostics provider double-freed the resolver's + // delivery buffer once per #include, corrupting the WASM heap. + // It usually surfaced two-or-three compiles later as "memory + // access out of bounds". Trigger a long include chain repeatedly + // and make sure no compile crashes. + writeFileSync(join(workspace, "leaf.nss"), + "const int LEAF_VAL = 1;"); + writeFileSync(join(workspace, "mid.nss"), + '#include "leaf"\nconst int MID_VAL = 2;'); + writeFileSync(join(workspace, "root_inc.nss"), + '#include "mid"\nconst int ROOT_VAL = 3;'); + const uri = openDocument("uses_chain.nss", + '#include "root_inc"\nvoid main() { int x = LEAF_VAL + MID_VAL + ROOT_VAL; }'); + + // First publish + let diags = await client.waitForDiagnostics(uri); + expect(diags).to.have.lengthOf(0); + + // Resave a bunch of times - any heap corruption would show up + // here as a sendDiagnostics with a "memory access out of bounds" + // error message OR no diagnostics arriving at all. + for (let i = 0; i < 10; i++) { + client.clearNotifications(); + saveDocument(uri, + '#include "root_inc"\nvoid main() { int x = LEAF_VAL + MID_VAL + ROOT_VAL; }'); + diags = await client.waitForDiagnostics(uri, 3000); + expect(diags).to.have.lengthOf(0, + `iter ${i}: expected clean compile, got: ${JSON.stringify(diags)}`); + } + }); + it("routes errors in #include'd files to the include's URI", async () => { const libUri = openDocument("buggy_lib.nss", "int helper() { return BadId(); }"); From fc7621a5703d20f10899ee6e300d23acd160919d Mon Sep 17 00:00:00 2001 From: PhilippeChab Date: Sat, 2 May 2026 17:36:51 -0400 Subject: [PATCH 7/8] Update WASM with iterative WalkParseTree / DeleteParseTree --- server/wasm/nwscript_compiler.wasm | Bin 215032 -> 215133 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/server/wasm/nwscript_compiler.wasm b/server/wasm/nwscript_compiler.wasm index 7dd80bcbff092116592efce64943aa6c40a000c2..0fa9fad0d2a5bb1a76805c36e2cbd64c99e8d758 100755 GIT binary patch delta 11445 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 Zbn)F;5#q1DNozvQfXbZnVfvpR{69kJz4ZV9 delta 11663 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 z Date: Sat, 2 May 2026 17:44:03 -0400 Subject: [PATCH 8/8] Resolve stock NWN scripts from nwnInstallation / nwnHome The old nwnsc-based diagnostics handed -h / -n flags to the binary, which then read scripts directly out of nwn_base.bif and friends. The WASM build can't open BIF archives, but Beamdog also ships the same scripts extracted under data/base_scripts/ and ovr/. Those directories are what the existing compiler.nwnInstallation / compiler.nwnHome settings already point at, so the resolver now walks them as a fallback when a script isn't in the workspace. Indexed once on first miss and cached for the life of the LSP process. The user-visible effect: #include "x0_i0_stringlib" and the rest of the stock NWN library now resolve, so files that depend on them (most real NWN modules) actually validate instead of bailing with "FILE NOT FOUND". The override directory under nwnHome is also indexed so per-game local edits show up. Workspace files still take precedence over both - if you've copied a stock script into your workspace and modified it, the workspace copy wins. Reload the LSP window after changing the NWN paths in settings; the index is built once per process. --- server/src/Providers/DiagnosticsProvider.ts | 90 +++++++++++++++++++-- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/server/src/Providers/DiagnosticsProvider.ts b/server/src/Providers/DiagnosticsProvider.ts index 0d92d45..352b281 100644 --- a/server/src/Providers/DiagnosticsProvider.ts +++ b/server/src/Providers/DiagnosticsProvider.ts @@ -1,4 +1,4 @@ -import { readFileSync } from "fs"; +import { existsSync, readFileSync, readdirSync, statSync } from "fs"; import { basename, join } from "path"; import { fileURLToPath, pathToFileURL } from "url"; import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver"; @@ -21,6 +21,12 @@ type FilesDiagnostics = { [uri: string]: Diagnostic[] }; export default class DiagnoticsProvider extends Provider { private modulePromise: Promise | null = null; + // {scriptName -> absolutePath} for stock NWN scripts found under the + // configured nwnHome / nwnInstallation directories. Built lazily on + // first miss in the workspace, then reused. Old nwnsc resolved these + // automatically by reading the BIF/KEY archives; we instead look at + // the directories Beamdog ships extracted. + private nwnScriptIndex: Map | null = null; constructor(server: ServerManager) { super(server); @@ -42,15 +48,85 @@ export default class DiagnoticsProvider extends Provider { } /** - * Resolve a script name to its source bytes. Looks up `name` (no - * extension) via the workspace-file-system glob, returns null if - * not found or unreadable. + * Walk a directory tree once and return a {scriptName -> absPath} map + * of every .nss file found. Used to seed the NWN-install fallback + * index so the resolver can hand the compiler stock scripts that + * normally live inside Beamdog's BIF archives. + */ + private indexNssDir(root: string): Map { + const out = new Map(); + const stack = [root]; + while (stack.length) { + const dir = stack.pop()!; + let entries: string[] = []; + try { entries = readdirSync(dir); } catch { continue; } + for (const name of entries) { + const full = join(dir, name); + try { + const st = statSync(full); + if (st.isDirectory()) { + stack.push(full); + } else if (st.isFile() && name.toLowerCase().endsWith(".nss")) { + const key = name.slice(0, -4).toLowerCase(); + // Workspace overrides win, so we don't overwrite an + // earlier entry. Stock scripts sit under predictable + // dirs but order doesn't matter much in practice. + if (!out.has(key)) out.set(key, full); + } + } catch { /* skip unreadable entries */ } + } + } + return out; + } + + /** + * Build the NWN-install lookup index from configured nwnHome / + * nwnInstallation directories. Cheap on subsequent calls thanks to + * caching - if the user changes the config we'll pick up new files + * by clearing the cache, which we do on every publish. + */ + private buildNwnScriptIndex(): Map { + const index = new Map(); + const { nwnHome, nwnInstallation } = this.server.config.compiler; + const candidates: string[] = []; + if (nwnInstallation) { + // Beamdog's install ships extracted base scripts under + // data/base_scripts and ovr/. Both are worth indexing. + candidates.push(join(nwnInstallation, "data")); + candidates.push(join(nwnInstallation, "ovr")); + } + if (nwnHome) { + // User's per-game scripts and overrides. + candidates.push(join(nwnHome, "override")); + candidates.push(join(nwnHome, "development")); + } + for (const root of candidates) { + if (!existsSync(root)) continue; + for (const [k, v] of this.indexNssDir(root)) { + if (!index.has(k)) index.set(k, v); + } + } + return index; + } + + /** + * Resolve a script name to its source bytes. Tries the workspace + * first (via the existing glob), then falls back to the NWN install + * index for stock includes like x0_i0_stringlib. */ private resolveScriptSource(name: string): string | null { try { - const path = this.server.workspaceFilesSystem?.getFilePath(name); - if (!path) return null; - return readFileSync(path).toString(); + const ws = this.server.workspaceFilesSystem?.getFilePath(name); + if (ws) return readFileSync(ws).toString(); + } catch { /* fall through to NWN index */ } + + if (!this.nwnScriptIndex) { + this.nwnScriptIndex = this.buildNwnScriptIndex(); + } + const stockPath = this.nwnScriptIndex.get(name.toLowerCase()); + if (!stockPath) return null; + try { + return readFileSync(stockPath).toString(); } catch { return null; }