UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

232 lines (231 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkForUndefinedVariables = exports.checkProgramForUndefinedVariables = exports.validateAndAnnotate = void 0; const errors_1 = require("../errors/errors"); const validityErrors_1 = require("../errors/validityErrors"); const parser_1 = require("../parser/parser"); const uniqueIds_1 = require("../utils/uniqueIds"); const walkers_1 = require("../utils/walkers"); const helpers_1 = require("../utils/ast/helpers"); class Declaration { constructor(isConstant) { this.isConstant = isConstant; this.accessedBeforeDeclaration = false; } } function validateAndAnnotate(program, context) { const accessedBeforeDeclarationMap = new Map(); const scopeHasCallExpressionMap = new Map(); function processBlock(node) { const initialisedIdentifiers = new Map(); for (const statement of node.body) { if (statement.type === 'VariableDeclaration') { initialisedIdentifiers.set((0, helpers_1.getSourceVariableDeclaration)(statement).id.name, new Declaration(statement.kind === 'const')); } else if (statement.type === 'FunctionDeclaration') { if (statement.id === null) { throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'); } initialisedIdentifiers.set(statement.id.name, new Declaration(true)); } } scopeHasCallExpressionMap.set(node, false); accessedBeforeDeclarationMap.set(node, initialisedIdentifiers); } function processFunction(node) { accessedBeforeDeclarationMap.set(node, new Map(node.params.map(id => [id.name, new Declaration(false)]))); scopeHasCallExpressionMap.set(node, false); } // initialise scope of variables (0, walkers_1.ancestor)(program, { Program: processBlock, BlockStatement: processBlock, FunctionDeclaration: processFunction, ArrowFunctionExpression: processFunction, ForStatement(forStatement, _ancestors) { const init = forStatement.init; if (init.type === 'VariableDeclaration') { accessedBeforeDeclarationMap.set(forStatement, new Map([ [(0, helpers_1.getSourceVariableDeclaration)(init).id.name, new Declaration(init.kind === 'const')] ])); scopeHasCallExpressionMap.set(forStatement, false); } } }); function validateIdentifier(id, ancestors) { const name = id.name; const lastAncestor = ancestors[ancestors.length - 2]; for (let i = ancestors.length - 1; i >= 0; i--) { const a = ancestors[i]; const map = accessedBeforeDeclarationMap.get(a); if (map?.has(name)) { map.get(name).accessedBeforeDeclaration = true; if (lastAncestor.type === 'AssignmentExpression' && lastAncestor.left === id) { if (map.get(name).isConstant) { context.errors.push(new errors_1.ConstAssignment(lastAncestor, name)); } if (a.type === 'ForStatement' && a.init !== lastAncestor && a.update !== lastAncestor) { context.errors.push(new validityErrors_1.NoAssignmentToForVariable(lastAncestor)); } } break; } } } const customWalker = { ...walkers_1.base, VariableDeclarator(node, st, c) { // don't visit the id if (node.init) { c(node.init, st, 'Expression'); } } }; (0, walkers_1.ancestor)(program, { VariableDeclaration(node, ancestors) { const lastAncestor = ancestors[ancestors.length - 2]; const { name } = (0, helpers_1.getSourceVariableDeclaration)(node).id; const accessedBeforeDeclaration = accessedBeforeDeclarationMap .get(lastAncestor) .get(name).accessedBeforeDeclaration; node.typability = accessedBeforeDeclaration ? 'Untypable' : 'NotYetTyped'; }, Identifier: validateIdentifier, FunctionDeclaration(node, ancestors) { // a function declaration can be typed if there are no function calls in the same scope before it const lastAncestor = ancestors[ancestors.length - 2]; node.typability = scopeHasCallExpressionMap.get(lastAncestor) ? 'Untypable' : 'NotYetTyped'; }, Pattern(node, ancestors) { if (node.type === 'Identifier') { validateIdentifier(node, ancestors); } else if (node.type === 'MemberExpression') { if (node.object.type === 'Identifier') { validateIdentifier(node.object, ancestors); } } }, CallExpression(call, ancestors) { for (let i = ancestors.length - 1; i >= 0; i--) { const a = ancestors[i]; if (scopeHasCallExpressionMap.has(a)) { scopeHasCallExpressionMap.set(a, true); break; } } } }, customWalker); /* simple(program, { VariableDeclaration(node: TypeAnnotatedNode<es.VariableDeclaration>) { console.log(getVariableDecarationName(node) + " " + node.typability); }, FunctionDeclaration(node: TypeAnnotatedNode<es.FunctionDeclaration>) { console.log(node.id!.name + " " + node.typability); } }) */ return program; } exports.validateAndAnnotate = validateAndAnnotate; function checkProgramForUndefinedVariables(program, context) { const usedIdentifiers = new Set([ ...(0, uniqueIds_1.getIdentifiersInProgram)(program), ...(0, uniqueIds_1.getIdentifiersInNativeStorage)(context.nativeStorage) ]); const globalIds = (0, uniqueIds_1.getNativeIds)(program, usedIdentifiers); return checkForUndefinedVariables(program, context, globalIds, false); } exports.checkProgramForUndefinedVariables = checkProgramForUndefinedVariables; function checkForUndefinedVariables(program, context, globalIds, skipUndefined) { const preludes = context.prelude ? (0, uniqueIds_1.getFunctionDeclarationNamesInProgram)((0, parser_1.parse)(context.prelude, context)) : new Set(); const env = context.runtime.environments[0].head || {}; const builtins = context.nativeStorage.builtins; const identifiersIntroducedByNode = new Map(); function processBlock(node) { const identifiers = new Set(); for (const statement of node.body) { if (statement.type === 'VariableDeclaration') { const { id: { name } } = (0, helpers_1.getSourceVariableDeclaration)(statement); identifiers.add(name); } else if (statement.type === 'FunctionDeclaration') { if (statement.id === null) { throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'); } identifiers.add(statement.id.name); } else if (statement.type === 'ImportDeclaration') { for (const specifier of statement.specifiers) { identifiers.add(specifier.local.name); } } } identifiersIntroducedByNode.set(node, identifiers); } function processFunction(node, _ancestors) { identifiersIntroducedByNode.set(node, new Set(node.params.map(id => id.type === 'Identifier' ? id.name : id.argument.name))); } const identifiersToAncestors = new Map(); (0, walkers_1.ancestor)(program, { Program: processBlock, BlockStatement: processBlock, FunctionDeclaration: processFunction, ArrowFunctionExpression: processFunction, ForStatement(forStatement) { const init = forStatement.init; if (init.type === 'VariableDeclaration') { const { id: { name } } = (0, helpers_1.getSourceVariableDeclaration)(init); identifiersIntroducedByNode.set(forStatement, new Set([name])); } }, Identifier(identifier, ancestors) { identifiersToAncestors.set(identifier, [...ancestors]); }, Pattern(node, ancestors) { if (node.type === 'Identifier') { identifiersToAncestors.set(node, [...ancestors]); } else if (node.type === 'MemberExpression') { if (node.object.type === 'Identifier') { identifiersToAncestors.set(node.object, [...ancestors]); } } } }); const nativeInternalNames = new Set(Object.values(globalIds).map(({ name }) => name)); for (const [identifier, ancestors] of identifiersToAncestors) { const name = identifier.name; const isCurrentlyDeclared = ancestors.some(a => identifiersIntroducedByNode.get(a)?.has(name)); if (isCurrentlyDeclared) { continue; } const isPreviouslyDeclared = context.nativeStorage.previousProgramsIdentifiers.has(name); if (isPreviouslyDeclared) { continue; } const isBuiltin = builtins.has(name); if (isBuiltin) { continue; } const isPrelude = preludes.has(name); if (isPrelude) { continue; } const isInEnv = name in env; if (isInEnv) { continue; } const isNativeId = nativeInternalNames.has(name); if (!isNativeId && !skipUndefined) { throw new errors_1.UndefinedVariable(name, identifier); } } } exports.checkForUndefinedVariables = checkForUndefinedVariables; //# sourceMappingURL=validator.js.map