Created
November 4, 2020 00:09
-
-
Save microamp/4a4819626f29c9210a6be2bb08401b93 to your computer and use it in GitHub Desktop.
Eva.js (WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const assert = require("assert"); | |
const Environment = require("./Environment"); | |
const GlobalEnvironment = new Environment({ | |
null: null, | |
true: true, | |
false: false, | |
VERSION: "0.1", | |
// Print function: | |
print: (...args) => { | |
console.log(...args); | |
}, | |
// Arithmetic operators: | |
"+": (exp1, exp2) => { | |
return exp1 + exp2; | |
}, | |
"-": (exp1, exp2 = null) => { | |
if (exp2 === null) { | |
return -exp1; | |
} | |
return exp1 - exp2; | |
}, | |
"*": (exp1, exp2) => { | |
return exp1 * exp2; | |
}, | |
"/": (exp1, exp2) => { | |
return exp1 / exp2; | |
}, | |
// Comparison: | |
"=": (exp1, exp2) => { | |
return exp1 === exp2; | |
}, | |
">": (exp1, exp2) => { | |
return exp1 > exp2; | |
}, | |
">=": (exp1, exp2) => { | |
return exp1 >= exp2; | |
}, | |
"<": (exp1, exp2) => { | |
return exp1 < exp2; | |
}, | |
"<=": (exp1, exp2) => { | |
return exp1 <= exp2; | |
}, | |
}); | |
class StackFrame { | |
constructor(env) { | |
this.env = env; | |
} | |
} | |
class Eva { | |
constructor(global = GlobalEnvironment) { | |
this.global = global; | |
this.stack = [new StackFrame(global)]; | |
} | |
eval(exp, env = this.global) { | |
// Scalar types | |
if (this._isNumber(exp)) { | |
return exp; | |
} | |
if (this._isString(exp)) { | |
return exp.slice(1, -1); | |
} | |
// Arithmetic operators | |
// (Moved to built-in functions (see above)) | |
// Variable declaration and lookup | |
if (exp[0] === "var") { | |
const [_, name, value] = exp; | |
return env.define(name, this.eval(value, env)); | |
} | |
if (this._isVariableName(exp)) { | |
return env.lookup(exp); | |
} | |
// Block | |
if (exp[0] === "begin") { | |
let blockEnv = new Environment({}, env); | |
return this._evalBlock(exp, blockEnv); | |
} | |
// Variable assignment | |
if (exp[0] === "set") { | |
const [_, name, value] = exp; | |
return env.assign(name, this.eval(value, env)); | |
} | |
// If | |
if (exp[0] === "if") { | |
const [_, condition, consequent, alternate] = exp; | |
if (this.eval(condition, env)) { | |
return this.eval(consequent, env); | |
} | |
return this.eval(alternate, env); | |
} | |
// While | |
if (exp[0] === "while") { | |
const [_, condition, body] = exp; | |
let result; | |
while (this.eval(condition, env)) { | |
result = this.eval(body, env); | |
} | |
return result; | |
} | |
// User-defined function: (def square (x) (* x x)) | |
// Syntactic sugar for: (var square (lambda (x) (* x x))) | |
if (exp[0] === "def") { | |
const [_, name, params, body] = exp; | |
// JIT-transpile to a variable declaration | |
// const fn = { | |
// params, | |
// body, | |
// env, // Closure! | |
// }; | |
// return env.define(name, fn); | |
const varExp = ["var", name, ["lambda", params, body]]; | |
return this.eval(varExp, env); | |
} | |
// Lambda function | |
if (exp[0] === "lambda") { | |
const [_, params, body] = exp; | |
return { | |
params, | |
body, | |
env, // Closure! | |
}; | |
} | |
// Built-in function | |
if (Array.isArray(exp)) { | |
const fn = this.eval(exp[0], env); | |
const args = exp.slice(1).map((arg) => this.eval(arg, env)); | |
// 1. Native function: | |
if (typeof fn === "function") { | |
return fn(...args); | |
} | |
// 2. User-defined function: | |
const activationRecord = {}; | |
fn.params.forEach((param, i) => { | |
activationRecord[param] = args[i]; | |
}); | |
// const activationEnv = new Environment( | |
// activationRecord, | |
// env // Dynamic scope! | |
// ); | |
const activationEnv = new Environment(activationRecord, fn.env); | |
console.log("Allocating new stack frame for function invocation..."); | |
this.stack.push(new StackFrame(activationEnv)); | |
console.log("Done"); | |
const value = this._evalBody(fn.body, activationEnv); | |
console.log("Delegating stack frame..."); | |
this.stack.pop(); | |
console.log("Done"); | |
return value; | |
} | |
throw `Unimplemented: ${JSON.stringify(exp)}`; | |
} | |
_evalBody(body, env) { | |
if (body[0] === "begin") { | |
return this._evalBlock(body, env); | |
} | |
return this.eval(body, env); | |
} | |
_evalBlock(block, env) { | |
let result; | |
const [_, ...exps] = block; | |
exps.forEach((exp) => { | |
result = this.eval(exp, env); | |
}); | |
return result; | |
} | |
_isNumber(exp) { | |
return typeof exp === "number"; | |
} | |
_isString(exp) { | |
return typeof exp === "string" && exp[0] === '"' && exp.slice(-1) === '"'; | |
} | |
_isVariableName(exp) { | |
return typeof exp === "string" && /^[+\-*/<>=a-zA-Z0-9_]*$/.test(exp); | |
} | |
} | |
module.exports = Eva; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment