UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

587 lines 23.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.nonDetEvaluate = exports.apply = exports.evaluate = exports.evaluators = void 0; const lodash_1 = require("lodash"); const constants_1 = require("../constants"); const heap_1 = require("../cse-machine/heap"); const utils_1 = require("../cse-machine/utils"); const errors = require("../errors/errors"); const runtimeSourceError_1 = require("../errors/runtimeSourceError"); const astCreator_1 = require("../utils/ast/astCreator"); const operators_1 = require("../utils/operators"); const rttc = require("../utils/rttc"); const closure_1 = require("./closure"); class BreakValue { } class ContinueValue { } class ReturnValue { constructor(value) { this.value = value; } } const createEnvironment = (context, closure, args, callExpression) => { const environment = { name: closure.functionName, tail: closure.environment, head: {}, heap: new heap_1.default(), id: (0, utils_1.uniqueId)(context) }; if (callExpression) { environment.callExpression = { ...callExpression, arguments: args.map(astCreator_1.primitive) }; } closure.node.params.forEach((param, index) => { const ident = param; environment.head[ident.name] = args[index]; }); return environment; }; const createBlockEnvironment = (context, name = 'blockEnvironment') => { return { name, tail: currentEnvironment(context), head: {}, heap: new heap_1.default(), thisContext: context, id: (0, utils_1.uniqueId)(context) }; }; const handleRuntimeError = (context, error) => { context.errors.push(error); context.runtime.environments = context.runtime.environments.slice(-context.numberOfOuterEnvironments); throw error; }; const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement declaration'); function declareIdentifier(context, name, node) { const environment = currentEnvironment(context); if (environment.head.hasOwnProperty(name)) { const descriptors = Object.getOwnPropertyDescriptors(environment.head); return handleRuntimeError(context, new errors.VariableRedeclaration(node, name, descriptors[name].writable)); } environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED; return environment; } function declareVariables(context, node) { for (const declaration of node.declarations) { declareIdentifier(context, declaration.id.name, node); } } function declareFunctionAndVariableIdentifiers(context, node) { for (const statement of node.body) { switch (statement.type) { case 'VariableDeclaration': declareVariables(context, statement); break; case 'FunctionDeclaration': if (statement.id === null) { throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'); } declareIdentifier(context, statement.id.name, statement); break; } } } function defineVariable(context, name, value, constant = false) { const environment = context.runtime.environments[0]; if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) { return handleRuntimeError(context, new errors.VariableRedeclaration(context.runtime.nodes[0], name, !constant)); } Object.defineProperty(environment.head, name, { value, writable: !constant, enumerable: true }); return environment; } function undefineVariable(context, name) { const environment = context.runtime.environments[0]; Object.defineProperty(environment.head, name, { value: DECLARED_BUT_NOT_YET_ASSIGNED, writable: true, enumerable: true }); } const currentEnvironment = (context) => context.runtime.environments[0]; const popEnvironment = (context) => context.runtime.environments.shift(); const pushEnvironment = (context, environment) => context.runtime.environments.unshift(environment); const getVariable = (context, name, ensureVariableAssigned) => { let environment = context.runtime.environments[0]; while (environment) { if (environment.head.hasOwnProperty(name)) { if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { if (ensureVariableAssigned) { return handleRuntimeError(context, new errors.UnassignedVariable(name, context.runtime.nodes[0])); } else { return DECLARED_BUT_NOT_YET_ASSIGNED; } } else { return environment.head[name]; } } else { environment = environment.tail; } } return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])); }; const setVariable = (context, name, value) => { let environment = context.runtime.environments[0]; while (environment) { if (environment.head.hasOwnProperty(name)) { if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { break; } const descriptors = Object.getOwnPropertyDescriptors(environment.head); if (descriptors[name].writable) { environment.head[name] = value; return undefined; } return handleRuntimeError(context, new errors.ConstAssignment(context.runtime.nodes[0], name)); } else { environment = environment.tail; } } return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])); }; const checkNumberOfArguments = (context, callee, args, exp) => { if (callee.node.params.length !== args.length) { return handleRuntimeError(context, new errors.InvalidNumberOfArguments(exp, callee.node.params.length, args.length)); } return undefined; }; /** * Returns a random integer for a given interval (inclusive). */ function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } function* getAmbRArgs(context, call) { const args = (0, lodash_1.cloneDeep)(call.arguments); while (args.length > 0) { const r = randomInt(0, args.length - 1); const arg = args.splice(r, 1)[0]; yield* evaluate(arg, context); } } function* getArgs(context, call) { const args = (0, lodash_1.cloneDeep)(call.arguments); return yield* cartesianProduct(context, args, []); } /* Given a list of non deterministic nodes, this generator returns every * combination of values of these nodes */ function* cartesianProduct(context, nodes, nodeValues) { if (nodes.length === 0) { yield nodeValues; } else { const currentNode = nodes.shift(); // we need the postfix ! to tell compiler that nodes array is nonempty const nodeValueGenerator = evaluate(currentNode, context); for (const nodeValue of nodeValueGenerator) { nodeValues.push(nodeValue); yield* cartesianProduct(context, nodes, nodeValues); nodeValues.pop(); } nodes.unshift(currentNode); } } function* getAmbArgs(context, call) { for (const arg of call.arguments) { yield* evaluate(arg, context); } } function transformLogicalExpression(node) { if (node.operator === '&&') { return (0, astCreator_1.conditionalExpression)(node.left, node.right, (0, astCreator_1.literal)(false), node.loc); } else { return (0, astCreator_1.conditionalExpression)(node.left, (0, astCreator_1.literal)(true), node.right, node.loc); } } function* reduceIf(node, context) { const testGenerator = evaluate(node.test, context); for (const test of testGenerator) { const error = rttc.checkIfStatement(node, test, context.chapter); if (error) { return handleRuntimeError(context, error); } yield test ? node.consequent : node.alternate; } } function* evaluateBlockSatement(context, node) { declareFunctionAndVariableIdentifiers(context, node); yield* evaluateSequence(context, node.body); } function* evaluateSequence(context, sequence) { if (sequence.length === 0) { return yield undefined; } const firstStatement = sequence[0]; const sequenceValGenerator = evaluate(firstStatement, context); if (sequence.length === 1) { yield* sequenceValGenerator; } else { sequence.shift(); let shouldUnshift = true; for (const sequenceValue of sequenceValGenerator) { // prevent unshifting of cut operator shouldUnshift = sequenceValue !== constants_1.CUT; if (sequenceValue instanceof ReturnValue || sequenceValue instanceof BreakValue || sequenceValue instanceof ContinueValue) { yield sequenceValue; continue; } const res = yield* evaluateSequence(context, sequence); if (res === constants_1.CUT) { // prevent unshifting of statements before cut shouldUnshift = false; break; } } if (shouldUnshift) sequence.unshift(firstStatement); else return constants_1.CUT; } } function* evaluateConditional(node, context) { const branchGenerator = reduceIf(node, context); for (const branch of branchGenerator) { yield* evaluate(branch, context); } } /** * WARNING: Do not use object literal shorthands, e.g. * { * *Literal(node: es.Literal, ...) {...}, * *ThisExpression(node: es.ThisExpression, ..._ {...}, * ... * } * They do not minify well, raising uncaught syntax errors in production. * See: https://github.com/webpack/webpack/issues/7566 */ // tslint:disable:object-literal-shorthand // prettier-ignore exports.evaluators = { /** Simple Values */ Literal: function* (node, _context) { yield node.value; }, ArrowFunctionExpression: function* (node, context) { yield closure_1.default.makeFromArrowFunction(node, currentEnvironment(context), context); }, ArrayExpression: function* (node, context) { const arrayGenerator = cartesianProduct(context, node.elements, []); for (const array of arrayGenerator) { yield array.slice(); // yield a new array to avoid modifying previous ones } }, Identifier: function* (node, context) { return yield getVariable(context, node.name, true); }, CallExpression: function* (node, context) { const callee = node.callee; if (rttc.isIdentifier(callee)) { switch (callee.name) { case 'amb': return yield* getAmbArgs(context, node); case 'ambR': return yield* getAmbRArgs(context, node); case 'cut': return yield constants_1.CUT; } } const calleeGenerator = evaluate(node.callee, context); for (const calleeValue of calleeGenerator) { const argsGenerator = getArgs(context, node); for (const args of argsGenerator) { yield* apply(context, calleeValue, args, node, undefined); } } }, UnaryExpression: function* (node, context) { const argGenerator = evaluate(node.argument, context); for (const argValue of argGenerator) { const error = rttc.checkUnaryExpression(node, node.operator, argValue, context.chapter); if (error) { return handleRuntimeError(context, error); } yield (0, operators_1.evaluateUnaryExpression)(node.operator, argValue); } return; }, BinaryExpression: function* (node, context) { const pairGenerator = cartesianProduct(context, [node.left, node.right], []); for (const pair of pairGenerator) { const leftValue = pair[0]; const rightValue = pair[1]; const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, leftValue, rightValue); if (error) { return handleRuntimeError(context, error); } yield (0, operators_1.evaluateBinaryExpression)(node.operator, leftValue, rightValue); } return; }, ConditionalExpression: function* (node, context) { yield* evaluateConditional(node, context); }, LogicalExpression: function* (node, context) { const conditional = transformLogicalExpression(node); yield* evaluateConditional(conditional, context); }, VariableDeclaration: function* (node, context) { const declaration = node.declarations[0]; const constant = node.kind === 'const'; const id = declaration.id; const valueGenerator = evaluate(declaration.init, context); for (const value of valueGenerator) { defineVariable(context, id.name, value, constant); yield value; undefineVariable(context, id.name); } return undefined; }, MemberExpression: function* (node, context) { // es.PrivateIdentifier is a ES2022 feature const pairGenerator = cartesianProduct(context, [node.property, node.object], []); for (const pair of pairGenerator) { const prop = pair[0]; const obj = pair[1]; const error = rttc.checkMemberAccess(node, obj, prop); if (error) { return yield handleRuntimeError(context, error); } yield obj[prop]; } return; }, AssignmentExpression: function* (node, context) { if (node.left.type === 'MemberExpression') { // es.PrivateIdentifier is a ES2022 feature const tripleGenerator = cartesianProduct(context, [node.right, node.left.property, node.left.object], []); for (const triple of tripleGenerator) { const val = triple[0]; const prop = triple[1]; const obj = triple[2]; const error = rttc.checkMemberAccess(node, obj, prop); if (error) { return yield handleRuntimeError(context, error); } const originalElementValue = obj[prop]; obj[prop] = val; yield val; obj[prop] = originalElementValue; } return; } const id = node.left; const originalValue = getVariable(context, id.name, false); const valueGenerator = evaluate(node.right, context); for (const value of valueGenerator) { setVariable(context, id.name, value); yield value; setVariable(context, id.name, originalValue); } return; }, FunctionDeclaration: function* (node, context) { const id = node.id; if (id === null) { throw new Error("Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing."); } const closure = new closure_1.default(node, currentEnvironment(context), context); defineVariable(context, id.name, closure, true); yield undefined; undefineVariable(context, id.name); }, IfStatement: function* (node, context) { yield* evaluateConditional(node, context); }, ExpressionStatement: function* (node, context) { return yield* evaluate(node.expression, context); }, ContinueStatement: function* (_node, _context) { yield new ContinueValue(); }, BreakStatement: function* (_node, _context) { yield new BreakValue(); }, WhileStatement: function* (node, context) { let value; // tslint:disable-line function* loop() { const testGenerator = evaluate(node.test, context); for (const test of testGenerator) { const error = rttc.checkIfStatement(node.test, test, context.chapter); if (error) return handleRuntimeError(context, error); if (test && !(value instanceof ReturnValue) && !(value instanceof BreakValue)) { const iterationValueGenerator = evaluate((0, lodash_1.cloneDeep)(node.body), context); for (const iterationValue of iterationValueGenerator) { value = iterationValue; yield* loop(); } } else { if (value instanceof BreakValue || value instanceof ContinueValue) { yield undefined; } else { yield value; } } } } yield* loop(); }, ForStatement: function* (node, context) { let value; function* loop() { const testGenerator = evaluate(node.test, context); for (const test of testGenerator) { const error = rttc.checkIfStatement(node.test, test, context.chapter); if (error) return handleRuntimeError(context, error); if (test && !(value instanceof ReturnValue) && !(value instanceof BreakValue)) { const iterationEnvironment = createBlockEnvironment(context, 'forBlockEnvironment'); pushEnvironment(context, iterationEnvironment); for (const name in loopEnvironment.head) { if (loopEnvironment.head.hasOwnProperty(name)) { declareIdentifier(context, name, node); defineVariable(context, name, loopEnvironment.head[name], true); } } const iterationValueGenerator = evaluate((0, lodash_1.cloneDeep)(node.body), context); for (const iterationValue of iterationValueGenerator) { value = iterationValue; popEnvironment(context); const updateNode = evaluate(node.update, context); for (const _update of updateNode) { yield* loop(); } pushEnvironment(context, iterationEnvironment); } popEnvironment(context); } else { if (value instanceof BreakValue || value instanceof ContinueValue) { yield undefined; } else { yield value; } } } } // Create a new block scope for the loop variables const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment'); pushEnvironment(context, loopEnvironment); const initNode = node.init; if (initNode.type === 'VariableDeclaration') { declareVariables(context, initNode); } const initNodeGenerator = evaluate(node.init, context); for (const _init of initNodeGenerator) { const loopGenerator = loop(); for (const loopValue of loopGenerator) { popEnvironment(context); yield loopValue; pushEnvironment(context, loopEnvironment); } } popEnvironment(context); }, ReturnStatement: function* (node, context) { const returnExpression = node.argument; const returnValueGenerator = evaluate(returnExpression, context); for (const returnValue of returnValueGenerator) { yield new ReturnValue(returnValue); } }, BlockStatement: function* (node, context) { // Create a new environment (block scoping) const environment = createBlockEnvironment(context, 'blockEnvironment'); pushEnvironment(context, environment); const resultGenerator = evaluateBlockSatement(context, node); for (const result of resultGenerator) { popEnvironment(context); yield result; pushEnvironment(context, environment); } popEnvironment(context); }, Program: function* (node, context) { context.numberOfOuterEnvironments += 1; const environment = createBlockEnvironment(context, 'programEnvironment'); pushEnvironment(context, environment); return yield* evaluateBlockSatement(context, node); } }; // tslint:enable:object-literal-shorthand function* evaluate(node, context) { const result = yield* exports.evaluators[node.type](node, context); return result; } exports.evaluate = evaluate; exports.nonDetEvaluate = evaluate; function* apply(context, fun, args, node, thisContext) { if (fun instanceof closure_1.default) { checkNumberOfArguments(context, fun, args, node); const environment = createEnvironment(context, fun, args, node); environment.thisContext = thisContext; pushEnvironment(context, environment); const applicationValueGenerator = evaluateBlockSatement(context, (0, lodash_1.cloneDeep)(fun.node.body)); // This function takes a value that may be a ReturnValue. // If so, it returns the value wrapped in the ReturnValue. // If not, it returns the default value. function unwrapReturnValue(result, defaultValue) { if (result instanceof ReturnValue) { return result.value; } else { return defaultValue; } } for (const applicationValue of applicationValueGenerator) { popEnvironment(context); yield unwrapReturnValue(applicationValue, undefined); pushEnvironment(context, environment); } popEnvironment(context); } else if (typeof fun === 'function') { try { yield fun.apply(thisContext, args); } catch (e) { // Recover from exception context.runtime.environments = context.runtime.environments.slice(-context.numberOfOuterEnvironments); const loc = node.loc ?? constants_1.UNKNOWN_LOCATION; if (!(e instanceof runtimeSourceError_1.RuntimeSourceError || e instanceof errors.ExceptionError)) { // The error could've arisen when the builtin called a source function which errored. // If the cause was a source error, we don't want to include the error. // However if the error came from the builtin itself, we need to handle it. return handleRuntimeError(context, new errors.ExceptionError(e, loc)); } throw e; } } else { return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node)); } return; } exports.apply = apply; //# sourceMappingURL=interpreter-non-det.js.map