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 0000000..d9ebfbc Binary files /dev/null and b/server/wasm/nwscript_compiler.wasm differ