UNPKG

@maniascript/parser

Version:
251 lines (250 loc) 9.88 kB
import { Node, VariableDeclaration, Identifier, Main, FunctionDeclaration, FunctionParameterDeclaration, BlockStatement, ForStatement, Kind, ForeachStatement, WhileStatement, MeanwhileStatement, SwitchCase, SwitchtypeCase, ConditionalBranch, AssignmentStatement, LabelStatement, LabelDeclaration } from './ast.js'; class ScopeManager { scopes; nodeToScope; #currentScope; constructor() { this.scopes = []; this.nodeToScope = new Map(); this.#currentScope = null; } openScope(type, node) { const scope = new Scope(type, node, this.#currentScope); this.#currentScope = scope; this.scopes.push(scope); this.nodeToScope.set(node, scope); } closeScope() { this.#currentScope = this.#currentScope !== null ? this.#currentScope.parent : null; } getScope(node) { let scope = null; let currentNode = node; while (scope === null && currentNode !== undefined) { scope = this.nodeToScope.get(currentNode) ?? null; currentNode = currentNode.parent; } return scope; } static canCreateBlockScope(blockStatement) { return (blockStatement.parent === undefined || (blockStatement.parent.kind !== Kind.FunctionDeclaration && blockStatement.parent.kind !== Kind.Main && blockStatement.parent.kind !== Kind.ForeachStatement && blockStatement.parent.kind !== Kind.ForStatement && blockStatement.parent.kind !== Kind.WhileStatement && blockStatement.parent.kind !== Kind.MeanwhileStatement && blockStatement.parent.kind !== Kind.SwitchCase && blockStatement.parent.kind !== Kind.SwitchtypeCase && blockStatement.parent.kind !== Kind.ConditionalBranch)); } analyze(ast) { this.scopes = []; this.nodeToScope.clear(); if (ast.program !== undefined) { this.openScope(ScopeType.Global, ast.program); ast.program.visit((node) => { if (node instanceof VariableDeclaration) { if (node.alias !== undefined) { this.#currentScope?.defineVariable(node.alias, { isGlobal: node.isGlobal, isTrait: node.forTarget !== undefined }); } else { this.#currentScope?.defineVariable(node.name, { isGlobal: node.isGlobal, isTrait: node.forTarget !== undefined }); } } else if (node instanceof Main || node instanceof FunctionDeclaration) { this.openScope(ScopeType.Function, node); } else if (node instanceof FunctionParameterDeclaration) { this.#currentScope?.defineVariable(node.name, { isFunctionParameter: true }); } else if (node instanceof ForStatement) { this.openScope(ScopeType.For, node); this.#currentScope?.defineVariable(node.value, { isForValue: true }); } else if (node instanceof ForeachStatement) { this.openScope(ScopeType.Foreach, node); if (node.key !== undefined) { this.#currentScope?.defineVariable(node.key, { isForeachKey: true }); } this.#currentScope?.defineVariable(node.value, { isForeachValue: true }); } else if (node instanceof WhileStatement) { this.openScope(ScopeType.While, node); } else if (node instanceof MeanwhileStatement) { this.openScope(ScopeType.Meanwhile, node); } else if (node instanceof SwitchCase || node instanceof SwitchtypeCase) { this.openScope(ScopeType.SwitchCase, node); } else if (node instanceof ConditionalBranch) { this.openScope(ScopeType.ConditionalBranch, node); } else if (node instanceof LabelDeclaration) { this.openScope(ScopeType.LabelDeclaration, node); } else if (node instanceof BlockStatement && ScopeManager.canCreateBlockScope(node)) { this.openScope(ScopeType.Block, node); } else if (node instanceof Identifier && node.isExpression) { this.#currentScope?.referenceVariable(node); } }, (node) => { if (node instanceof Main || node instanceof FunctionDeclaration) { this.closeScope(); } else if (node instanceof ForStatement) { this.closeScope(); } else if (node instanceof ForeachStatement) { this.closeScope(); } else if (node instanceof WhileStatement) { this.closeScope(); } else if (node instanceof MeanwhileStatement) { this.closeScope(); } else if (node instanceof SwitchCase || node instanceof SwitchtypeCase) { this.closeScope(); } else if (node instanceof ConditionalBranch) { this.closeScope(); } else if (node instanceof LabelDeclaration) { this.closeScope(); } else if (node instanceof BlockStatement && ScopeManager.canCreateBlockScope(node)) { this.closeScope(); } else if (node instanceof LabelStatement) { this.#currentScope?.referenceLabel(); } }); this.closeScope(); } } } var ScopeType; (function (ScopeType) { ScopeType["Global"] = "Global"; ScopeType["Function"] = "Function"; ScopeType["For"] = "For"; ScopeType["Foreach"] = "Foreach"; ScopeType["While"] = "While"; ScopeType["Meanwhile"] = "Meanwhile"; ScopeType["SwitchCase"] = "SwitchCase"; ScopeType["ConditionalBranch"] = "ConditionalBranch"; ScopeType["LabelDeclaration"] = "LabelDeclaration"; ScopeType["Block"] = "Block"; })(ScopeType || (ScopeType = {})); class Variable { name; references; node; isGlobal; isFunctionParameter; isForValue; isForeachKey; isForeachValue; isTrait; static getNameFromIdentifier(identifier) { return identifier.namespace !== undefined ? `${identifier.namespace}::${identifier.name}` : identifier.name; } constructor(node, parameters) { this.name = Variable.getNameFromIdentifier(node); this.references = []; this.node = node; this.isGlobal = parameters?.isGlobal ?? false; this.isFunctionParameter = parameters?.isFunctionParameter ?? false; this.isForValue = parameters?.isForValue ?? false; this.isForeachKey = parameters?.isForeachKey ?? false; this.isForeachValue = parameters?.isForeachValue ?? false; this.isTrait = parameters?.isTrait ?? false; } } var ReferenceFlag; (function (ReferenceFlag) { ReferenceFlag[ReferenceFlag["Read"] = 0] = "Read"; ReferenceFlag[ReferenceFlag["Write"] = 1] = "Write"; })(ReferenceFlag || (ReferenceFlag = {})); class Reference { variable; node; flag; constructor(variable, node) { this.variable = variable; this.node = node; if (node.parent !== undefined && node.parent instanceof AssignmentStatement && node.parent.left === node) { this.flag = ReferenceFlag.Write; } else { this.flag = ReferenceFlag.Read; } } isRead() { return this.flag === ReferenceFlag.Read; } isWrite() { return this.flag === ReferenceFlag.Write; } } class Scope { type; variables; references; couldReferenceVariableInLabel; parent; children; node; constructor(type, node, parent) { this.type = type; this.variables = new Map(); this.references = []; this.couldReferenceVariableInLabel = false; this.parent = parent ?? null; this.children = []; this.node = node; if (this.parent !== null) { this.parent.children.push(this); } } defineVariable(node, parameters) { const variable = new Variable(node, parameters); this.variables.set(variable.name, variable); return variable; } referenceVariable(node) { const variable = this.getVariable(node); const reference = new Reference(variable, node); this.references.push(reference); if (variable !== null) { variable.references.push(reference); } return reference; } getVariable(name) { let scope = this; // eslint-disable-line @typescript-eslint/no-this-alias let variable = null; let fullName; if (typeof name === 'string') { fullName = name; } else { fullName = Variable.getNameFromIdentifier(name); } while (scope !== null && variable === null) { variable = scope.variables.get(fullName) ?? null; scope = scope.parent; } return variable; } referenceLabel() { let scope = this; // eslint-disable-line @typescript-eslint/no-this-alias while (scope !== null && !scope.couldReferenceVariableInLabel) { scope.couldReferenceVariableInLabel = true; scope = scope.parent; } } } export { ScopeManager, ScopeType, Variable, Scope };