toylang
Version:
A toy programming language built with TypeScript for learning purposes
299 lines (298 loc) • 11.5 kB
JavaScript
"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;