Skip to content

Instantly share code, notes, and snippets.

@makenowjust
Created July 26, 2014 09:43
Show Gist options
  • Save makenowjust/e47124c80dea30edb075 to your computer and use it in GitHub Desktop.
Save makenowjust/e47124c80dea30edb075 to your computer and use it in GitHub Desktop.
ジンジャエールハッカソンで作ったLisp処理系
function tokenize(src) {
return src.replace(/([()])/g, ' $1 ').split(/\s+/g).filter(Boolean);
}
function parse(tks) {
return parseList(tks, false);
function parseList(tks, nest) {
var
tk, list = [], num;
while (tk = tks.shift()) {
switch (tk) {
case '(':
list.push(parseList(tks, true));
break;
case ')':
if (!nest) throw Error('parse error');
return list;
default:
num = parseFloat(tk);
if (isNaN(num)) {
// symbol
list.push(tk);
} else {
// number
list.push(num);
}
}
}
if (nest) throw Error('parse error');
return list;
}
}
function evaluate(ls, env, cont) {
if (Array.isArray(ls)) {
switch (ls[0]) {
case 'quote':
if (ls.length !== 2) return cont(Error('eval error'));
cont(null, ls[1]);
break;
case 'lambda':
if (ls.length < 2) return cont(Error('eval error'));
if (typeof ls[1] !== 'string' && !Array.isArray(ls[1]))
return cont(Error('eval error'));
cont(null, function (args, env, cont) {
env = Object.create(env);
if (Array.isArray(ls[1])) {
if (ls[1].length !== args.length) return cont(Error('eval error'));
ls[1].forEach(function (name, i) {
env[name] = args[i];
});
} else {
env[ls[1]] = args;
}
execute(ls.slice(2), env, function (err, res) {
if (err) return cont(err);
cont(null, res.length === 0 ? [] : res[res.length - 1]);
});
});
break;
case 'begin':
execute(ls.slice(1), env, function (err, res) {
if (err) return cont(err);
cont(null, res.length === 0 ? [] : res[res.length - 1]);
});
break;
case 'eval':
evaluate(ls.slice(1), env, cont);
break;
case 'call/cc':
if (ls.length !== 2) return cont(Error('eval error'));
evaluate(ls[1], env, function (err, fn) {
if (err) return cont(err);
if (typeof fn !== 'function') return cont(Error('eval error'));
fn([function (args, _env, _cont) {
if (args.length === 0) {
args[0] = [];
}
if (args.length !== 1) return _cont(Error('eval error'));
cont(null, args[0]);
}], env, cont);
});
break;
case 'define':
if (ls.length !== 3) return cont(Error('eval error'));
if (typeof ls[1] !== 'string') return cont(Error('eval error'));
evaluate(ls[2], env, function (err, r) {
if (err) return cont(err);
env[ls[1]] = r;
cont(null, []);
});
break;
case 'if':
if (ls.length === 3) {
ls[4] = ['quote', []];
}
if (ls.length !== 4) return cont(Error('eval error'));
evaluate(ls[1], env, function (err, cond) {
if (err) return cont(err);
if (Array.isArray(cond) && cond.length === 0) {
// else
evaluate(ls[3], env, cont);
} else {
// then
evaluate(ls[2], env, cont);
}
});
break;
default:
if (ls.length === 0) return cont(Error('eval error'));
evaluate(ls[0], env, function (err, fn) {
if (err) return cont(err);
execute(ls.slice(1), env, function (err, args) {
if (err) return cont(err);
fn(args, env, cont);
});
});
}
} else {
if (typeof ls === 'string') {
if (ls in env) {
cont(null, env[ls]);
} else {
cont(Error('eval error'));
}
} else {
cont(null, ls);
}
}
}
function execute(ls, env, cont) {
(function loop(res, i) {
if (ls.length === i) return cont(null, res);
evaluate(ls[i], env, function (err, r) {
if (err) return cont(err);
loop(res.concat([r]), i + 1);
});
})([], 0);
}
function repl(cont) {
var
readline = require('readline'),
rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
}),
global = Object.create(null);
rl.setPrompt('λ ');
rl.prompt();
rl.on('line', function (line) {
var
tks = tokenize(line);
try {
tks = parse(tks);
} catch (err) {
console.log(err);
rl.prompt();
return;
}
console.log('parse => ', tks);
execute(tks, global, function (err, r) {
if (err) {
console.log(err);
} else {
console.log('result => ', r[r.length - 1]);
}
rl.prompt();
});
});
}
exports.tokenize = tokenize;
exports.parse = parse;
exports.evaluate = evaluate;
exports.execute = execute;
exports.repl = repl;
if (require.main === module) {
repl();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment