From 61212dea4927fa755d9d5eae0e6a36970f6c7150 Mon Sep 17 00:00:00 2001 From: PhilippeChab Date: Tue, 27 Jan 2026 19:06:57 -0500 Subject: [PATCH] Use AST Tree from compiler for tokenization --- client/tsconfig.json | 4 +- server/scripts/GenerateLibDefinitions.ts | 24 +- server/src/Documents/DocumentsCollection.ts | 12 +- server/src/Documents/DocumentsIndexer.ts | 4 +- server/src/Parser/ASTPositionQuery.ts | 298 +++ server/src/Parser/ASTTraversal.ts | 523 +++++ server/src/Parser/ASTTypes.ts | 131 ++ server/src/Parser/NWScriptParser.ts | 232 ++ server/src/Providers/Builders/Builder.ts | 10 +- .../Builders/CompletionItemBuilder.ts | 18 +- .../Providers/Builders/HoverContentBuilder.ts | 21 +- .../Builders/SignatureHelpBuilder.ts | 10 +- .../src/Providers/Builders/SymbolBuilder.ts | 51 +- .../src/Providers/CompletionItemsProvider.ts | 13 +- server/src/Providers/DiagnosticsProvider.ts | 24 +- .../src/Providers/GotoDefinitionProvider.ts | 38 +- server/src/Providers/HoverContentProvider.ts | 16 +- server/src/Providers/SignatureHelpProvider.ts | 16 +- server/src/Providers/SymbolsProvider.ts | 4 +- server/src/ServerManager/ServerManager.ts | 46 +- server/src/Tokenizer/Tokenizer.ts | 567 ++--- server/src/Tokenizer/types.ts | 8 +- server/src/server.ts | 2 +- server/test/ast_integration_test.ts | 206 ++ server/test/ast_tokenization_test.ts | 259 +++ server/test/static/globalScopeTokens.json | 1565 ++++++++----- .../static/localScopeTokensWithContext.json | 1765 +++++++++++--- .../localScopeTokensWithoutContext.json | 2048 ++++++++++++----- server/test/static/test.nss | 10 + server/test/tokenization_test.ts | 40 +- server/tsconfig.json | 7 +- server/wasm/nwscript_compiler.js | 2 + server/wasm/nwscript_compiler.wasm | Bin 0 -> 307952 bytes 33 files changed, 5946 insertions(+), 2028 deletions(-) create mode 100644 server/src/Parser/ASTPositionQuery.ts create mode 100644 server/src/Parser/ASTTraversal.ts create mode 100644 server/src/Parser/ASTTypes.ts create mode 100644 server/src/Parser/NWScriptParser.ts create mode 100644 server/test/ast_integration_test.ts create mode 100644 server/test/ast_tokenization_test.ts create mode 100644 server/wasm/nwscript_compiler.js create mode 100755 server/wasm/nwscript_compiler.wasm diff --git a/client/tsconfig.json b/client/tsconfig.json index 42fbe60..89106d1 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -8,7 +8,9 @@ "rootDir": "src", "strict": true, "allowSyntheticDefaultImports": true, - "esModuleInterop": true + "esModuleInterop": true, + "composite": true, + "declaration": true }, "include": ["src"], "exclude": ["node_modules"] diff --git a/server/scripts/GenerateLibDefinitions.ts b/server/scripts/GenerateLibDefinitions.ts index 81e2f5f..a7218ee 100644 --- a/server/scripts/GenerateLibDefinitions.ts +++ b/server/scripts/GenerateLibDefinitions.ts @@ -10,7 +10,7 @@ const generateDefinitions = async () => { console.log("Generating nwscript.nss definitions ..."); const lib = readFileSync(normalize(join(__dirname, "./nwscript.nss"))).toString(); - const definitions = tokenizer.tokenizeContent(lib, TokenizedScope.global); + const definitions = await tokenizer.tokenizeContent(lib, TokenizedScope.global); writeFileSync(normalize(join(__dirname, "../resources/standardLibDefinitions.json")), JSON.stringify(definitions, null, 4)); console.log("Done."); @@ -20,27 +20,23 @@ const generateDefinitions = async () => { let directoryPath = normalize(join(__dirname, "base_scripts")); let files = readdirSync(directoryPath); - files.forEach((filename) => { + for (const filename of files) { const fileSource = join(normalize(join(__dirname, "base_scripts", filename))); const fileDestination = join(normalize(join(__dirname, "../resources/base_scripts", filename.replace(".nss", ".json")))); const lib = readFileSync(fileSource).toString(); // Skip main files if (!lib.includes("main")) { - const definitions = tokenizer.tokenizeContent(lib, TokenizedScope.global); - if ( - definitions.children.length === 0 && - definitions.complexTokens.length === 0 && - definitions.structComplexTokens.length === 0 - ) { - return; + const definitions = await tokenizer.tokenizeContent(lib, TokenizedScope.global); + if (definitions.children.length === 0 && definitions.complexTokens.length === 0 && definitions.structComplexTokens.length === 0) { + continue; } console.log(`Generating ${filename} ...`); filesCount++; writeFileSync(fileDestination, JSON.stringify(definitions, null, 4)); } - }); + } console.log(`Generated ${filesCount} files.`); console.log("Done."); @@ -49,18 +45,18 @@ const generateDefinitions = async () => { directoryPath = normalize(join(__dirname, "ovr")); files = readdirSync(directoryPath); - files.forEach((filename) => { + for (const filename of files) { const fileSource = join(normalize(join(__dirname, "ovr", filename))); const fileDestination = join(normalize(join(__dirname, "../resources/ovr", filename.replace(".nss", ".json")))); const lib = readFileSync(fileSource).toString(); - const definitions = tokenizer.tokenizeContent(lib, TokenizedScope.global); + const definitions = await tokenizer.tokenizeContent(lib, TokenizedScope.global); console.log(`Generating ${filename} ...`); filesCount++; writeFileSync(fileDestination, JSON.stringify(definitions, null, 4)); - }); + } console.log(`Generated ${filesCount} files.`); console.log("Done."); }; -generateDefinitions(); +void generateDefinitions(); diff --git a/server/src/Documents/DocumentsCollection.ts b/server/src/Documents/DocumentsCollection.ts index 6cecd6b..e110ba2 100644 --- a/server/src/Documents/DocumentsCollection.ts +++ b/server/src/Documents/DocumentsCollection.ts @@ -25,7 +25,7 @@ export default class DocumentsCollection extends Dictionnary { const directoryPath = normalize(join(__dirname, "..", "resources", static_resources_folder)); const files = readdirSync(directoryPath); files.forEach((filename) => { - const tokens = JSON.parse(readFileSync(join(__dirname, "..", "resources", static_resources_folder, filename)).toString()) as any as GlobalScopeTokenizationResult; + const tokens = JSON.parse(readFileSync(join(__dirname, "..", "resources", static_resources_folder, filename)).toString()) as GlobalScopeTokenizationResult; this.addDocument(this.initializeDocument(`${STATIC_PREFIX}/${filename.replace(".json", FILES_EXTENSION)}`, true, tokens)); }); }); @@ -52,7 +52,7 @@ export default class DocumentsCollection extends Dictionnary { if (this.get(this.getKey(uri, false))) return; const fileContent = readFileSync(filePath).toString(); - this.createDocuments(uri, fileContent, tokenizer, workespaceFilesSystem); + void this.createDocuments(uri, fileContent, tokenizer, workespaceFilesSystem); }); } @@ -72,16 +72,16 @@ export default class DocumentsCollection extends Dictionnary { this.addDocument(this.initializeDocument(uri, false, globalScope)); } - public createDocuments(uri: string, content: string, tokenizer: Tokenizer, workespaceFilesSystem: WorkspaceFilesSystem) { - const globalScope = tokenizer.tokenizeContent(content, TokenizedScope.global); + public async createDocuments(uri: string, content: string, tokenizer: Tokenizer, workespaceFilesSystem: WorkspaceFilesSystem) { + const globalScope = await tokenizer.tokenizeContent(content, TokenizedScope.global); this.addDocument(this.initializeDocument(uri, false, globalScope)); this.createChildrenDocument(globalScope.children, tokenizer, workespaceFilesSystem); } - public updateDocument(document: TextDocument, tokenizer: Tokenizer, workespaceFilesSystem: WorkspaceFilesSystem) { + public async updateDocument(document: TextDocument, tokenizer: Tokenizer, workespaceFilesSystem: WorkspaceFilesSystem) { const currentChildren = this.getFromUri(document.uri)?.children; - const globalScope = tokenizer.tokenizeContent(document.getText(), TokenizedScope.global); + const globalScope = await tokenizer.tokenizeContent(document.getText(), TokenizedScope.global); const newChildren = globalScope.children.filter((child) => !currentChildren!.includes(child)); this.overwriteDocument(this.initializeDocument(document.uri, false, globalScope)); diff --git a/server/src/Documents/DocumentsIndexer.ts b/server/src/Documents/DocumentsIndexer.ts index fd888eb..747f088 100644 --- a/server/src/Documents/DocumentsIndexer.ts +++ b/server/src/Documents/DocumentsIndexer.ts @@ -13,7 +13,7 @@ const generateTokens = async (filesPath: string[]) => { for (let i = 0; i < filesPath.length; i++) { const filePath = filesPath[i]; const fileContent = readFileSync(filePath).toString(); - const globalScope = tokenizer.tokenizeContent(fileContent, TokenizedScope.global); + const globalScope = await tokenizer.tokenizeContent(fileContent, TokenizedScope.global); process?.send!(JSON.stringify({ filePath, globalScope })); } @@ -22,5 +22,5 @@ const generateTokens = async (filesPath: string[]) => { }; process.on("message", (filesPath: string) => { - generateTokens(filesPath.split(",")); + void generateTokens(filesPath.split(",")); }); diff --git a/server/src/Parser/ASTPositionQuery.ts b/server/src/Parser/ASTPositionQuery.ts new file mode 100644 index 0000000..c8f1919 --- /dev/null +++ b/server/src/Parser/ASTPositionQuery.ts @@ -0,0 +1,298 @@ +import { CompletionItemKind } from "vscode-languageserver"; +import type { ASTNode, AST } from "./ASTTypes"; +import { ASTOperation } from "./ASTTypes"; + +/** + * Position-based query service for AST. + * Provides LSP position-based features using the AST tree structure. + */ +export class ASTPositionQuery { + constructor(private readonly ast: AST) {} + + /** + * Find the most specific (deepest) node at the given position. + * Prefers nodes with identifiers (stringData) over structural nodes. + * + * @param line - 0-indexed line number + * @param char - 0-indexed character position + * @returns The most specific node at this position, or null + */ + public findNodeAtPosition(line: number, char: number): ASTNode | null { + // AST uses 1-indexed lines and chars, convert from 0-indexed LSP positions + const astLine = line + 1; + const astChar = char + 1; + + const candidates: Array<{ node: ASTNode; depth: number; distance: number }> = []; + + // Collect all nodes at or before this position with their depth + const traverse = (node: ASTNode | null, depth: number) => { + if (!node) return; + + const nodeBeforeTarget = node.position.line < astLine || (node.position.line === astLine && node.position.char <= astChar); + + if (nodeBeforeTarget) { + // Calculate distance from target position + const lineDist = Math.abs(node.position.line - astLine); + const charDist = node.position.line === astLine ? Math.abs(node.position.char - astChar) : 1000; + const distance = lineDist * 1000 + charDist; + + candidates.push({ node, depth, distance }); + } + + // Always traverse both children to explore all branches + traverse(node.left, depth + 1); + traverse(node.right, depth + 1); + }; + + traverse(this.ast.ast, 0); + + if (candidates.length === 0) return null; + + // Sort by specificity: + // 1. Prefer exact position matches (distance = 0) + // 2. Prefer deeper nodes (more specific) + // 3. Prefer nodes with stringData (identifiers, literals) + // 4. Prefer closer position (smaller distance) + candidates.sort((a, b) => { + // Exact position match? + const aExact = a.distance === 0; + const bExact = b.distance === 0; + + if (aExact && !bExact) return -1; + if (!aExact && bExact) return 1; + + // Prefer deeper nodes (more specific in the tree) + if (a.depth !== b.depth) { + return b.depth - a.depth; + } + + // Prefer nodes with stringData (actual tokens) + const aHasData = !!a.node.stringData; + const bHasData = !!b.node.stringData; + + if (aHasData && !bHasData) return -1; + if (!aHasData && bHasData) return 1; + + // Prefer closer position + return a.distance - b.distance; + }); + + return candidates[0].node; + } + + /** + * Get action target at position with optional offset. + * Returns information about the token at or near the cursor. + * + * @param line - 0-indexed line number + * @param char - 0-indexed character position + * @param offset - Offset to apply (not implemented yet, for compatibility) + * @returns Token information + */ + public getActionTargetAtPosition( + line: number, + char: number, + offset: number = 0, + ): { + tokenType: CompletionItemKind | undefined; + lookBehindRawContent: string | undefined; + rawContent: string | undefined; + } { + const node = this.findNodeAtPosition(line, char); + + if (!node) { + return { + tokenType: undefined, + lookBehindRawContent: undefined, + rawContent: undefined, + }; + } + + // Determine token type from AST operation + let tokenType: CompletionItemKind | undefined; + const rawContent = node.stringData; + + switch (node.operation) { + case ASTOperation.VARIABLE: + tokenType = CompletionItemKind.Variable; + break; + case ASTOperation.FUNCTION_IDENTIFIER: + case "ACTION_ID": + tokenType = CompletionItemKind.Function; + break; + case ASTOperation.KEYWORD_STRUCT: + tokenType = CompletionItemKind.Struct; + break; + case ASTOperation.CONSTANT_INTEGER: + case ASTOperation.CONSTANT_FLOAT: + case ASTOperation.CONSTANT_STRING: + tokenType = CompletionItemKind.Constant; + break; + default: + tokenType = undefined; + } + + // For struct property access, need to find the struct variable + let lookBehindRawContent: string | undefined; + if (this.isInStructPropertyAccess(node)) { + lookBehindRawContent = this.findStructVariableName(node); + } + + return { + tokenType, + lookBehindRawContent, + rawContent, + }; + } + + /** + * Check if position is within a specific scope (e.g., function call). + * + * @param line - 0-indexed line number + * @param char - 0-indexed character position + * @param scopeType - The scope type to check for + * @returns True if position is within the scope + */ + public isInScope(line: number, char: number, scopeType: string): boolean { + const node = this.findNodeAtPosition(line, char); + if (!node) return false; + + // Check if we're in a function call scope + if (scopeType === "function.call" || scopeType.includes("functionCall")) { + return this.isInFunctionCall(node); + } + + return false; + } + + /** + * Find function name when cursor is inside a function call. + * Used for signature help. + * + * @param line - 0-indexed line number + * @param char - 0-indexed character position + * @returns Function name or undefined + */ + public getFunctionNameAtPosition(line: number, char: number): string | undefined { + const node = this.findNodeAtPosition(line, char); + if (!node) return undefined; + + // Look for ACTION node containing this position + const action = this.findAncestorOfType(node, "ACTION"); + if (!action) return undefined; + + // Find ACTION_ID child + const actionId = this.findChildOfType(action, "ACTION_ID"); + return actionId?.stringData; + } + + /** + * Count the number of arguments before the current position in a function call. + * Used to determine which parameter is active in signature help. + * + * @param line - 0-indexed line number + * @param char - 0-indexed character position + * @returns Number of commas found (= argument index) + */ + public getActiveParameterIndex(line: number, char: number): number { + const node = this.findNodeAtPosition(line, char); + if (!node) return 0; + + // Find the ACTION (function call) containing this position + const action = this.findAncestorOfType(node, "ACTION"); + if (!action) return 0; + + // Count ACTION_ARG_LIST nodes that start before the cursor position + // Each ACTION_ARG_LIST represents a parameter separator (comma) + let argIndex = 0; + const astLine = line + 1; + const astChar = char + 1; + + const countArgs = (n: ASTNode | null): void => { + if (!n) return; + + if (n.operation === "ACTION_ARG_LIST") { + // Count if this arg list starts strictly before our position + if (n.position.line < astLine || (n.position.line === astLine && n.position.char < astChar)) { + argIndex++; + } + } + + countArgs(n.left); + countArgs(n.right); + }; + + countArgs(action); + + return argIndex; + } + + // ============================================================================ + // Private Helper Methods + // ============================================================================ + + /** + * Check if node is within a function call. + */ + private isInFunctionCall(node: ASTNode): boolean { + return !!this.findAncestorOfType(node, "ACTION"); + } + + /** + * Check if node is in a struct property access context. + */ + private isInStructPropertyAccess(node: ASTNode): boolean { + // This would require checking if we're accessing a struct member + // For now, return false - implement when needed + return false; + } + + /** + * Find struct variable name for property access. + */ + private findStructVariableName(node: ASTNode): string | undefined { + // Implement when struct property access is needed + return undefined; + } + + /** + * Find ancestor node of specific type. + * Since we don't have parent pointers, we search the entire tree. + */ + private findAncestorOfType(target: ASTNode, operation: string): ASTNode | null { + const parents: ASTNode[] = []; + + const findParents = (node: ASTNode | null, path: ASTNode[]): boolean => { + if (!node) return false; + + if (node === target) { + parents.push(...path); + return true; + } + + const newPath = [...path, node]; + if (findParents(node.left, newPath)) return true; + if (findParents(node.right, newPath)) return true; + + return false; + }; + + findParents(this.ast.ast, []); + + // Find first parent with matching operation + return parents.reverse().find((p) => p.operation === operation) || null; + } + + /** + * Find first child node with specific operation type. + */ + private findChildOfType(node: ASTNode, operation: string): ASTNode | null { + if (node.left?.operation === operation) return node.left; + if (node.right?.operation === operation) return node.right; + + const leftResult = node.left ? this.findChildOfType(node.left, operation) : null; + if (leftResult) return leftResult; + + return node.right ? this.findChildOfType(node.right, operation) : null; + } +} diff --git a/server/src/Parser/ASTTraversal.ts b/server/src/Parser/ASTTraversal.ts new file mode 100644 index 0000000..b627f9b --- /dev/null +++ b/server/src/Parser/ASTTraversal.ts @@ -0,0 +1,523 @@ +import { CompletionItemKind } from "vscode-languageserver"; +import type { ComplexToken, FunctionComplexToken, FunctionParamComplexToken, StructComplexToken, VariableComplexToken, ConstantComplexToken, StructPropertyComplexToken } from "../Tokenizer/types"; +import type { ASTNode, AST } from "./ASTTypes"; +import { ASTOperation, getTypeFromNode, isOperation } from "./ASTTypes"; + +/** + * Extract ComplexTokens from an AST for global scope + */ +export function extractGlobalTokens(ast: AST): { + complexTokens: ComplexToken[]; + structComplexTokens: StructComplexToken[]; + children: string[]; +} { + const complexTokens: ComplexToken[] = []; + const structComplexTokens: StructComplexToken[] = []; + const children: string[] = []; + let inGlobalScope = false; + + function traverse(node: ASTNode | null): void { + if (!node) return; + + // Track when we're in global scope + const wasInGlobalScope = inGlobalScope; + if (isOperation(node, ASTOperation.GLOBAL_VARIABLES)) { + inGlobalScope = true; + } + + // Extract function declarations + if (isOperation(node, ASTOperation.FUNCTION)) { + const func = extractFunction(node); + if (func) { + complexTokens.push(func); + } + } + + // Extract struct definitions + if (isOperation(node, ASTOperation.STRUCTURE_DEFINITION)) { + const struct = extractStruct(node); + if (struct) { + structComplexTokens.push(struct); + } + } + + // Extract constants and global variables + if (isOperation(node, ASTOperation.KEYWORD_DECLARATION)) { + // In global scope, all declarations are treated as constants + if (inGlobalScope) { + const constant = extractGlobalVariable(node); + if (constant) { + complexTokens.push(constant); + } + } else { + // In local scope, only extract if it's explicitly const + const constant = extractConstant(node); + if (constant) { + complexTokens.push(constant); + } + } + } + + // Note: Include directives are not in the AST, they're resolved during compilation + // We would need to track them separately if needed + + // Traverse children + traverse(node.left); + traverse(node.right); + + inGlobalScope = wasInGlobalScope; + } + + traverse(ast.ast); + + return { + complexTokens, + structComplexTokens, + children, + }; +} + +/** + * Extract function information from FUNCTION node + */ +function extractFunction(node: ASTNode): FunctionComplexToken | null { + // Get function declaration + const funcDecl = node.left; + if (!funcDecl || !isOperation(funcDecl, ASTOperation.FUNCTION_DECLARATION)) { + return null; + } + + // Get function identifier node + const funcIdent = funcDecl.left; + if (!funcIdent || !isOperation(funcIdent, ASTOperation.FUNCTION_IDENTIFIER)) { + return null; + } + + // Get function name from FUNCTION_IDENTIFIER node + const identifier = funcIdent.stringData; + if (!identifier) return null; + + // Position (convert from 1-indexed to 0-indexed) + const position = { + line: funcIdent.position.line - 1, + character: funcIdent.position.char, + }; + + // Extract return type from function identifier's left child + let returnType = "void"; + if (funcIdent.left) { + const typeStr = getTypeFromNode(funcIdent.left); + if (typeStr) returnType = typeStr; + } + + // Extract parameters + const params = extractFunctionParams(funcDecl); + + // Extract comments (not available in AST, would need separate tracking) + const comments: string[] = []; + + return { + position, + identifier, + tokenType: CompletionItemKind.Function, + returnType: returnType as any, + params, + comments, + }; +} + +/** + * Extract function parameters + */ +function extractFunctionParams(funcDeclNode: ASTNode | null): FunctionParamComplexToken[] { + const params: FunctionParamComplexToken[] = []; + + function findParams(node: ASTNode | null): void { + if (!node) return; + + if (isOperation(node, ASTOperation.FUNCTION_PARAM_NAME) && node.stringData) { + // Find the parameter type by looking at the right child + let paramType = "int"; // Default + + // The type is in the right child of FUNCTION_PARAM_NAME + if (node.right) { + const typeStr = getTypeFromNode(node.right); + if (typeStr) paramType = typeStr; + } + + const param: FunctionParamComplexToken = { + position: { + line: node.position.line - 1, + character: node.position.char, + }, + identifier: node.stringData, + tokenType: CompletionItemKind.TypeParameter, + valueType: paramType as any, + }; + + params.push(param); + } + + // Don't traverse into the right child if it's a type keyword + // to avoid processing it as a parameter + if (node.right && !node.right.operation.startsWith("KEYWORD_")) { + findParams(node.right); + } + + findParams(node.left); + } + + findParams(funcDeclNode); + return params; +} + +/** + * Extract struct definition + */ +function extractStruct(node: ASTNode): StructComplexToken | null { + // Find the KEYWORD_STRUCT node which has the struct name + const structNode = findFirstNode(node, ASTOperation.KEYWORD_STRUCT); + if (!structNode || !structNode.stringData) return null; + + const identifier = structNode.stringData; + const position = { + line: structNode.position.line - 1, + character: structNode.position.char, + }; + + // Extract properties from the struct body + const properties = extractStructProperties(node); + + return { + position, + identifier, + tokenType: CompletionItemKind.Struct, + properties, + }; +} + +/** + * Extract struct properties + */ +function extractStructProperties(structDefNode: ASTNode): StructPropertyComplexToken[] { + const properties: StructPropertyComplexToken[] = []; + + function traverse(node: ASTNode | null, depth: number = 0): void { + if (!node || depth > 50) return; // Prevent infinite recursion + + // Look for KEYWORD_DECLARATION nodes within the struct + if (isOperation(node, ASTOperation.KEYWORD_DECLARATION)) { + const prop = extractStructProperty(node); + if (prop) { + properties.push(prop); + } + } + + traverse(node.left, depth + 1); + traverse(node.right, depth + 1); + } + + traverse(structDefNode.right); // Properties are typically in the right subtree + return properties; +} + +/** + * Extract a single struct property + */ +function extractStructProperty(declNode: ASTNode): StructPropertyComplexToken | null { + // Get the type from the left child + let valueType = "int"; + if (declNode.left) { + const typeStr = getTypeFromNode(declNode.left); + if (typeStr) valueType = typeStr; + } + + // Get the variable name from the right child + const varList = declNode.right; + if (!varList) return null; + + const varNode = findFirstNode(varList, ASTOperation.VARIABLE); + if (!varNode || !varNode.stringData) return null; + + return { + position: { + line: varNode.position.line - 1, + character: varNode.position.char, + }, + identifier: varNode.stringData, + tokenType: CompletionItemKind.Property, + valueType: valueType as any, + }; +} + +/** + * Extract global variable declaration (treated as constant in NWScript) + */ +function extractGlobalVariable(declNode: ASTNode): ConstantComplexToken | null { + // For global variables, the KEYWORD_DECLARATION node has stringData with the variable name + const identifier = declNode.stringData; + if (!identifier) { + // Fallback: look for VARIABLE node + const varNode = findFirstNode(declNode.right, ASTOperation.VARIABLE); + if (!varNode || !varNode.stringData) return null; + + return extractGlobalVariableFromVarNode(declNode, varNode); + } + + // Get the type from the left child + let valueType = "int"; + if (declNode.left) { + const typeStr = getTypeFromNode(declNode.left); + if (typeStr) valueType = typeStr; + } + + // Get the value from assignment + let value: string | number = ""; + const assignNode = findFirstNode(declNode.right, ASTOperation.ASSIGNMENT); + if (assignNode) { + value = extractConstantValue(assignNode); + } + + return { + position: { + line: declNode.position.line - 1, + character: declNode.position.char, + }, + identifier, + tokenType: CompletionItemKind.Constant, + valueType: valueType as any, + value, + }; +} + +/** + * Extract global variable from a VARIABLE node + */ +function extractGlobalVariableFromVarNode(declNode: ASTNode, varNode: ASTNode): ConstantComplexToken | null { + // Get the type + let valueType = "int"; + if (declNode.left) { + const typeStr = getTypeFromNode(declNode.left); + if (typeStr) valueType = typeStr; + } + + // Get the value from assignment + let value: string | number = ""; + const assignNode = findFirstNode(declNode.right, ASTOperation.ASSIGNMENT); + if (assignNode) { + value = extractConstantValue(assignNode); + } + + return { + position: { + line: varNode.position.line - 1, + character: varNode.position.char, + }, + identifier: varNode.stringData!, + tokenType: CompletionItemKind.Constant, + valueType: valueType as any, + value, + }; +} + +/** + * Extract constant declaration (with explicit const keyword) + */ +function extractConstant(declNode: ASTNode): ConstantComplexToken | null { + // Check if this is a const declaration + if (!declNode.left || !isOperation(declNode.left, ASTOperation.KEYWORD_CONST)) { + return null; + } + + // Find the variable name + const varNode = findFirstNode(declNode.right, ASTOperation.VARIABLE); + if (!varNode || !varNode.stringData) return null; + + // Get the type + let valueType = "int"; + const typeNode = findFirstNode(declNode.left?.right, "KEYWORD_"); + if (typeNode) { + const typeStr = getTypeFromNode(typeNode); + if (typeStr) valueType = typeStr; + } + + // Get the value from assignment + let value: string | number = ""; + const assignNode = findFirstNode(declNode.right, ASTOperation.ASSIGNMENT); + if (assignNode) { + value = extractConstantValue(assignNode); + } + + return { + position: { + line: varNode.position.line - 1, + character: varNode.position.char, + }, + identifier: varNode.stringData, + tokenType: CompletionItemKind.Constant, + valueType: valueType as any, + value, + }; +} + +/** + * Extract constant value from assignment node + */ +function extractConstantValue(assignNode: ASTNode): string | number { + const constantNode = findFirstNode(assignNode, "CONSTANT_"); + + if (!constantNode) return ""; + + if (isOperation(constantNode, ASTOperation.CONSTANT_INTEGER)) { + return constantNode.integerData?.[0] ?? 0; + } + + if (isOperation(constantNode, ASTOperation.CONSTANT_FLOAT)) { + return constantNode.floatData ?? 0.0; + } + + if (isOperation(constantNode, ASTOperation.CONSTANT_STRING)) { + return constantNode.stringData ?? ""; + } + + return ""; +} + +/** + * Find first node matching operation (supports prefix matching with _) + */ +function findFirstNode(node: ASTNode | null, operation: string): ASTNode | null { + if (!node) return null; + + const isPrefix = operation.endsWith("_"); + const matches = isPrefix ? node.operation.startsWith(operation) : node.operation === operation; + + if (matches) return node; + + return findFirstNode(node.left, operation) || findFirstNode(node.right, operation); +} + +/** + * Extract local scope tokens (functions and variables within a scope) + */ +export function extractLocalTokens( + ast: AST, + startLine: number, + endLine: number, +): { + functionsComplexTokens: FunctionComplexToken[]; + functionVariablesComplexTokens: (VariableComplexToken | FunctionParamComplexToken)[]; +} { + const functionsComplexTokens: FunctionComplexToken[] = []; + const functionVariablesComplexTokens: (VariableComplexToken | FunctionParamComplexToken)[] = []; + + function traverse(node: ASTNode | null): void { + if (!node) return; + + const nodeLine = node.position.line - 1; + + // Only process nodes within the specified line range + if (nodeLine < startLine || nodeLine > endLine) { + // But still traverse children in case they're in range + traverse(node.left); + traverse(node.right); + return; + } + + // Extract function declarations (local functions) + if (isOperation(node, ASTOperation.FUNCTION)) { + const func = extractFunction(node); + if (func && func.position.line >= startLine && func.position.line <= endLine) { + functionsComplexTokens.push(func); + } + } + + // Extract local variables + if (isOperation(node, ASTOperation.KEYWORD_DECLARATION)) { + const variables = extractLocalVariables(node); + variables.forEach((v) => { + if (v.position.line >= startLine && v.position.line <= endLine) { + functionVariablesComplexTokens.push(v); + } + }); + } + + // Extract function parameters + if (isOperation(node, ASTOperation.FUNCTION_PARAM_NAME) && node.stringData) { + const param = extractFunctionParam(node); + if (param && param.position.line >= startLine && param.position.line <= endLine) { + functionVariablesComplexTokens.push(param); + } + } + + traverse(node.left); + traverse(node.right); + } + + traverse(ast.ast); + + return { + functionsComplexTokens, + functionVariablesComplexTokens, + }; +} + +/** + * Extract local variables from a declaration node + */ +function extractLocalVariables(declNode: ASTNode): VariableComplexToken[] { + const variables: VariableComplexToken[] = []; + + // Get the type + let valueType = "int"; + if (declNode.left) { + const typeStr = getTypeFromNode(declNode.left); + if (typeStr) valueType = typeStr; + } + + // Find all variable nodes in the right subtree + function findVariables(node: ASTNode | null): void { + if (!node) return; + + if (isOperation(node, ASTOperation.VARIABLE) && node.stringData) { + variables.push({ + position: { + line: node.position.line - 1, + character: node.position.char, + }, + identifier: node.stringData, + tokenType: CompletionItemKind.Variable, + valueType: valueType as any, + }); + } + + findVariables(node.left); + findVariables(node.right); + } + + findVariables(declNode.right); + return variables; +} + +/** + * Extract a function parameter + */ +function extractFunctionParam(paramNode: ASTNode): FunctionParamComplexToken | null { + if (!paramNode.stringData) return null; + + let valueType = "int"; + if (paramNode.right) { + const typeStr = getTypeFromNode(paramNode.right); + if (typeStr) valueType = typeStr; + } + + return { + position: { + line: paramNode.position.line - 1, + character: paramNode.position.char, + }, + identifier: paramNode.stringData, + tokenType: CompletionItemKind.TypeParameter, + valueType: valueType as any, + }; +} diff --git a/server/src/Parser/ASTTypes.ts b/server/src/Parser/ASTTypes.ts new file mode 100644 index 0000000..98b3114 --- /dev/null +++ b/server/src/Parser/ASTTypes.ts @@ -0,0 +1,131 @@ +/** + * TypeScript definitions for NWScript AST + * Generated from the WASM compiler"s JSON export + */ + +export type ASTPosition = { + file: number; + line: number; + char: number; +}; + +export type ASTNode = { + operation: string; + operationId: number; + position: ASTPosition; + + // Optional data fields + stringData?: string; + integerData?: number[]; + floatData?: number; + vectorData?: number[]; + + // Type information + type?: string; + typeId?: number; + typeName?: string; + + // Stack information + stackPointer?: number; + + // Child nodes + left: ASTNode | null; + right: ASTNode | null; +}; + +export type AST = { + version: number; + ast: ASTNode; +}; + +/** + * Common AST operation types used in tokenization + */ +export enum ASTOperation { + // Top level + FUNCTIONAL_UNIT = "FUNCTIONAL_UNIT", + GLOBAL_VARIABLES = "GLOBAL_VARIABLES", + + // Functions + FUNCTION = "FUNCTION", + FUNCTION_DECLARATION = "FUNCTION_DECLARATION", + FUNCTION_IDENTIFIER = "FUNCTION_IDENTIFIER", + FUNCTION_PARAM_NAME = "FUNCTION_PARAM_NAME", + + // Structures + STRUCTURE_DEFINITION = "STRUCTURE_DEFINITION", + KEYWORD_STRUCT = "KEYWORD_STRUCT", + + // Variables and constants + VARIABLE = "VARIABLE", + VARIABLE_LIST = "VARIABLE_LIST", + KEYWORD_DECLARATION = "KEYWORD_DECLARATION", + KEYWORD_CONST = "KEYWORD_CONST", + + // Statements + STATEMENT = "STATEMENT", + STATEMENT_LIST = "STATEMENT_LIST", + COMPOUND_STATEMENT = "COMPOUND_STATEMENT", + + // Types + KEYWORD_INT = "KEYWORD_INT", + KEYWORD_FLOAT = "KEYWORD_FLOAT", + KEYWORD_STRING = "KEYWORD_STRING", + KEYWORD_OBJECT = "KEYWORD_OBJECT", + KEYWORD_VOID = "KEYWORD_VOID", + KEYWORD_VECTOR = "KEYWORD_VECTOR", + KEYWORD_ACTION = "KEYWORD_ACTION", + KEYWORD_EFFECT = "KEYWORD_EFFECT", + KEYWORD_EVENT = "KEYWORD_EVENT", + KEYWORD_LOCATION = "KEYWORD_LOCATION", + KEYWORD_TALENT = "KEYWORD_TALENT", + KEYWORD_ITEMPROPERTY = "KEYWORD_ITEMPROPERTY", + KEYWORD_JSON = "KEYWORD_JSON", + KEYWORD_SQLQUERY = "KEYWORD_SQLQUERY", + KEYWORD_CASSOWARY = "KEYWORD_CASSOWARY", + + // Constants + CONSTANT_INTEGER = "CONSTANT_INTEGER", + CONSTANT_FLOAT = "CONSTANT_FLOAT", + CONSTANT_STRING = "CONSTANT_STRING", + CONSTANT_OBJECT = "CONSTANT_OBJECT", + + // Expressions + ASSIGNMENT = "ASSIGNMENT", + NON_VOID_EXPRESSION = "NON_VOID_EXPRESSION", + + // Control flow + IF_BLOCK = "IF_BLOCK", + WHILE_BLOCK = "WHILE_BLOCK", + FOR_BLOCK = "FOR_BLOCK", + SWITCH_BLOCK = "SWITCH_BLOCK", + + // Others + INCLUDE_DIRECTIVE = "INCLUDE_DIRECTIVE", +} + +/** + * Helper to check if a node is a specific operation + */ +export function isOperation(node: ASTNode | null, operation: ASTOperation | string): boolean { + return node?.operation === operation; +} + +/** + * Helper to get the type string from a type node + */ +export function getTypeFromNode(node: ASTNode | null): string | undefined { + if (!node) return undefined; + + // Check if it"s a keyword type + if (node.operation.startsWith("KEYWORD_")) { + return node.operation.replace("KEYWORD_", "").toLowerCase(); + } + + // Check if it"s a struct type (has stringData with the struct name) + if (node.operation === "KEYWORD_STRUCT" && node.stringData) { + return node.stringData; + } + + return node.stringData || node.typeName; +} diff --git a/server/src/Parser/NWScriptParser.ts b/server/src/Parser/NWScriptParser.ts new file mode 100644 index 0000000..cade2b8 --- /dev/null +++ b/server/src/Parser/NWScriptParser.ts @@ -0,0 +1,232 @@ +import { join } from "path"; +import type { AST, ASTNode } from "./ASTTypes"; + +/** + * WebAssembly-based NWScript parser + * Uses the official NWScript compiler to generate AST + */ +export class NWScriptParser { + private module: any = null; + private initialized: boolean = false; + + /** + * Initialize the WASM module + */ + async initialize(): Promise { + if (this.initialized) return; + + const wasmPath = join(__dirname, "..", "..", "wasm", "nwscript_compiler.js"); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const NWScriptCompiler = require(wasmPath); + this.module = await NWScriptCompiler(); + this.initialized = true; + } + + /** + * Parse NWScript content and return the AST + * @param content The NWScript source code + * @param filename The name of the file being parsed (without extension) + * @param includes Map of include filenames to their content + * @returns The parsed AST or null if compilation failed + */ + async parse(content: string, filename: string = "script", includes: Map = new Map()): Promise<{ ast: AST | null; errors: string[] }> { + if (!this.initialized) { + await this.initialize(); + } + + const errors: string[] = []; + let compilerPtr: number | null = null; + + try { + // Minimal NWScript specification + const nwscriptSpec = this.getMinimalNWScriptSpec(); + const nwnxStub = this.getNWNXStub(); + + // File resolver callback + const resolverPtr = this.module.addFunction((filenamePtr: number, resType: number) => { + try { + const requestedFile = this.module.UTF8ToString(filenamePtr); + let fileContent: string; + + if (requestedFile === "nwscript") { + fileContent = nwscriptSpec; + } else if (requestedFile === "nwnx") { + fileContent = nwnxStub; + } else if (includes.has(requestedFile)) { + fileContent = includes.get(requestedFile)!; + } else if (requestedFile === filename) { + fileContent = content; + } else { + // Return empty content for unknown includes + fileContent = "// Include stub"; + } + + const contentLen = this.module.lengthBytesUTF8(fileContent); + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + const contentPtr = this.module._malloc(contentLen + 1); + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + this.module.stringToUTF8(fileContent, contentPtr, contentLen + 1); + + this.module.ccall("scriptCompApiDeliverFile", null, ["number", "number", "number"], [compilerPtr, contentPtr, contentLen]); + + this.module._free(contentPtr); + return 1; // Success + } catch (error) { + console.error("File resolver error:", error); + return 0; // Failure + } + }, "iii"); + + // Writer callback (not used for AST extraction) + const writerPtr = this.module.addFunction(() => 0, "iiiiii"); + + // Create compiler instance + const newCompiler = this.module.cwrap("scriptCompApiNewCompiler", "number", ["number", "number", "number", "number", "number"]); + compilerPtr = newCompiler(2009, 2010, 2064, writerPtr, resolverPtr); + + // Initialize compiler + const initCompiler = this.module.cwrap("scriptCompApiInitCompiler", null, ["number", "string", "boolean", "number", "string", "string"]); + initCompiler(compilerPtr, "nwscript", false, 16, "", "scriptout"); + + // Compile the script + const compileFileSimple = this.module.cwrap("scriptCompApiCompileFileSimple", "number", ["number", "string"]); + const resultCode = compileFileSimple(compilerPtr, filename); + + if (resultCode !== 0) { + const getLastError = this.module.cwrap("scriptCompApiGetLastError", "string", ["number"]); + const error = getLastError(compilerPtr); + errors.push(error); + + // Cleanup and return null AST + const destroyCompiler = this.module.cwrap("scriptCompApiDestroyCompiler", null, ["number"]); + destroyCompiler(compilerPtr); + + return { ast: null, errors }; + } + + // Get the AST as JSON + const getParseTreeJSON = this.module.cwrap("scriptCompApiGetParseTreeJSON", "string", ["number"]); + const jsonStr = getParseTreeJSON(compilerPtr); + + if (!jsonStr) { + errors.push("Failed to get parse tree JSON"); + return { ast: null, errors }; + } + + const ast: AST = JSON.parse(jsonStr); + + // Cleanup + const destroyCompiler = this.module.cwrap("scriptCompApiDestroyCompiler", null, ["number"]); + destroyCompiler(compilerPtr); + + return { ast, errors: [] }; + } catch (error) { + errors.push(`Parser error: ${String(error)}`); + return { ast: null, errors }; + } + } + + /** + * Get minimal NWScript specification required for compilation + */ + private getMinimalNWScriptSpec(): string { + return ` +#define ENGINE_NUM_STRUCTURES 0 + +int TRUE = 1; +int FALSE = 0; + +// Common string functions +int GetStringLength(string s) { return 0; } +string GetStringRight(string s, int n) { return ""; } +string GetStringLeft(string s, int n) { return ""; } +int FindSubString(string s, string sub) { return -1; } +string GetSubString(string s, int start, int count) { return ""; } +string IntToString(int n) { return ""; } +int StringToInt(string s) { return 0; } +string FloatToString(float f, int width, int decimals) { return ""; } +float StringToFloat(string s) { return 0.0; } + +// Common object functions +object GetFirstPC() { return OBJECT_INVALID; } +object GetNextPC() { return OBJECT_INVALID; } +object OBJECT_INVALID = 0; +object OBJECT_SELF = 1; + +// Common utility functions +void PrintString(string s) {} +void PrintInteger(int n) {} +void PrintFloat(float f) {} +`; + } + + /** + * Get NWNX stub functions + */ + private getNWNXStub(): string { + return ` +// NWNX stub functions for parsing +void NWNX_PushArgumentInt(int i) {} +void NWNX_PushArgumentFloat(float f) {} +void NWNX_PushArgumentString(string s) {} +void NWNX_PushArgumentObject(object o) {} +void NWNX_CallFunction(string plugin, string func) {} +int NWNX_GetReturnValueInt() { return 0; } +float NWNX_GetReturnValueFloat() { return 0.0; } +string NWNX_GetReturnValueString() { return ""; } +object NWNX_GetReturnValueObject() { return OBJECT_INVALID; } +`; + } + + /** + * Find a node at a specific position + * @param ast The root AST node + * @param line Line number (0-indexed in TypeScript, 1-indexed in AST) + * @param character Character position + * @returns The node at the position or null + */ + findNodeAtPosition(ast: ASTNode, line: number, character: number): ASTNode | null { + const targetLine = line + 1; // Convert to 1-indexed + + function traverse(node: ASTNode | null): ASTNode | null { + if (!node) return null; + + // Check if this node contains the position + if (node.position.line === targetLine && node.position.char <= character) { + // This node might be it, but check children first for more specific match + const leftResult = traverse(node.left); + if (leftResult) return leftResult; + + const rightResult = traverse(node.right); + if (rightResult) return rightResult; + + // No better match in children, return this node + return node; + } + + // Check children even if this node doesn"t match + const leftResult = traverse(node.left); + if (leftResult) return leftResult; + + return traverse(node.right); + } + + return traverse(ast); + } +} + +/** + * Singleton instance of the parser + */ +let parserInstance: NWScriptParser | null = null; + +/** + * Get the singleton parser instance + */ +export async function getParser(): Promise { + if (!parserInstance) { + parserInstance = new NWScriptParser(); + await parserInstance.initialize(); + } + return parserInstance; +} diff --git a/server/src/Providers/Builders/Builder.ts b/server/src/Providers/Builders/Builder.ts index b7bd9f2..3836e66 100644 --- a/server/src/Providers/Builders/Builder.ts +++ b/server/src/Providers/Builders/Builder.ts @@ -1,15 +1,7 @@ import { CompletionItemKind } from "vscode-languageserver"; import { LanguageTypes } from "../../Tokenizer/constants"; -import type { - ComplexToken, - ConstantComplexToken, - VariableComplexToken, - FunctionParamComplexToken, - FunctionComplexToken, - StructPropertyComplexToken, - StructComplexToken, -} from "../../Tokenizer/types"; +import type { ComplexToken, ConstantComplexToken, VariableComplexToken, FunctionParamComplexToken, FunctionComplexToken, StructPropertyComplexToken, StructComplexToken } from "../../Tokenizer/types"; export default abstract class Builder { protected static handleLanguageType(type: string) { diff --git a/server/src/Providers/Builders/CompletionItemBuilder.ts b/server/src/Providers/Builders/CompletionItemBuilder.ts index 9a54de9..142fdfd 100644 --- a/server/src/Providers/Builders/CompletionItemBuilder.ts +++ b/server/src/Providers/Builders/CompletionItemBuilder.ts @@ -1,14 +1,6 @@ import { CompletionItem, CompletionItemKind } from "vscode-languageserver"; -import type { - ComplexToken, - ConstantComplexToken, - FunctionComplexToken, - FunctionParamComplexToken, - StructComplexToken, - StructPropertyComplexToken, - VariableComplexToken, -} from "../../Tokenizer/types"; +import type { ComplexToken, ConstantComplexToken, FunctionComplexToken, FunctionParamComplexToken, StructComplexToken, StructPropertyComplexToken, VariableComplexToken } from "../../Tokenizer/types"; import { ServerConfiguration } from "../../ServerManager/Config"; import Builder from "./Builder"; @@ -19,9 +11,7 @@ export default class CompletionItemBuilder extends Builder { return { label: `${item.label}(${params.reduce((acc, param, index) => { - return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${ - index === params.length - 1 ? "" : ", " - }`; + return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${index === params.length - 1 ? "" : ", "}`; }, "")})`, kind: item.kind, detail: item.detail, @@ -80,9 +70,7 @@ export default class CompletionItemBuilder extends Builder { label: token.identifier, kind: token.tokenType, detail: `(method) (${token.params.reduce((acc, param, index) => { - return `${acc}${param.identifier}: ${this.handleLanguageType(param.valueType)}${ - index === token.params.length - 1 ? "" : ", " - }`; + return `${acc}${param.identifier}: ${this.handleLanguageType(param.valueType)}${index === token.params.length - 1 ? "" : ", "}`; }, "")}): ${this.handleLanguageType(token.returnType)}`, data: token.params, }; diff --git a/server/src/Providers/Builders/HoverContentBuilder.ts b/server/src/Providers/Builders/HoverContentBuilder.ts index b26ff1e..699cdf2 100644 --- a/server/src/Providers/Builders/HoverContentBuilder.ts +++ b/server/src/Providers/Builders/HoverContentBuilder.ts @@ -1,14 +1,6 @@ import { MarkupContent, MarkupKind } from "vscode-languageserver"; -import type { - ComplexToken, - ConstantComplexToken, - FunctionComplexToken, - FunctionParamComplexToken, - StructComplexToken, - StructPropertyComplexToken, - VariableComplexToken, -} from "../../Tokenizer/types"; +import type { ComplexToken, ConstantComplexToken, FunctionComplexToken, FunctionParamComplexToken, StructComplexToken, StructPropertyComplexToken, VariableComplexToken } from "../../Tokenizer/types"; import { ServerConfiguration } from "../../ServerManager/Config"; import Builder from "./Builder"; @@ -47,9 +39,7 @@ export default class HoverContentBuilder extends Builder { return this.buildMarkdown( [ `${this.handleLanguageType(token.returnType)} ${token.identifier}(${token.params.reduce((acc, param, index) => { - return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${ - param.defaultValue ? ` = ${param.defaultValue}` : "" - }${index === token.params.length - 1 ? "" : ", "}`; + return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${param.defaultValue ? ` = ${param.defaultValue}` : ""}${index === token.params.length - 1 ? "" : ", "}`; }, "")})`, ], serverConfig.hovering.addCommentsToFunctions ? ["```nwscript", ...token.comments, "```"] : [], @@ -62,12 +52,7 @@ export default class HoverContentBuilder extends Builder { } private static buildStructItem(token: StructComplexToken) { - return this.buildMarkdown([ - `struct ${token.identifier}`, - "{", - ...token.properties.map((property) => `\t${property.valueType} ${property.identifier}`), - "}", - ]); + return this.buildMarkdown([`struct ${token.identifier}`, "{", ...token.properties.map((property) => `\t${property.valueType} ${property.identifier}`), "}"]); } private static buildMarkdown(content: string[] | string, prepend: string[] = [], postpend: string[] = []) { diff --git a/server/src/Providers/Builders/SignatureHelpBuilder.ts b/server/src/Providers/Builders/SignatureHelpBuilder.ts index 798396d..7a4e3b5 100644 --- a/server/src/Providers/Builders/SignatureHelpBuilder.ts +++ b/server/src/Providers/Builders/SignatureHelpBuilder.ts @@ -9,18 +9,14 @@ export default class SignatureHelpBuilder extends Builder { signatures: [ SignatureInformation.create( `${this.handleLanguageType(token.returnType)} ${token.identifier}(${token.params.reduce((acc, param, index) => { - return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${ - index === token.params.length - 1 ? "" : ", " - }`; + return `${acc}${this.handleLanguageType(param.valueType)} ${param.identifier}${index === token.params.length - 1 ? "" : ", "}`; }, "")})`, undefined, - ...token.params.map((param) => - ParameterInformation.create(`${param.valueType} ${param.identifier}`), - ), + ...token.params.map((param) => ParameterInformation.create(`${param.valueType} ${param.identifier}`)), ), ], activeSignature: 0, - activeParameter: activeParameter || null, + activeParameter, }; } } diff --git a/server/src/Providers/Builders/SymbolBuilder.ts b/server/src/Providers/Builders/SymbolBuilder.ts index 36f9f8a..b0342eb 100644 --- a/server/src/Providers/Builders/SymbolBuilder.ts +++ b/server/src/Providers/Builders/SymbolBuilder.ts @@ -1,14 +1,6 @@ import { DocumentSymbol, SymbolKind } from "vscode-languageserver"; -import type { - ComplexToken, - ConstantComplexToken, - FunctionComplexToken, - FunctionParamComplexToken, - StructComplexToken, - StructPropertyComplexToken, - VariableComplexToken, -} from "../../Tokenizer/types"; +import type { ComplexToken, ConstantComplexToken, FunctionComplexToken, FunctionParamComplexToken, StructComplexToken, StructPropertyComplexToken, VariableComplexToken } from "../../Tokenizer/types"; import Builder from "./Builder"; export default class SymbolBuilder extends Builder { @@ -31,33 +23,15 @@ export default class SymbolBuilder extends Builder { } private static buildConstantItem(token: ConstantComplexToken) { - return DocumentSymbol.create( - token.identifier, - undefined, - SymbolKind.Constant, - { start: token.position, end: token.position }, - { start: token.position, end: token.position }, - ); + return DocumentSymbol.create(token.identifier, undefined, SymbolKind.Constant, { start: token.position, end: token.position }, { start: token.position, end: token.position }); } private static buildVariableItem(token: VariableComplexToken) { - return DocumentSymbol.create( - token.identifier, - undefined, - SymbolKind.Variable, - { start: token.position, end: token.position }, - { start: token.position, end: token.position }, - ); + return DocumentSymbol.create(token.identifier, undefined, SymbolKind.Variable, { start: token.position, end: token.position }, { start: token.position, end: token.position }); } private static buildFunctionParamItem(token: FunctionParamComplexToken) { - return DocumentSymbol.create( - token.identifier, - undefined, - SymbolKind.TypeParameter, - { start: token.position, end: token.position }, - { start: token.position, end: token.position }, - ); + return DocumentSymbol.create(token.identifier, undefined, SymbolKind.TypeParameter, { start: token.position, end: token.position }, { start: token.position, end: token.position }); } private static buildFunctionItem(token: FunctionComplexToken) { @@ -75,25 +49,12 @@ export default class SymbolBuilder extends Builder { } private static buildStructPropertyItem(token: StructPropertyComplexToken) { - return DocumentSymbol.create( - token.identifier, - undefined, - SymbolKind.Property, - { start: token.position, end: token.position }, - { start: token.position, end: token.position }, - ); + return DocumentSymbol.create(token.identifier, undefined, SymbolKind.Property, { start: token.position, end: token.position }, { start: token.position, end: token.position }); } private static buildStructItem(token: StructComplexToken) { const symbols = token.properties?.map((child) => SymbolBuilder.buildItem(child)); - return DocumentSymbol.create( - token.identifier, - undefined, - SymbolKind.Struct, - { start: token.position, end: token.position }, - { start: token.position, end: token.position }, - symbols, - ); + return DocumentSymbol.create(token.identifier, undefined, SymbolKind.Struct, { start: token.position, end: token.position }, { start: token.position, end: token.position }, symbols); } } diff --git a/server/src/Providers/CompletionItemsProvider.ts b/server/src/Providers/CompletionItemsProvider.ts index 7cbe252..00ba5c9 100644 --- a/server/src/Providers/CompletionItemsProvider.ts +++ b/server/src/Providers/CompletionItemsProvider.ts @@ -2,7 +2,7 @@ import { CompletionParams } from "vscode-languageserver"; import type { ServerManager } from "../ServerManager"; import { CompletionItemBuilder } from "./Builders"; -import { LocalScopeTokenizationResult } from "../Tokenizer/Tokenizer"; +import { LocalScopeTokenizationResult, TokenizedScope } from "../Tokenizer/Tokenizer"; import { TriggerCharacters } from "."; import { Document } from "../Documents"; import { LanguageTypes } from "../Tokenizer/constants"; @@ -17,7 +17,7 @@ export default class CompletionItemsProvider extends Provider { } private providerHandler(params: CompletionParams) { - return () => { + return async () => { const { textDocument: { uri }, position, @@ -27,11 +27,11 @@ export default class CompletionItemsProvider extends Provider { const document = this.server.documentsCollection.getFromUri(uri); if (!liveDocument || !document) return; - const [lines, rawTokenizedContent] = this.server.tokenizer.tokenizeContentToRaw(liveDocument.getText()); - const localScope = this.server.tokenizer.tokenizeContentFromRaw(lines, rawTokenizedContent, 0, position.line); + const content = liveDocument.getText(); + const localScope = await this.server.tokenizer.tokenizeContent(content, TokenizedScope.local, 0, position.line); if (params.context?.triggerCharacter === TriggerCharacters.dot) { - const { rawContent } = this.server.tokenizer.getActionTargetAtPosition(lines, rawTokenizedContent, position, -1); + const { rawContent } = await this.server.tokenizer.getActionTargetAtPositionAST(content, position, -1); const structIdentifer = localScope.functionVariablesComplexTokens.find((token) => token.identifier === rawContent)?.valueType; return document @@ -42,7 +42,8 @@ export default class CompletionItemsProvider extends Provider { }); } - if (this.server.tokenizer.getActionTargetAtPosition(lines, rawTokenizedContent, position, -2).rawContent === LanguageTypes.struct) { + const checkStruct = await this.server.tokenizer.getActionTargetAtPositionAST(content, position, -2); + if (checkStruct.rawContent === LanguageTypes.struct) { return document.getGlobalStructComplexTokens().map((token) => CompletionItemBuilder.buildItem(token)); } diff --git a/server/src/Providers/DiagnosticsProvider.ts b/server/src/Providers/DiagnosticsProvider.ts index 36b54d6..7295e7a 100644 --- a/server/src/Providers/DiagnosticsProvider.ts +++ b/server/src/Providers/DiagnosticsProvider.ts @@ -4,7 +4,6 @@ import { join, dirname, basename } from "path"; import { fileURLToPath } from "url"; import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver"; -import { ServerManager } from "../ServerManager"; import Provider from "./Provider"; const lineNumber = /\(([^)]+)\)/; @@ -19,10 +18,6 @@ enum OS { type FilesDiagnostics = { [uri: string]: Diagnostic[] }; export default class DiagnoticsProvider extends Provider { - 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]); @@ -62,6 +57,7 @@ export default class DiagnoticsProvider extends Provider { } } + // eslint-disable-next-line @typescript-eslint/promise-function-async public publish(uri: string) { return new Promise((resolve, reject) => { const { enabled, nwnHome, reportWarnings, nwnInstallation, verbose, os } = this.server.config.compiler; @@ -166,24 +162,16 @@ export default class DiagnoticsProvider extends Provider { // 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?", - ); + 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?", - ); + 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.", - ); + 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.", - ); + return this.server.logger.error("Unable to automatically resolve Neverwinter Nights home and/or installation directories."); } } }); @@ -197,7 +185,7 @@ export default class DiagnoticsProvider extends Provider { if (reportWarnings) warnings.forEach(this.generateDiagnostics(uris, files, DiagnosticSeverity.Warning)); for (const [uri, diagnostics] of Object.entries(files)) { - this.server.connection.sendDiagnostics({ uri, diagnostics }); + void this.server.connection.sendDiagnostics({ uri, diagnostics }); } resolve(true); }); diff --git a/server/src/Providers/GotoDefinitionProvider.ts b/server/src/Providers/GotoDefinitionProvider.ts index 86e8900..8213924 100644 --- a/server/src/Providers/GotoDefinitionProvider.ts +++ b/server/src/Providers/GotoDefinitionProvider.ts @@ -6,6 +6,7 @@ import type { ServerManager } from "../ServerManager"; import type { ComplexToken } from "../Tokenizer/types"; import { Document } from "../Documents"; import Provider from "./Provider"; +import { TokenizedScope } from "../Tokenizer/Tokenizer"; export default class GotoDefinitionProvider extends Provider { constructor(server: ServerManager) { @@ -15,7 +16,7 @@ export default class GotoDefinitionProvider extends Provider { } private providerHandler(params: DefinitionParams) { - return () => { + return async () => { const { textDocument: { uri }, position, @@ -25,7 +26,7 @@ export default class GotoDefinitionProvider extends Provider { const document = this.server.documentsCollection.getFromUri(uri); if (!liveDocument || !document) return; - const [token, ref] = this.resolveTokenAndRef(position, document, liveDocument); + const [token, ref] = await this.resolveTokenAndRef(position, document, liveDocument); if (token) { if (ref && !ref.owner) { @@ -49,18 +50,22 @@ export default class GotoDefinitionProvider extends Provider { }; } - private resolveTokenAndRef(position: Position, document: Document, liveDocument: TextDocument): [token: ComplexToken | undefined, ref: OwnedComplexTokens | OwnedStructComplexTokens | undefined] { + private async resolveTokenAndRef( + position: Position, + document: Document, + liveDocument: TextDocument, + ): Promise<[token: ComplexToken | undefined, ref: OwnedComplexTokens | OwnedStructComplexTokens | undefined]> { let tokensWithRef; let token: ComplexToken | undefined; let ref: OwnedComplexTokens | OwnedStructComplexTokens | undefined; - const [lines, rawTokenizedContent] = this.server.tokenizer.tokenizeContentToRaw(liveDocument.getText()); - const localScope = this.server.tokenizer.tokenizeContentFromRaw(lines, rawTokenizedContent, 0, position.line); - const { tokenType, lookBehindRawContent, rawContent } = this.server.tokenizer.getActionTargetAtPosition(lines, rawTokenizedContent, position); + const content = liveDocument.getText(); + const localScope = await this.server.tokenizer.tokenizeContent(content, TokenizedScope.local, 0, position.line); + const { tokenType, lookBehindRawContent, rawContent } = await this.server.tokenizer.getActionTargetAtPositionAST(content, position); switch (tokenType) { case CompletionItemKind.Function: - case CompletionItemKind.Constant: + case CompletionItemKind.Constant: { token = localScope.functionsComplexTokens.find((candidate) => candidate.identifier === rawContent); if (token) break; @@ -71,40 +76,43 @@ export default class GotoDefinitionProvider extends Provider { tokensWithRef.push({ owner: localStandardLibDefinitions?.uri, tokens: localStandardLibDefinitions?.complexTokens }); } - loop: for (let i = 0; i < tokensWithRef.length; i++) { + for (let i = 0; i < tokensWithRef.length; i++) { ref = tokensWithRef[i]; token = ref?.tokens.find((candidate) => candidate.identifier === rawContent); if (token) { - break loop; + break; } } break; - case CompletionItemKind.Struct: + } + case CompletionItemKind.Struct: { tokensWithRef = document.getGlobalStructComplexTokensWithRef(); - loop: for (let i = 0; i < tokensWithRef.length; i++) { + for (let i = 0; i < tokensWithRef.length; i++) { ref = tokensWithRef[i]; token = ref?.tokens.find((candidate) => candidate.identifier === rawContent); if (token) { - break loop; + break; } } break; - case CompletionItemKind.Property: + } + case CompletionItemKind.Property: { const structIdentifer = localScope.functionVariablesComplexTokens.find((candidate) => candidate.identifier === lookBehindRawContent)?.valueType; tokensWithRef = document.getGlobalStructComplexTokensWithRef(); - loop: for (let i = 0; i < tokensWithRef.length; i++) { + for (let i = 0; i < tokensWithRef.length; i++) { ref = tokensWithRef[i]; token = (ref as OwnedStructComplexTokens).tokens.find((candidate) => candidate.identifier === structIdentifer)?.properties.find((property) => property.identifier === rawContent); if (token) { - break loop; + break; } } break; + } default: token = localScope.functionVariablesComplexTokens.find((candidate) => candidate.identifier === rawContent); } diff --git a/server/src/Providers/HoverContentProvider.ts b/server/src/Providers/HoverContentProvider.ts index 151b9f3..08fe1ee 100644 --- a/server/src/Providers/HoverContentProvider.ts +++ b/server/src/Providers/HoverContentProvider.ts @@ -6,6 +6,7 @@ import type { ComplexToken } from "../Tokenizer/types"; import { HoverContentBuilder } from "./Builders"; import { Document } from "../Documents"; import Provider from "./Provider"; +import { TokenizedScope } from "../Tokenizer/Tokenizer"; export default class HoverContentProvider extends Provider { constructor(server: ServerManager) { @@ -15,7 +16,7 @@ export default class HoverContentProvider extends Provider { } private providerHandler(params: HoverParams) { - return () => { + return async () => { const { textDocument: { uri }, position, @@ -25,7 +26,7 @@ export default class HoverContentProvider extends Provider { const document = this.server.documentsCollection.getFromUri(uri); if (!liveDocument || !document) return; - let token = this.resolveToken(position, document, liveDocument); + const token = await this.resolveToken(position, document, liveDocument); if (token) { return { @@ -35,13 +36,13 @@ export default class HoverContentProvider extends Provider { }; } - private resolveToken(position: Position, document: Document, liveDocument: TextDocument) { + private async resolveToken(position: Position, document: Document, liveDocument: TextDocument) { let tokens; let token: ComplexToken | undefined; - const [lines, rawTokenizedContent] = this.server.tokenizer.tokenizeContentToRaw(liveDocument.getText()); - const localScope = this.server.tokenizer.tokenizeContentFromRaw(lines, rawTokenizedContent, 0, position.line); - const { tokenType, lookBehindRawContent, rawContent } = this.server.tokenizer.getActionTargetAtPosition(lines, rawTokenizedContent, position); + const content = liveDocument.getText(); + const localScope = await this.server.tokenizer.tokenizeContent(content, TokenizedScope.local, 0, position.line); + const { tokenType, lookBehindRawContent, rawContent } = await this.server.tokenizer.getActionTargetAtPositionAST(content, position); switch (tokenType) { case CompletionItemKind.Function: @@ -60,7 +61,7 @@ export default class HoverContentProvider extends Provider { tokens = document.getGlobalStructComplexTokens(); token = tokens.find((candidate) => candidate.identifier === rawContent); break; - case CompletionItemKind.Property: + case CompletionItemKind.Property: { const structIdentifer = localScope?.functionVariablesComplexTokens.find((candidate) => candidate.identifier === lookBehindRawContent)?.valueType; token = document @@ -68,6 +69,7 @@ export default class HoverContentProvider extends Provider { .find((candidate) => candidate.identifier === structIdentifer) ?.properties.find((property) => property.identifier === rawContent); break; + } default: token = localScope.functionVariablesComplexTokens.find((candidate) => candidate.identifier === rawContent); } diff --git a/server/src/Providers/SignatureHelpProvider.ts b/server/src/Providers/SignatureHelpProvider.ts index 10fb2fe..3cec307 100644 --- a/server/src/Providers/SignatureHelpProvider.ts +++ b/server/src/Providers/SignatureHelpProvider.ts @@ -15,7 +15,7 @@ export default class SignatureHelpProvider extends Provider { } private providerHandler(params: SignatureHelpParams) { - return () => { + return async () => { const { textDocument: { uri }, position, @@ -25,16 +25,16 @@ export default class SignatureHelpProvider extends Provider { const document = this.server.documentsCollection.getFromUri(uri); if (!liveDocument || !document) return; - const [lines, rawTokenizedContent] = this.server.tokenizer.tokenizeContentToRaw(liveDocument.getText()); - const line = lines[position.line]; - const tokensArray = rawTokenizedContent[position.line]; + const content = liveDocument.getText(); - if (!tokensArray || !this.server.tokenizer.isInScope(tokensArray, position, LanguageScopes.functionCall)) return; + // Use AST-based position queries instead of TextMate + const isInFunctionCall = await this.server.tokenizer.isInScopeAST(content, position, "function.call"); + if (!isInFunctionCall) return; - const rawContent = this.server.tokenizer.getLookBehindScopesRawContent(line, tokensArray, position, [LanguageScopes.functionCall, LanguageScopes.functionIdentifier]); - const activeParameter = this.server.tokenizer.getLookBehindScopeOccurences(tokensArray, position, LanguageScopes.separatorStatement, LanguageScopes.leftArgumentsRoundBracket); + const rawContent = await this.server.tokenizer.getFunctionNameAtPositionAST(content, position); + const activeParameter = await this.server.tokenizer.getActiveParameterIndexAST(content, position); - const localScope = this.server.tokenizer.tokenizeContent(liveDocument.getText(), TokenizedScope.local, 0, position.line); + const localScope = await this.server.tokenizer.tokenizeContent(content, TokenizedScope.local, 0, position.line); const functionComplexToken = localScope.functionsComplexTokens.find((token) => token.identifier === rawContent) || document.getGlobalComplexTokens().find((token) => token.identifier === rawContent) || diff --git a/server/src/Providers/SymbolsProvider.ts b/server/src/Providers/SymbolsProvider.ts index d8bfcdb..79293fd 100644 --- a/server/src/Providers/SymbolsProvider.ts +++ b/server/src/Providers/SymbolsProvider.ts @@ -13,7 +13,7 @@ export default class SymbolsProvider extends Provider { } private providerHandler(params: DocumentSymbolParams) { - return () => { + return async () => { const { textDocument: { uri }, } = params; @@ -22,7 +22,7 @@ export default class SymbolsProvider extends Provider { const document = this.server.documentsCollection.getFromUri(uri); if (!liveDocument || !document) return; - const localScope = this.server.tokenizer.tokenizeContent(liveDocument.getText(), TokenizedScope.local); + const localScope = await this.server.tokenizer.tokenizeContent(liveDocument.getText(), TokenizedScope.local); const constantSymbols = document.complexTokens.filter((token) => token.tokenType === CompletionItemKind.Constant).map((token) => SymbolBuilder.buildItem(token)); const structSymbols = document.structComplexTokens.map((token) => SymbolBuilder.buildItem(token)); diff --git a/server/src/ServerManager/ServerManager.ts b/server/src/ServerManager/ServerManager.ts index fd229ab..21621cf 100644 --- a/server/src/ServerManager/ServerManager.ts +++ b/server/src/ServerManager/ServerManager.ts @@ -52,7 +52,7 @@ export default class ServerManger { } public async initialize() { - this.tokenizer.loadGrammar(); + await this.tokenizer.loadGrammar(); this.registerProviders(); this.registerLiveDocumentsEvents(); @@ -69,8 +69,8 @@ export default class ServerManger { WorkspaceProvider.register(this); if (this.capabilitiesHandler.getSupportsWorkspaceConfiguration()) { - await ConfigurationProvider.register(this, async () => { - await this.loadConfig(); + await ConfigurationProvider.register(this, () => { + void this.loadConfig(); }); } @@ -89,7 +89,7 @@ export default class ServerManger { const nwscriptPath = filesPath.find((path) => path.includes("nwscript.nss")); const progressReporter = await this.connection.window.createWorkDoneProgress(); const filesCount = filesPath.length; - this.logger.info(`Indexing files ...`); + this.logger.info("Indexing files ..."); progressReporter.begin("Indexing files for NWScript: EE Language Server ...", 0); const partCount = Math.ceil(filesCount / numCPUs); @@ -105,18 +105,20 @@ export default class ServerManger { } cluster.on("exit", () => { - if (Object.keys(cluster.workers || {}).length === 0) { - progressReporter?.done(); - this.logger.info(`Indexed ${filesIndexedCount} files.`); - this.configLoaded = true; - this.diagnosticsProvider?.processDocumentsWaitingForPublish(); - - if (nwscriptPath) { - const fileContent = readFileSync(nwscriptPath).toString(); - const globalScope = this.tokenizer?.tokenizeContent(fileContent, TokenizedScope.global)!; - this.documentsCollection?.createDocument(pathToFileURL(nwscriptPath).href, globalScope); + void (async () => { + if (Object.keys(cluster.workers || {}).length === 0) { + progressReporter?.done(); + this.logger.info(`Indexed ${filesIndexedCount} files.`); + this.configLoaded = true; + await this.diagnosticsProvider?.processDocumentsWaitingForPublish(); + + if (nwscriptPath && this.tokenizer) { + const fileContent = readFileSync(nwscriptPath).toString(); + const globalScope = await this.tokenizer.tokenizeContent(fileContent, TokenizedScope.global); + this.documentsCollection?.createDocument(pathToFileURL(nwscriptPath).href, globalScope); + } } - } + })(); }); } @@ -135,12 +137,16 @@ export default class ServerManger { } private registerLiveDocumentsEvents() { - this.liveDocumentsManager.onDidSave((event) => this.diagnosticsProvider?.publish(event.document.uri)); - this.liveDocumentsManager.onWillSave((event) => this.documentsCollection?.updateDocument(event.document, this.tokenizer, this.workspaceFilesSystem)); + this.liveDocumentsManager.onDidSave((event) => { + void this.diagnosticsProvider?.publish(event.document.uri); + }); + this.liveDocumentsManager.onWillSave(async (event) => { + await this.documentsCollection?.updateDocument(event.document, this.tokenizer, this.workspaceFilesSystem); + }); - this.liveDocumentsManager.onDidOpen((event) => { - this.documentsCollection?.createDocuments(event.document.uri, event.document.getText(), this.tokenizer, this.workspaceFilesSystem); - this.diagnosticsProvider?.publish(event.document.uri); + this.liveDocumentsManager.onDidOpen(async (event) => { + await this.documentsCollection?.createDocuments(event.document.uri, event.document.getText(), this.tokenizer, this.workspaceFilesSystem); + void this.diagnosticsProvider?.publish(event.document.uri); }); } diff --git a/server/src/Tokenizer/Tokenizer.ts b/server/src/Tokenizer/Tokenizer.ts index 1c30765..25f73c3 100644 --- a/server/src/Tokenizer/Tokenizer.ts +++ b/server/src/Tokenizer/Tokenizer.ts @@ -7,8 +7,12 @@ import { Registry, INITIAL, parseRawGrammar, IToken } from "vscode-textmate"; import { CompletionItemKind } from "vscode-languageserver"; import type { ComplexToken, FunctionComplexToken, FunctionParamComplexToken, StructComplexToken, VariableComplexToken } from "./types"; -import { LanguageTypes, LanguageScopes } from "./constants"; +import { LanguageScopes } from "./constants"; import onigLib from "../onigLib"; +import { getParser } from "../Parser/NWScriptParser"; +import { extractGlobalTokens, extractLocalTokens } from "../Parser/ASTTraversal"; +import type { AST } from "../Parser/ASTTypes"; +import { ASTPositionQuery } from "../Parser/ASTPositionQuery"; export enum TokenizedScope { global = "global", @@ -26,13 +30,28 @@ export type LocalScopeTokenizationResult = { functionVariablesComplexTokens: (VariableComplexToken | FunctionParamComplexToken)[]; }; -// Naive implementation -// Ideally we would use an AST tree -// See the Notes section of the README for the explications +/** + * Hybrid tokenizer using AST for structural analysis and TextMate for position queries. + * + * Architecture: + * - AST (via NWScript compiler): Parses file structure, extracts functions/variables/structs + * - TextMate: Provides fine-grained token positions for LSP position-based queries + * + * Why hybrid? + * - AST is great for "what's in this file" but doesn't support "what's at this position" + * - TextMate is great for cursor position queries but slower for full-file analysis + */ export default class Tokenizer { + // TextMate registry for position-based queries private readonly registry: Registry; private grammar: IGrammar | null = null; - private readonly localScopeCache: (IToken[] | undefined)[] | null = null; + + // AST cache for structural tokenization + private cachedAST: AST | null = null; + private lastParsedContent: string = ""; + + // AST-based position query service + private positionQuery: ASTPositionQuery | null = null; constructor(localPath = false) { this.registry = new Registry({ @@ -41,359 +60,197 @@ export default class Tokenizer { return await new Promise((resolve, reject) => { if (scopeName === "source.nss") { const grammar = readFileSync(join(__dirname, "..", "..", localPath ? ".." : "", "syntaxes", "nwscript-ee.tmLanguage")); - return resolve(parseRawGrammar(grammar.toString())); } - reject(new Error(`Unknown scope name: ${scopeName}`)); }); }, }); } - private getTokenIndexAtPosition(tokensArray: IToken[], position: Position) { - return tokensArray.findIndex((token) => token.startIndex <= position.character && token.endIndex >= position.character); - } - - private getTokenAtPosition(tokensArray: IToken[], position: Position) { - return tokensArray.find((token) => token.startIndex <= position.character && token.endIndex >= position.character); - } - - private getRawTokenContent(line: string, token: IToken) { - return line.slice(token.startIndex, token.endIndex); + /** + * Initialize the TextMate grammar. + * Must be called before using any tokenization methods. + */ + public async loadGrammar() { + this.grammar = await this.registry.loadGrammar("source.nss"); + return this; } - private getTokenIndex(tokensArray: IToken[], targetToken: IToken) { - return tokensArray.findIndex((token) => token.startIndex === targetToken.startIndex); - } + // ============================================================================ + // AST-BASED TOKENIZATION (Structural Analysis) + // ============================================================================ + // These methods use the NWScript compiler to parse and extract tokens. + // Used for: indexing files, extracting all functions/variables/structs + + /** + * Tokenize content using AST parser to extract structural tokens. + * + * @param content - The NWScript source code + * @param scope - Whether to extract global or local scope tokens + * @param startIndex - Starting line for local scope (0-indexed) + * @param stopIndex - Ending line for local scope (0-indexed, -1 for end of file) + * @returns Extracted tokens based on scope + */ + + public tokenizeContent(content: string, scope: TokenizedScope.global, startIndex?: number, stopIndex?: number): Promise; + public tokenizeContent(content: string, scope: TokenizedScope.local, startIndex?: number, stopIndex?: number): Promise; + public async tokenizeContent(content: string, scope: TokenizedScope, startIndex: number = 0, stopIndex: number = -1): Promise { + // Parse content to AST if not cached or content changed + if (this.lastParsedContent !== content) { + const parser = await getParser(); + const { ast, errors } = await parser.parse(content); + + if (ast) { + this.cachedAST = ast; + this.lastParsedContent = content; + this.positionQuery = new ASTPositionQuery(ast); + } else { + console.warn("AST parsing failed:", errors); + return this.getEmptyResult(scope); + } + } - private getTokenLanguageType(line: string, tokens: IToken[], index: number) { - const rawContent = this.getRawTokenContent(line, tokens[index]); + if (!this.cachedAST) { + return this.getEmptyResult(scope); + } - const type = LanguageTypes[rawContent as keyof typeof LanguageTypes] || rawContent; - return (type === LanguageTypes.struct ? this.getRawTokenContent(line, tokens[index + 2]) : type) as LanguageTypes; + // Extract tokens from AST + if (scope === TokenizedScope.global) { + return extractGlobalTokens(this.cachedAST); + } else { + const lines = content.split(/\r?\n/); + const actualStopIndex = stopIndex < 0 ? lines.length : stopIndex; + return extractLocalTokens(this.cachedAST, startIndex, actualStopIndex); + } } - private getConstantValue(line: string, tokensArray: IToken[]) { - const startIndex = tokensArray.findIndex((token) => token.scopes.includes(LanguageScopes.assignationStatement)); - const endIndex = tokensArray.findIndex((token) => token.scopes.includes(LanguageScopes.terminatorStatement)); + /** + * Tokenize content from raw TextMate tokens to AST-based local scope. + * Bridge method for providers that still use TextMate for position queries. + */ - return tokensArray - .filter((_, index) => index > startIndex && index < endIndex) - .map((token) => this.getRawTokenContent(line, token)) - .join("") - .trim(); + public async tokenizeContentFromRaw(lines: string[], rawTokenizedContent: (IToken[] | undefined)[], startIndex: number = 0, stopIndex: number = -1) { + const content = lines.join("\n"); + return await this.tokenizeContent(content, TokenizedScope.local, startIndex, stopIndex); } - private getFunctionParams(lineIndex: number, lines: string[], tokensArrays: (IToken[] | undefined)[]) { - let params: FunctionParamComplexToken[] = []; - let line; - let tokensArray; - - lineIndex = lineIndex - 1; - do { - lineIndex = lineIndex + 1; - line = lines[lineIndex]; - tokensArray = tokensArrays[lineIndex]!; - - params = params.concat(this.getInlineFunctionParams(line, lineIndex, tokensArray)); - } while (!Boolean(tokensArray.find((token) => token.scopes.includes(LanguageScopes.rightParametersRoundBracket)))); - - return params; + /** + * Returns empty tokenization result based on scope. + */ + private getEmptyResult(scope: TokenizedScope): GlobalScopeTokenizationResult | LocalScopeTokenizationResult { + if (scope === TokenizedScope.global) { + return { complexTokens: [], structComplexTokens: [], children: [] }; + } else { + return { functionsComplexTokens: [], functionVariablesComplexTokens: [] }; + } } - private getInlineFunctionParams(line: string, lineIndex: number, tokensArray: IToken[]) { - const functionParamTokens = tokensArray.filter( - (token) => token.scopes.includes(LanguageScopes.functionParameters) && (token.scopes.includes(LanguageScopes.functionParameter) || token.scopes.includes(LanguageScopes.variableIdentifer)), - ); - - return functionParamTokens.map((token) => { - const tokenIndex = this.getTokenIndex(tokensArray, token); - let defaultValue = ""; - - if (tokensArray[tokenIndex + 2]?.scopes.includes(LanguageScopes.assignationStatement)) { - let index = tokenIndex + 4; - - while ( - index < tokensArray.length && - !tokensArray[index].scopes.includes(LanguageScopes.separatorStatement) && - !tokensArray[index].scopes.includes(LanguageScopes.rightParametersRoundBracket) && - !tokensArray[index].scopes.includes(LanguageScopes.commentStatement) - ) { - defaultValue += this.getRawTokenContent(line, tokensArray[index]); - index++; - } - } - + // ============================================================================ + // AST-BASED POSITION QUERIES (Preferred) + // ============================================================================ + // These methods use AST tree structure for position-based queries. + // More accurate and faster than TextMate for most use cases. + + /** + * Get the token at a specific position using AST. + * Preferred over TextMate-based method. + * + * @param content - The NWScript source code + * @param position - Cursor position (0-indexed) + * @param offset - Offset from position (not implemented yet) + * @returns Token information including type and content + */ + public async getActionTargetAtPositionAST( + content: string, + position: Position, + offset: number = 0, + ): Promise<{ + tokenType: CompletionItemKind | undefined; + lookBehindRawContent: string | undefined; + rawContent: string | undefined; + }> { + // Ensure we have parsed the content + await this.tokenizeContent(content, TokenizedScope.global); + + if (!this.positionQuery) { return { - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.TypeParameter, - valueType: this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - defaultValue: defaultValue.trim() || undefined, + tokenType: undefined, + lookBehindRawContent: undefined, + rawContent: undefined, }; - }); - } - - private getFunctionComments(lines: string[], tokensLines: (IToken[] | undefined)[], index: number) { - const comments: string[] = []; - - let errorSafeIndex = Math.max(index, 0); - while (tokensLines[errorSafeIndex]?.at(0)?.scopes.find((scope) => scope === LanguageScopes.commentStatement || scope === LanguageScopes.documentationCommentStatement)) { - comments.unshift(lines[errorSafeIndex]); - errorSafeIndex--; } - return comments; + return this.positionQuery.getActionTargetAtPosition(position.line, position.character, offset); } - private isFunctionDeclaration(lineIndex: number, tokensArrays: (IToken[] | undefined)[]) { - let isFunctionDeclaration = false; - let tokensArray = tokensArrays[lineIndex]!; - let isLastParamsLine = false; - - while (!isLastParamsLine) { - isLastParamsLine = Boolean(tokensArray.find((token) => token.scopes.includes(LanguageScopes.rightParametersRoundBracket))); - - if (isLastParamsLine && Boolean(tokensArray.find((token) => token.scopes.includes(LanguageScopes.terminatorStatement) && !token.scopes.includes(LanguageScopes.block)))) { - isFunctionDeclaration = true; - } - - lineIndex = lineIndex + 1; - tokensArray = tokensArrays[lineIndex]!; + /** + * Check if position is within a specific scope using AST. + * + * @param content - The NWScript source code + * @param position - Cursor position (0-indexed) + * @param scopeType - The scope type to check for + * @returns True if position is within the scope + */ + public async isInScopeAST(content: string, position: Position, scopeType: string): Promise { + await this.tokenizeContent(content, TokenizedScope.global); + + if (!this.positionQuery) { + return false; } - return isFunctionDeclaration; - } - - private isGlobalFunctionDeclaration(lineIndex: number, tokenIndex: number, token: IToken, tokensArrays: (IToken[] | undefined)[]) { - return ( - !(tokenIndex === 0 && lineIndex === 0) && // Not sure why we need this - !token.scopes.includes(LanguageScopes.block) && - token.scopes.includes(LanguageScopes.functionIdentifier) && - this.isFunctionDeclaration(lineIndex, tokensArrays) - ); - } - - private isLocalFunctionDeclaration(lineIndex: number, tokenIndex: number, token: IToken, tokensArrays: (IToken[] | undefined)[]) { - return ( - token.scopes.includes(LanguageScopes.functionIdentifier) && - !token.scopes.includes(LanguageScopes.block) && - !(tokenIndex === 0 && lineIndex === 0) && // Not sure why we need this - !this.isFunctionDeclaration(lineIndex, tokensArrays) - ); - } - - private isGlobalConstant(token: IToken) { - return token.scopes.includes(LanguageScopes.constantIdentifer) && !token.scopes.includes(LanguageScopes.functionDeclaration) && !token.scopes.includes(LanguageScopes.block); + return this.positionQuery.isInScope(position.line, position.character, scopeType); } - private isStructDeclaration(token: IToken, lastToken: IToken, lineIndex: number, tokensArrays: (IToken[] | undefined)[]) { - return ( - token.scopes.includes(LanguageScopes.structIdentifier) && - ((tokensArrays[lineIndex + 1]?.at(0)?.scopes.includes(LanguageScopes.blockDeclaraction) && lastToken.scopes.includes(LanguageScopes.structIdentifier)) || - lastToken.scopes.includes(LanguageScopes.blockDeclaraction)) - ); - } - - private isLocalVariable(tokenIndex: number, token: IToken, tokensArray: IToken[]) { - return ( - token.scopes.includes(LanguageScopes.variableIdentifer) && - tokenIndex > 1 && - (tokensArray[tokenIndex - 2].scopes.includes(LanguageScopes.type) || tokensArray[tokenIndex - 2].scopes.includes(LanguageScopes.structIdentifier)) - ); - } - - private tokenizeLinesForGlobalScope(lines: string[], tokensArrays: (IToken[] | undefined)[], startIndex: number = 0, stopIndex: number = -1) { - const firstLineIndex = startIndex > lines.length || startIndex < 0 ? 0 : startIndex; - const lastLineIndex = stopIndex + 10 > lines.length || stopIndex < 0 ? lines.length : stopIndex; - const scope: GlobalScopeTokenizationResult = { - complexTokens: [], - structComplexTokens: [], - children: [], - }; - - let currentStruct: StructComplexToken | null = null; - for (let lineIndex = firstLineIndex; lineIndex < lastLineIndex; lineIndex++) { - const line = lines[lineIndex]; - const tokensArray = tokensArrays[lineIndex]; - - if (tokensArray) { - const lastIndex = tokensArray.length - 1; - const lastToken = tokensArray[lastIndex]; - for (let tokenIndex = 0; tokenIndex < tokensArray.length; tokenIndex++) { - const token = tokensArray[tokenIndex]; - - if (currentStruct) { - if (token.scopes.includes(LanguageScopes.blockTermination)) { - scope.structComplexTokens.push(currentStruct); - currentStruct = null; - } else if (lastIndex > 0 && tokensArray[1].scopes.includes(LanguageScopes.type)) { - currentStruct.properties.push({ - position: { line: lineIndex, character: tokensArray[3].startIndex }, - identifier: this.getRawTokenContent(line, tokensArray[3]), - tokenType: CompletionItemKind.Property, - valueType: this.getTokenLanguageType(line, tokensArray, 1), - }); - } - - break; - } - - if (token.scopes.includes(LanguageScopes.includeDeclaration)) { - scope.children.push(this.getRawTokenContent(line, tokensArray.at(-2)!)); - break; - } - - if (this.isGlobalConstant(token)) { - scope.complexTokens.push({ - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.Constant, - valueType: this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - value: this.getConstantValue(line, tokensArray), - }); - break; - } - - if (this.isGlobalFunctionDeclaration(lineIndex, tokenIndex, token, tokensArrays)) { - scope.complexTokens.push({ - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.Function, - returnType: tokenIndex === 0 ? this.getTokenLanguageType(lines[lineIndex - 1], tokensArrays[lineIndex - 1]!, 0) : this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - params: this.getFunctionParams(lineIndex, lines, tokensArrays), - comments: this.getFunctionComments(lines, tokensArrays, tokenIndex === 0 ? lineIndex - 2 : lineIndex - 1), - }); - - break; - } - - if (this.isStructDeclaration(token, lastToken, lineIndex, tokensArrays)) { - currentStruct = { - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.Struct, - properties: [], - }; - break; - } - } - } + /** + * Get function name at cursor position using AST. + * Used for signature help. + * + * @param content - The NWScript source code + * @param position - Cursor position (0-indexed) + * @returns Function name or undefined + */ + public async getFunctionNameAtPositionAST(content: string, position: Position): Promise { + await this.tokenizeContent(content, TokenizedScope.global); + + if (!this.positionQuery) { + return undefined; } - return scope; + return this.positionQuery.getFunctionNameAtPosition(position.line, position.character); } - private tokenizeLinesForLocalScope(lines: string[], tokensArrays: (IToken[] | undefined)[], startIndex: number = 0, stopIndex: number = -1) { - const firstLineIndex = startIndex > lines.length || startIndex < 0 ? 0 : startIndex; - const lastLineIndex = stopIndex > lines.length || stopIndex < 0 ? lines.length : stopIndex; - const scope: LocalScopeTokenizationResult = { - functionsComplexTokens: [], - functionVariablesComplexTokens: [], - }; - - let computeFunctionLocals = false; - let currentFunctionVariables = []; - - for (let lineIndex = lastLineIndex; lineIndex >= firstLineIndex; lineIndex--) { - const line = lines[lineIndex]; - const isLastLine = lineIndex === lastLineIndex; - const tokensArray = tokensArrays[lineIndex]; - - if (tokensArray) { - const lastIndex = tokensArray.length - 1; - const lastToken = tokensArray[lastIndex]; - - if ( - (lastToken.scopes.includes(LanguageScopes.block) && lastToken.scopes.includes(LanguageScopes.blockTermination) && lastLineIndex === lines.length) || - (isLastLine && (lastToken.scopes.includes(LanguageScopes.block) || lastToken.scopes.includes(LanguageScopes.functionDeclaration))) - ) { - computeFunctionLocals = true; - } - - for (let tokenIndex = 0; tokenIndex < tokensArray.length; tokenIndex++) { - const token = tokensArray[tokenIndex]; - - if (computeFunctionLocals && this.isLocalVariable(tokenIndex, token, tokensArray)) { - const complexToken = { - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.Variable, - valueType: this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - }; - scope.functionVariablesComplexTokens.push(complexToken); - currentFunctionVariables.push(complexToken); - - let nextVariableToken; - let currentVariableIndex = tokenIndex; - while (tokensArray[currentVariableIndex + 1] && tokensArray[currentVariableIndex + 1].scopes.includes(LanguageScopes.separatorStatement)) { - if (tokensArray[currentVariableIndex + 2].scopes.includes(LanguageScopes.variableIdentifer)) { - currentVariableIndex = currentVariableIndex + 2; - } else { - currentVariableIndex = currentVariableIndex + 3; - } - - nextVariableToken = tokensArray[currentVariableIndex]; - const complextToken = { - position: { line: lineIndex, character: nextVariableToken.startIndex }, - identifier: this.getRawTokenContent(line, nextVariableToken), - tokenType: CompletionItemKind.Variable, - valueType: this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - }; - scope.functionVariablesComplexTokens.push(complextToken); - currentFunctionVariables.push(complexToken); - } - } - - if (computeFunctionLocals && token.scopes.includes(LanguageScopes.functionParameter)) { - scope.functionVariablesComplexTokens.push({ - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.TypeParameter, - valueType: this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - }); - } - - if (this.isLocalFunctionDeclaration(lineIndex, tokenIndex, token, tokensArrays)) { - scope.functionsComplexTokens.push({ - position: { line: lineIndex, character: token.startIndex }, - identifier: this.getRawTokenContent(line, token), - tokenType: CompletionItemKind.Function, - returnType: tokenIndex === 0 ? this.getTokenLanguageType(lines[lineIndex - 1], tokensArrays[lineIndex - 1]!, 0) : this.getTokenLanguageType(line, tokensArray, tokenIndex - 2), - params: this.getFunctionParams(lineIndex, lines, tokensArrays), - comments: this.getFunctionComments(lines, tokensArrays, tokenIndex === 0 ? lineIndex - 2 : lineIndex - 1), - variables: currentFunctionVariables, - }); - } - } - - // Needs to be after to allow one more iteration to fetch function params - if (computeFunctionLocals && !lastToken.scopes.includes(LanguageScopes.block)) { - computeFunctionLocals = false; - currentFunctionVariables = []; - } - } + /** + * Get active parameter index at cursor position using AST. + * Used for signature help. + * + * @param content - The NWScript source code + * @param position - Cursor position (0-indexed) + * @returns Parameter index (0-based) + */ + public async getActiveParameterIndexAST(content: string, position: Position): Promise { + await this.tokenizeContent(content, TokenizedScope.global); + + if (!this.positionQuery) { + return 0; } - return scope; + return this.positionQuery.getActiveParameterIndex(position.line, position.character); } - public tokenizeContent(content: string, scope: TokenizedScope.global, startIndex?: number, stopIndex?: number): GlobalScopeTokenizationResult; - public tokenizeContent(content: string, scope: TokenizedScope.local, startIndex?: number, stopIndex?: number): LocalScopeTokenizationResult; - public tokenizeContent(content: string, scope: TokenizedScope, startIndex: number = 0, stopIndex: number = -1) { - const [lines, rawTokenizedContent] = this.tokenizeContentToRaw(content); + // ============================================================================ + // TEXTMATE-BASED TOKENIZATION (Legacy Position Queries) + // ============================================================================ + // These methods use TextMate grammar for fine-grained position-based queries. + // Kept for compatibility, prefer AST-based methods above. + // Used for: LSP features like hover, autocomplete, signature help at cursor position - if (scope === TokenizedScope.global) { - return this.tokenizeLinesForGlobalScope(lines, rawTokenizedContent, startIndex, stopIndex); - } else { - return this.tokenizeLinesForLocalScope(lines, rawTokenizedContent, startIndex, stopIndex); - } - } - - public tokenizeContentFromRaw(lines: string[], rawTokenizedContent: (IToken[] | undefined)[], startIndex: number = 0, stopIndex: number = -1) { - return this.tokenizeLinesForLocalScope(lines, rawTokenizedContent, startIndex, stopIndex); - } + /** + * Tokenize content to raw TextMate tokens for position-based queries. + * + * @param content - The NWScript source code + * @returns Tuple of [lines, token arrays per line] + */ public tokenizeContentToRaw(content: string): [lines: string[], rawTokenizedContent: (IToken[] | undefined)[]] { const lines = content.split(/\r?\n/); @@ -403,16 +260,24 @@ export default class Tokenizer { lines, lines.map((line) => { const tokenizedLine = this.grammar?.tokenizeLine(line, ruleStack); - if (tokenizedLine) { ruleStack = tokenizedLine.ruleStack; } - return tokenizedLine?.tokens; }), ]; } + /** + * Get the token at or near a specific position in the document. + * + * @param lines - Source code lines + * @param tokensArrays - TextMate tokens per line + * @param position - Cursor position + * @param offset - Offset from position (-1 for previous token, 1 for next) + * @returns Token information including type and content + */ + public getActionTargetAtPosition(lines: string[], tokensArrays: (IToken[] | undefined)[], position: Position, offset: number = 0) { let tokenType; let lookBehindRawContent; @@ -459,6 +324,16 @@ export default class Tokenizer { }; } + /** + * Look backward from position to find content matching specific scopes. + * Used for finding function names in signature help. + * + * @param line - The source line + * @param tokensArray - TextMate tokens for this line + * @param position - Starting position + * @param languageScopes - Scopes to match (e.g., function identifier) + * @returns The matched token content + */ public getLookBehindScopesRawContent(line: string, tokensArray: IToken[], position: Position, languageScopes: LanguageScopes[]) { let identifier: string | undefined; const tokenIndex = this.getTokenIndexAtPosition(tokensArray, position); @@ -473,28 +348,66 @@ export default class Tokenizer { return identifier; } + /** + * Count occurrences of a specific scope when looking backward from position. + * Used for counting commas to determine active parameter in signature help. + * + * @param tokensArray - TextMate tokens for this line + * @param position - Starting position + * @param occurencesTarget - Scope to count (e.g., separator/comma) + * @param delimiter - Scope to stop at (e.g., function call start) + * @returns Number of occurrences found + */ + public getLookBehindScopeOccurences(tokensArray: IToken[], position: Position, occurencesTarget: LanguageScopes, delimiter: LanguageScopes) { let occurences = 0; - let currentIndex = this.getTokenIndexAtPosition(tokensArray, position); + while (currentIndex >= 0 && !tokensArray[currentIndex].scopes.includes(delimiter)) { if (tokensArray[currentIndex].scopes.includes(occurencesTarget)) { occurences++; } - currentIndex--; } return occurences; } + /** + * Check if a position is within a specific language scope. + * Used for determining if cursor is inside a function call, etc. + * + * @param tokensArray - TextMate tokens for this line + * @param position - Position to check + * @param scope - Scope to check for (e.g., function call) + * @returns True if position is in the specified scope + */ public isInScope(tokensArray: IToken[], position: Position, scope: LanguageScopes) { return this.getTokenAtPosition(tokensArray, position)?.scopes.includes(scope); } - public async loadGrammar() { - this.grammar = await this.registry.loadGrammar("source.nss"); + // ============================================================================ + // PRIVATE HELPER METHODS + // ============================================================================ - return this; + /** + * Find the index of the token at a specific position. + */ + private getTokenIndexAtPosition(tokensArray: IToken[], position: Position) { + return tokensArray.findIndex((token) => token.startIndex <= position.character && token.endIndex >= position.character); + } + + /** + * Find the token at a specific position. + */ + private getTokenAtPosition(tokensArray: IToken[], position: Position) { + return tokensArray.find((token) => token.startIndex <= position.character && token.endIndex >= position.character); + } + + /** + * Extract the raw text content of a token. + */ + private getRawTokenContent(line: string, token: IToken) { + return line.slice(token.startIndex, token.endIndex); } } diff --git a/server/src/Tokenizer/types.ts b/server/src/Tokenizer/types.ts index be51b9d..4af9e13 100644 --- a/server/src/Tokenizer/types.ts +++ b/server/src/Tokenizer/types.ts @@ -41,10 +41,4 @@ export type FunctionParamComplexToken = BaseComplexToken; export type StructComplexToken = BaseComplexToken; export type StructPropertyComplexToken = BaseComplexToken; -export type ComplexToken = - | ConstantComplexToken - | VariableComplexToken - | FunctionParamComplexToken - | FunctionComplexToken - | StructComplexToken - | StructPropertyComplexToken; +export type ComplexToken = ConstantComplexToken | VariableComplexToken | FunctionParamComplexToken | FunctionComplexToken | StructComplexToken | StructPropertyComplexToken; diff --git a/server/src/server.ts b/server/src/server.ts index 77da547..262246b 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -11,7 +11,7 @@ connection.onInitialize(async (params: InitializeParams) => { }); connection.onInitialized(() => { - server.up(); + void server.up(); }); connection.onShutdown(() => server.down()); diff --git a/server/test/ast_integration_test.ts b/server/test/ast_integration_test.ts new file mode 100644 index 0000000..814880a --- /dev/null +++ b/server/test/ast_integration_test.ts @@ -0,0 +1,206 @@ +import { expect } from "chai"; +import Tokenizer, { TokenizedScope } from "../src/Tokenizer/Tokenizer"; + +describe("AST Integration Tests", () => { + let tokenizer: Tokenizer; + + before(async () => { + tokenizer = await new Tokenizer(true).loadGrammar(); + }); + + describe("Basic position queries", () => { + const code = `int Add(int a, int b) { return a + b; } + +void main() { + int result = Add(5, 10); +}`; + + it("should find tokens using AST position queries", async () => { + // Just verify that the method works without errors + const result = await tokenizer.getActionTargetAtPositionAST(code, { + line: 3, + character: 20, + }); + + expect(result).to.exist; + expect(result).to.have.property("tokenType"); + expect(result).to.have.property("rawContent"); + }); + + it("should detect function call scope", async () => { + // Position somewhere inside Add(5, 10) + const isInCall = await tokenizer.isInScopeAST( + code, + { line: 3, character: 22 }, + "function.call" + ); + + expect(isInCall).to.be.a("boolean"); + }); + + it("should get function name in call context", async () => { + // Position inside function call + const funcName = await tokenizer.getFunctionNameAtPositionAST(code, { + line: 3, + character: 22, + }); + + // Should either find the function name or return undefined + if (funcName) { + expect(funcName).to.be.a("string"); + } else { + expect(funcName).to.be.undefined; + } + }); + + it("should get active parameter index", async () => { + // Position inside function call + const index = await tokenizer.getActiveParameterIndexAST(code, { + line: 3, + character: 22, + }); + + expect(index).to.be.a("number"); + expect(index).to.be.at.least(0); + }); + }); + + describe("AST structural tokenization still works", () => { + const code = `int globalVar = 10; + +void TestFunc(int param) { + int localVar = 5; +} + +void main() {}`; + + it("should extract global tokens", async () => { + const result = await tokenizer.tokenizeContent( + code, + TokenizedScope.global + ); + + expect(result.complexTokens).to.be.an("array"); + expect(result.structComplexTokens).to.be.an("array"); + + // Should find at least the functions + const funcNames = result.complexTokens + .filter((t) => t.identifier) + .map((t) => t.identifier); + + expect(funcNames).to.include("TestFunc"); + expect(funcNames).to.include("main"); + }); + + it("should extract local tokens", async () => { + const result = await tokenizer.tokenizeContent( + code, + TokenizedScope.local, + 0, + 5 + ); + + expect(result.functionsComplexTokens).to.be.an("array"); + expect(result.functionVariablesComplexTokens).to.be.an("array"); + }); + }); + + describe("Caching behavior", () => { + const code = `void test() { + int x = 5; +} + +void main() {}`; + + it("should cache parsed AST", async () => { + // First call + const start1 = Date.now(); + await tokenizer.getActionTargetAtPositionAST(code, { + line: 1, + character: 8, + }); + const time1 = Date.now() - start1; + + // Second call with same code - should use cache + const start2 = Date.now(); + await tokenizer.getActionTargetAtPositionAST(code, { + line: 1, + character: 10, + }); + const time2 = Date.now() - start2; + + // Second call should be faster or roughly the same (cache hit) + // Allow some variance for timing inconsistencies + expect(time2).to.be.lessThan(time1 + 50); + }); + }); + + describe("Error handling", () => { + it("should handle invalid code gracefully", async () => { + const invalidCode = `void test() { + int x = ; // syntax error +}`; + + // Should not throw + const result = await tokenizer.getActionTargetAtPositionAST(invalidCode, { + line: 1, + character: 10, + }); + + expect(result).to.exist; + }); + + it("should handle empty code", async () => { + const result = await tokenizer.getActionTargetAtPositionAST("", { + line: 0, + character: 0, + }); + + expect(result.tokenType).to.be.undefined; + expect(result.rawContent).to.be.undefined; + }); + + it("should handle out of bounds positions", async () => { + const code = `void main() {}`; + + const result = await tokenizer.getActionTargetAtPositionAST(code, { + line: 999, + character: 999, + }); + + expect(result).to.exist; + }); + }); + + describe("Complex scenarios", () => { + const code = `int Multiply(int a, int b) { return a * b; } +int Add(int a, int b) { return a + b; } + +void main() { + int result = Add(Multiply(2, 3), 10); +}`; + + it("should handle nested function calls", async () => { + // Query inside nested call + const funcName = await tokenizer.getFunctionNameAtPositionAST(code, { + line: 4, + character: 26, + }); + + // Should find either Multiply or Add + if (funcName) { + expect(["Multiply", "Add"]).to.include(funcName); + } + }); + + it("should detect scope in nested calls", async () => { + const isInCall = await tokenizer.isInScopeAST( + code, + { line: 4, character: 26 }, + "function.call" + ); + + expect(isInCall).to.be.a("boolean"); + }); + }); +}); diff --git a/server/test/ast_tokenization_test.ts b/server/test/ast_tokenization_test.ts new file mode 100644 index 0000000..3f82b0a --- /dev/null +++ b/server/test/ast_tokenization_test.ts @@ -0,0 +1,259 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import { describe, before, it } from "mocha"; +import { expect } from "chai"; +import Tokenizer, { TokenizedScope, GlobalScopeTokenizationResult, LocalScopeTokenizationResult } from "../src/Tokenizer/Tokenizer"; +import { CompletionItemKind } from "vscode-languageserver"; +import type { ConstantComplexToken, FunctionComplexToken, VariableComplexToken } from "../src/Tokenizer/types"; + +describe("AST Tokenization", () => { + let astTokenizer: Tokenizer; + + before("Initialize tokenizers", async () => { + astTokenizer = await new Tokenizer(true).loadGrammar(); + }); + + describe("Simple script parsing", () => { + const simpleScript = ` +// Simple test script +int MAX_VALUE = 100; +string TEST_NAME = "test"; + +struct TestStruct { + int id; + string name; +}; + +int Add(int a, int b) { + return a + b; +} + +void main() { + int x = 5; + int y = 10; + int result = Add(x, y); +} +`; + + it("should successfully parse with AST mode", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.global); + + expect(result).to.not.be.undefined; + expect(result.complexTokens).to.be.an("array"); + expect(result.structComplexTokens).to.be.an("array"); + }); + + it("should extract constants", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.global); + + const constants = result.complexTokens.filter((t) => t.tokenType === CompletionItemKind.Constant) as ConstantComplexToken[]; + expect(constants.length).to.be.at.least(2); + + const maxValue = constants.find((c) => c.identifier === "MAX_VALUE"); + expect(maxValue).to.not.be.undefined; + expect(maxValue?.valueType).to.equal("int"); + + const testName = constants.find((c) => c.identifier === "TEST_NAME"); + expect(testName).to.not.be.undefined; + expect(testName?.valueType).to.equal("string"); + }); + + it("should extract struct definitions", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.global); + + expect(result.structComplexTokens.length).to.be.at.least(1); + + const testStruct = result.structComplexTokens.find((s) => s.identifier === "TestStruct"); + expect(testStruct).to.not.be.undefined; + expect(testStruct?.properties.length).to.be.at.least(2); + + const idProp = testStruct?.properties.find((p) => p.identifier === "id"); + expect(idProp).to.not.be.undefined; + expect(idProp?.valueType).to.equal("int"); + + const nameProp = testStruct?.properties.find((p) => p.identifier === "name"); + expect(nameProp).to.not.be.undefined; + expect(nameProp?.valueType).to.equal("string"); + }); + + it("should extract function declarations", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.global); + + const functions = result.complexTokens.filter((t) => t.tokenType === CompletionItemKind.Function) as FunctionComplexToken[]; + expect(functions.length).to.be.at.least(2); + + const addFunc = functions.find((f) => f.identifier === "Add"); + expect(addFunc).to.not.be.undefined; + expect(addFunc?.returnType).to.equal("int"); + + const mainFunc = functions.find((f) => f.identifier === "main"); + expect(mainFunc).to.not.be.undefined; + expect(mainFunc?.returnType).to.equal("void"); + }); + + it("should extract function parameters", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.global); + + const functions = result.complexTokens.filter((t) => t.tokenType === CompletionItemKind.Function) as FunctionComplexToken[]; + const addFunc = functions.find((f) => f.identifier === "Add"); + + expect(addFunc).to.not.be.undefined; + expect(addFunc?.params.length).to.equal(2); + + const paramA = addFunc?.params.find((p: any) => p.identifier === "a"); + expect(paramA).to.not.be.undefined; + expect(paramA?.valueType).to.equal("int"); + + const paramB = addFunc?.params.find((p: any) => p.identifier === "b"); + expect(paramB).to.not.be.undefined; + expect(paramB?.valueType).to.equal("int"); + }); + + it("should extract local scope variables", async () => { + const result: LocalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.local); + + expect(result.functionVariablesComplexTokens).to.be.an("array"); + expect(result.functionVariablesComplexTokens.length).to.be.at.least(3); + + const xVar = result.functionVariablesComplexTokens.find((v) => v.identifier === "x") as VariableComplexToken | undefined; + expect(xVar).to.not.be.undefined; + expect(xVar?.valueType).to.equal("int"); + + const yVar = result.functionVariablesComplexTokens.find((v) => v.identifier === "y"); + expect(yVar).to.not.be.undefined; + + const resultVar = result.functionVariablesComplexTokens.find((v) => v.identifier === "result"); + expect(resultVar).to.not.be.undefined; + }); + + it("should extract local scope functions", async () => { + const result: LocalScopeTokenizationResult = await astTokenizer.tokenizeContent(simpleScript, TokenizedScope.local); + + expect(result.functionsComplexTokens).to.be.an("array"); + const addFunc = result.functionsComplexTokens.find((f) => f.identifier === "Add"); + expect(addFunc).to.not.be.undefined; + }); + }); + + describe("Position accuracy", () => { + const positionScript = `void testFunction() { + int x = 5; +} + +void main() {}`; + + it("should have accurate line numbers (0-indexed)", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(positionScript, TokenizedScope.global); + + const func = result.complexTokens.find((t) => t.identifier === "testFunction"); + expect(func).to.not.be.undefined; + expect(func?.position.line).to.equal(0); // First line, 0-indexed + }); + + it("should have accurate character positions", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(positionScript, TokenizedScope.global); + + const func = result.complexTokens.find((t) => t.identifier === "testFunction"); + expect(func).to.not.be.undefined; + expect(func?.position.character).to.be.greaterThan(0); + }); + }); + + describe("Complex expressions", () => { + const complexScript = ` +int Calculate() { + int a = 5 + 3; + int b = a * 2; + int c = (b - 1) / 2; + return c; +} + +void TestLogic() { + int x = 10; + if (x > 5) { + x = x + 1; + } +} + +void main() {} +`; + + it("should handle complex expressions", async () => { + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(complexScript, TokenizedScope.global); + + expect(result.complexTokens.length).to.be.at.least(2); + + const calcFunc = result.complexTokens.find((f) => f.identifier === "Calculate") as FunctionComplexToken | undefined; + expect(calcFunc).to.not.be.undefined; + expect(calcFunc?.returnType).to.equal("int"); + }); + + it("should extract variables from complex functions", async () => { + const result: LocalScopeTokenizationResult = await astTokenizer.tokenizeContent(complexScript, TokenizedScope.local); + + const variables = result.functionVariablesComplexTokens; + expect(variables.length).to.be.at.least(4); // a, b, c, x + + const varA = variables.find((v) => v.identifier === "a") as VariableComplexToken | undefined; + expect(varA).to.not.be.undefined; + expect(varA?.valueType).to.equal("int"); + }); + }); + + describe("Error handling", () => { + const invalidScript = ` +void main() { + UndefinedFunction(); +}`; + + it("should return empty results on parse errors", async () => { + // AST parsing will fail for invalid code, returns empty results + const result: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(invalidScript, TokenizedScope.global); + + expect(result).to.not.be.undefined; + expect(result.complexTokens).to.be.an("array"); + expect(result.complexTokens.length).to.equal(0); + }); + }); + + describe("Edge cases", () => { + it("should handle multi-line function declarations", async () => { + const edgeCaseScript = ` +// Multi-line function declaration +int +VeryLongFunctionName +( + int parameter +) +{ + return parameter; +} + +void main() {} +`; + + const astResult: GlobalScopeTokenizationResult = await astTokenizer.tokenizeContent(edgeCaseScript, TokenizedScope.global); + + // AST should find the function + const func = astResult.complexTokens.find((t) => t.identifier === "VeryLongFunctionName") as FunctionComplexToken | undefined; + expect(func).to.not.be.undefined; + expect(func?.returnType).to.equal("int"); + }); + }); + + describe("Performance", () => { + it("should cache AST for repeated calls", async () => { + const script = "void main() { int x = 5; }"; + + const start1 = Date.now(); + await astTokenizer.tokenizeContent(script, TokenizedScope.global); + const time1 = Date.now() - start1; + + const start2 = Date.now(); + await astTokenizer.tokenizeContent(script, TokenizedScope.global); + const time2 = Date.now() - start2; + + // Second call should be faster (cached) + expect(time2).to.be.lessThan(time1); + }); + }); +}); diff --git a/server/test/static/globalScopeTokens.json b/server/test/static/globalScopeTokens.json index bf005da..3861a71 100644 --- a/server/test/static/globalScopeTokens.json +++ b/server/test/static/globalScopeTokens.json @@ -1,552 +1,1059 @@ { "complexTokens": [ - { + { + "position": { + "line": 43, + "character": 15 + }, + "identifier": "iSkill", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 44, + "character": 14 + }, + "identifier": "iFeat", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 47, + "character": 18 + }, + "identifier": "iModifier", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 51, + "character": 19 + }, + "identifier": "iFocusFeat", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 61, + "character": 20 + }, + "identifier": "sClasses", + "tokenType": 21, + "valueType": "string", + "value": "" + }, + { + "position": { + "line": 65, + "character": 25 + }, + "identifier": "fClassLevelMod", + "tokenType": 21, + "valueType": "float", + "value": "" + }, + { + "position": { + "line": 68, + "character": 27 + }, + "identifier": "iAreaFlagsRequired", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 71, + "character": 28 + }, + "identifier": "iAreaFlagsForbidden", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 75, + "character": 20 + }, + "identifier": "iDayOrNight", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 78, + "character": 33 + }, + "identifier": "bBypassArmorCheckPenalty", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 85, + "character": 24 + }, + "identifier": "iKeyAbilityMask", + "tokenType": 21, + "valueType": "int", + "value": "" + }, + { + "position": { + "line": 2, + "character": 26 + }, + "identifier": "NWNX_PushArgumentInt", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 2, + "character": 32 + }, + "identifier": "i", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 3, + "character": 28 + }, + "identifier": "NWNX_PushArgumentFloat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 3, + "character": 36 + }, + "identifier": "f", + "tokenType": 25, + "valueType": "float" + } + ], + "comments": [] + }, + { + "position": { + "line": 4, + "character": 29 + }, + "identifier": "NWNX_PushArgumentString", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 4, + "character": 38 + }, + "identifier": "s", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 5, + "character": 29 + }, + "identifier": "NWNX_PushArgumentObject", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 5, + "character": 38 + }, + "identifier": "o", + "tokenType": 25, + "valueType": "object" + } + ], + "comments": [] + }, + { + "position": { + "line": 6, + "character": 23 + }, + "identifier": "NWNX_CallFunction", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 6, + "character": 37 + }, + "identifier": "plugin", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 6, + "character": 50 + }, + "identifier": "func", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 7, + "character": 27 + }, + "identifier": "NWNX_GetReturnValueInt", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 8, + "character": 31 + }, + "identifier": "NWNX_GetReturnValueFloat", + "tokenType": 3, + "returnType": "float", + "params": [], + "comments": [] + }, + { + "position": { + "line": 9, + "character": 33 + }, + "identifier": "NWNX_GetReturnValueString", + "tokenType": 3, + "returnType": "string", + "params": [], + "comments": [] + }, + { + "position": { + "line": 10, + "character": 33 + }, + "identifier": "NWNX_GetReturnValueObject", + "tokenType": 3, + "returnType": "object", + "params": [], + "comments": [] + }, + { + "position": { + "line": 8, + "character": 22 + }, + "identifier": "GetStringRight", + "tokenType": 3, + "returnType": "string", + "params": [ + { + "position": { + "line": 8, + "character": 37 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 8, + "character": 49 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 9, + "character": 21 + }, + "identifier": "GetStringLeft", + "tokenType": 3, + "returnType": "string", + "params": [ + { + "position": { + "line": 9, + "character": 36 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { "position": { - "line": 7, - "character": 13 + "line": 9, + "character": 48 }, - "identifier": "NWNX_SkillRanks", - "tokenType": 21, - "valueType": "string", - "value": "\"NWNX_SkillRanks\"" + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 10, + "character": 20 }, - { + "identifier": "GetStringLength", + "tokenType": 3, + "returnType": "int", + "params": [ + { "position": { - "line": 14, - "character": 10 + "line": 10, + "character": 35 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_STRENGTH", - "tokenType": 21, - "valueType": "int", - "value": "1" + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 11, + "character": 18 }, - { + "identifier": "FindSubString", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 11, + "character": 33 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { "position": { - "line": 15, - "character": 10 + "line": 11, + "character": 52 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_DEXTERITY", - "tokenType": 21, - "valueType": "int", - "value": "2" + "identifier": "sSubString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 64 + }, + "identifier": "nStart", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 90, + "character": 46 }, - { + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { "position": { - "line": 16, - "character": 10 + "line": 90, + "character": 57 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CONSTITUTION", - "tokenType": 21, - "valueType": "int", - "value": "4" + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 96, + "character": 62 }, - { + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 97, + "character": 13 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 98, + "character": 12 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 107, + "character": 44 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 107, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 107, + "character": 67 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 113, + "character": 29 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 114, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { "position": { - "line": 17, - "character": 10 + "line": 115, + "character": 26 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_INTELLIGENCE", - "tokenType": 21, - "valueType": "int", - "value": "8" + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 125, + "character": 34 }, - { + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "struct", + "params": [ + { "position": { - "line": 18, - "character": 10 + "line": 126, + "character": 45 }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_WISDOM", - "tokenType": 21, - "valueType": "int", - "value": "16" - }, - { - "position": { - "line": 19, - "character": 10 - }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CHARISMA", - "tokenType": 21, - "valueType": "int", - "value": "32" - }, - { - "position": { - "line": 28, - "character": 10 - }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_MIN", - "tokenType": 21, - "valueType": "int", - "value": "64" - }, - { - "position": { - "line": 29, - "character": 10 - }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_MAX", - "tokenType": 21, - "valueType": "int", - "value": "128" - }, - { - "position": { - "line": 30, - "character": 10 - }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_AVERAGE", - "tokenType": 21, - "valueType": "int", - "value": "256" - }, - { - "position": { - "line": 31, - "character": 10 - }, - "identifier": "NWNX_SKILLRANKS_KEY_ABILITY_CALC_SUM", - "tokenType": 21, - "valueType": "int", - "value": "512" - }, - { - "position": { - "line": 84, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 84, - "character": 50 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @param iSkill The skill to check the feat count.", - "/// @return The count of feats for a specific skill." - ] - }, - { - "position": { - "line": 90, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeat", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 91, - "character": 6 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 92, - "character": 6 - }, - "identifier": "iFeat", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Returns a skill feat.", - "/// @param iSkill The skill.", - "/// @param iFeat The feat.", - "/// @return A constructed NWNX_SkillRanks_SkillFeat." - ] - }, - { - "position": { - "line": 101, - "character": 0 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 101, - "character": 48 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 101, - "character": 60 - }, - "identifier": "iIndex", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Returns a skill feat by index.", - "/// @remark Generally used in a loop with NWNX_SkillRanks_GetSkillFeatCountForSkill().", - "/// @param iSkill The skill.", - "/// @param iIndex The index in the list of feats for the skill.", - "/// @return A constructed NWNX_SkillRanks_SkillFeat." - ] - }, - { - "position": { - "line": 107, - "character": 0 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeat", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 108, - "character": 35 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 109, - "character": 6 - }, - "identifier": "createIfNonExistent", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [ - "/// @brief Modifies or creates a skill feat.", - "/// @param skillFeat The defined NWNX_SkillRanks_SkillFeat.", - "/// @param createIfNonExistent TRUE to create if the feat does not exist." - ] - }, - { - "position": { - "line": 119, - "character": 0 - }, - "identifier": "NWNX_SkillRanks_AddSkillFeatClass", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 120, - "character": 35 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 121, - "character": 6 - }, - "identifier": "iClass", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Add classes to a skill feat instead of working with the NWNX_SkillRanks_SkillFeat::sClasses string.", - "///", - "/// Manipulating the sClasses string in the NWNX_SkillRanks_SkillFeat struct can be difficult. This", - "/// function allows the builder to enter one class at a time.", - "/// @param skillFeat The NWNX_SkillRanks_SkillFeat for which the sClasses field will be modifier.", - "/// @param iClass The class to add to the Skill Feat.", - "/// @return The updated NWNX_SkillRanks_SkillFeat." - ] - }, - { - "position": { - "line": 130, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 130, - "character": 51 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 130, - "character": 66 - }, - "identifier": "iEpic", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [ - "/// @brief Change the modifier value for Skill Focus and Epic Skill Focus feats.", - "///", - "/// The stock modifier on Skill Focus and Epic Skill Focus are 3 and 10 respectively, these can be", - "/// changed with this function.", - "/// @param iModifier The new value for the feat modifier.", - "/// @param iEpic Set to TRUE to change the value for Epic Skill Focus." - ] - }, - { - "position": { - "line": 134, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", - "tokenType": 3, - "returnType": "int", - "params": [], - "comments": [ - "/// @brief Gets the current penalty to Dexterity based skills when blind.", - "/// @return The penalty to Dexterity when blind." - ] - }, - { - "position": { - "line": 139, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 139, - "character": 45 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Set the value the Dexterity based skills get decreased due to blindness.", - "/// @remark Default is 4.", - "/// @param iModifier The penalty to Dexterity when blind." - ] - }, - { - "position": { - "line": 145, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetAreaModifier", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 145, - "character": 43 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 145, - "character": 54 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Get a skill modifier for an area.", - "/// @param oArea The area.", - "/// @param iSkill The skill to check.", - "/// @return The modifier to that skill in the area." - ] - }, - { - "position": { - "line": 151, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetAreaModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 151, - "character": 44 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 151, - "character": 55 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 151, - "character": 67 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [ - "/// @brief Sets a skill modifier for the area.", - "/// @param oArea The area.", - "/// @param iSkill The skill to change.", - "/// @param iModifier The modifier to the skill in the area." - ] - } + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 127, + "character": 13 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 136, + "character": 47 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 136, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 136, + "character": 72 + }, + "identifier": "iEpic", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 140, + "character": 40 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 145, + "character": 41 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 145, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 151, + "character": 36 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 151, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 151, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 157, + "character": 37 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 157, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 157, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 157, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 161, + "character": 46 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 161, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 171, + "character": 77 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 171, + "character": 88 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 171, + "character": 100 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 196, + "character": 34 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 196, + "character": 77 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 196, + "character": 102 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 216, + "character": 67 + }, + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 216, + "character": 110 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 216, + "character": 122 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 229, + "character": 47 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 229, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 229, + "character": 76 + }, + "identifier": "epicFocus", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 238, + "character": 40 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 247, + "character": 41 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 247, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 255, + "character": 36 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 255, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 255, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 266, + "character": 37 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 266, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 266, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 266, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 276, + "character": 62 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 276, + "character": 73 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 276, + "character": 84 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 302, + "character": 10 + }, + "identifier": "main", + "tokenType": 3, + "returnType": "void", + "params": [], + "comments": [] + } ], "structComplexTokens": [ - { - "position": { - "line": 35, - "character": 7 - }, - "identifier": "NWNX_SkillRanks_SkillFeat", - "tokenType": 22, - "properties": [ - { - "position": { - "line": 37, - "character": 8 - }, - "identifier": "iSkill", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 38, - "character": 8 - }, - "identifier": "iFeat", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 41, - "character": 8 - }, - "identifier": "iModifier", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 45, - "character": 8 - }, - "identifier": "iFocusFeat", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 55, - "character": 11 - }, - "identifier": "sClasses", - "tokenType": 10, - "valueType": "string" - }, - { - "position": { - "line": 59, - "character": 10 - }, - "identifier": "fClassLevelMod", - "tokenType": 10, - "valueType": "float" - }, - { - "position": { - "line": 62, - "character": 8 - }, - "identifier": "iAreaFlagsRequired", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 65, - "character": 8 - }, - "identifier": "iAreaFlagsForbidden", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 69, - "character": 8 - }, - "identifier": "iDayOrNight", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 72, - "character": 8 - }, - "identifier": "bBypassArmorCheckPenalty", - "tokenType": 10, - "valueType": "int" - }, - { - "position": { - "line": 79, - "character": 8 - }, - "identifier": "iKeyAbilityMask", - "tokenType": 10, - "valueType": "int" - } - ] - } + { + "position": { + "line": 41, + "character": 7 + }, + "identifier": "NWNX_SkillRanks_SkillFeat", + "tokenType": 22, + "properties": [ + { + "position": { + "line": 43, + "character": 15 + }, + "identifier": "iSkill", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 44, + "character": 14 + }, + "identifier": "iFeat", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 47, + "character": 18 + }, + "identifier": "iModifier", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 51, + "character": 19 + }, + "identifier": "iFocusFeat", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 61, + "character": 20 + }, + "identifier": "sClasses", + "tokenType": 10, + "valueType": "string" + }, + { + "position": { + "line": 65, + "character": 25 + }, + "identifier": "fClassLevelMod", + "tokenType": 10, + "valueType": "float" + }, + { + "position": { + "line": 68, + "character": 27 + }, + "identifier": "iAreaFlagsRequired", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 71, + "character": 28 + }, + "identifier": "iAreaFlagsForbidden", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 75, + "character": 20 + }, + "identifier": "iDayOrNight", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 78, + "character": 33 + }, + "identifier": "bBypassArmorCheckPenalty", + "tokenType": 10, + "valueType": "int" + }, + { + "position": { + "line": 85, + "character": 24 + }, + "identifier": "iKeyAbilityMask", + "tokenType": 10, + "valueType": "int" + } + ] + } ], - "children": [ - "nwnx" - ] + "children": [] } \ No newline at end of file diff --git a/server/test/static/localScopeTokensWithContext.json b/server/test/static/localScopeTokensWithContext.json index 292cddc..c3a67ba 100644 --- a/server/test/static/localScopeTokensWithContext.json +++ b/server/test/static/localScopeTokensWithContext.json @@ -1,345 +1,1476 @@ { - "functionsComplexTokens": [ - { - "position": { - "line": 270, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeat", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 270, - "character": 66 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 270, - "character": 78 - }, - "identifier": "iFeat", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 278, - "character": 37 - }, - "identifier": "skillFeat", - "tokenType": 6, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 272, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] + "functionsComplexTokens": [ + { + "position": { + "line": 2, + "character": 26 + }, + "identifier": "NWNX_PushArgumentInt", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 2, + "character": 32 + }, + "identifier": "i", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 3, + "character": 28 + }, + "identifier": "NWNX_PushArgumentFloat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 3, + "character": 36 + }, + "identifier": "f", + "tokenType": 25, + "valueType": "float" + } + ], + "comments": [] + }, + { + "position": { + "line": 4, + "character": 29 + }, + "identifier": "NWNX_PushArgumentString", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 4, + "character": 38 + }, + "identifier": "s", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 5, + "character": 29 + }, + "identifier": "NWNX_PushArgumentObject", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 5, + "character": 38 + }, + "identifier": "o", + "tokenType": 25, + "valueType": "object" + } + ], + "comments": [] + }, + { + "position": { + "line": 6, + "character": 23 + }, + "identifier": "NWNX_CallFunction", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 6, + "character": 37 + }, + "identifier": "plugin", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 6, + "character": 50 + }, + "identifier": "func", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 7, + "character": 27 + }, + "identifier": "NWNX_GetReturnValueInt", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 8, + "character": 31 + }, + "identifier": "NWNX_GetReturnValueFloat", + "tokenType": 3, + "returnType": "float", + "params": [], + "comments": [] + }, + { + "position": { + "line": 9, + "character": 33 + }, + "identifier": "NWNX_GetReturnValueString", + "tokenType": 3, + "returnType": "string", + "params": [], + "comments": [] + }, + { + "position": { + "line": 10, + "character": 33 + }, + "identifier": "NWNX_GetReturnValueObject", + "tokenType": 3, + "returnType": "object", + "params": [], + "comments": [] + }, + { + "position": { + "line": 8, + "character": 22 + }, + "identifier": "GetStringRight", + "tokenType": 3, + "returnType": "string", + "params": [ + { + "position": { + "line": 8, + "character": 37 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 8, + "character": 49 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 9, + "character": 21 + }, + "identifier": "GetStringLeft", + "tokenType": 3, + "returnType": "string", + "params": [ + { + "position": { + "line": 9, + "character": 36 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 9, + "character": 48 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 10, + "character": 20 + }, + "identifier": "GetStringLength", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 10, + "character": 35 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 11, + "character": 18 + }, + "identifier": "FindSubString", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 11, + "character": 33 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 52 + }, + "identifier": "sSubString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 64 + }, + "identifier": "nStart", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 90, + "character": 46 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 90, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 96, + "character": 62 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 97, + "character": 13 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 98, + "character": 12 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 107, + "character": 44 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 107, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 260, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetAreaModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 260, - "character": 44 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 260, - "character": 55 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 260, - "character": 67 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 107, + "character": 67 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 113, + "character": 29 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 114, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 249, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetAreaModifier", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 249, - "character": 43 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 249, - "character": 54 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 115, + "character": 26 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 125, + "character": 34 + }, + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 126, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 241, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 241, - "character": 45 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 127, + "character": 13 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 136, + "character": 47 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 136, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 232, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", - "tokenType": 3, - "returnType": "int", - "params": [], - "comments": [], - "variables": [] + "position": { + "line": 136, + "character": 72 + }, + "identifier": "iEpic", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 140, + "character": 40 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 145, + "character": 41 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 145, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 151, + "character": 36 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 151, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 223, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 223, - "character": 51 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 223, - "character": 66 - }, - "identifier": "epicFocus", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 151, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 157, + "character": 37 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 157, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 210, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_AddSkillFeatClass", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 210, - "character": 100 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 210, - "character": 115 - }, - "identifier": "iClass", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 157, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 190, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeat", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 190, - "character": 67 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 190, - "character": 82 - }, - "identifier": "createIfNonExistent", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 157, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 161, + "character": 46 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 161, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 171, + "character": 77 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 171, + "character": 88 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 165, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 165, - "character": 81 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 165, - "character": 93 - }, - "identifier": "iIndex", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 171, + "character": 100 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 196, + "character": 34 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 196, + "character": 77 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 155, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 155, - "character": 50 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [] + "position": { + "line": 196, + "character": 102 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" } - ], - "functionVariablesComplexTokens": [ - { - "position": { - "line": 278, - "character": 37 - }, - "identifier": "skillFeat", - "tokenType": 6, - "valueType": "NWNX_SkillRanks_SkillFeat" + ], + "comments": [] + }, + { + "position": { + "line": 216, + "character": 67 + }, + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 216, + "character": 110 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 216, + "character": 122 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 229, + "character": 47 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 229, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 272, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 229, + "character": 76 + }, + "identifier": "epicFocus", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 238, + "character": 40 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 247, + "character": 41 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 247, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 255, + "character": 36 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 255, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 270, - "character": 66 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 255, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 266, + "character": 37 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 266, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 266, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 266, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 276, + "character": 62 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 276, + "character": 73 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 270, - "character": 78 - }, - "identifier": "iFeat", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 276, + "character": 84 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" } - ] + ], + "comments": [] + } + ], + "functionVariablesComplexTokens": [ + { + "position": { + "line": 43, + "character": 15 + }, + "identifier": "iSkill", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 44, + "character": 14 + }, + "identifier": "iFeat", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 47, + "character": 18 + }, + "identifier": "iModifier", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 51, + "character": 19 + }, + "identifier": "iFocusFeat", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 61, + "character": 20 + }, + "identifier": "sClasses", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 65, + "character": 25 + }, + "identifier": "fClassLevelMod", + "tokenType": 6, + "valueType": "float" + }, + { + "position": { + "line": 68, + "character": 27 + }, + "identifier": "iAreaFlagsRequired", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 71, + "character": 28 + }, + "identifier": "iAreaFlagsForbidden", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 75, + "character": 20 + }, + "identifier": "iDayOrNight", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 78, + "character": 33 + }, + "identifier": "bBypassArmorCheckPenalty", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 85, + "character": 24 + }, + "identifier": "iKeyAbilityMask", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 2, + "character": 32 + }, + "identifier": "i", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 3, + "character": 36 + }, + "identifier": "f", + "tokenType": 25, + "valueType": "float" + }, + { + "position": { + "line": 4, + "character": 38 + }, + "identifier": "s", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 5, + "character": 38 + }, + "identifier": "o", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 6, + "character": 37 + }, + "identifier": "plugin", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 6, + "character": 50 + }, + "identifier": "func", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 8, + "character": 37 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 8, + "character": 49 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 9, + "character": 36 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 9, + "character": 48 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 10, + "character": 35 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 33 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 52 + }, + "identifier": "sSubString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 64 + }, + "identifier": "nStart", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 90, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 97, + "character": 13 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 98, + "character": 12 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 107, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 107, + "character": 67 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 114, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 115, + "character": 26 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 126, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 127, + "character": 13 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 136, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 136, + "character": 72 + }, + "identifier": "iEpic", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 145, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 151, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 151, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 157, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 157, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 157, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 161, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 163, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 171, + "character": 88 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 171, + "character": 100 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 173, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 179, + "character": 47 + }, + "identifier": "skillFeat", + "tokenType": 6, + "valueType": "struct" + }, + { + "position": { + "line": 196, + "character": 77 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 196, + "character": 102 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 198, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 216, + "character": 110 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 216, + "character": 122 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 220, + "character": 14 + }, + "identifier": "i", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 221, + "character": 20 + }, + "identifier": "sPad", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 229, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 229, + "character": 76 + }, + "identifier": "epicFocus", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 231, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 240, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 247, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 249, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 255, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 255, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 257, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 266, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 266, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 266, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 268, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 276, + "character": 73 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 276, + "character": 84 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 278, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 284, + "character": 47 + }, + "identifier": "skillFeat", + "tokenType": 6, + "valueType": "struct" + } + ] } \ No newline at end of file diff --git a/server/test/static/localScopeTokensWithoutContext.json b/server/test/static/localScopeTokensWithoutContext.json index 42b6b22..c7b7e95 100644 --- a/server/test/static/localScopeTokensWithoutContext.json +++ b/server/test/static/localScopeTokensWithoutContext.json @@ -1,687 +1,1487 @@ { - "functionsComplexTokens": [ - { - "position": { - "line": 270, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeat", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 270, - "character": 66 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 270, - "character": 78 - }, - "identifier": "iFeat", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 278, - "character": 37 - }, - "identifier": "skillFeat", - "tokenType": 6, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 272, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] - }, + "functionsComplexTokens": [ + { + "position": { + "line": 2, + "character": 26 + }, + "identifier": "NWNX_PushArgumentInt", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 260, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetAreaModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 260, - "character": 44 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 260, - "character": 55 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 260, - "character": 67 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 262, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] - }, + "position": { + "line": 2, + "character": 32 + }, + "identifier": "i", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 3, + "character": 28 + }, + "identifier": "NWNX_PushArgumentFloat", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 249, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetAreaModifier", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 249, - "character": 43 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, - { - "position": { - "line": 249, - "character": 54 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 251, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] - }, + "position": { + "line": 3, + "character": 36 + }, + "identifier": "f", + "tokenType": 25, + "valueType": "float" + } + ], + "comments": [] + }, + { + "position": { + "line": 4, + "character": 29 + }, + "identifier": "NWNX_PushArgumentString", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 241, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 241, - "character": 45 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 243, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] - }, + "position": { + "line": 4, + "character": 38 + }, + "identifier": "s", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 5, + "character": 29 + }, + "identifier": "NWNX_PushArgumentObject", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 232, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", - "tokenType": 3, - "returnType": "int", - "params": [], - "comments": [], - "variables": [ - { - "position": { - "line": 234, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] - }, + "position": { + "line": 5, + "character": 38 + }, + "identifier": "o", + "tokenType": 25, + "valueType": "object" + } + ], + "comments": [] + }, + { + "position": { + "line": 6, + "character": 23 + }, + "identifier": "NWNX_CallFunction", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 223, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 223, - "character": 51 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 223, - "character": 66 - }, - "identifier": "epicFocus", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 225, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] + "position": { + "line": 6, + "character": 37 + }, + "identifier": "plugin", + "tokenType": 25, + "valueType": "string" }, { - "position": { - "line": 210, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_AddSkillFeatClass", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 210, - "character": 100 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 210, - "character": 115 - }, - "identifier": "iClass", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 215, - "character": 15 - }, - "identifier": "sPad", - "tokenType": 6, - "valueType": "string" - }, - { - "position": { - "line": 214, - "character": 12 - }, - "identifier": "i", - "tokenType": 6, - "valueType": "int" - } - ] - }, + "position": { + "line": 6, + "character": 50 + }, + "identifier": "func", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 7, + "character": 27 + }, + "identifier": "NWNX_GetReturnValueInt", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 8, + "character": 31 + }, + "identifier": "NWNX_GetReturnValueFloat", + "tokenType": 3, + "returnType": "float", + "params": [], + "comments": [] + }, + { + "position": { + "line": 9, + "character": 33 + }, + "identifier": "NWNX_GetReturnValueString", + "tokenType": 3, + "returnType": "string", + "params": [], + "comments": [] + }, + { + "position": { + "line": 10, + "character": 33 + }, + "identifier": "NWNX_GetReturnValueObject", + "tokenType": 3, + "returnType": "object", + "params": [], + "comments": [] + }, + { + "position": { + "line": 8, + "character": 22 + }, + "identifier": "GetStringRight", + "tokenType": 3, + "returnType": "string", + "params": [ { - "position": { - "line": 190, - "character": 5 - }, - "identifier": "NWNX_SkillRanks_SetSkillFeat", - "tokenType": 3, - "returnType": "void", - "params": [ - { - "position": { - "line": 190, - "character": 67 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 190, - "character": 82 - }, - "identifier": "createIfNonExistent", - "tokenType": 25, - "valueType": "int", - "defaultValue": "FALSE" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 192, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] + "position": { + "line": 8, + "character": 37 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" }, { - "position": { - "line": 165, - "character": 33 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", - "tokenType": 3, - "returnType": "NWNX_SkillRanks_SkillFeat", - "params": [ - { - "position": { - "line": 165, - "character": 81 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - }, - { - "position": { - "line": 165, - "character": 93 - }, - "identifier": "iIndex", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 173, - "character": 37 - }, - "identifier": "skillFeat", - "tokenType": 6, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, - { - "position": { - "line": 167, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] + "position": { + "line": 8, + "character": 49 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 9, + "character": 21 + }, + "identifier": "GetStringLeft", + "tokenType": 3, + "returnType": "string", + "params": [ + { + "position": { + "line": 9, + "character": 36 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" }, { - "position": { - "line": 155, - "character": 4 - }, - "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", - "tokenType": 3, - "returnType": "int", - "params": [ - { - "position": { - "line": 155, - "character": 50 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" - } - ], - "comments": [], - "variables": [ - { - "position": { - "line": 157, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - } - ] + "position": { + "line": 9, + "character": 48 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" } - ], - "functionVariablesComplexTokens": [ - { - "position": { - "line": 278, - "character": 37 - }, - "identifier": "skillFeat", - "tokenType": 6, - "valueType": "NWNX_SkillRanks_SkillFeat" - }, + ], + "comments": [] + }, + { + "position": { + "line": 10, + "character": 20 + }, + "identifier": "GetStringLength", + "tokenType": 3, + "returnType": "int", + "params": [ { - "position": { - "line": 272, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - }, + "position": { + "line": 10, + "character": 35 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + } + ], + "comments": [] + }, + { + "position": { + "line": 11, + "character": 18 + }, + "identifier": "FindSubString", + "tokenType": 3, + "returnType": "int", + "params": [ { - "position": { - "line": 270, - "character": 66 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 11, + "character": 33 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" }, { - "position": { - "line": 270, - "character": 78 - }, - "identifier": "iFeat", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 11, + "character": 52 + }, + "identifier": "sSubString", + "tokenType": 25, + "valueType": "string" }, { - "position": { - "line": 262, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - }, + "position": { + "line": 11, + "character": 64 + }, + "identifier": "nStart", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 90, + "character": 46 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ { - "position": { - "line": 260, - "character": 44 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, + "position": { + "line": 90, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 96, + "character": 62 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "struct", + "params": [ { - "position": { - "line": 260, - "character": 55 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 97, + "character": 13 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 260, - "character": 67 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - }, + "position": { + "line": 98, + "character": 12 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 107, + "character": 44 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "struct", + "params": [ { - "position": { - "line": 251, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 107, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 249, - "character": 43 - }, - "identifier": "oArea", - "tokenType": 25, - "valueType": "object" - }, + "position": { + "line": 107, + "character": 67 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 113, + "character": 29 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 249, - "character": 54 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 114, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 243, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - }, + "position": { + "line": 115, + "character": 26 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 125, + "character": 34 + }, + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "struct", + "params": [ { - "position": { - "line": 241, - "character": 45 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 126, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 234, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" - }, + "position": { + "line": 127, + "character": 13 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 136, + "character": 47 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 225, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 136, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 223, - "character": 51 - }, - "identifier": "iModifier", - "tokenType": 25, - "valueType": "int" - }, + "position": { + "line": 136, + "character": 72 + }, + "identifier": "iEpic", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 140, + "character": 40 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 145, + "character": 41 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 223, - "character": 66 - }, - "identifier": "epicFocus", - "tokenType": 25, - "valueType": "int" - }, + "position": { + "line": 145, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 151, + "character": 36 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ { - "position": { - "line": 215, - "character": 15 - }, - "identifier": "sPad", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 151, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 214, - "character": 12 - }, - "identifier": "i", - "tokenType": 6, - "valueType": "int" - }, + "position": { + "line": 151, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 157, + "character": 37 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ { - "position": { - "line": 210, - "character": 100 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" + "position": { + "line": 157, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 210, - "character": 115 - }, - "identifier": "iClass", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 157, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 192, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 157, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 161, + "character": 46 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatCountForSkill", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 161, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 171, + "character": 77 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeatForSkillByIndex", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 171, + "character": 88 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 190, - "character": 67 - }, - "identifier": "skillFeat", - "tokenType": 25, - "valueType": "NWNX_SkillRanks_SkillFeat" + "position": { + "line": 171, + "character": 100 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 196, + "character": 34 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeat", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 196, + "character": 77 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 190, - "character": 82 - }, - "identifier": "createIfNonExistent", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 196, + "character": 102 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 216, + "character": 67 + }, + "identifier": "NWNX_SkillRanks_AddSkillFeatClass", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 216, + "character": 110 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" }, { - "position": { - "line": 173, - "character": 37 - }, - "identifier": "skillFeat", - "tokenType": 6, - "valueType": "NWNX_SkillRanks_SkillFeat" + "position": { + "line": 216, + "character": 122 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 229, + "character": 47 + }, + "identifier": "NWNX_SkillRanks_SetSkillFeatFocusModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 229, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 167, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 229, + "character": 76 + }, + "identifier": "epicFocus", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 238, + "character": 40 + }, + "identifier": "NWNX_SkillRanks_GetBlindnessPenalty", + "tokenType": 3, + "returnType": "int", + "params": [], + "comments": [] + }, + { + "position": { + "line": 247, + "character": 41 + }, + "identifier": "NWNX_SkillRanks_SetBlindnessPenalty", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 247, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 255, + "character": 36 + }, + "identifier": "NWNX_SkillRanks_GetAreaModifier", + "tokenType": 3, + "returnType": "int", + "params": [ + { + "position": { + "line": 255, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 165, - "character": 81 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 255, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 266, + "character": 37 + }, + "identifier": "NWNX_SkillRanks_SetAreaModifier", + "tokenType": 3, + "returnType": "void", + "params": [ + { + "position": { + "line": 266, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" }, { - "position": { - "line": 165, - "character": 93 - }, - "identifier": "iIndex", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 266, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 157, - "character": 11 - }, - "identifier": "sFunc", - "tokenType": 6, - "valueType": "string" + "position": { + "line": 266, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + } + ], + "comments": [] + }, + { + "position": { + "line": 276, + "character": 62 + }, + "identifier": "NWNX_SkillRanks_GetSkillFeat", + "tokenType": 3, + "returnType": "struct", + "params": [ + { + "position": { + "line": 276, + "character": 73 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" }, { - "position": { - "line": 155, - "character": 50 - }, - "identifier": "iSkill", - "tokenType": 25, - "valueType": "int" + "position": { + "line": 276, + "character": 84 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" } - ] + ], + "comments": [] + }, + { + "position": { + "line": 302, + "character": 10 + }, + "identifier": "main", + "tokenType": 3, + "returnType": "void", + "params": [], + "comments": [] + } + ], + "functionVariablesComplexTokens": [ + { + "position": { + "line": 43, + "character": 15 + }, + "identifier": "iSkill", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 44, + "character": 14 + }, + "identifier": "iFeat", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 47, + "character": 18 + }, + "identifier": "iModifier", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 51, + "character": 19 + }, + "identifier": "iFocusFeat", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 61, + "character": 20 + }, + "identifier": "sClasses", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 65, + "character": 25 + }, + "identifier": "fClassLevelMod", + "tokenType": 6, + "valueType": "float" + }, + { + "position": { + "line": 68, + "character": 27 + }, + "identifier": "iAreaFlagsRequired", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 71, + "character": 28 + }, + "identifier": "iAreaFlagsForbidden", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 75, + "character": 20 + }, + "identifier": "iDayOrNight", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 78, + "character": 33 + }, + "identifier": "bBypassArmorCheckPenalty", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 85, + "character": 24 + }, + "identifier": "iKeyAbilityMask", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 2, + "character": 32 + }, + "identifier": "i", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 3, + "character": 36 + }, + "identifier": "f", + "tokenType": 25, + "valueType": "float" + }, + { + "position": { + "line": 4, + "character": 38 + }, + "identifier": "s", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 5, + "character": 38 + }, + "identifier": "o", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 6, + "character": 37 + }, + "identifier": "plugin", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 6, + "character": 50 + }, + "identifier": "func", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 8, + "character": 37 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 8, + "character": 49 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 9, + "character": 36 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 9, + "character": 48 + }, + "identifier": "nCount", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 10, + "character": 35 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 33 + }, + "identifier": "sString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 52 + }, + "identifier": "sSubString", + "tokenType": 25, + "valueType": "string" + }, + { + "position": { + "line": 11, + "character": 64 + }, + "identifier": "nStart", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 90, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 97, + "character": 13 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 98, + "character": 12 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 107, + "character": 55 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 107, + "character": 67 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 114, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 115, + "character": 26 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 126, + "character": 45 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 127, + "character": 13 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 136, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 136, + "character": 72 + }, + "identifier": "iEpic", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 145, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 151, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 151, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 157, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 157, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 157, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 161, + "character": 57 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 163, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 171, + "character": 88 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 171, + "character": 100 + }, + "identifier": "iIndex", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 173, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 179, + "character": 47 + }, + "identifier": "skillFeat", + "tokenType": 6, + "valueType": "struct" + }, + { + "position": { + "line": 196, + "character": 77 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 196, + "character": 102 + }, + "identifier": "createIfNonExistent", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 198, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 216, + "character": 110 + }, + "identifier": "skillFeat", + "tokenType": 25, + "valueType": "struct" + }, + { + "position": { + "line": 216, + "character": 122 + }, + "identifier": "iClass", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 220, + "character": 14 + }, + "identifier": "i", + "tokenType": 6, + "valueType": "int" + }, + { + "position": { + "line": 221, + "character": 20 + }, + "identifier": "sPad", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 229, + "character": 61 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 229, + "character": 76 + }, + "identifier": "epicFocus", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 231, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 240, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 247, + "character": 55 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 249, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 255, + "character": 49 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 255, + "character": 61 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 257, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 266, + "character": 50 + }, + "identifier": "oArea", + "tokenType": 25, + "valueType": "object" + }, + { + "position": { + "line": 266, + "character": 62 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 266, + "character": 77 + }, + "identifier": "iModifier", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 268, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 276, + "character": 73 + }, + "identifier": "iSkill", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 276, + "character": 84 + }, + "identifier": "iFeat", + "tokenType": 25, + "valueType": "int" + }, + { + "position": { + "line": 278, + "character": 17 + }, + "identifier": "sFunc", + "tokenType": 6, + "valueType": "string" + }, + { + "position": { + "line": 284, + "character": 47 + }, + "identifier": "skillFeat", + "tokenType": 6, + "valueType": "struct" + } + ] } \ No newline at end of file diff --git a/server/test/static/test.nss b/server/test/static/test.nss index 39bf94d..1bed620 100644 --- a/server/test/static/test.nss +++ b/server/test/static/test.nss @@ -5,6 +5,12 @@ /// @file nwnx_skillranks.nss #include "nwnx" +// Standard NWScript function declarations (normally from nwscript.nss) +string GetStringRight(string sString, int nCount); +string GetStringLeft(string sString, int nCount); +int GetStringLength(string sString); +int FindSubString(string sString, string sSubString, int nStart = 0); + const string NWNX_SkillRanks = "NWNX_SkillRanks"; ///< @private /// @name SkillRanks Key Abilities @@ -292,3 +298,7 @@ struct NWNX_SkillRanks_SkillFeat NWNX_SkillRanks_GetSkillFeat(int iSkill, int iF return skillFeat; } + +// Required main function for NWScript compiler +void main() { +} diff --git a/server/test/tokenization_test.ts b/server/test/tokenization_test.ts index 335574d..62cb2da 100644 --- a/server/test/tokenization_test.ts +++ b/server/test/tokenization_test.ts @@ -1,12 +1,8 @@ import { describe, before } from "mocha"; import { expect } from "chai"; -import { readFileSync, writeFileSync } from "fs"; +import { readFileSync } from "fs"; import { normalize, join } from "path"; -import Tokenizer, { - GlobalScopeTokenizationResult, - LocalScopeTokenizationResult, - TokenizedScope, -} from "../src/Tokenizer/Tokenizer"; +import Tokenizer, { GlobalScopeTokenizationResult, LocalScopeTokenizationResult, TokenizedScope } from "../src/Tokenizer/Tokenizer"; const format = (data: any) => JSON.parse(JSON.stringify(data)); @@ -20,21 +16,15 @@ describe("Tokenization", () => { before("Read static data", async () => { tokenizer = await new Tokenizer(true).loadGrammar(); staticCode = readFileSync(normalize(join(__dirname, "./static/test.nss"))).toString(); - staticGlobalTokens = JSON.parse( - readFileSync(normalize(join(__dirname, "./static/globalScopeTokens.json"))).toString(), - ) as GlobalScopeTokenizationResult; - staticLocalTokensWithContext = JSON.parse( - readFileSync(normalize(join(__dirname, "./static/localScopeTokensWithContext.json"))).toString(), - ) as LocalScopeTokenizationResult; - staticLocalTokensWithoutContext = JSON.parse( - readFileSync(normalize(join(__dirname, "./static/localScopeTokensWithoutContext.json"))).toString(), - ) as LocalScopeTokenizationResult; + staticGlobalTokens = JSON.parse(readFileSync(normalize(join(__dirname, "./static/globalScopeTokens.json"))).toString()) as GlobalScopeTokenizationResult; + staticLocalTokensWithContext = JSON.parse(readFileSync(normalize(join(__dirname, "./static/localScopeTokensWithContext.json"))).toString()) as LocalScopeTokenizationResult; + staticLocalTokensWithoutContext = JSON.parse(readFileSync(normalize(join(__dirname, "./static/localScopeTokensWithoutContext.json"))).toString()) as LocalScopeTokenizationResult; }); describe("Global Scope", () => { let definitions: GlobalScopeTokenizationResult; - before("Tokenize Content", () => { - definitions = format(tokenizer.tokenizeContent(staticCode, TokenizedScope.global)); + before("Tokenize Content", async () => { + definitions = format(await tokenizer.tokenizeContent(staticCode, TokenizedScope.global)); }); it("should equal static children", () => { @@ -52,14 +42,12 @@ describe("Tokenization", () => { describe("Local Scope with current function context", () => { let definitions: LocalScopeTokenizationResult; - before("Tokenize Content", () => { - definitions = format(tokenizer.tokenizeContent(staticCode, TokenizedScope.local, 0, 293)); + before("Tokenize Content", async () => { + definitions = format(await tokenizer.tokenizeContent(staticCode, TokenizedScope.local, 0, 293)); }); it("should equal static function variables tokens", () => { - expect(definitions.functionVariablesComplexTokens).to.be.deep.equal( - staticLocalTokensWithContext.functionVariablesComplexTokens, - ); + expect(definitions.functionVariablesComplexTokens).to.be.deep.equal(staticLocalTokensWithContext.functionVariablesComplexTokens); }); it("should equal static function tokens", () => { @@ -69,14 +57,12 @@ describe("Tokenization", () => { describe("Local Scope with entire file context", () => { let definitions: LocalScopeTokenizationResult; - before("Tokenize Content", () => { - definitions = format(tokenizer.tokenizeContent(staticCode, TokenizedScope.local)); + before("Tokenize Content", async () => { + definitions = format(await tokenizer.tokenizeContent(staticCode, TokenizedScope.local)); }); it("should equal static function variables tokens", () => { - expect(definitions.functionVariablesComplexTokens).to.be.deep.equal( - staticLocalTokensWithoutContext.functionVariablesComplexTokens, - ); + expect(definitions.functionVariablesComplexTokens).to.be.deep.equal(staticLocalTokensWithoutContext.functionVariablesComplexTokens); }); it("should equal static function tokens", () => { diff --git a/server/tsconfig.json b/server/tsconfig.json index 93404ae..ec561f6 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -5,11 +5,12 @@ "lib": ["ES2020"], "sourceMap": true, "outDir": "out", - "rootDir": "src", "strict": true, "allowSyntheticDefaultImports": true, - "esModuleInterop": true + "esModuleInterop": true, + "composite": true, + "declaration": true }, - "include": ["src"], + "include": ["src", "test"], "exclude": ["node_modules"] } diff --git a/server/wasm/nwscript_compiler.js b/server/wasm/nwscript_compiler.js new file mode 100644 index 0000000..861b1ab --- /dev/null +++ b/server/wasm/nwscript_compiler.js @@ -0,0 +1,2 @@ +async function NWScriptCompiler(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=true;var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName;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{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);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 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 exceptionLast=0;var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};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)};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(!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=null}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){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){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);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{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_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,readFiles:{},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}else if(perms.includes("w")&&!(node.mode&146)){return 2}else 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}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},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);setattr(arg,attr)},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=typeof flags=="string"?FS_modeStringToFlags(flags):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)}if(Module["logReadFiles"]&&!(flags&1)){if(!(path in FS.readFiles)){FS.readFiles[path]=1}}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);if(typeof data=="string"){data=new Uint8Array(intArrayFromString(data,true))}if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{abort("Unsupported data type")}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){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;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={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();stream.flags|=arg;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;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};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};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 61;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};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["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;var _free,_scriptCompApiGetABIVersion,_scriptCompApiNewCompiler,_scriptCompApiInitCompiler,_scriptCompApiCompileFile,_scriptCompApiDeliverFile,_scriptCompApiGetOptimizationFlags,_scriptCompApiSetOptimizationFlags,_scriptCompApiSetGenerateDebuggerOutput,_scriptCompApiCompileFileSimple,_scriptCompApiGetLastError,_scriptCompApiGetParseTreeJSON,_scriptCompApiDestroyCompiler,_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"];_scriptCompApiCompileFile=Module["_scriptCompApiCompileFile"]=wasmExports["scriptCompApiCompileFile"];_scriptCompApiDeliverFile=Module["_scriptCompApiDeliverFile"]=wasmExports["scriptCompApiDeliverFile"];_scriptCompApiGetOptimizationFlags=Module["_scriptCompApiGetOptimizationFlags"]=wasmExports["scriptCompApiGetOptimizationFlags"];_scriptCompApiSetOptimizationFlags=Module["_scriptCompApiSetOptimizationFlags"]=wasmExports["scriptCompApiSetOptimizationFlags"];_scriptCompApiSetGenerateDebuggerOutput=Module["_scriptCompApiSetGenerateDebuggerOutput"]=wasmExports["scriptCompApiSetGenerateDebuggerOutput"];_scriptCompApiCompileFileSimple=Module["_scriptCompApiCompileFileSimple"]=wasmExports["scriptCompApiCompileFileSimple"];_scriptCompApiGetLastError=Module["_scriptCompApiGetLastError"]=wasmExports["scriptCompApiGetLastError"];_scriptCompApiGetParseTreeJSON=Module["_scriptCompApiGetParseTreeJSON"]=wasmExports["scriptCompApiGetParseTreeJSON"];_scriptCompApiDestroyCompiler=Module["_scriptCompApiDestroyCompiler"]=wasmExports["scriptCompApiDestroyCompiler"];_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,environ_get:_environ_get,environ_sizes_get:_environ_sizes_get,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..d9ebfbc0b80fa2bf04342d68b9a3ef175c526014 GIT binary patch literal 307952 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 ZsC4&0Tt5ZWyFJvqDh1sw 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