├── 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 | ![Screenshot](./LambdaJS.gif) 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 | 28 |
29 | 33 |
34 | 35 | 38 | 39 |
40 | 41 | 42 | 43 |
44 | 45 |

Syntax

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 77 |
LambdaJavaScriptJavaScript 1.8
variablexxx
abstractionλx.M or \x.Mfunction(x){ return M }function(x) M
application((M) N)(M)(N)(M)(N)
declaration-var a = M;let a = M;
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 |
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 | --------------------------------------------------------------------------------