UNPKG

cookeylang-ts

Version:
230 lines (229 loc) 9.29 kB
"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;