Skip to content

Instantly share code, notes, and snippets.

@altras
Created October 22, 2014 09:58
Show Gist options
  • Save altras/744e94d8f1cc9666f9b3 to your computer and use it in GitHub Desktop.
Save altras/744e94d8f1cc9666f9b3 to your computer and use it in GitHub Desktop.
Egg programming language :D
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("Unexpected syntax: " + program);
return parseApply(expr, program.slice(match[0].length));
}
function skipSpace(string) {
var skippable = string.match(/^(\s|#.*)*/);
return string.slice(skippable[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;
}
// operator: {type: "word", name: "+"},
// args: [{type: "word", name: "a"},
// {type: "value", value: 10}]}
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);
// Since undefined does not exist in Egg, we return false,
// for lack of a 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["set"] = function(args, env) {
if (args.length != 2 || args[0].type != "word")
throw new SyntaxError("Bad use of set");
var varName = args[0].name;
var value = evaluate(args[1], env);
for (var scope = env; scope; scope = Object.getPrototypeOf(scope)) {
if (Object.prototype.hasOwnProperty.call(scope, varName)) {
scope[varName] = value;
return value;
}
}
throw new ReferenceError("Setting undefined variable " + varName);
};
var topEnv = Object.create(null);
topEnv["true"] = true;
topEnv["false"] = false;
["+", "-", "*", "/", "==", "<", ">"].forEach(function(op) {
topEnv[op] = new Function("a, b", "return a " + op + " b;");
});
topEnv["print"] = function(value) {
console.log(value);
return value;
};
topEnv["array"] = function() {
return Array.prototype.slice.call(arguments, 0);
};
topEnv["length"] = function(array) {
return array.length;
};
topEnv["element"] = function(array, position) {
return array[position];
};
function run() {
var env = Object.create(topEnv);
var program = Array.prototype.slice
.call(arguments, 0).join("\n");
return evaluate(parse(program), env);
}
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);
};
};
@altras
Copy link
Author

altras commented Oct 22, 2014

the funny http://eloquentjavascript.net/11_language.html :D
run("do(define(x, 4),",
" define(setx, fun(val, set(x, val))),",
" setx(50),",
" print(x))");
// → 50

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment