lib/interp/ast.js
// AST class nodes
const xRegExp = require("xregexp");
const utils = require("../utils.js");
const { SpecialForms } = require("./environment.js");
class Value {
constructor(token) {
this.type = "value";
this.value = token.value;
}
evaluate() {
return this.value;
}
}
class Word {
constructor(token) {
this.type = "word";
this.name = token.value;
}
evaluate(env) {
// Variable
if (this.name in env) {
return env[this.name];
// Object variable referenced without using 'this'
} else if ("this" in env && this.name in env["this"]) {
return env["this"][this.name];
} else {
throw new ReferenceError(`Undefined variable: ${this.name}`);
}
}
}
class Apply {
constructor(expr) {
this.type = "apply";
this.operator = expr;
this.args = [];
}
evaluate(env) {
// Check if its a specialForm function
if (this.operator.type === "word" && this.operator.name in SpecialForms) {
return SpecialForms[this.operator.name].fun(this.args, env);
}
// Evaluated operator
let op = this.operator.evaluate(env);
if(op.fun !== undefined) {
op = op.fun;
}
// Evaluated arguments
let evArgs = this.args.map(arg => arg.evaluate(env));
if (typeof op === "function") {
// Is a function
return op(...evArgs);
}
if (typeof op !== "undefined") {
// Is an object, number, string or boolean
let name = evArgs[0];
let methodArgs = evArgs.slice(1);
// Check if the name of the method/property is defined on the object
if (typeof op[name] !== "undefined") {
// Execute as function
if (typeof op[name] === "function") {
return op[name](...methodArgs);
// Return as property
} else {
return op[name];
}
// If the name of the method is not defined on the object...
} else {
// Check if its a Map property.
if (op instanceof Map) {
if (typeof op.get(name) === "function") {
return op.get(name)(...methodArgs);
} else {
return op.get(name);
}
}
// Try to call the 'missing' method
if (typeof op["__missing__"] === "function") {
return op["__missing__"](...methodArgs);
// As a last resort, throw an Exception
} else {
throw new SyntaxError(`The method '${name}' was not found on the
object '${utils.ins(op)}'`);
}
}
}
throw new TypeError(`Could not resolve the apply expression`);
}
}
class Regex {
constructor(token) {
this.type = "regex";
this.body = token.body;
this.flags = token.flags;
}
evaluate() {
return new xRegExp(this.body, this.flags);
}
}
module.exports = {
Value,
Word,
Apply,
Regex
};