UNPKG

eggtended-js

Version:

A extended version of the Egg programming language from the Eloquent Javascript book.

256 lines (206 loc) 7.43 kB
const estraverse = require("estraverse"); const { TopEnv, SpecialForms } = require("../interp/environment.js"); const { Apply, Word } = require("../interp/ast.js"); const SCOPE_OPERATORS = ["do", "if", "while", "for", "foreach", "fun", "->", "object"]; const DEFINE_OPERATORS = [":=", "define", "def"]; const SET_OPERATORS = ["set", "<-"]; const FUNCTION_OPERATORS = ["fun", "->"]; const OBJECT_OPERATORS = ["object", "obj"]; class Semantic { constructor(symboltable) { if (symboltable === undefined) { this.symboltable = Object.create(null); } else { this.symboltable = symboltable; } } check(tree) { const methods = this; tree = estraverse.replace(tree, { enter: function(node, parent) { if (node.type === "apply") { node = methods.checkApplyEnter(node); } else if (node.type === "word") { node = methods.checkWordEnter(node, parent); } return node; }, leave: function(node) { if (node.type === "apply") { node = methods.checkApplyLeave(node); } return node; }, keys: { apply: ["args"], word: [], value: [], regex: [] }, fallback: "iteration" }); return tree; } static check(tree) { return new Semantic().check(tree); } checkApplyEnter(node) { if (node.operator.type == "word") { const operator_name = node.operator.name; // Assert that the arguments passed to the apply are correct this.assertApplyArgs(node); // If entered on a new scope, create a child symboltable if (SCOPE_OPERATORS.includes(operator_name)) { this.symboltable = Object.create(this.symboltable); } // If the operator is a "define" operator, add the new symbol to the symboltable if (DEFINE_OPERATORS.includes(operator_name)) { node.args[0] = this.addToSymboltable(node.args[0]); } if (SET_OPERATORS.includes(operator_name)) { const symbol = this.findInSymboltable(node.args[0].name); if (symbol && symbol.const) { throw new TypeError(`${node.args[0].name} is const and can't be reassigned!`); } } // Add function arguments to function symboltable if (FUNCTION_OPERATORS.includes(operator_name)) { for (let i = 0; i < node.args.length - 1; ++i) { this.addToSymboltable(node.args[i]); } } // Add "this" symbol, and object properties, to object symboltable if (OBJECT_OPERATORS.includes(operator_name)) { const apply = new Apply(new Word({ value: "const" })); apply.args = [new Word({ value: "this" })]; this.addToSymboltable(apply); for (let i = 0; i < node.args.length; i += 2) { this.addToSymboltable(new Word({ value: node.args[i].value })); } } // Add the "iterator" symbol to foreach symboltable if (operator_name === "foreach") { this.addToSymboltable(node.args[0]); } } return node; } assertApplyArgs(node) { const operator_name = node.operator.name; // Check if operator is defined in SpecialForms let operator = operator_name in SpecialForms ? SpecialForms[operator_name] : undefined; // Check if operator is defined in TopEnv operator = operator_name in TopEnv ? TopEnv[operator_name] : operator; if (operator !== undefined) { // Check that the number of argument passed to the operator is correct if (operator.n_args !== undefined && !operator.n_args(node.args.length)) { throw new SyntaxError( `Bad number of args passed to ${operator_name}. (Got: ${ node.args.length }. Expected: ${operator.n_args})` ); } // Check that the passed arguments match all the expectations const match_error = (index, error_msg) => { return new TypeError( `The ${index}º argument doesn't match the expectation: "${error_msg}"` ); }; if (operator.args_expect !== undefined) { for (let i = 0; i < operator.args_expect.length; ++i) { if (!operator.args_expect[i](node.args[i])) { throw match_error(i + 1, operator.args_expect[i].error_msg); } } } if (operator.args_expect_mid !== undefined) { const firstArgsLen = operator.args_expect_firsts !== undefined ? operator.args_expect_firsts.length : 0; const lastArgsLen = operator.args_expect_lasts !== undefined ? operator.args_expect_lasts.length : 0; const lastArgsStartIndex = node.args.length - lastArgsLen; const firstArgs = node.args.slice(0, firstArgsLen); const midArgs = node.args.slice(firstArgsLen, lastArgsStartIndex); const lastArgs = node.args.slice(lastArgsStartIndex); for (let i = 0; i < firstArgs.length; ++i) { if (!operator.args_expect_firsts[i](firstArgs[i])) { throw match_error(i + 1, operator.args_expect_firsts[i].error_msg); } } for (let i = 0; i < midArgs.length; ++i) { const expect_index = i % operator.args_expect_mid.length; if (!operator.args_expect_mid[expect_index](midArgs[i])) { throw match_error( i + firstArgsLen + 1, operator.args_expect_mid[expect_index].error_msg ); } } for (let i = 0; i < lastArgs.length; ++i) { if (!operator.args_expect_lasts[i](lastArgs[i])) { throw match_error( i + firstArgsLen + lastArgsStartIndex, operator.args_expect_lasts[i].error_msg ); } } } } } addToSymboltable(node) { const traits = {}; // Accumulate all the traits (const, types [not implemented], ...) while (node.type === "apply") { if (node.operator.name === "const") { traits["const"] = true; node = node.args[0]; } } // Add to the symboltable only if not const if ( Object.prototype.hasOwnProperty.call(this.symboltable, node.name) && this.symboltable[node.name].const ) { throw new TypeError(`${node.name} is const and can't be reassigned!`); } else { this.symboltable[node.name] = traits; } return node; } findInSymboltable(name) { // Find an element on the symboltable or the parent ones for (let table = this.symboltable; table; table = Object.getPrototypeOf(table)) { if (Object.prototype.hasOwnProperty.call(table, name)) { return table[name]; } } return undefined; } checkWordEnter(node, parent) { if (!node.name.match("__.*__")) { const symbol = this.findInSymboltable(node.name); if (symbol === undefined && parent.operator.name !== "fun") { throw new ReferenceError(`Trying to use the undefined symbol ${node.name}`); } } return node; } checkApplyLeave(node) { if (node.operator.type == "word") { const operator_name = node.operator.name; // If left the scope, remove the last symboltable if (SCOPE_OPERATORS.includes(operator_name)) { this.symboltable = Object.getPrototypeOf(this.symboltable); } } return node; } } module.exports = { Semantic };