js-slang
Version:
Javascript-based implementations of Source, written in Typescript
232 lines (231 loc) • 10.1 kB
JavaScript
"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