cookeylang-ts
Version:
A dynamic, interpreted language.
230 lines (229 loc) • 9.29 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Interpreter = void 0;
const token_1 = require("./token");
const visitor_1 = require("./expr/visitor");
const environment_1 = require("./environment");
const errors_1 = require("./errors");
const functions_1 = require("./functions");
class Interpreter extends visitor_1.Visitor {
constructor(trees) {
super();
this.globals = new environment_1.Environment();
this.locals = new Map();
this.trees = trees;
this.environment = this.globals;
this.globals.define(token_1.defualtToken, token_1.identifierToken("printLine"), new functions_1.FuncCallable(1, (_, args) => console.log(this.stringify(args[0])), () => "<Native Function>"));
this.globals.define(token_1.defualtToken, token_1.identifierToken("print"), new functions_1.FuncCallable(1, (_, args) => process.stdout.write(this.stringify(args[0])), () => "<Native Function>"));
this.globals.define(token_1.defualtToken, token_1.identifierToken("clearConsole"), new functions_1.FuncCallable(0, () => console.clear(), () => "<Native Function>"));
}
init() {
try {
for (const tree of this.trees) {
tree.visit(this);
}
}
catch (e) {
if (e instanceof errors_1.CookeyError) {
console.log(`<${e.lineData.file}> [ ${e.lineData.line} : ${e.lineData.col} ] ${e.message}`);
if (e.calls.length > 0)
for (const call of e.calls)
console.log(`at <${call.file}> [ ${call.line} : ${call.col} ]`);
}
}
}
initBlock(stmts, env) {
let parent = this.environment;
try {
this.environment = env;
for (const stmt of stmts) {
stmt.visit(this);
}
}
finally {
this.environment = parent;
}
}
resolve(expr, depth) {
this.locals.set(expr, depth);
}
FuncDecl(self) {
let func = new functions_1.UserCallable(self, this.environment);
this.environment.define(new token_1.Token(0, 0, "<native>", token_1.TType.VAR, "var"), self.name, func);
return null;
}
VarDecl(self) {
let value = null;
if (self.value)
value = self.value.visit(this);
this.environment.define(self.mut, self.name, value);
return null;
}
ExprStmt(self) {
self.expr.visit(this);
return null;
}
IfStmt(self) {
if (self.condition.visit(this) == true)
return self.thenBr.visit(this);
else if (self.elseBr)
return self.elseBr.visit(this);
return null;
}
WhileStmt(self) {
while (this.isTrue(self.condition.visit(this))) {
self.body.visit(this);
}
return null;
}
ExitStmt(self) {
let exitCode = self.exit.visit(this);
process.exit(exitCode);
}
RetStmt(self) {
let value = self.value.visit(this);
throw new errors_1.CookeyRet(value);
}
Block(self) {
this.initBlock(self.stmts, new environment_1.Environment(this.environment));
return null;
}
Literal(self) {
return self.value;
}
Assign(self) {
let value = self.value.visit(this);
let distance = this.locals.get(self);
if (distance != null)
this.environment.assignAt(distance, self.name, value);
else
this.environment.assign(self.name, value);
return value;
}
Logic(self) {
let left = self.left.visit(this);
if (self.op.type == token_1.TType.OR) {
if (this.isTrue(left))
return left;
}
else {
if (!this.isTrue(left))
return left;
}
return self.right.visit(this);
}
Lambda(self) {
return new functions_1.UserCallable(self, this.environment);
}
Binary(self) {
let left = self.left.visit(this);
let right = self.right.visit(this);
switch (self.op.type) {
case token_1.TType.PLUS:
if (typeof left == "number" && typeof right == "number")
return left + right;
if (typeof left == "string" && typeof right == "string")
return left + right;
throw new errors_1.CookeyError(self.lineData, "Only number addition or string concatenation is allowed for the '+' operator.");
case token_1.TType.MINUS:
if (typeof left == "number" && typeof right == "number")
return left - right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '-' operator.");
case token_1.TType.TIMES:
if (typeof left == "number" && typeof right == "number")
return left * right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '*' operator.");
case token_1.TType.DIVIDE:
if (typeof left == "number" && typeof right == "number")
return left / right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '/' operator.");
case token_1.TType.MODULO:
if (typeof left == "number" && typeof right == "number")
return left % right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '%' operator.");
case token_1.TType.POWER:
if (typeof left == "number" && typeof right == "number")
return left ** right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '^' operator.");
case token_1.TType.GREATER:
if (typeof left == "number" && typeof right == "number")
return left > right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '>' operator.");
case token_1.TType.GREATER_EQ:
if (typeof left == "number" && typeof right == "number")
return left >= right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '>=' operator.");
case token_1.TType.LESS:
if (typeof left == "number" && typeof right == "number")
return left < right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '<' operator.");
case token_1.TType.LESS_EQ:
if (typeof left == "number" && typeof right == "number")
return left <= right;
throw new errors_1.CookeyError(self.lineData, "Only numbers are allowed for the '<=' operator.");
case token_1.TType.EQ_EQ: return this.isEqual(left, right);
case token_1.TType.BANG_EQ: return !this.isEqual(left, right);
}
return 0;
}
Call(self) {
let callee = self.callee.visit(this);
let args = [];
for (const arg of self.args) {
args.push(arg.visit(this));
}
let func = callee;
if (!(func instanceof functions_1.FuncCallable))
throw new errors_1.CookeyError(self.lineData, "Only functions and classes can be called.");
return func.call(this, args, self.lineData);
}
Unary(self) {
let right = self.right.visit(this);
switch (self.op.type) {
case token_1.TType.PLUS:
if (typeof right == "number")
return right;
throw new errors_1.CookeyError(self.lineData, "Unary '+' can only be applied to numbers.");
case token_1.TType.MINUS:
if (typeof right == "number")
return -right;
throw new errors_1.CookeyError(self.lineData, "Only numbers can be negated.");
case token_1.TType.BANG:
return !this.isTrue(right);
}
return false;
}
Variable(self) {
return this.findVar(self.name, self);
}
Grouping(self) {
return self.value.visit(this);
}
findVar(name, expr) {
let distance = this.locals.get(expr);
if (distance != null)
return this.environment.getAt(distance, name.value).val;
else
return this.globals.get(name).val;
}
isTrue(val) {
if (val == null)
return false;
if (typeof val == "boolean")
return val;
return true;
}
isEqual(a, b) {
if (a == null && b == null)
return true;
if (a == null)
return false;
return a == b;
}
stringify(str) {
if (str == null)
return "NaV";
return str.toString();
}
}
exports.Interpreter = Interpreter;