UNPKG

toylang

Version:

A toy programming language built with TypeScript for learning purposes

299 lines (298 loc) 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Interpreter = void 0; var Environment_1 = require("./Environment"); var CallableFunction_1 = require("./CallableFunction"); var RuntimeError_1 = require("./RuntimeError"); var Return_1 = require("./Return"); var StdLib_1 = require("./StdLib"); function checkNumberOperands(node, left, right) { if (typeof left === "number" && typeof right === "number") return true; throw new RuntimeError_1.RuntimeError("Operands must be numbers.", node); } function checkNumberOperand(node, operand) { if (typeof operand === "number") return; throw new RuntimeError_1.RuntimeError("Operand must be number.", node); } var Interpreter = /** @class */ (function () { function Interpreter() { var _this = this; this.ast = null; this.globals = new Environment_1.Environment(); // Initialize standard library Object.keys(StdLib_1.stdlib).forEach(function (libName) { _this.globals.add(libName, StdLib_1.stdlib[libName]); }); this.environment = this.globals; } Interpreter.prototype.execute = function (ast) { this.ast = ast; return this.visit(this.ast); }; Interpreter.prototype.visit = function (node) { if (node == null) return; switch (node.type) { case "ExpressionStatement": return this.visitExpression(node); case "IfStatement": return this.visitIfStatement(node); case "WhileStatement": return this.visitWhileStatement(node); case "ForStatement": return this.visitForStatement(node); case "VariableStatement": return this.visitVariableStatement(node); case "BlockStatement": return this.visitBlockStatement(node); case "CallExpression": return this.visitCallExpression(node); case "UnaryExpression": return this.visitUnaryExpression(node); case "BinaryExpression": return this.visitBinaryExpression(node); case "LogicalExpression": return this.visitLogicalExpression(node); case "AssignmentExpression": return this.visitAssignmentExpression(node); case "FunctionDeclaration": return this.visitFunctionDeclaration(node); case "ReturnStatement": return this.visitReturnStatement(node); case "Program": return this.visitProgram(node); case "NumericLiteral": case "StringLiteral": return this.visitLiterals(node); case "Identifier": return this.visitIdentifier(node); default: return null; } }; Interpreter.prototype.visitIdentifier = function (node) { return this.environment.get(node.name); }; Interpreter.prototype.visitExpression = function (node) { return this.visit(node.expression); }; Interpreter.prototype.visitAssignmentExpression = function (node) { var value = this.visit(node.right); var left = node.left; var lhs = this.environment.get(left.name); switch (node.operator) { case "=": this.environment.assign(left.name, value); break; case "+=": lhs += value; this.environment.assign(left.name, lhs); break; case "-=": if (typeof value !== "number") { throw new RuntimeError_1.RuntimeError("Operand must be a number", node); } lhs -= value; this.environment.assign(left.name, lhs); break; case "/=": if (typeof value !== "number") { throw new RuntimeError_1.RuntimeError("Operand must be a number", node); } lhs /= value; this.environment.assign(left.name, lhs); break; case "*=": if (typeof value !== "number") { throw new RuntimeError_1.RuntimeError("Operand must be a number", node); } lhs *= value; this.environment.assign(left.name, lhs); break; } return lhs; }; Interpreter.prototype.visitReturnStatement = function (node) { var value = null; if (node.argument !== null) value = this.visit(node.argument); throw new Return_1.Return(value); }; Interpreter.prototype.visitFunctionDeclaration = function (node) { var fun = new CallableFunction_1.ToyLangFunction(node, this.environment); this.environment.add(node.name.name, fun); return null; }; Interpreter.prototype.visitCallExpression = function (node) { // this.printFunction(node); // calle should be MusketFunction. var callee = this.visit(node.callee); if (!callee.prototype instanceof CallableFunction_1.CallableFunction) { throw new RuntimeError_1.RuntimeError("Can only call functions and classes", node); } var args = []; for (var _i = 0, _a = node.arguments; _i < _a.length; _i++) { var argument = _a[_i]; args.push(this.visit(argument)); } // Todo fix infinity if (args.length != callee.arity() && callee.arity() !== Infinity) { throw new RuntimeError_1.RuntimeError("Expected " + callee.arity() + " arguments but got " + args.length + ".", node); } return callee.call(this, args); }; Interpreter.prototype.visitVariableStatement = function (node) { for (var _i = 0, _a = node.declarations; _i < _a.length; _i++) { var declaration = _a[_i]; var value = null; if (declaration.init != null) { value = this.visit(declaration.init); } this.environment.add(declaration.id.name, value); return null; } return null; }; Interpreter.prototype.visitBlockStatement = function (node) { this.executeBlock(node.body, new Environment_1.Environment(this.environment)); return null; }; // scopes!! Interpreter.prototype.executeBlock = function (statements, environment) { // save the current env var previous = this.environment; try { // set the current env to the block's env this.environment = environment; for (var _i = 0, statements_1 = statements; _i < statements_1.length; _i++) { var statement = statements_1[_i]; this.visit(statement); } } finally { // restore the previous env this.environment = previous; } }; Interpreter.prototype.visitIfStatement = function (node) { var test = this.visit(node.test); if (test) { this.visit(node.consequent); } else if (node.alternate !== null) { this.visit(node.alternate); } return null; }; Interpreter.prototype.visitWhileStatement = function (node) { while (this.visit(node.test)) { this.visit(node.body); } return null; }; Interpreter.prototype.visitForStatement = function (node) { // hacky if (node.init === null && node.test === null && node.update === null) { for (;;) { this.visit(node.body); } } for (this.visit(node.init); this.visit(node.test); this.visit(node.update)) { this.visit(node.body); } return null; }; Interpreter.prototype.visitBinaryExpression = function (node) { var op = node.operator; var left = this.visit(node.left); var right = this.visit(node.right); switch (op) { case "==": return left == right; case "!=": return left != right; case "+": if (typeof left === "number" && typeof right === "number") { return left + right; } if (typeof left === "string" && typeof right === "string") { return left + right; } throw new RuntimeError_1.RuntimeError("Runtime: Mismatched type, cannot \"" + typeof left + "\" + \"" + typeof right + "\"", node); case "-": checkNumberOperands(node, left, right); return left - right; case "/": checkNumberOperands(node, left, right); return left / right; case "*": checkNumberOperands(node, left, right); return left * right; case ">": checkNumberOperands(node, left, right); return left > right; case "<": checkNumberOperands(node, left, right); return left < right; case ">=": checkNumberOperands(node, left, right); return left >= right; case "<=": checkNumberOperands(node, left, right); return left <= right; } throw new Error("Runtime: Unknown Binary operation"); }; Interpreter.prototype.visitLogicalExpression = function (node) { var op = node.operator; var left = this.visit(node.left); var right = this.visit(node.right); switch (op) { case "&&": return left && right; case "||": return left || right; } throw new Error("Runtime: Unknown Logical operation"); }; Interpreter.prototype.visitLiterals = function (node) { switch (node.type) { case "NumericLiteral": return node.value; case "StringLiteral": return node.value; case "BooleanLiteral": return node.value; case "NullLiteral": return null; default: throw new SyntaxError("Runtime: Unexpected literal"); } }; Interpreter.prototype.visitUnaryExpression = function (node) { var right = this.visit(node.argument); switch (node.operator) { case "-": checkNumberOperand(node, right); return -right; case "+": return right; case "!": return !right; } return null; }; Interpreter.prototype.visitProgram = function (node) { var _this = this; node.body.map(function (statement) { return _this.visit(statement); }); return null; }; return Interpreter; }()); exports.Interpreter = Interpreter;