-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlambda.js
More file actions
162 lines (145 loc) · 5.01 KB
/
lambda.js
File metadata and controls
162 lines (145 loc) · 5.01 KB
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
function Var(id) {
this.type = 'var';
this.id = id;
this.free_vars = new Set([id]);
}
function App(func, arg) {
this.type = 'app';
this.func = func;
this.arg = arg;
this.free_vars = new Set([...func.free_vars, ...arg.free_vars]);
}
function Abs(v, expr) {
this.type = 'abs';
this.var = v;
this.expr = expr;
this.free_vars = new Set([...expr.free_vars]);
this.free_vars.delete(this.var.id);
}
// pretty print
// unnecessary parenthesises uses in some abstractions
function pp(expr) {
switch (expr.type) {
case 'var': return expr.id;
case 'abs': return `(\\${expr.var.id}.${pp(expr.expr)})`;
case 'app':
return pp(expr.func) + ' ' + (expr.arg.type == 'app' ? '('+pp(expr.arg)+')' : pp(expr.arg));
}
}
function fixpoint(stepper) {
return (expr) => {
var expr_evaled = stepper(expr);
while(expr_evaled.stepped) {
expr_evaled = stepper(expr_evaled.node);
}
return expr_evaled;
};
}
var redexes = 0;
function normal_step(node) {
switch (node.type) {
case 'var': return { stepped: false, node: node };
case 'app':
switch(node.func.type) {
case 'var':
case 'app':
var func_evaled = normal_step(node.func);
if (func_evaled.stepped) {
return {stepped: true, node: new App(func_evaled.node, node.arg)};
}
var arg_evaled = normal_step(node.arg);
return { stepped: arg_evaled.stepped, node: new App(node.func, arg_evaled.node) };
case 'abs': // redex
redexes++;
return {stepped: true, node: substitute(node.arg, node.func.var, node.func.expr)}
}
break;
case 'abs':
var expr_evaled = normal_step(node.expr);
return { stepped : expr_evaled.stepped, node : new Abs(node.var, expr_evaled.node) };
}
}
// sub e for x (variable) in expr
function substitute(e, x, expr) {
switch (expr.type) {
case 'var' : return expr.id == x.id ? e : expr;
case 'app' : return new App(substitute(e, x, expr.func), substitute(e, x, expr.arg));
case 'abs' :
if(expr.var.id == x.id) {
return expr;
}
else if(!e.free_vars.has(expr.var.id)) {
return new Abs(expr.var, substitute(e, x, expr.expr));
}
else {
do {
var z = rename(expr.var.id);
} while(e.free_vars.has(z) || variables(expr.expr).has(z));
return new Abs(new Var(z), substitute(e, x, substitute(new Var(z), expr.var, expr.expr)));
}
}
}
function rename(variable) {
var [match, prefix, num] = /^(.*?)([\d]*)$/.exec(variable);
return prefix + (num == '' ? 1 : parseInt(num) + 1);
}
function variables(expr) {
switch (expr.type) {
case 'var' : return new Set([expr.id]);
case 'app' : return new Set([...variables(expr.func), ...variables(expr.arg)]);
case 'abs' : return new Set([...variables(expr.expr), expr.var.id]);
}
}
function move_from_stack_to_output_while(stack, output, condition) {
while(stack.length > 0 && condition()) {
var top = stack.pop();
var s = output.pop();
var f = output.pop();
output.push(top == '.' ? new Abs(f, s) : new App(f, s));
}
}
// shunting yard algorithm
function parse(tokens) {
var output = [];
var stack = [];
while(tokens.length > 0) {
var current = tokens.shift();
if(current.match(/\w+/)) {
output.push(new Var(current));
}
else if(current.match(/\s+/)) { // only swap if both o1 and o2 are application
move_from_stack_to_output_while(stack, output, () => stack[stack.length-1].match(/\s+/));
stack.push(current);
}
else if(current == "(" || current == '.') {
stack.push(current);
}
else if(current == ")") {
move_from_stack_to_output_while(stack, output, () => stack[stack.length-1] != '(');
if(stack.length == 0) {
console.log("mismatched parenthesis");
}
stack.pop(); // pop off left paren
}
}
if(stack.indexOf('(') != -1 || stack.indexOf(')') != -1) {
console.log("mismatched parenthesis");
}
else {
move_from_stack_to_output_while(stack, output, () => true);
}
return output.pop();
}
function lex(program) {
return split_tokens(remove_extra_spaces(program.trim()));
}
// all spaces are lambda application => whitespace matters
function split_tokens(prog_str) {
return prog_str.split(/(\)|\(|\\|\.|\w+|\s+)/).filter(t => t != '');
}
// remove any space that isn't in one of the following spots: )_(, x_(, )_x, x_x, x_\, )_\
function remove_extra_spaces(program) {
return program
.replace(/([^\)\w])\s+/g, '$1')
.replace(/\s+([^\(\w\\])/g, '$1')
}