Skip to content

Instantly share code, notes, and snippets.

@microamp
Created November 4, 2020 00:09
Show Gist options
  • Save microamp/4a4819626f29c9210a6be2bb08401b93 to your computer and use it in GitHub Desktop.
Save microamp/4a4819626f29c9210a6be2bb08401b93 to your computer and use it in GitHub Desktop.
Eva.js (WIP)
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