UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

380 lines 17.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Resolver = void 0; const ast_types_1 = require("./ast-types"); const tokenizer_1 = require("./tokenizer"); const tokens_1 = require("./tokens"); const errors_1 = require("./errors"); const levenshtein = require('fast-levenshtein'); const RedefineableTokenSentinel = new tokenizer_1.Token(tokens_1.TokenType.AT, "", 0, 0, 0); class Environment { constructor(source, enclosing, names) { this.source = source; this.enclosing = enclosing; this.names = names; this.functions = new Set(); this.moduleBindings = new Set(); } /* * Does a full lookup up the environment chain for a name. * Returns the distance of the name from the current environment. * If name isn't found, return -1. * */ lookupName(identifier) { const name = identifier.lexeme; let distance = 0; let curr = this; while (curr !== null) { if (curr.names.has(name)) { break; } distance += 1; curr = curr.enclosing; } return (curr === null) ? -1 : distance; } /* Looks up the name but only for the current environment. */ lookupNameCurrentEnv(identifier) { return this.names.get(identifier.lexeme); } lookupNameCurrentEnvWithError(identifier) { if (this.lookupName(identifier) < 0) { throw new errors_1.ResolverErrors.NameNotFoundError(identifier.line, identifier.col, this.source, identifier.indexInSource, identifier.indexInSource + identifier.lexeme.length, this.suggestName(identifier)); } } lookupNameParentEnvWithError(identifier) { const name = identifier.lexeme; let parent = this.enclosing; if (parent === null || !parent.names.has(name)) { throw new errors_1.ResolverErrors.NameNotFoundError(identifier.line, identifier.col, this.source, identifier.indexInSource, identifier.indexInSource + name.length, this.suggestName(identifier)); } } declareName(identifier) { const lookup = this.lookupNameCurrentEnv(identifier); if (lookup !== undefined && lookup !== RedefineableTokenSentinel) { throw new errors_1.ResolverErrors.NameReassignmentError(identifier.line, identifier.col, this.source, identifier.indexInSource, identifier.indexInSource + identifier.lexeme.length, lookup); } this.names.set(identifier.lexeme, identifier); } // Same as declareName but allowed to re-declare later. declarePlaceholderName(identifier) { const lookup = this.lookupNameCurrentEnv(identifier); if (lookup !== undefined) { throw new errors_1.ResolverErrors.NameReassignmentError(identifier.line, identifier.col, this.source, identifier.indexInSource, identifier.indexInSource + identifier.lexeme.length, lookup); } this.names.set(identifier.lexeme, RedefineableTokenSentinel); } suggestNameCurrentEnv(identifier) { const name = identifier.lexeme; let minDistance = Infinity; let minName = null; for (const declName of this.names.keys()) { const dist = levenshtein.get(name, declName); if (dist < minDistance) { minDistance = dist; minName = declName; } } return minName; } /* * Finds name closest to name in all environments up to builtin environment. * Calculated using min levenshtein distance. * */ suggestName(identifier) { const name = identifier.lexeme; let minDistance = Infinity; let minName = null; let curr = this; while (curr !== null) { for (const declName of curr.names.keys()) { const dist = levenshtein.get(name, declName); if (dist < minDistance) { minDistance = dist; minName = declName; } } curr = curr.enclosing; } if (minDistance >= 4) { // This is pretty far, so just return null return null; } return minName; } } class Resolver { constructor(source, ast) { this.source = source; this.ast = ast; // The global environment this.environment = new Environment(source, null, new Map([ // misc library ["get_time", new tokenizer_1.Token(tokens_1.TokenType.NAME, "get_time", 0, 0, 0)], ["print", new tokenizer_1.Token(tokens_1.TokenType.NAME, "print", 0, 0, 0)], ["raw_print", new tokenizer_1.Token(tokens_1.TokenType.NAME, "raw_print", 0, 0, 0)], ["str", new tokenizer_1.Token(tokens_1.TokenType.NAME, "str", 0, 0, 0)], ["error", new tokenizer_1.Token(tokens_1.TokenType.NAME, "error", 0, 0, 0)], ["prompt", new tokenizer_1.Token(tokens_1.TokenType.NAME, "prompt", 0, 0, 0)], ["is_float", new tokenizer_1.Token(tokens_1.TokenType.NAME, "is_float", 0, 0, 0)], ["is_int", new tokenizer_1.Token(tokens_1.TokenType.NAME, "is_int", 0, 0, 0)], ["is_string", new tokenizer_1.Token(tokens_1.TokenType.NAME, "is_string", 0, 0, 0)], ["is_function", new tokenizer_1.Token(tokens_1.TokenType.NAME, "is_function", 0, 0, 0)], ["is_boolean", new tokenizer_1.Token(tokens_1.TokenType.NAME, "is_boolean", 0, 0, 0)], ["parse_int", new tokenizer_1.Token(tokens_1.TokenType.NAME, "parse_int", 0, 0, 0)], ["char_at", new tokenizer_1.Token(tokens_1.TokenType.NAME, "char_at", 0, 0, 0)], ["arity", new tokenizer_1.Token(tokens_1.TokenType.NAME, "arity", 0, 0, 0)], ["None", new tokenizer_1.Token(tokens_1.TokenType.NAME, "None", 0, 0, 0)], ["NaN", new tokenizer_1.Token(tokens_1.TokenType.NAME, "NaN", 0, 0, 0)], ["Infinity", new tokenizer_1.Token(tokens_1.TokenType.NAME, "Infinity", 0, 0, 0)], // math constants ["math_pi", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_pi", 0, 0, 0)], ["math_e", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_e", 0, 0, 0)], ["math_inf", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_inf", 0, 0, 0)], ["math_nan", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_nan", 0, 0, 0)], ["math_tau", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_tau", 0, 0, 0)], // math library ["math_abs", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_abs", 0, 0, 0)], ["math_acos", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_acos", 0, 0, 0)], ["math_acosh", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_acosh", 0, 0, 0)], ["math_asin", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_asin", 0, 0, 0)], ["math_asinh", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_asinh", 0, 0, 0)], ["math_atan", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_atan", 0, 0, 0)], ["math_atan2", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_atan2", 0, 0, 0)], ["math_atanh", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_atanh", 0, 0, 0)], ["math_cbrt", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_cbrt", 0, 0, 0)], ["math_ceil", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_ceil", 0, 0, 0)], ["math_clz32", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_clz32", 0, 0, 0)], ["math_cos", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_cos", 0, 0, 0)], ["math_cosh", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_cosh", 0, 0, 0)], ["math_exp", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_exp", 0, 0, 0)], ["math_expm1", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_expm1", 0, 0, 0)], ["math_floor", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_floor", 0, 0, 0)], ["math_fround", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_fround", 0, 0, 0)], ["math_hypot", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_hypot", 0, 0, 0)], ["math_imul", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_imul", 0, 0, 0)], ["math_log", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_log", 0, 0, 0)], ["math_log1p", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_log1p", 0, 0, 0)], ["math_log2", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_log2", 0, 0, 0)], ["math_log10", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_log10", 0, 0, 0)], ["math_max", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_max", 0, 0, 0)], ["math_min", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_min", 0, 0, 0)], ["math_pow", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_pow", 0, 0, 0)], ["math_random", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_random", 0, 0, 0)], ["math_round", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_round", 0, 0, 0)], ["math_sign", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_sign", 0, 0, 0)], ["math_sin", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_sin", 0, 0, 0)], ["math_sinh", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_sinh", 0, 0, 0)], ["math_sqrt", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_sqrt", 0, 0, 0)], ["math_tan", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_tan", 0, 0, 0)], ["math_tanh", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_tanh", 0, 0, 0)], ["math_trunc", new tokenizer_1.Token(tokens_1.TokenType.NAME, "math_trunc", 0, 0, 0)], ])); this.functionScope = null; } resolve(stmt) { if (stmt === null) { return; } if (stmt instanceof Array) { // Resolve all top-level functions first. Python allows functions declared after // another function to be used in that function. for (const st of stmt) { if (st instanceof ast_types_1.StmtNS.FunctionDef) { this.environment?.declarePlaceholderName(st.name); } } for (const st of stmt) { st.accept(this); } } else { stmt.accept(this); } } varDeclNames(names) { const res = Array.from(names.values()) .filter(name => ( // Filter out functions and module bindings. // Those will be handled separately, so they don't // need to be hoisted. !this.environment?.functions.has(name.lexeme) && !this.environment?.moduleBindings.has(name.lexeme))); return res.length === 0 ? null : res; } functionVarConstraint(identifier) { if (this.functionScope == null) { return; } let curr = this.environment; while (curr !== this.functionScope) { if (curr !== null && curr.names.has(identifier.lexeme)) { const token = curr.names.get(identifier.lexeme); if (token === undefined) { throw new Error("placeholder error"); } throw new errors_1.ResolverErrors.NameReassignmentError(identifier.line, identifier.col, this.source, identifier.indexInSource, identifier.indexInSource + identifier.lexeme.length, token); } curr = curr?.enclosing ?? null; } } //// STATEMENTS visitFileInputStmt(stmt) { // Create a new environment. const oldEnv = this.environment; this.environment = new Environment(this.source, this.environment, new Map()); this.resolve(stmt.statements); // Grab identifiers from that new environment. That are NOT functions. // stmt.varDecls = this.varDeclNames(this.environment.names) this.environment = oldEnv; } visitIndentCreation(stmt) { // Create a new environment this.environment = new Environment(this.source, this.environment, new Map()); } visitDedentCreation(stmt) { // Switch to the previous environment. if (this.environment?.enclosing !== undefined) { this.environment = this.environment.enclosing; } } visitFunctionDefStmt(stmt) { this.environment?.declareName(stmt.name); this.environment?.functions.add(stmt.name.lexeme); // Create a new environment. // const oldEnv = this.environment; // Assign the parameters to the new environment. const newEnv = new Map(stmt.parameters.map(param => [param.lexeme, param])); this.environment = new Environment(this.source, this.environment, newEnv); // const params = new Map( // stmt.parameters.map(param => [param.lexeme, param]) // ); // if (this.environment !== null) { // this.environment.names = params; // } this.functionScope = this.environment; this.resolve(stmt.body); // Grab identifiers from that new environment. That are NOT functions. // stmt.varDecls = this.varDeclNames(this.environment.names) // Restore old environment // this.environment = oldEnv; } visitAnnAssignStmt(stmt) { this.resolve(stmt.ann); this.resolve(stmt.value); this.functionVarConstraint(stmt.name); this.environment?.declareName(stmt.name); } visitAssignStmt(stmt) { this.resolve(stmt.value); this.functionVarConstraint(stmt.name); this.environment?.declareName(stmt.name); } visitAssertStmt(stmt) { this.resolve(stmt.value); } visitForStmt(stmt) { this.environment?.declareName(stmt.target); this.resolve(stmt.iter); this.resolve(stmt.body); } visitIfStmt(stmt) { this.resolve(stmt.condition); this.resolve(stmt.body); this.resolve(stmt.elseBlock); } // @TODO we need to treat all global statements as variable declarations in the global // scope. visitGlobalStmt(stmt) { // Do nothing because global can also be declared in our // own scope. } // @TODO nonlocals mean that any variable following that name in the current env // should not create a variable declaration, but instead point to an outer variable. visitNonLocalStmt(stmt) { this.environment?.lookupNameParentEnvWithError(stmt.name); } visitReturnStmt(stmt) { if (stmt.value !== null) { this.resolve(stmt.value); } } visitWhileStmt(stmt) { this.resolve(stmt.condition); this.resolve(stmt.body); } visitSimpleExprStmt(stmt) { this.resolve(stmt.expression); } visitFromImportStmt(stmt) { for (const name of stmt.names) { this.environment?.declareName(name); this.environment?.moduleBindings.add(name.lexeme); } } visitContinueStmt(stmt) { } visitBreakStmt(stmt) { } visitPassStmt(stmt) { } //// EXPRESSIONS visitVariableExpr(expr) { this.environment?.lookupNameCurrentEnvWithError(expr.name); } visitLambdaExpr(expr) { // Create a new environment. const oldEnv = this.environment; // Assign the parameters to the new environment. const newEnv = new Map(expr.parameters.map(param => [param.lexeme, param])); this.environment = new Environment(this.source, this.environment, newEnv); this.resolve(expr.body); // Restore old environment this.environment = oldEnv; } visitMultiLambdaExpr(expr) { // Create a new environment. const oldEnv = this.environment; // Assign the parameters to the new environment. const newEnv = new Map(expr.parameters.map(param => [param.lexeme, param])); this.environment = new Environment(this.source, this.environment, newEnv); this.resolve(expr.body); // Grab identifiers from that new environment. expr.varDecls = Array.from(this.environment.names.values()); // Restore old environment this.environment = oldEnv; } visitUnaryExpr(expr) { this.resolve(expr.right); } visitGroupingExpr(expr) { this.resolve(expr.expression); } visitBinaryExpr(expr) { this.resolve(expr.left); this.resolve(expr.right); } visitBoolOpExpr(expr) { this.resolve(expr.left); this.resolve(expr.right); } visitCompareExpr(expr) { this.resolve(expr.left); this.resolve(expr.right); } visitCallExpr(expr) { this.resolve(expr.callee); this.resolve(expr.args); } visitTernaryExpr(expr) { this.resolve(expr.predicate); this.resolve(expr.consequent); this.resolve(expr.alternative); } visitLiteralExpr(expr) { } visitBigIntLiteralExpr(expr) { } } exports.Resolver = Resolver; //# sourceMappingURL=resolver.js.map