Skip to content

Instantly share code, notes, and snippets.

@wldcordeiro
Last active November 12, 2019 12:52
Show Gist options
  • Save wldcordeiro/2ec9aeb4e3fa512eec26 to your computer and use it in GitHub Desktop.
Save wldcordeiro/2ec9aeb4e3fa512eec26 to your computer and use it in GitHub Desktop.
The Egg programming language from Eloquent JS Chapter 11
function parseExpression(program) {
program = skipSpace(program);
var match, expr;
if (match = /^"([^"]*)"/.exec(program)) {
expr = {type: "value", value: match[1]};
} else if (match = /^\d+\b/.exec(program)) {
expr = {type: "value", value: Number(match[0])};
} else if (match = /^[^\s(),"]+/.exec(program)) {
expr = {type: "word", name: match[0]};
} else {
throw new SyntaxError(`Unexpect syntax: ${program}`);
}
return parseApply(expr, program.slice(match[0].length));
}
function skipSpace(string) {
return string.slice(/^(\s|#.*)*/.exec(string)[0].length);
}
function parseApply(expr, program) {
program = skipSpace(program);
if (program[0] != '(') {
return {expr: expr, rest: program};
}
program = skipSpace(program.slice(1));
expr = {type: 'apply', operator: expr, args: []};
while (program[0] != ')') {
var arg = parseExpression(program);
expr.args.push(arg.expr);
program = skipSpace(arg.rest);
if (program[0] == ',') {
program = skipSpace(program.slice(1));
} else if (program[0] != ')') {
throw new SyntaxError("Expected ',' or ')'");
}
}
return parseApply(expr, program.slice(1));
}
function parse(program) {
var result = parseExpression(program);
if (skipSpace(result.rest).length > 0) {
throw new SyntaxError('Unexpected text after program');
}
return result.expr;
}
function evaluate(expr, env) {
switch(expr.type) {
case 'value':
return expr.value;
case 'word':
if (expr.name in env) {
return env[expr.name];
} else {
throw new ReferenceError(`Undefined variable: ${expr.name}`);
}
case 'apply':
if (expr.operator.type == 'word' && expr.operator.name in specialForms) {
return specialForms[expr.operator.name](expr.args, env);
}
var op = evaluate(expr.operator, env);
if (typeof op != "function") {
throw new TypeError('Applying a non-function');
}
return op.apply(null, expr.args.map(function(arg) {
return evaluate(arg, env);
}));
}
}
var specialForms = Object.create(null);
specialForms['if'] = function(args, env) {
if (args.length != 3) {
throw new SyntaxError('Bad number of args to if');
}
if (evaluate(args[0], env) !== false) {
return evaluate(args[1], env);
} else {
return evaluate(args[2], env);
}
};
specialForms['while'] = function(args, env) {
if (args.length != 2) {
throw new SyntaxError('Bad number of args to while');
}
while(evaluate(args[0], env) !== false) {
evaluate(args[1], env);
}
// Egg has no undefined so we return false when there's no meaningful result.
return false;
};
specialForms['do'] = function(args, env) {
var value = false;
args.forEach(function(arg) {
value = evaluate(arg, env);
});
return value;
};
specialForms['define'] = function(args, env) {
if (args.length != 2 || args[0].type != 'word') {
throw new SyntaxError('Bad use of define');
}
var value = evaluate(args[1], env);
env[args[0].name] = value;
return value;
};
specialForms['fun'] = function(args, env) {
if (!args.length) {
throw new SyntaxError('Functions need a body.')
}
function name(expr) {
if (expr.type != 'word') {
throw new SyntaxError('Arg names must be words');
}
return expr.name;
}
var argNames = args.slice(0, args.length - 1).map(name);
var body = args[args.length - 1];
return function() {
if (arguments.length != argNames.length) {
throw new TypeError('Wrong number of arguments');
}
var localEnv = Object.create(env);
for (var i = 0; i < arguments.length; i++) {
localEnv[argNames[i]] = arguments[i];
}
return evaluate(body, localEnv);
};
};
specialForms["set"] = function(args, env) {
if (args.length != 2 || args[0].type != 'word') {
throw new SyntaxError('Bad use of set');
}
var valName = args[0].name;
var value = evaluate(args[1], env);
for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) {
if (Object.prototype.hasOwnProperty.call(scope, valName)) {
scope[valName] = value;
return value;
}
}
throw new ReferenceError(`Tried setting an undefined variable: ${valName}`);
};
var topEnv = Object.create(null);
topEnv['true'] = true;
topEnv['false'] = false;
['+', '-', '*', '/', '==', '<', '>'].forEach(op => {
topEnv[op] = new Function('a, b', `return a ${op} b;`);
});
topEnv['print'] = function(value) {
console.log(value);
return value;
};
topEnv["array"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
return args;
};
topEnv["length"] = function(array) {
return array.length;
};
topEnv["element"] = function(array, n) {
return array[n];
};
function run() {
var env = Object.create(topEnv);
var program = Array.prototype.slice.call(arguments, 0).join('\n');
return evaluate(parse(program), env);
}
// Examples
run("do(define(x, 4),",
" define(setx, fun(val, set(x, val))),",
" setx(50),",
" print(x))");
run("do(define(sum, fun(array,",
" do(define(i, 0),",
" define(sum, 0),",
" while(<(i, length(array)),",
" do(define(sum, +(sum, element(array, i))),",
" define(i, +(i, 1)))),",
" sum))),",
" print(sum(array(1, 2, 3))))");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment