├── LambdaJS.gif
├── .htaccess
├── README.md
├── default.css
├── compatibility.js
├── lambda.css
├── index.xhtml
├── pp.js
├── app.js
├── lambda.js
└── ui.js
/LambdaJS.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tarao/LambdaJS/HEAD/LambdaJS.gif
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | AddType "text/html; charset=utf-8" .xhtml
2 |
3 | DirectoryIndex -index.html -index.xhtml
4 |
5 | RewriteEngine On
6 | RewriteRule ^$ index.xhtml [L]
7 | RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml
8 | RewriteCond %{LA-F:REQUEST_FILENAME} \.xhtml
9 | RewriteRule .* - "[T=application/xhtml+xml; charset=utf-8]"
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | LambdaJS
2 | ========
3 |
4 | 
5 |
6 | LambdaJS is an interpreter of Lambda calculus written in JavaScript. It accepts terms of Lambda calculus written in JavaScript syntax.
7 |
8 | Supported browsers and known issues
9 | -----------------------------------
10 |
11 | The latest version of Google Chrome or Firefox is recommended.
12 |
13 | It should work on IE, Safari and Opera but less tested on these browsers.
14 | There were some minor issues on old versions of IE and Opera.
15 |
16 | Features
17 | --------
18 |
19 | - Written in JavaScript and no need for server-side execution
20 | - Visualizes reduction steps
21 | - Allows users to choose evaluation strategy
22 | - Accepts syntax of Lambda calculus, JavaScript or mixture of both
23 |
24 | Key bindings on the console
25 | ---------------------------
26 |
27 | |Key |Action |
28 | |:-----------------------|:---------------|
29 | |`Enter` |run |
30 | |`Ctrl`+`L` |clear the screen|
31 | |`Ctrl`+`P`, `Up arrow` |previous input |
32 | |`Ctrl`+`N`, `Down arrow`|next input |
33 | |`\` |input '&lambda';|
34 |
35 | Running example
36 | ---------------
37 |
38 | See https://tarao.github.io/LambdaJS .
39 |
--------------------------------------------------------------------------------
/default.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /* general */
4 |
5 | *
6 | {
7 | font-style: normal;
8 | font-weight: normal;
9 | text-decoration: none;
10 | background-color: inherit;
11 | color: inherit;
12 | line-height: 1.3;
13 | }
14 |
15 | a
16 | {
17 | text-decoration: underline;
18 | }
19 |
20 | a:hover, a:active
21 | {
22 | text-decoration: none;
23 | outline-style: solid;
24 | outline-width: 1px;
25 | }
26 | a:link
27 | {
28 | color: darkblue;
29 | }
30 | a:visited
31 | {
32 | color: purple;
33 | }
34 | a:link:hover
35 | {
36 | background-color: #e0e0ff;
37 | outline-color: blue;
38 | }
39 | a:visited:hover
40 | {
41 | background-color: #ffe0ff;
42 | outline-color: purple;
43 | }
44 | a:active
45 | {
46 | color: red;
47 | outline-color: red ! important;
48 | background-color: #ffe0e0 ! important;
49 | }
50 |
51 | body
52 | {
53 | font-family: sans-serif;
54 | margin: 1em;
55 | padding: 1em 2em;
56 | color: #404040;
57 | min-height: 640px;
58 | }
59 |
60 | h1,h2,h3,h4,h5,h6
61 | {
62 | font-weight: bold;
63 | text-decoration: none;
64 | padding: 0;
65 | margin: 1em 0;
66 | }
67 |
68 | h1
69 | {
70 | font-size: 150%;
71 | margin: 1em 0;
72 | border-width: 0.2em;
73 | border-color: #333333;
74 | border-style: none none double none;
75 | }
76 |
77 | h1:first-letter
78 | {
79 | color: white;
80 | background-color: #333333;
81 | padding: 0.2em 0.4em;
82 | margin-right: 0.1em;
83 | }
84 |
85 | h2
86 | {
87 | margin-top: 3em;
88 | padding: 0.1em;
89 | font-size: 100%;
90 | background-color: #808080;
91 | color: white;
92 | border: solid 0.1em #333333;
93 | border-width: 0.15em;
94 | text-transform: uppercase;
95 | }
96 |
97 | h2:first-letter
98 | {
99 | font-weight: bold;
100 | font-size: 120%;
101 | text-transform: uppercase;
102 | }
103 |
104 | p
105 | {
106 | margin: 1em 0.5em;
107 | text-indent: 1em;
108 | background-color: transparent;
109 | }
110 |
111 | p.epigraph
112 | {
113 | margin: 1em;
114 | text-indent: 0;
115 | background-color: transparent;
116 | line-height: 3.0;
117 | }
118 |
119 | code
120 | {
121 | display: block;
122 | font-family: monospace ! important;
123 | border: 1px #999999 solid;
124 | background-color: #eeeeee;
125 | padding: 0.2em;
126 | overflow: scroll;
127 | }
128 |
129 | #article
130 | {
131 | clear: both;
132 | background-color: transparent;
133 | min-height: 600px;
134 | }
135 |
136 | address
137 | {
138 | text-align: right;
139 | font-size: 75%;
140 | }
141 |
142 | #footer
143 | {
144 | margin: 0.5em 0;
145 | border: solid #404040 1px;
146 | border-style: solid none none none;
147 | }
148 |
149 | #footer *
150 | {
151 | margin: 0.5em 0;
152 | }
153 |
154 | #footer address
155 | {
156 | font-style: italic !important;
157 | }
158 |
--------------------------------------------------------------------------------
/compatibility.js:
--------------------------------------------------------------------------------
1 | if (!Array.prototype.indexOf) {
2 | Array.prototype.indexOf = function(elt, from) {
3 | var len = this.length;
4 | if (typeof from == 'undefined') from = 0;
5 | from = Number(from);
6 | from = (from < 0) ? Math.ceil(from) : Math.floor(from);
7 | if (from < 0) from += len;
8 | for (; from < len; from++) {
9 | if (from in this && this[from] === elt) return from;
10 | }
11 | return -1;
12 | };
13 | }
14 | if (!Array.prototype.filter) {
15 | Array.prototype.filter = function(fun, thisp) {
16 | var len = this.length;
17 | if (typeof fun != "function") {
18 | throw new TypeError('filter: not a function');
19 | }
20 | var rv = new Array();
21 | for (var i = 0; i < len; i++) {
22 | if (i in this) {
23 | var val = this[i]; // in case fun mutates this
24 | if (fun.call(thisp, val, i, this)) rv.push(val);
25 | }
26 | }
27 | return rv;
28 | };
29 | }
30 | if (!Array.prototype.forEach) {
31 | Array.prototype.forEach = function(fun, thisp) {
32 | var len = this.length;
33 | if (typeof fun != 'function') {
34 | throw new TypeError('forEach: not a function');
35 | }
36 | for (var i=0; i < len; i++) {
37 | if (i in this) fun.call(thisp, this[i], i, this);
38 | }
39 | };
40 | }
41 | if (!Array.prototype.every) {
42 | Array.prototype.every = function(fun, thisp) {
43 | var len = this.length;
44 | if (typeof fun != 'function') {
45 | throw new TypeError('every: not a function');
46 | }
47 | for (var i = 0; i < len; i++) {
48 | if (i in this && !fun.call(thisp, this[i], i, this)) {
49 | return false;
50 | }
51 | }
52 | return true;
53 | };
54 | }
55 | if (!Array.prototype.map) {
56 | Array.prototype.map = function(fun, thisp) {
57 | var len = this.length;
58 | if (typeof fun != 'function') {
59 | throw new TypeError('map: not a function');
60 | }
61 | var rv = new Array(len);
62 | for (var i = 0; i < len; i++) {
63 | if (i in this) rv[i] = fun.call(thisp, this[i], i, this);
64 | }
65 | return rv;
66 | };
67 | }
68 | if (!Array.prototype.some) {
69 | Array.prototype.some = function(fun, thisp) {
70 | var len = this.length;
71 | if (typeof fun != "function") {
72 | throw new TypeError('some: not a function');
73 | }
74 | for (var i = 0; i < len; i++) {
75 | if (i in this && fun.call(thisp, this[i], i, this)) return true;
76 | }
77 | return false;
78 | };
79 | }
80 | if (!Array.prototype.reduce) {
81 | Array.prototype.reduce = function(fun, initial) {
82 | var len = this.length;
83 | if (typeof fun != 'function') {
84 | throw TypeError('reduce: not a function ');
85 | }
86 | var i = 0;
87 | var prev;
88 | var rv;
89 | if (typeof initial != 'undefined') {
90 | rv = initial;
91 | } else {
92 | do {
93 | if (i in this) {
94 | rv = this[i++];
95 | break;
96 | }
97 | if (++i >= len) throw new TypeError('reduce: empty array');
98 | } while (true);
99 | }
100 | for (; i < len; i++) {
101 | if (i in this) rv = fun.call(null, rv, this[i], i, this);
102 | }
103 | return rv;
104 | };
105 | }
106 | if (!Array.prototype.reduceRight) {
107 | Array.prototype.reduceRight = function(fun, initial) {
108 | var len = this.length;
109 | if (typeof fun != "function") {
110 | throw new TypeError('reduceRight: not a function');
111 | }
112 | var i = len - 1;
113 | var rv;
114 | if (typeof initial != 'undefined') {
115 | rv = initial;
116 | } else {
117 | do {
118 | if (i in this) {
119 | rv = this[i--];
120 | break;
121 | }
122 | if (--i < 0) throw new TypeError('reduceRight: empty array');
123 | } while (true);
124 | }
125 | for (; i >= 0; i--) {
126 | if (i in this) rv = fun.call(null, rv, this[i], i, this);
127 | }
128 | return rv;
129 | };
130 | }
131 |
--------------------------------------------------------------------------------
/lambda.css:
--------------------------------------------------------------------------------
1 | .console
2 | {
3 | height: 8em;
4 | overflow-x: scroll;
5 | overflow-y: scroll;
6 | white-space: nowrap;
7 | border-style: solid;
8 | border-width: 4px;
9 | border-color: #333333;
10 | background-color: #000000;
11 | color: #ffffff;
12 | font-family: monospace;
13 | }
14 |
15 | #console
16 | {
17 | height: 20em;
18 | }
19 |
20 | .console ul
21 | {
22 | list-style-type: none;
23 | margin: 0;
24 | padding: 0 .5em;
25 | }
26 |
27 | .console ul li
28 | {
29 | margin-left: 0;
30 | }
31 |
32 | .console input
33 | {
34 | width: 100%;
35 | display: inline;
36 | border-style: none;
37 | background-color: #181818;
38 | ime-mode: disabled;
39 | }
40 |
41 | .console ul li.userinput
42 | {
43 | font-weight: bold;
44 | }
45 |
46 | .console ul li.error
47 | {
48 | color: #ff0000;
49 | }
50 |
51 | /* beta redex */
52 |
53 | .console .application.marked > .abstraction > .lambda,
54 | .console .application.marked > .abstraction > .binding,
55 | .console .application.marked .bound,
56 | .console .application.redex:hover > .abstraction > .lambda,
57 | .console .application.redex:hover > .abstraction > .binding
58 | {
59 | color: #00cccc;
60 | }
61 |
62 | .console .application.marked > .argument,
63 | .console .application.redex:hover > .argument
64 | {
65 | border-bottom-style: solid;
66 | border-bottom-width: 1px;
67 | border-bottom-color: #ffffff;
68 | }
69 |
70 | /* eta redex */
71 |
72 | .console .abstraction.marked > .lambda,
73 | .console .abstraction.marked > .binding,
74 | .console .abstraction.marked .bound,
75 | .console .abstraction.redex:hover > .lambda,
76 | .console .abstraction.redex:hover > .binding,
77 | .console .abstraction.redex:hover > .application:last-child > .variable:last-child
78 | {
79 | color: #00cccc;
80 | }
81 |
82 | /* redex */
83 |
84 | .console .application.redex > .abstraction > .lambda,
85 | .console .abstraction.redex > .lambda
86 | {
87 | color: #8c8ce8;
88 | }
89 |
90 | .console .redex
91 | {
92 | text-decoration: none;
93 | }
94 |
95 | .console .redex:hover
96 | {
97 | cursor: pointer;
98 | outline-style: solid;
99 | outline-width: 1px;
100 | outline-color: #6666ff;
101 | background-color: #333366;
102 | }
103 |
104 | .console .redex:active,
105 | .console .redex:active:hover
106 | {
107 | outline-style: solid;
108 | outline-width: 1px;
109 | outline-color: #ff3333 ! important;
110 | background-color: #663333 ! important;
111 | }
112 |
113 | .console .shadowed:hover,
114 | .console .shadowed:active,
115 | .console .shadowed:active:hover
116 | {
117 | outline-style: inherit ! important;
118 | outline-width: inherit ! important;
119 | outline-color: inherit ! important;
120 | background-color: inherit ! important;
121 | }
122 |
123 | .console .shadowed:hover > .abstraction > .lambda
124 | {
125 | color: #8c8ce8;
126 | }
127 |
128 | .console .shadowed:hover > .abstraction > .binding
129 | {
130 | color: inherit ! important;
131 | }
132 |
133 | .console .shadowed:hover > .argument
134 | {
135 | border-bottom-style: none ! important;
136 | }
137 |
138 | .console .reduce,
139 | .console .convert
140 | {
141 | margin-right: .5em;
142 | }
143 |
144 | .console .eta.reduce,
145 | .console .eta.reduce + *
146 | {
147 | color: #fefece;
148 | }
149 |
150 | .console .alpha.convert,
151 | .console .alpha.convert + *
152 | {
153 | color: #cefefe;
154 | }
155 |
156 | .prompt
157 | {
158 | color: #999999;
159 | font-weight: bold;
160 | margin-right: .5em;
161 | }
162 |
163 | .abort
164 | {
165 | cursor: pointer;
166 | padding: .3em;
167 | margin: 4px 0 0 0;
168 | border: solid 4px #333333;
169 | background-color: #444444;
170 | color: #ffffff;
171 | -moz-border-radius: 4px;
172 | -webkit-border-radius: 4px;
173 | text-decoration: none;
174 | font-weight: bold;
175 | }
176 |
177 | .abort:hover
178 | {
179 | outline-style: none;
180 | border-color: #666666;
181 | background-color: #999999;
182 | }
183 |
184 | .abort:active,
185 | .abort:active:hover
186 | {
187 | outline-style: none;
188 | border-color: #ffcccc;
189 | }
190 |
191 | .abort .icon
192 | {
193 | padding: .1em .3em;
194 | margin-right: .3em;
195 | vertical-align: middle;
196 | text-align: center;
197 | background-color: #990000;
198 | -moz-border-radius: 4px;
199 | -webkit-border-radius: 4px;
200 | }
201 |
202 | .resize-corner
203 | {
204 | color: #999999;
205 | padding: 0 4px 4px 0;
206 | }
207 |
208 | #store
209 | {
210 | display: none;
211 | }
212 |
213 | #strategy,
214 | #allow-eta-reduction
215 | {
216 | display: inline-block;
217 | height: 1.3em;
218 | margin: .5em 0 0 0;
219 | vertical-align: top;
220 | }
221 |
222 | ul.select
223 | {
224 | list-style-type: none;
225 | height: 1.3em;
226 | margin: .5em 0 0 0;
227 | padding: 0;
228 | }
229 |
230 | ul.select li
231 | {
232 | float: left;
233 | margin: 0 .5em 0 0;
234 | }
235 |
236 | ul.select li.label
237 | {
238 | font-weight: bold;
239 | }
240 |
241 | ul.select li a
242 | {
243 | cursor: pointer;
244 | margin: 0;
245 | padding: 0 .3em;
246 | font-weight: bold;
247 | text-decoration: none;
248 | color: #999999;
249 | background-color: #eeeeee;
250 | -moz-border-radius: .3em;
251 | -webkit-border-radius: .3em;
252 | }
253 |
254 | ul.select li a:hover
255 | {
256 | color: #9999ee;
257 | background-color: #e0e0f0;
258 | border-style: none;
259 | outline-style: none;
260 | text-decoration: underline;
261 | }
262 |
263 | ul.select li a:active,
264 | ul.select li a:active:hover
265 | {
266 | color: red;
267 | border-style: none;
268 | outline-style: none;
269 | }
270 |
271 | ul.select li.selected a,
272 | ul.select li.selected a:link:hover,
273 | ul.select li.selected a:visited:hover
274 | {
275 | color: #ffffff;
276 | background-color: #666666;
277 | text-decoration: none;
278 | border: solid 2px #999999;
279 | }
280 |
281 | #wait
282 | {
283 | border: solid 2px #666666;
284 | width: 3em;
285 | text-align: right;
286 | }
287 |
288 | #syntax th, #syntax td
289 | {
290 | padding: .5em;
291 | vertical-align: top;
292 | }
293 |
294 | #syntax th, #syntax th a
295 | {
296 | background-color: #999999;
297 | color: #ffffff;
298 | font-weight: bold;
299 | }
300 |
301 | #syntax td
302 | {
303 | background-color: #eeeeee;
304 | }
305 |
306 | #syntax dl
307 | {
308 | margin: 0;
309 | }
310 |
311 | #syntax code
312 | {
313 | display: inline;
314 | border-style: none;
315 | background-color: inherit;
316 | overflow: visible;
317 | }
318 |
319 | #syntax code var
320 | {
321 | font-style: italic;
322 | }
323 |
324 | #example dt
325 | {
326 | display: inline;
327 | padding: .1em .5em;
328 | font-weight: bold;
329 | color: black;
330 | background-color: #eeeeee;
331 | border-width: .15em;
332 | border-color: #666666;
333 | border-style: dashed none dashed none;
334 | }
335 |
--------------------------------------------------------------------------------
/index.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Lambda calculus in JS syntax
18 |
19 |
20 |
21 |
Lambda calculus in JavaScript syntax
22 |
23 |
24 |
25 |
26 | Evaluation strategy:
27 |
28 |
29 |
30 |
31 | allow η-reduction
32 |
33 |
34 |
35 |
38 |
39 |
44 |
45 |
Syntax
46 |
47 |
48 | Lambda JavaScript JavaScript 1.8
49 | variable xxx
50 | abstraction λx.M or \x.M function(x){ return M }function(x) M
51 | application ((M ) N )(M )(N )(M )(N )
52 | declaration - var a = M ;let a = M ;
53 | syntactic sugar
54 | λx…yz.M
55 | for λx…y.λz.M
56 | λx.M N
57 | for λx.(M N )
58 | (x M )
59 | for ((x) M )
60 | (L M N )
61 | for ((L M ) N)
62 |
63 | function(x,…,y,z){ return M }
64 | for function(x,…,y){ return function(z){ return M } }
65 | x(M )
66 | for (x)(M )
67 | L (M )(N )
68 | for (L (M ))(N )
69 |
70 | function(x,…,y,z) M
71 | for function(x,…,y) function(z) M
72 | x(M )
73 | for (x)(M )
74 | L (M )(N )
75 | for (L (M ))(N )
76 |
77 |
78 |
79 |
Example
80 |
81 |
82 |
code1
83 |
84 | (function(x){ return x })(a)
85 |
86 |
run code1
87 |
88 |
code2
89 |
90 | (function(x,y){ return x })(a)
91 |
92 |
run code2
93 |
94 |
code3
95 |
96 | var k = function(x,y){ return x };
97 | k(a)(b)
98 |
99 |
run code3
100 |
101 |
code4
102 |
103 | var s = function(x,y,z){ return x(z)(y(z)) };
104 | var k = function(x,y){ return x };
105 | s(k)(k)
106 |
107 |
run code4
108 |
109 |
code5
110 |
111 | var s = function(x,y,z){ return x(z)(y(z)) };
112 | var k = function(x,y){ return x };
113 | s(a)(k)(c)
114 |
115 |
run code5
116 |
117 |
code6
118 |
119 | var o = function(x){ return x(x)(c) };
120 | o(o)
121 |
122 |
run code6
123 |
124 |
code7 (leftmost outermost)
125 |
126 | var k = function(x,y){ return x };
127 | var o = function(x){ return x(x)(c) };
128 | k(a)(o(o))
129 |
130 |
run code7
131 |
132 |
code8 (call by value)
133 |
134 | var k = function(x,y){ return x };
135 | var o = function(x){ return x(x)(c) };
136 | k(a)(o(o))
137 |
138 |
run code8
139 |
140 |
code9
141 |
142 | var _0 = function(s,z){ return z };
143 | var _1 = function(s,z){ return s(z) };
144 | var _2 = function(s,z){ return s(s(z)) };
145 | var _3 = function(s,z){ return s(s(s(z))) };
146 |
147 | var add = function(m,n,s,z){ return m(s)(n(s)(z)) };
148 |
149 | add(_2)(_3)
150 |
151 |
run code9
152 |
153 |
code10
154 |
155 | var pair = function(P,Q,x){ return x(P)(Q) }; // cons
156 | var fst = function(p){ return p(function(x,y){ return x }) }; // car
157 | var snd = function(p){ return p(function(x,y){ return y }) }; // cdr
158 |
159 | var p1 = pair(pair(a)(b))(pair(c)(d));
160 | fst(snd(p1))
161 |
162 |
run code10
163 |
164 |
code11
165 |
166 | var t = function(x,y){ return x }; // true
167 | var f = function(x,y){ return y }; // false
168 | var cond = function(B,P,Q){ return B(P)(Q) }; // if-then-else
169 | cond(t)(a)(b) // if true then a else b
170 |
171 |
run code11
172 |
173 |
code12
174 |
175 | var Y = function(f){ return (function(x){ return f((x)(x)) })(function(x){ return f((x)(x)) }) };
176 |
177 | var _0 = function(s,z){ return z };
178 | var _1 = function(s,z){ return s(z) };
179 | var _2 = function(s,z){ return s(s(z)) };
180 | var _3 = function(s,z){ return s(s(s(z))) };
181 |
182 | var t = function(x,y){ return x }; // true
183 | var f = function(x,y){ return y }; // false
184 | var if0 = function(n){ return n(function(x){ return f })(t) }; // if n equals to _0
185 |
186 | var mul = function(m,n,s,z){ return n(m(s))(z) };
187 | var pred = function(n,s,z){ return n(function(f,g){ return g(f(s)) })(function(x){ return z })(function(x){ return x }) };
188 |
189 | var fact = function(r,n){ return if0(n)(_1)(mul(n)(r(pred(n)))) };
190 | Y(fact)(_3)
191 |
192 |
run code12
193 |
194 |
195 |
Source code
196 |
197 |
See: http://github.com/tarao/LambdaJS
198 |
199 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/pp.js:
--------------------------------------------------------------------------------
1 | if (typeof LambdaJS == 'undefined') var LambdaJS = {};
2 |
3 | (function(ns) {
4 | with (UI) {
5 | var append = function(child) { return function(node) {
6 | node.appendChild($node(child)); return node;
7 | }; };
8 | var reduce = function() { var args=arguments; return function(node) {
9 | var arr = []; arr.push.apply(arr, args);
10 | return arr.reduce(function(a1, a2) {
11 | return a1.concat((a2 instanceof Array) ? a2 : [a2]);
12 | }, []).reduce(function(x, f) {
13 | return ((typeof f == 'function') ? f : append(f))(x);
14 | }, node);
15 | }; };
16 | var appendParen = function(child){ return reduce('(', child, ')'); };
17 | ns.PP = {
18 | JavaScript: function() {
19 | var self = new ns.PP.Lambda();
20 | self.pp = self._pp;
21 | self.name = 'JavaScript';
22 | self.lambda = function(argNodes, bodyNode) {
23 | var lambda = $new('span', {
24 | klass: 'lambda', child: 'function'
25 | });
26 | return reduce(lambda, '(', argNodes.shift(),
27 | argNodes.map(function(arg) {
28 | return reduce([ ',', arg ]);
29 | }), ')', self.body(bodyNode));
30 | };
31 | self.body = function(bodyNode) {
32 | return reduce('{ return ', bodyNode, ' }');
33 | };
34 | self.apply = function(fun, arg){ return reduce(fun, arg); };
35 | self.arg = function(arg) {
36 | var argNode = self._pp(arg);
37 | var paren = $new('span', { klass: 'argument' });
38 | return append(appendParen(argNode)(paren));
39 | };
40 | return self;
41 | },
42 | JavaScript18: function() {
43 | var self = new ns.PP.JavaScript();
44 | self.body = function(body){ return reduce(' ', body); };
45 | return self;
46 | },
47 | Lambda: function() {
48 | var self = { name: 'Lambda', callback: function(){} };
49 | self.setCallback = function(func){ self.callback = func; };
50 | self.pp = function(exp){
51 | var node = self._pp(exp);
52 | if (exp.type == 'App') {
53 | node = $new('span', { child: [ '(', node, ')' ] });
54 | }
55 | return node;
56 | };
57 | self._pp = function(exp) {
58 | return $node(LambdaJS.Util.promote(exp).pp(self));
59 | };
60 | self.lambda = function(argNodes, bodyNode) {
61 | var lambda = $new('span', {
62 | klass: 'lambda', child: '\u03bb'
63 | });
64 | return reduce(lambda, argNodes, '.', bodyNode);
65 | };
66 | self.apply = function(fun, arg){ return reduce(fun,' ',arg); };
67 | self.arg = function(arg) {
68 | var argNode = self._pp(arg);
69 | if (arg.type != 'Var') {
70 | var paren = $new('span', { klass: 'argument' });
71 | return append(appendParen(argNode)(paren));
72 | } else {
73 | argNode.className += ' argument';
74 | return append(argNode);
75 | }
76 | };
77 | self.markBound = function(exp, v) {
78 | switch (exp.type) {
79 | case 'Var':
80 | if (exp.v == v.v) {
81 | (exp = exp.clone()).bound=true;
82 | }
83 | break;
84 | case 'App':
85 | exp.fun = self.markBound(exp.fun, v);
86 | exp.arg = self.markBound(exp.arg, v);
87 | break;
88 | case 'Abs':
89 | if (exp.arg.v != v.v) {
90 | exp.body = self.markBound(exp.body, v);
91 | }
92 | break;
93 | }
94 | return exp;
95 | };
96 | self.ppApp = function(app) {
97 | var fun = app.fun;
98 | var arg = app.arg;
99 | var klass = 'application';
100 | var node;
101 | if (app.marked) {
102 | node = $new('span', { klass: klass + ' marked' });
103 | fun.body = self.markBound(fun.body, fun.arg);
104 | } else if (app.redex) {
105 | node = $new('a', { klass: klass + ' redex shadowed' });
106 | new UI.Observer(node, 'onclick', function(e) {
107 | app.marked = true;
108 | self.callback();
109 | self.callback = function(){};
110 | e.stop();
111 | });
112 | new UI.Observer(node, 'onmouseover', function(e) {
113 | if (/shadowed/.test(node.className)) {
114 | node.className = node.className.split(/\s+/)
115 | .filter(function(x){ return x!='shadowed'; })
116 | .join(' ');
117 | }
118 | e.stop();
119 | });
120 | new UI.Observer(node, 'onmouseout', function(e) {
121 | node.className += ' shadowed';
122 | });
123 | } else {
124 | node = $new('span', { klass: klass });
125 | }
126 | fun = self._pp(fun);
127 | var appendFun = (app.fun.type == 'Abs') ?
128 | appendParen(fun) : append(fun);
129 | var appendArg = self.arg(app.arg);
130 | return self.apply(appendFun, appendArg)(node);
131 | };
132 | self.ppAbs = function(abs) {
133 | var arg = self._pp(abs.arg);
134 | arg.className += ' binding';
135 | var body = abs.body;
136 | var args = [ arg ];
137 |
138 | var node;
139 | var klass = 'abstraction';
140 | if (abs.marked) {
141 | abs.body = self.markBound(abs.body, abs.arg);
142 | node = $new('span', { klass: klass + ' marked' });
143 | } else if (abs.redex) {
144 | node = $new('a', {
145 | klass: klass + ' redex shadowed'
146 | });
147 | new UI.Observer(node, 'onclick', function(e) {
148 | abs.marked = true;
149 | self.callback();
150 | self.callback = function(){};
151 | e.stop();
152 | });
153 | new UI.Observer(node, 'onmouseover', function(e) {
154 | if (/shadowed/.test(node.className)) {
155 | node.className = node.className.split(/\s+/)
156 | .filter(function(x){ return x!='shadowed'; })
157 | .join(' ');
158 | }
159 | e.stop();
160 | });
161 | new UI.Observer(node, 'onmouseout', function(e) {
162 | node.className += ' shadowed';
163 | });
164 | } else {
165 | while (body.type == 'Abs') {
166 | if (body.marked || body.redex) break;
167 | args.push(self._pp(body.arg));
168 | body = body.body;
169 | }
170 | node = $new('span', {
171 | klass: klass
172 | });
173 | }
174 | return self.lambda(args, self._pp(body))(node);
175 | };
176 | self.ppVar = function(v) {
177 | var klass = 'variable';
178 | if (v.bound) klass = [ klass, 'bound' ].join(' ');
179 | var span = $new('span', {
180 | klass: klass, child: v.v+''
181 | });
182 | return span;
183 | };
184 | return self;
185 | }
186 | };
187 | }
188 | })(LambdaJS);
189 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | if (typeof LambdaJS == 'undefined') var LambdaJS = {};
2 | if (typeof LambdaJS.App == 'undefined') LambdaJS.App = {};
3 |
4 | (function(ns) {
5 | ns.testJS18 = function() {
6 | return [
7 | '(function(x) x)(1)',
8 | 'let x = 1'
9 | ].every(function(t) {
10 | try {
11 | eval(t);
12 | return true;
13 | } catch (e) {
14 | return false;
15 | }
16 | });
17 | };
18 | ns.isJS18Enabled = function() {
19 | if (typeof ns._isJS18Enabled == 'undefined') {
20 | ns._isJS18Enabled = ns.testJS18();
21 | }
22 | return ns._isJS18Enabled;
23 | };
24 | ns.hideSyntax = function(table, hide) {
25 | var hideCols = function(table, i) {
26 | for (var j=0; j < table.rows.length; j++) {
27 | var row = table.rows[j];
28 | if (row) {
29 | var elm = row.cells[i];
30 | if (elm) elm.style.display = 'none';
31 | }
32 | }
33 | };
34 | var head = table.rows[0];
35 | if (!head) return;
36 | for (var i=0; i < head.cells.length; i++) {
37 | if (head.cells[i].className == hide) {
38 | hideCols(table, i);
39 | break;
40 | }
41 | }
42 | };
43 | ns.Repl = function(elm, cont) {
44 | var ReductionLabel = {
45 | beta: '\u03b2',
46 | eta: '\u03b7'
47 | };
48 | var self = {
49 | getWait: function(){ return 500; },
50 | getStrategy: function() {
51 | return new LambdaJS.Strategy.Leftmost();
52 | },
53 | getAllowEta: function() {
54 | return UI.$('input-allow-eta').checked;
55 | },
56 | getPP: function() {
57 | return new LambdaJS.PP.Lambda();
58 | },
59 | env: new LambdaJS.Env(),
60 | destruct: function() {
61 | delete self.strategy; delete self.marker; delete self.exp;
62 | if (self.abort) self.abort.die();
63 | },
64 | contDefault: function() {
65 | self.console.prompt();
66 | self.destruct();
67 | },
68 | parseDefault: function(cmd){ return self.env.evalLine(cmd); }
69 | };
70 | self.cont = cont || self.contDefault;
71 | self.parse = self.parseDefault;
72 | self.makeAbortButton = function() {
73 | var parent = self.console.enclosing;
74 | self.abort = new UI.AbortButton(parent, {
75 | position: 'absolute'
76 | }, function() {
77 | if (self.marker) {
78 | self.marker.setCallback(function(){});
79 | self.marker = null;
80 | }
81 | self.cont();
82 | });
83 | var btn = self.abort.button;
84 | var height = btn.offsetHeight;
85 | var val = function(p){ return parseInt(UI.getStyle(btn, p))||0; };
86 | height += val('borderTopWidth') || val('borderWidth');
87 | height += val('marginTop') || val('margin');
88 | btn.style.right = 0; btn.style.bottom = (-height)+'px';
89 | };
90 | self.console = new UI.Console(elm, function(cmd) {
91 | self.sandbox(function() {
92 | self.exp = self.parse(cmd);
93 | if (self.exp) {
94 | self.strategy = self.getStrategy();
95 | self.console.insert(self.getPP().pp(self.exp));
96 | self.makeAbortButton();
97 | self.mark();
98 | } else {
99 | self.cont();
100 | }
101 | return true;
102 | }, self.cont);
103 | }, null, function(evnt) {
104 | if ((evnt.charCode || evnt.keyCode) == 220) {
105 | UI.insertText(self.console.input, '\u03bb');
106 | return true;
107 | }
108 | return false;
109 | });
110 | self.sandbox = function(fun, cont) {
111 | try {
112 | if (fun()) return;
113 | } catch (e) {
114 | var meta = [];
115 | [ 'fileName', 'lineNumber' ].forEach(function(x) {
116 | if (/^([a-z]+)/.test(x) && e[x])
117 | meta.push(RegExp.$1 + ': ' + e[x]);
118 | });
119 | meta = meta.length ? ' ['+meta.join(', ')+']' : '';
120 | self.console.err(e.message + meta);
121 | }
122 | cont();
123 | };
124 | self.mark = function() {
125 | self.sandbox(function() {
126 | var strategy = self.getStrategy();
127 | self.exp = strategy.mark(self.exp, self.getAllowEta());
128 | if (strategy.marked) {
129 | setTimeout(function() {
130 | if (self.abort()) return;
131 | self.marker = self.getPP();
132 | UI.replaceLastChild(self.console.view.lastChild,
133 | self.marker.pp(self.exp));
134 | self.reduce(self.marker);
135 | }, self.getWait());
136 | return true;
137 | }
138 | }, self.cont);
139 | };
140 | self.reduce = function(marker) {
141 | self.sandbox(function() {
142 | var strategy = self.getStrategy();
143 | self.exp = strategy.reduceMarked(self.exp);
144 |
145 | var output = [];
146 | if (strategy.alpha) {
147 | var conv = UI.$new('span', {
148 | klass: 'alpha convert',
149 | child: '='
150 | });
151 | conv.appendChild(UI.$new('sub', {
152 | child: '\u03b1'
153 | }));
154 | output.push([ conv, self.marker.pp(strategy.alpha) ]);
155 | }
156 | if (strategy.reduced) {
157 | var red = UI.$new('span', {
158 | klass: strategy.reduced + ' reduce',
159 | child: '\u2192'
160 | });
161 | red.appendChild(UI.$new('sub', {
162 | child: ReductionLabel[strategy.reduced]
163 | }));
164 | output.push([ red, self.marker.pp(self.exp) ]);
165 | }
166 |
167 | if (output.length > 0) {
168 | setTimeout(function() {
169 | if (self.abort()) return;
170 | output.forEach(function(o) {
171 | self.console.insert.apply(null, o);
172 | });
173 | self.mark();
174 | }, self.getWait());
175 | } else {
176 | marker.setCallback(function(){ self.mark(); });
177 | }
178 | return true;
179 | }, self.cont);
180 | };
181 | self.cont();
182 | return self;
183 | };
184 | ns.StaticCode = function(decl) {
185 | var self = ns.StaticCode;
186 | self.hash = {};
187 | self.run = function(id) {
188 | var code = self.hash[id];
189 | if (code) code.run();
190 | };
191 | self.forEach = function(fun) {
192 | if (typeof fun == 'string') {
193 | var name = fun;
194 | fun = function(obj){ obj[name](); };
195 | }
196 | for (var id in self.hash) fun(self.hash[id]);
197 | };
198 | self.toLambda = function(){ self.forEach('toLambda'); };
199 | self.toJavaScript = function(){ self.forEach('toJavaScript'); };
200 | self.toJavaScript18 = function(){ self.forEach('toJavaScript18'); };
201 | var Code = function(node, decl) {
202 | var self = { node: node, code: node.textContent, decl: decl };
203 | if (typeof node.textContent == 'undefined') { // IE fix
204 | var s = ''; var len=node.childNodes.length; var child;
205 | for (var i=0; i < len && (child=node.childNodes[i]); i++) {
206 | if (child instanceof Text) s += child.toString();
207 | }
208 | self.code = s;
209 | }
210 | (node.className||'').split(/\s+/).forEach(function(name) {
211 | name = name.split('-').map(function(s) {
212 | return s.charAt(0).toUpperCase()+s.substring(1);
213 | }).join('');
214 | if (name in LambdaJS.Strategy) self.st = name;
215 | });
216 | var conv = function(pp, decl, code) {
217 | return code.split(/[\n\r]/).map(function(l) {
218 | var expr = l; var pre = ''; var post = '';
219 | if (/^(var|let)\s+([^\s=]+)\s*=\s*(.*)$/.test(expr)) {
220 | var d = RegExp.$1; var v = RegExp.$2; expr = RegExp.$3;
221 | pre = [ decl(d), v, '=', '' ].join(' ');
222 | }
223 | if (new RegExp('^([^;]*)(;.*)$').test(expr) ||
224 | new RegExp('^(.*?)( //.*)$').test(expr) ||
225 | new RegExp('^()(//.*)$').test(expr)) {
226 | expr = RegExp.$1; post = RegExp.$2;
227 | }
228 | var env = new LambdaJS.Env();
229 | try {
230 | if (expr.length > 0) {
231 | expr = env.evalLine(expr);
232 | expr = expr || { pp: function(){ return ''; } };
233 | expr = UI.text(pp.pp(expr));
234 | }
235 | return pre+expr+post;
236 | } catch (e) {
237 | return e.message;
238 | }
239 | }).join('\n');
240 | };
241 | self.t = function(what) {
242 | if (!self['code'+what]) {
243 | var pp = new LambdaJS.PP[what]();
244 | self['code'+what] = conv(pp, self.decl, self.code);
245 | }
246 | UI.removeAllChildren(self.node);
247 | self.node.appendChild(UI.$text(self['code'+what]));
248 | return self['code'+what];
249 | };
250 | self.toLambda = function(){ return self.t('Lambda'); };
251 | self.toJavaScript = function() {
252 | UI.removeAllChildren(self.node);
253 | self.node.appendChild(UI.$text(self.code));
254 | return self.code;
255 | };
256 | self.toJavaScript18 = function(){ return self.t('JavaScript18'); };
257 | self.run = function() {
258 | var parent = self.node.parentNode.parentNode;
259 | if (self.repl) {
260 | self.repl.abort.doAbort();
261 | parent = self.repl.console.enclosing;
262 | self.repl.console.destroy();
263 | parent.removeChild(UI.$('result-'+self.node.id));
264 | }
265 | var div = UI.$new('div', {
266 | klass: 'console', id: 'result-'+self.node.id
267 | });
268 | parent.appendChild(div);
269 | var repl = self.repl = new ns.Repl(div, function(){});
270 | var get = function(k){ return UI.$('input-'+k).value; };
271 | repl.getStrategy = function() {
272 | var st = self.st || get('strategy') || 'LeftmostOutermost';
273 | return new LambdaJS.Strategy[st];
274 | };
275 | repl.getPP = function() {
276 | return new LambdaJS.PP[get('pp') || 'JavaScript'];
277 | };
278 | repl.getWait = function() {
279 | var wait = get('wait');
280 | return (typeof wait != 'undefined') ? wait : 500;
281 | };
282 | repl.cont = function() {
283 | repl.destruct();
284 | repl.cont = repl.contDefault;
285 | repl.parse = repl.parseDefault;
286 | repl.console.prompt();
287 | };
288 | repl.parse = function(c){ return repl.env.evalLines(c); };
289 | repl.console.insert([
290 | '[', repl.getStrategy().name,
291 | '/', repl.getPP().name,
292 | ']' ].join(' '));
293 | repl.console.command(self.code);
294 | };
295 | return self;
296 | };
297 | var name = 'LambdaJS.App.StaticCode';
298 | var links = UI.doc.getElementsByTagName('a');
299 | for (var i=0; i < links.length; i++) {
300 | var node;
301 | if (links[i].id.match(/^run-(.+)/) && (node=UI.$(RegExp.$1))) {
302 | links[i].href = 'javascript:'+name+'.run(\''+node.id+'\')';
303 | self.hash[node.id] = new Code(node, decl);
304 | }
305 | }
306 | return self;
307 | };
308 | })(LambdaJS.App);
309 |
310 | function init(id) {
311 | with (LambdaJS.App) {
312 | // hide unsupported syntax
313 | hideSyntax(document.getElementById('syntax'),
314 | isJS18Enabled() ? 'javascript' : 'javascript18');
315 |
316 | // examples
317 | var declLet = function(){ return 'let'; };
318 | var declVar = function(){ return 'var'; };
319 | var exmpls = new StaticCode(isJS18Enabled() ? declLet : declVar);
320 |
321 | // REPL
322 | var elm = document.getElementById(id);
323 | var repl = new Repl(elm);
324 |
325 | var makeSelector = function(what, dflt, extra) {
326 | extra = extra || function(){};
327 | var lc = what.toLowerCase();
328 | var cat = LambdaJS[what]; var hash = {};
329 | for (var k in cat) hash[k] = { name: new cat[k]().name };
330 | new UI.Selector(lc, hash, function(key) {
331 | repl['get'+what] = function(){ return new cat[key]; };
332 | UI.$('input-'+lc).value = key;
333 | extra(key);
334 | if (repl.console.input) repl.console.input.focus();
335 | }, UI.$('input-'+lc).value || dflt);
336 | };
337 |
338 | // strategy
339 | makeSelector('Strategy', 'LeftmostOutermost');
340 |
341 | // output
342 | if (!isJS18Enabled()) delete LambdaJS.PP.JavaScript18;
343 | var lang = /\#js$/.test(location.href) ? 'JavaScript' : 'Lambda';
344 | makeSelector('PP', lang, function(key){ exmpls['to'+key](); });
345 |
346 | // wait
347 | var ul = UI.$('pp');
348 | ul.appendChild(UI.$new('li', { klass: 'label', child: 'Wait:' }));
349 | var input = UI.$new('input', { id: 'wait' });
350 | var sync = function(){ UI.$('input-wait').value = input.value; };
351 | new UI.Observer(input, 'onchange', sync);
352 | new UI.Observer(input, 'onkeyup', sync);
353 | var w = UI.$('input-wait').value;
354 | input.value = w.length ? w : 500;
355 | sync();
356 | ul.appendChild(input);
357 | repl.getWait = function(){ return input.value; };
358 | }
359 | };
360 |
--------------------------------------------------------------------------------
/lambda.js:
--------------------------------------------------------------------------------
1 | if (typeof LambdaJS == 'undefined') var LambdaJS = {};
2 |
3 | (function(ns) {
4 | ns.Sandbox = function() {
5 | var window = null;
6 | var document = null;
7 | var alert = null;
8 | this.fun = ns.Parser.makeFun;
9 | this.run = function(code){ with (this) return eval(code); };
10 | };
11 |
12 | ns.Env = function() {
13 | var Tokens = function(tokens) {
14 | var self = { tokens: tokens||[] };
15 | self.push = function(t) {
16 | if (t != 'fun' && self.tokens.indexOf(t) == -1 &&
17 | Tokens.keywords.indexOf(t) == -1) {
18 | try {
19 | new ns.Sandbox().run(['var',t,'=','1;'].join(' '));
20 | self.tokens.push(t);
21 | } catch (e) {
22 | Tokens.keywords.push(t);
23 | }
24 | }
25 | return self;
26 | };
27 | self.parse = function(str) {
28 | while (str.length &&
29 | /([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)$/.test(str)) {
30 | str = RegExp.$2;
31 | self.push(RegExp.$1);
32 | }
33 | return self;
34 | };
35 | self.toCode = function() {
36 | return self.tokens.map(function(t) {
37 | var a = ['var',t,'=','LambdaJS.Util.promote(\''+t+'\');'];
38 | return a.join(' ');
39 | });
40 | };
41 | return self;
42 | };
43 | Tokens.keywords = [];
44 | var self = {
45 | parser: new ns.Parser(),
46 | sandbox: new ns.Sandbox(),
47 | stack: [], predefs: new Tokens()
48 | };
49 | self.evalResolvingReference = function(code) {
50 | self.predefs.parse(code);
51 | var predefs = self.predefs.toCode();
52 | return self.sandbox.run(predefs.concat([code]).join('\n'));
53 | };
54 | self.evalCode = function(code) {
55 | var joined = self.stack.concat([code]).join('\n');
56 | var ret = self.evalResolvingReference(joined);
57 | if (typeof ret == 'undefined') {
58 | self.stack.push(code);
59 | }
60 | return ret;
61 | };
62 | self.evalLine = function(line) {
63 | line = self.parser.parseLine(line);
64 | return self.evalCode(line);
65 | };
66 | self.evalLines = function(lines) {
67 | lines = self.parser.parse(lines);
68 | return lines.reduce(function(r,l){return self.evalCode(l);}, null);
69 | };
70 | return self;
71 | };
72 |
73 | ns.Util = {
74 | promote: function(v) {
75 | if (['Abs', 'App', 'Var'].indexOf(v.type||'') == -1) {
76 | return new ns.Semantics.Var(v);
77 | }
78 | return v;
79 | },
80 | freshVar: function(used, v) {
81 | if (!used[v]) return ns.Util.promote(v);
82 | if (/^([a-z])([0-9]*)$/.test(v)) {
83 | var code = RegExp.$1.charCodeAt(0)+1;
84 | var num = RegExp.$2;
85 | if ('z'.charCodeAt(0) < code) {
86 | if (!num.length) num = 0;
87 | return this.freshVar(used, 'a'+(num+1));
88 | } else {
89 | return this.freshVar(used, String.fromCharCode(code)+num);
90 | }
91 | } else {
92 | return this.freshVar(used, 'a');
93 | }
94 | }
95 | };
96 |
97 | ns.Ast = function(type, args) {
98 | var self = function(arg) {
99 | return new ns.Semantics.App(self, ns.Util.promote(arg).clone());
100 | };
101 | self.type = type;
102 | switch (type) {
103 | case 'Abs':
104 | self.arg = ns.Util.promote(args[0]);
105 | self.body = args[1].call(null, new ns.Semantics.Var(args[0]));
106 | break;
107 | case 'App':
108 | self.fun = args[0];
109 | self.arg = args[1];
110 | break;
111 | case 'Var':
112 | self.v = args[0];
113 | break;
114 | }
115 | return self;
116 | };
117 |
118 | ns.Semantics = {
119 | Base: function(type, args) {
120 | var self = ns.Ast(type, args);
121 | [ 'mark', 'reduceMarked', 'pp' ].forEach(function(m) {
122 | self[m] = function(visitor) {
123 | var args = Array.prototype.slice.call(arguments, 1);
124 | args = [ self ].concat(args);
125 | var id = function(x){ return x; };
126 | return (visitor[m+self.type]||id).apply(visitor, args);
127 | };
128 | });
129 | return self;
130 | },
131 | Abs: function(arg, func) {
132 | var self = ns.Semantics.Base('Abs', arguments);
133 | self.clone = function() {
134 | var c = new ns.Semantics.Abs(self.arg.clone(), function() {
135 | return self.body.clone();
136 | });
137 | c.marked = self.marked;
138 | c.redex = self.redex;
139 | return c;
140 | };
141 | self.subst = function(arg, v) { // (\x.M)[v:=N]
142 | if (v.v == self.arg.v) // (\x.M)[x:=N] = \x.M
143 | return { exp: self };
144 | var fv1 = self.body.fv(); // fv(M)
145 | var fv2 = arg.fv(); // fv(N)
146 | var abs = self;
147 | var alpha;
148 | if (fv1[v.v||self.arg.v] && fv2[self.arg.v]) {
149 | // alpha conversion
150 | fv2[v.v||self.arg.v] = true;
151 | var fresh = ns.Util.freshVar(fv2, 'a');
152 | abs.body = abs.body.subst(fresh, abs.arg).exp;
153 | abs.arg = abs.arg.subst(fresh, abs.arg).exp;
154 | alpha = abs.clone();
155 | }
156 | abs.body = abs.body.subst(arg.clone(), v).exp;
157 | return {
158 | alpha: alpha,
159 | exp: abs
160 | };
161 | };
162 | self.fv = function() {
163 | var fv = self.body.fv();
164 | fv[self.arg.v] = false;
165 | return fv;
166 | };
167 | self.isEtaRedex = function() {
168 | var abs = self;
169 | if (abs.body.type != 'App') return false;
170 | if (abs.body.arg.type != 'Var') return false;
171 | if (abs.arg.v != abs.body.arg.v) return false;
172 | var fv = abs.body.fun.fv();
173 | if (fv[abs.arg.v]) return false;
174 | return true;
175 | };
176 | return self;
177 | },
178 | App: function(func, arg) {
179 | var self = ns.Semantics.Base('App', arguments);
180 | self.clone = function() {
181 | var c = new ns.Semantics.App(self.fun.clone(),
182 | self.arg.clone());
183 | c.marked = self.marked;
184 | c.redex = self.redex;
185 | return c;
186 | };
187 | self.subst = function(m, v) {
188 | var app = self;
189 | var fun = app.fun.subst(m, v);
190 | var arg = app.arg.subst(m.clone(), v);
191 | var alpha;
192 | if (fun.alpha || arg.alpha) {
193 | alpha = app.clone();
194 | alpha.fun = fun.alpha || app.fun.clone();
195 | alpha.arg = arg.alpha || app.arg.clone()
196 | }
197 | app.fun = fun.exp;
198 | app.arg = arg.exp;
199 | return {
200 | alpha: alpha,
201 | exp: app
202 | };
203 | };
204 | self.fv = function() {
205 | var fv1 = self.fun.fv();
206 | var fv2 = self.arg.fv();
207 | for (var p in fv1) fv2[p] = fv2[p] || fv1[p];
208 | return fv2;
209 | };
210 | return self;
211 | },
212 | Var: function(v) {
213 | var self = ns.Semantics.Base('Var', arguments);
214 | self.clone = function() {
215 | return new ns.Semantics.Var(self.v+'');
216 | };
217 | self.subst = function(arg, v) {
218 | return { exp: self.v == v.v ? arg : self };
219 | };
220 | self.fv = function() {
221 | var fv = {};
222 | fv[self.v] = true;
223 | return fv;
224 | };
225 | return self;
226 | }
227 | };
228 |
229 | var Strategy = {
230 | Base: function() {
231 | var self = { reduced: null };
232 | self.name = '(base)';
233 | self.reduce = function(exp) {
234 | return self.reduceMarked(self.mark(exp));
235 | };
236 | self.mark = function(exp, allowEta) {
237 | self.marked = false;
238 | return self._mark(exp, allowEta);
239 | };
240 | self._mark = function(exp, allowEta) {
241 | return ns.Util.promote(exp).mark(self, allowEta);
242 | };
243 | self.reduceMarked = function(exp) {
244 | self.reduced = null;
245 | self.alpha = null;
246 | return self._reduceMarked(exp);
247 | };
248 | self._reduceMarked = function(exp) {
249 | return ns.Util.promote(exp).reduceMarked(self);
250 | };
251 | self.reduceMarkedAbs = function(abs) {
252 | if (abs.marked) {
253 | // eta reduction
254 | self.reduced = 'eta';
255 | return abs.body.fun;
256 | }
257 |
258 | abs.body = self._reduceMarked(abs.body);
259 | if (self.alpha) {
260 | var alpha = self.alpha;
261 | self.alpha = abs.clone();
262 | self.alpha.body = alpha;
263 | }
264 | return abs;
265 | };
266 | self.reduceMarkedApp = function(app) {
267 | var clone = app.clone();
268 | app.fun = self._reduceMarked(app.fun);
269 | app.arg = self._reduceMarked(app.arg);
270 | if (app.marked) {
271 | // beta reduction
272 | var reduced = app.fun.body.subst(app.arg, app.fun.arg);
273 | self.reduced = 'beta';
274 | if (reduced.alpha) {
275 | clone.fun.body = reduced.alpha;
276 | self.alpha = clone;
277 | }
278 | return reduced.exp;
279 | }
280 | return app;
281 | };
282 | return self;
283 | },
284 | Applicative: {
285 | markApp: function(app, allowEta) {
286 | app.fun = this._mark(app.fun, allowEta);
287 | if (this.marked) return app;
288 |
289 | app.arg = this._mark(app.arg, allowEta);
290 | if (this.marked) return app;
291 |
292 | if (app.fun.type == 'Abs') {
293 | this.marked = true;
294 | app.marked = true;
295 | }
296 |
297 | return app;
298 | }
299 | }
300 | };
301 | ns.Strategy = {
302 | LeftmostOutermost: function() {
303 | var self = new Strategy.Base();
304 | self.name = 'leftmost outermost';
305 | self.markAbs = function(abs, allowEta) {
306 | if (allowEta && abs.isEtaRedex()) {
307 | self.marked = true;
308 | abs.marked = true;
309 | return abs;
310 | }
311 | abs.body = self._mark(abs.body, allowEta);
312 | return abs;
313 | };
314 | self.markApp = function(app, allowEta) {
315 | if (app.fun.type == 'Abs') {
316 | self.marked = true;
317 | app.marked = true;
318 | } else {
319 | app.fun = self._mark(app.fun, allowEta);
320 | if (!self.marked) app.arg = self._mark(app.arg, allowEta);
321 | }
322 | return app;
323 | };
324 | return self;
325 | },
326 | LeftmostInnermost: function() {
327 | var self = new ns.Strategy.LeftmostOutermost();
328 | self.name = 'leftmost innermost';
329 | self.markApp = Strategy.Applicative.markApp;
330 | return self;
331 | },
332 | CallByName: function() {
333 | var self = new Strategy.Base();
334 | self.name = 'call by name';
335 | self.markAbs = function(abs, allowEta) {
336 | if (!allowEta) return abs;
337 | if (abs.isEtaRedex()) {
338 | self.marked = true;
339 | abs.marked = true;
340 | }
341 | return abs;
342 | };
343 | self.markApp = function(app, allowEta) {
344 | app.fun = self._mark(app.fun, allowEta);
345 | if (self.marked) return app;
346 |
347 | if (app.fun.type == 'Abs') {
348 | self.marked = true;
349 | app.marked = true;
350 | return app;
351 | }
352 |
353 | if (!self.marked) app.arg = self._mark(app.arg, allowEta);
354 |
355 | return app;
356 | };
357 | return self;
358 | },
359 | CallByValue: function() {
360 | var self = new ns.Strategy.CallByName();
361 | self.name = 'call by value';
362 | self.markApp = Strategy.Applicative.markApp;
363 | return self;
364 | },
365 | Manual: function() {
366 | var self = new Strategy.Base();
367 | self.name = 'manual';
368 | self.markAbs = function(abs, allowEta) {
369 | abs.redex = false;
370 | abs.body = self._mark(abs.body, allowEta);
371 | if (allowEta && abs.isEtaRedex()) {
372 | self.marked = true;
373 | abs.redex = true;
374 | return abs;
375 | }
376 | return abs;
377 | };
378 | self.markApp = function(app, allowEta) {
379 | app.redex = false;
380 | if (app.fun.type == 'Abs') {
381 | self.marked = true;
382 | app.redex = true;
383 | }
384 | app.fun = self._mark(app.fun, allowEta);
385 | app.arg = self._mark(app.arg, allowEta);
386 | return app;
387 | };
388 | var reduceMarked = self.reduceMarked;
389 | self.reduceMarked = function(exp) {
390 | var r = reduceMarked.call(self, exp);
391 | self.mark(exp);
392 | return r;
393 | };
394 | return self;
395 | }
396 | };
397 |
398 | ns.Parser = function() {
399 | var self = {};
400 | self.parse = function(text) {
401 | return (text||'')
402 | .replace(new RegExp('//.*?[\r\n]', 'g'), '')
403 | .replace(/[\r\n\t]/g, ' ')
404 | .replace(new RegExp('/\\*.*?\\*/', 'g'), '')
405 | .split(/;/).map(function(l) {
406 | return self.parseLine(l);
407 | }).filter(function(l) { return !/^\s*$/.test(l) });
408 | };
409 | self.parseLine = function(line) {
410 | if (new RegExp('^(.*?)//.*$').test(line)) {
411 | line = RegExp.$1;
412 | }
413 | line = line.replace(/^\s*/, '').replace(/\s*$/, '');
414 | if (/;/.test(line.charAt(line.length-1))) {
415 | line = line.substring(0, line.length-1);
416 | }
417 | if (line.length && !new RegExp('^(let|var)').test(line)) {
418 | line = '('+line+')';
419 | }
420 | return line.length ? self.parseExpr(line)+';' : '';
421 | };
422 | self.parseExpr = function(str, nest) {
423 | var arr = [];
424 | var app = false;
425 | var rec = function(str) {
426 | return self.parseExpr(str.replace(/^\s+/, ''), true);
427 | };
428 | while (str.length) {
429 | var first = str.charAt(0);
430 | if (/[\(\{\[]/.test(first)) {
431 | var index = self.matchParen(str);
432 | arr.push([
433 | first, rec(str.substring(1, index)), str.charAt(index)
434 | ].join(''));
435 | str = str.substring(index+1);
436 | } else if (/^function\s*\(([^\)]*)\)(.*)$/.test(str)) {
437 | var args = RegExp.$1;
438 | var body = RegExp.$2;
439 | escaped = args.split(/,/).map(function(arg) {
440 | arg = arg.replace(/^\s*/, '').replace(/\s*$/, '');
441 | return "'"+arg+"'";
442 | });
443 | arr.push([
444 | 'fun(['+escaped.join(',')+'],',
445 | 'function(', args, ') ',
446 | rec(body), ')'
447 | ].join(''));
448 | str = '';
449 | } else if (/^return([^\w].*)$/.test(str)) {
450 | arr.push([
451 | 'return ', rec(RegExp.$1)
452 | ].join(' '));
453 | str = '';
454 | } else if (/^[\u03bb\\](\w+)\.(.*)$/.test(str)) {
455 | str = [
456 | 'function(', RegExp.$1.split('').join(','), '){',
457 | 'return ', RegExp.$2, '}'
458 | ].join('');
459 | } else if (/^(\s+)(.*)$/.test(str)) {
460 | arr.push(RegExp.$1);
461 | str = RegExp.$2;
462 | if (nest && arr.length > 0) app = true;
463 | continue;
464 | } else if (/^([^(){}[\]\s]+)(.*)$/.test(str)) {
465 | var token = RegExp.$1;
466 | str = RegExp.$2;
467 | if (/^(.*?)(function[^\w].*|return[^\w].*)$/.test(token)) {
468 | token = RegExp.$1;
469 | str = RegExp.$2 + str;
470 | }
471 | arr.push(token);
472 | } else {
473 | throw { message: 'syntax error' };
474 | }
475 | if (app) {
476 | if (arr[arr.length-3] && arr[arr.length-1]) {
477 | var arg = arr.pop();
478 | arr.pop();
479 | var fun = arr.pop();
480 | arr.push(fun+'('+arg+')');
481 | app = false;
482 | }
483 | }
484 | }
485 | return arr.join('');
486 | };
487 | self.matchParen = function(str) {
488 | var paren = { '[': ']', '(': ')', '{': '}' };
489 | var open = str.charAt(0);
490 | var close = paren[open];
491 | var depth = 0;
492 | if (!close) return str.length;
493 | for (var i=0; i 0) {
512 | return new ns.Semantics.Abs(arg, function(x) {
513 | return ns.Parser.makeFun(args, f, stack.concat([x]));
514 | });
515 | }
516 | };
517 | })(LambdaJS);
518 |
--------------------------------------------------------------------------------
/ui.js:
--------------------------------------------------------------------------------
1 | if (typeof UI == 'undefined') var UI = {};
2 |
3 | (function(ns) {
4 | var forEach = function(hash, f){ for (var p in hash) f(p, hash[p]); };
5 | var merge = function() {
6 | var hash = {}; var args=[]; args.push.apply(args, arguments);
7 | args.forEach(function(arg) { forEach(arg, function(k,x) {
8 | if (typeof hash[k] == 'undefined') hash[k] = x;
9 | }); });
10 | return hash;
11 | }
12 | ns.doc = ns.doc || document;
13 | ns.isNode = function(x){ return x && typeof x.nodeType == 'number'; };
14 | ns.text = function(node){ return node.textContent||node.innerText||''; };
15 | ns._$ = function(id){ return ns.doc.getElementById(id); };
16 | ns.$ = function(id){ return ns.isNode(id) ? id : ns._$(id); };
17 | ns.$new = function(tag, args) {
18 | var elm = ns.doc.createElement(tag);
19 | args = args || {};
20 | if (args.id) elm.id = args.id;
21 | if (args.klass) elm.className = args.klass;
22 | forEach(args.style||{}, function(k,s){ elm.style[k] = s });
23 | if (args.child) {
24 | if (!(args.child instanceof Array)) args.child = [ args.child ];
25 | for (var i=0; i < args.child.length; i++) {
26 | var child = ns.$node(args.child[i]);
27 | elm.appendChild(child);
28 | }
29 | }
30 | return elm;
31 | };
32 | ns.$text = function(str){ return ns.doc.createTextNode(str); };
33 | ns.$node = function(x){ return ns.isNode(x) ? x : ns.$text(x); };
34 | ns.removeAllChildren = function(node) {
35 | while (node.firstChild) node.removeChild(node.firstChild);
36 | };
37 | ns.replaceLastChild = function(node, child) {
38 | var last = node.lastChild;
39 | if (last) node.removeChild(last);
40 | node.appendChild(child);
41 | };
42 | ns.insertText = function(node, text) {
43 | var start = node.selectionStart;
44 | var end = node.selectionEnd;
45 | if (typeof start == 'number' && typeof end == 'number') {
46 | var before = node.value.substring(0, start);
47 | var after = node.value.substring(end);
48 | node.value = before + text + after;
49 | node.selectionStart = node.selectionEnd = start+text.length;
50 | } else {
51 | node.value += text;
52 | }
53 | };
54 | ns.getStyle = function(node, name) {
55 | var style = (node.style||{})[name];
56 | if (!style) {
57 | var dv = ns.doc.defaultView || {};
58 | if (dv.getComputedStyle) { try {
59 | var styles = dv.getComputedStyle(node, null);
60 | name = name.replace(/([A-Z])/g, '-$1').toLowerCase();
61 | style = styles ? styles.getPropertyValue(name) : null;
62 | } catch(e) {
63 | return null;
64 | } } else if (node.currentStyle) {
65 | style = node.currentStyle[name];
66 | }
67 | }
68 | return style;
69 | };
70 | ns.getPosition = function(node) {
71 | var pos = { x:0, y:0 };
72 | do {
73 | pos.x += node.offsetLeft;
74 | pos.y += node.offsetTop;
75 | } while (node = node.offsetParent);
76 | return pos;
77 | };
78 | ns.getMousePosition = function(pos) {
79 | if (navigator.userAgent.indexOf('Chrome/') != -1 &&
80 | navigator.userAgent.indexOf('Safari') > -1 &&
81 | navigator.userAgent.indexOf('Version/' < 0)) {
82 | return { x: pos.clientX, y: pos.clientY };
83 | } else {
84 | var scroll = {}; var de = ns.doc.documentElement;
85 | if (window.innerWidth) {
86 | scroll.x = window.pageXOffset;
87 | scroll.y = window.pageYOffset;
88 | } else if (de && de.clientWidth) {
89 | scroll.x = de.scrollLeft;
90 | scroll.y = de.scrollTop;
91 | } else if (ns.doc.body.clientWidth) {
92 | scroll.x = ns.doc.body.scrollLeft;
93 | scroll.y = ns.doc.body.scrollTop;
94 | }
95 | return { x: pos.clientX + scroll.x, y: pos.clientY + scroll.y };
96 | }
97 | };
98 | with (ns) {
99 | ns.Event = function(e) {
100 | var self = { event: e };
101 | self.mousePos = function(){ return getMousePosition(self.event); };
102 | self.stop = function() {
103 | if (self.event.stopPropagation) {
104 | self.event.stopPropagation();
105 | self.event.preventDefault();
106 | } else {
107 | self.event.cancelBubble = true;
108 | self.event.returnValue = false;
109 | }
110 | };
111 | return self;
112 | };
113 | ns.Observer = function(node, event, obj, m) {
114 | var self = { node: node, event: event };
115 | var fun = obj;
116 | if (typeof m == 'string') {
117 | fun = obj[m];
118 | } else if (typeof m != 'undefined') {
119 | fun = m;
120 | }
121 | var callback = function(e){ return fun.call(obj, new Event(e)); };
122 | self.start = function() {
123 | if (self.node.addEventListener) {
124 | if (event.indexOf('on') == 0) self.event = event.substr(2);
125 | self.node.addEventListener(self.event, callback, false);
126 | } else if (self.node.attachEvent) {
127 | self.node.attachEvent(self.event, callback);
128 | }
129 | };
130 | self.stop = function() {
131 | if (self.node.removeEventListener) {
132 | self.node.removeEventListener(self.event, callback, false);
133 | } else if (self.node.detachEvent) {
134 | self.node.detachEvent(self.event, callback);
135 | }
136 | }
137 | self.start();
138 | return self;
139 | };
140 | ns.Selector = function(name, keys, action, dflt) {
141 | var self = { hash: {} };
142 | forEach(keys, function(k,v){ self.hash[v.name] = { key: k }; });
143 | self.ul = $(name) || $new('ul', { id: name });
144 | forEach(self.hash, function(label,x) {
145 | var key = x.key;
146 | var selected = (key==dflt || label==dflt);
147 | var a = $new('a', { child: label });
148 | var li = $new('li', {
149 | id: name+key, klass: selected ? 'selected' : '', child: a
150 | });
151 | self.ul.appendChild(x.li = li);
152 | if (selected) action(key);
153 | new Observer(a, 'onclick', (function(li) {
154 | return function(e) {
155 | forEach(self.hash, function(label,x) {
156 | if (label == text(li)) {
157 | li.className = 'selected';
158 | action(x.key);
159 | } else {
160 | x.li.className = '';
161 | }
162 | });
163 | e.stop();
164 | };
165 | })(li));
166 | });
167 | return self;
168 | };
169 | ns.AbortButton = function(parent, style, callback) {
170 | var self = function(){ return self.aborted; };
171 | self.aborted = false;
172 | self.doAbort = function(){ self.aborted=true; callback(); };
173 | self.button = $new('a', { klass: 'abort', style: style, child: [
174 | $new('span', { klass: 'icon', child: '\u2716' }), 'abort'
175 | ] });
176 | parent.appendChild(self.button);
177 | new Observer(self.button, 'onclick', self, 'doAbort');
178 | self.die = function() {
179 | if (!self.died) parent.removeChild(self.button);
180 | self.died = true;
181 | };
182 | return self;
183 | };
184 | ns.Draggable = function(node, opt) {
185 | opt = opt || {};
186 | node = $(node);
187 | if (!getStyle(node, 'position')) node.style.position = 'absolute';
188 | var self = { node: node, attach: $(opt.attach || node) || node };
189 | var nop = function(){ return true; };
190 | self.callback = merge(opt.callback, {start:nop,move:nop,end:nop});
191 | self.pos = {};
192 | var pinf = Number.POSITIVE_INFINITY;
193 | var ninf = Number.NEGATIVE_INFINITY;
194 | self.lower = merge(opt.lower, { x: ninf, y: ninf });
195 | self.upper = merge(opt.lower, { x: pinf, y: pinf });
196 | self.bound = {};
197 | [ 'x', 'y' ].forEach(function(p) {
198 | self.bound[p] = function(x) {
199 | if (self.lower[p] > x) x = self.lower[p];
200 | if (self.upper[p] < x) x = self.upper[p];
201 | return x;
202 | };
203 | });
204 | self.isDragging = function(){ return !!self.o; };
205 | self.isListening = function(){ return !!self.l; };
206 | self.isDisposed = function(){ return !self.isListening(); };
207 | self.start = function(e) {
208 | if (!self.isListening()) return;
209 | self.pos.cursor = e.mousePos();
210 | if (self.callback.start(self.node, e, self.pos.cursor)) {
211 | self.pos.node = getPosition(self.node);
212 | self.o = [ new Observer(doc, 'onmousemove', self, 'move'),
213 | new Observer(doc, 'onmouseup', self, 'stop') ];
214 | e.stop();
215 | }
216 | };
217 | self.move = function(e) {
218 | if (!self.isDragging() || self.isDisposed()) return;
219 | var pos = e.mousePos();
220 | if (self.callback.move(self.node, e, pos)) {
221 | [ 'x', 'y' ].forEach(function(p) {
222 | pos[p] += self.pos.node[p] - self.pos.cursor[p];
223 | pos[p] = self.bound[p](pos[p]);
224 | });
225 | self.node.style.left = pos.x + 'px';
226 | self.node.style.top = pos.y + 'px';
227 | e.stop();
228 | }
229 | };
230 | self.stop = function(e) {
231 | if (!self.isDragging() || self.isDisposed()) return;
232 | self.o.forEach(function(o){ o.stop(); });
233 | self.o = null;
234 | self.pos = {};
235 | if (self.callback.end(self.node, e) && e) e.stop();
236 | };
237 | self.dispose = function(){ self.stopListening(); };
238 | self.startListening = function() {
239 | if (self.isListening()) return;
240 | self.l = new Observer(self.attach, 'onmousedown',
241 | self, 'start');
242 | };
243 | self.stopListening = function(abort) {
244 | if (!self.isListening()) return;
245 | self.l.stop();
246 | self.l = null;
247 | if (abort && self.isDragging()) self.stop();
248 | };
249 | if (!opt.later) self.startListening();
250 | return self;
251 | };
252 | ns.Resizer = function(parent, opt) {
253 | var self = { parent: $(parent) };
254 | if (self.parent.parentNode.className == '_resizable') {
255 | self.target = self.parent;
256 | self.parent = self.parent.parentNode;
257 | } else {
258 | var outer = $new('div', { klass: '_resizable', style: {
259 | position: 'relative'
260 | } });
261 | self.parent.parentNode.replaceChild(outer, self.parent);
262 | outer.appendChild(self.parent);
263 | self.target = self.parent;
264 | self.parent = outer;
265 | }
266 | opt = opt || {};
267 | opt = merge(opt||{}, { zIndex: 100, position: 'corner' });
268 |
269 | var px = function(x){ return x+'px'; };
270 | var getPositonalStyle = function(node, what, post) {
271 | var get = function(which) {
272 | return parseInt(getStyle(node, what+which+(post||''))||
273 | getStyle(node, what+(post||''))||'0')||0;
274 | };
275 | var hash = { left: get('Left'), top: get('Top'),
276 | right: get('Right'), bottom: get('Bottom') };
277 | hash.w = hash.left + hash.right;
278 | hash.h = hash.top + hash.bottom;
279 | return hash;
280 | };
281 | var getBorder = function(node) {
282 | return getPositonalStyle(node, 'border', 'Width');
283 | };
284 | var getPadding = function(node, which) {
285 | return getPositonalStyle(node, 'padding');
286 | };
287 | var getSize = function(node) {
288 | var border = getBorder(node); var padding = getPadding(node);
289 | return { w: node.offsetWidth - border.w - padding.w,
290 | h: node.offsetHeight - border.h - padding.h };
291 | };
292 | var resize = function(who, size, delta) {
293 | var node = self[who];
294 | node.style[size] = px(getSize(node)[size.charAt(0)] + delta);
295 | };
296 | var rW = function(d){ resize('parent', 'width', d.x); };
297 | var rH = function(d){ resize('target', 'height', d.y); };
298 |
299 | var style = {}; var b = getBorder(self.target); var m=px(-2);
300 | switch (opt.position) {
301 | case 'right': self.resize = function(d){ rW(d); }; style = {
302 | cursor: 'e-resize', zIndex: opt.zIndex, right: m, top: 0,
303 | width: px((b.right||2)+2), height: '100%'
304 | }; break;
305 | case 'bottom': self.resize = function(d){ rH(d); }; style = {
306 | cursor: 's-resize', zIndex: opt.zIndex, left: 0, bottom: m,
307 | width: '100%', height: px((b.bottom||2)+2)
308 | }; break;
309 | case 'corner':
310 | default: self.resize = function(d){ rW(d); rH(d); }; style = {
311 | cursor: 'se-resize', zIndex: opt.zIndex,
312 | right: m, bottom: m
313 | }; break;
314 | }
315 | self.node = $new('div', { style: merge(style, {
316 | position: 'absolute', backgroundColor: 'transparent'
317 | }), klass: 'resize-'+opt.position });
318 | if (opt.handle) self.node.appendChild($node(opt.handle));
319 | self.parent.appendChild(self.node);
320 |
321 | self.destroy = function(){ self.parent.removeChild(self.node); };
322 |
323 | new Draggable(self.node, { callback: {
324 | start: function(node, e, pos){ self.pos = pos; return true; },
325 | move: function(node, e, pos) {
326 | self.resize({ x: pos.x-self.pos.x, y: pos.y-self.pos.y });
327 | self.pos = pos;
328 | return false;
329 | }
330 | } });
331 | return self;
332 | };
333 | ns.Console = function(parent, cmd, keyup, keydown) {
334 | var History = function(hist, index) {
335 | var self = { hist: hist||[], index: index||-1 };
336 | self.prev = function() {
337 | if (self.index < self.hist.length) self.index++;
338 | return self.hist[self.index] || '';
339 | };
340 | self.next = function() {
341 | if (self.index >= 0) self.index--;
342 | return self.hist[self.index] || '';
343 | };
344 | self.push = function(item) {
345 | if (item && self.hist[0] != item) self.hist.unshift(item);
346 | };
347 | self.clone = function(){ return History(self.hist, -1); };
348 | return self;
349 | };
350 | var self = {
351 | view: $new('ul'), parent: parent, promptChar: '>',
352 | history: new History(),
353 | command: cmd || function(){},
354 | keyup: keyup || function(){ return false; },
355 | keydown: keydown || function(){ return false; },
356 | resizer: {}
357 | };
358 | self.resizer.corner = new Resizer(parent, { handle: '\u25a0' });
359 | self.resizer.right = new Resizer(parent, { position: 'right' });
360 | self.resizer.bottom = new Resizer(parent, { position: 'bottom' });
361 | self.enclosing = parent.parentNode;
362 | parent.appendChild(self.view);
363 | self.destroy = function() {
364 | self.parent.removeChild(self.view);
365 | [ 'corner', 'right', 'bottom' ].forEach(function(which) {
366 | self.resizer[which].destroy();
367 | });
368 | };
369 | self.clear = function(node) {
370 | removeAllChildren(self.view);
371 | if (node) self.view.appendChild(node);
372 | if (self.input) self.input.focus();
373 | return self;
374 | };
375 | self.insert = function() {
376 | var li = $new('li');
377 | for (var i=0; i < arguments.length; i++) {
378 | var node = $node(arguments[i]);
379 | li.appendChild(node);
380 | }
381 | self.view.appendChild(li);
382 | self.parent.scrollTop = self.parent.scrollHeight;
383 | return li;
384 | };
385 | self.err = function(message) {
386 | var li = self.insert(message);
387 | li.className = 'error';
388 | return li;
389 | };
390 | self.prompt = function() {
391 | var p = $new('span', {
392 | klass: 'prompt', child: self.promptChar
393 | });
394 | self.input = $new('input');
395 | self.input.value = '';
396 | var li = self.insert(p, self.input);
397 | var inputPos = getPosition(self.input);
398 | var viewPos = getPosition(self.view);
399 | self.inputMargin = inputPos.x - viewPos.x + 2;
400 | self.reposition();
401 | var history = self.history.clone();
402 | new Observer(self.input, 'onkeyup', function(e) {
403 | var evnt = e.event;
404 | switch (evnt.charCode || evnt.keyCode) {
405 | case 13: // Enter
406 | var text = self.input.value;
407 | self.history.push(text);
408 | self.view.removeChild(li);
409 | self.input = null;
410 | self.insert(p, text).className = 'userinput';
411 | setTimeout(function(){ self.command(text); }, 0);
412 | break;
413 | default:
414 | if (!self.keyup(evnt)) return;
415 | }
416 | e.stop();
417 | });
418 | new Observer(self.input, 'onkeydown', function(e) {
419 | var evnt = e.event;
420 | switch (evnt.charCode || evnt.keyCode) {
421 | case 'L'.charCodeAt(0): if (!evnt.ctrlKey) return; // C-L
422 | self.clear(li);
423 | break;
424 | case 'P'.charCodeAt(0): if (!evnt.ctrlKey) return; // C-P
425 | case 38: // up
426 | self.input.value = history.prev();
427 | break;
428 | case 'N'.charCodeAt(0): if (!evnt.ctrlKey) return; // C-N
429 | case 40: // down
430 | self.input.value = history.next();
431 | break;
432 | default:
433 | if (!self.keydown(evnt)) return;
434 | }
435 | e.stop();
436 | });
437 | self.input.focus();
438 | return self;
439 | };
440 | self.reposition = function() {
441 | if (self.input) {
442 | var width = self.view.offsetWidth - self.inputMargin;
443 | self.input.style.width = width+'px';
444 | }
445 | };
446 | [ 'corner', 'right', 'bottom' ].forEach(function(p) {
447 | var fun = self.resizer[p].resize;
448 | self.resizer[p].resize = function() {
449 | fun.apply(self.resizer[p], arguments);
450 | self.reposition();
451 | };
452 | });
453 | return self;
454 | };
455 | }
456 | })(UI);
457 |
--------------------------------------------------------------------------------