forked from kanaka/miniMAL
-
Notifications
You must be signed in to change notification settings - Fork 0
/
step7_interop.js
112 lines (105 loc) · 4.13 KB
/
step7_interop.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// miniMAL
// Copyright (C) 2014 Joel Martin
// Licensed under MPL 2.0
// 2 args: eval_ast, 3 args: env_bind
function eval_ast_or_bind(ast, env, exprs) {
if (exprs) {
// Return new Env with symbols in ast bound to
// corresponding values in exprs
env = Object.create(env);
for (var i=0; i<ast.length; i++) {
if (ast[i] == "&") {
// variable length arguments
env[ast[i+1]] = Array.prototype.slice.call(exprs, i);
break;
} else {
env[ast[i]] = exprs[i];
}
}
return env;
}
// Evaluate the form/ast
return Array.isArray(ast) // list?
? ast.map(function(e){return EVAL(e, env);}) // list
: (typeof ast == "string") // symbol?
? ast in env // symbol in env?
? env[ast] // lookup symbol
: E.throw(ast + " not found") // undefined symbol
///: null[ast] // undefined symbol
: ast; // ast unchanged
}
function EVAL(ast, env) {
while (true) {
//console.log("EVAL:", ast);
if (!Array.isArray(ast)) return eval_ast_or_bind(ast, env);
// apply
if (ast[0] == "def") { // update current environment
return env[ast[1]] = EVAL(ast[2], env);
} else if (ast[0] == "let") { // new environment with bindings
env = Object.create(env);
for (var i in ast[1]) {
if (i%2) {
env[ast[1][i-1]] = EVAL(ast[1][i], env);
}
}
ast = ast[2]; // TCO
} else if (ast[0] == "`") { // quote (unevaluated)
return ast[1];
} else if (ast[0] == ".-") { // get or set attribute
var el = eval_ast_or_bind(ast.slice(1), env);
var x = el[0][el[1]];
return 2 in el ? el[0][el[1]] = el[2] : x;
} else if (ast[0] == ".") { // call object method
var el = eval_ast_or_bind(ast.slice(1), env);
var x = el[0][el[1]];
return x.apply(el[0], el.slice(2));
} else if (ast[0] == "do") { // multiple forms (for side-effects)
var el = eval_ast_or_bind(ast.slice(1,ast.length-1), env);
ast = ast[ast.length-1]; // TCO
} else if (ast[0] == "if") { // branching conditional
ast = EVAL(ast[1], env) ? ast[2] : ast[3]; // TCO
} else if (ast[0] == "fn") { // define new function (lambda)
var f = function() {
return EVAL(ast[2], eval_ast_or_bind(ast[1], env, arguments));
}
f.ast = [ast[2], env, ast[1]]; // f.ast compresses more than f.data
return f;
} else { // invoke list form
var el = eval_ast_or_bind(ast, env);
var f = el[0];
if (f.ast) {
ast = f.ast[0];
env = eval_ast_or_bind(f.ast[2], f.ast[1], el.slice(1)); // TCO
} else {
return f.apply(f, el.slice(1))
}
}
}
}
E = Object.create(GLOBAL);
E["js"] = eval;
E["eval"] = function(a) { return EVAL(a, E); }
// These could all also be interop
E["="] = function(a,b) { return a===b; }
E["<"] = function(a,b) { return a<b; }
E["+"] = function(a,b) { return a+b; }
E["-"] = function(a,b) { return a-b; }
E["*"] = function(a,b) { return a*b; }
E["/"] = function(a,b) { return a/b; }
///E["list"] = function(a,b) { return Array.prototype.slice.call(arguments); }
///E["map"] = function(a,b) { return b.map(a); }
E["read-string"] = function(a) { return JSON.parse(a); }
E["slurp"] = function(a) { return require('fs').readFileSync(a,'utf-8'); }
E["load-file"] = function(a) { return EVAL(JSON.parse(E["slurp"](a)),E); }
// Node specific
function rep(a) { return JSON.stringify(EVAL(JSON.parse(a),E)); }
if (process.argv.length > 2) {
E['*ARGV*'] = process.argv.slice(3);
rep('["load-file", ["`", "' + process.argv[2] + '"]]');
} else {
require('repl').start({
prompt: "user> ",
ignoreUndefined: true,
eval: function(l,c,f,cb) { console.log(rep(l.slice(1,l.length-2))); cb() }
});
}