annotate lib/js/urweb.js @ 693:655bcc9b77e0

_Really_ implement embedded closure GC; extend Scriptcheck to figure out when client IDs must be assigned
author Adam Chlipala <adamc@hcoop.net>
date Sat, 04 Apr 2009 14:03:39 -0400
parents 09df0c85f306
children a5d8b470d7ca
rev   line source
adamc@692 1 // Lists
adamc@692 2
adamc@580 3 function cons(v, ls) {
adamc@690 4 return { next : ls, data : v };
adamc@690 5 }
adamc@690 6 function concat(ls1, ls2) {
adamc@690 7 return (ls1 ? cons(ls1.data, concat(ls1.next, ls2)) : ls2);
adamc@690 8 }
adamc@690 9 function member(x, ls) {
adamc@690 10 for (; ls; ls = ls.next)
adamc@690 11 if (ls.data == x)
adamc@690 12 return true;
adamc@690 13 return false;
adamc@690 14 }
adamc@690 15 function remove(x, ls) {
adamc@690 16 return (ls ? (ls.data == x ? ls.next : cons(ls.data, remove(x, ls.next))) : null);
adamc@690 17 }
adamc@690 18 function union(ls1, ls2) {
adamc@690 19 return (ls1 ? (member(ls1.data, ls2) ? union(ls1.next, ls2) : cons(ls1.data, union(ls1.next, ls2))) : ls2);
adamc@580 20 }
adamc@670 21
adamc@690 22
adamc@692 23 // Embedding closures in XML strings
adamc@692 24
adamc@693 25 function cs(f) {
adamc@693 26 return {closure: f};
adamc@693 27 }
adamc@693 28
adamc@693 29 function isWeird(v) {
adamc@693 30 return v.closure != null || v.cat1 != null;
adamc@693 31 }
adamc@693 32
adamc@692 33 function cat(s1, s2) {
adamc@693 34 if (isWeird(s1) || isWeird(s2))
adamc@693 35 return {cat1: s1, cat2: s2};
adamc@693 36 else
adamc@692 37 return s1 + s2;
adamc@692 38 }
adamc@692 39
adamc@692 40 var closures = [];
adamc@692 41
adamc@692 42 function newClosure(f) {
adamc@692 43 var n = closures.length;
adamc@692 44 closures[n] = f;
adamc@692 45 return n;
adamc@692 46 }
adamc@692 47
adamc@692 48 function cr(n) {
adamc@692 49 return closures[n]();
adamc@692 50 }
adamc@692 51
adamc@692 52 function flatten(tr) {
adamc@693 53 if (tr.cat1 != null)
adamc@693 54 return flatten(tr.cat1) + flatten(tr.cat2);
adamc@693 55 else if (tr.closure != null)
adamc@693 56 return "cr(" + newClosure(tr.closure) + ")";
adamc@693 57 else
adamc@692 58 return tr;
adamc@692 59 }
adamc@692 60
adamc@692 61 function clearClosures() {
adamc@692 62 closures = [];
adamc@692 63 }
adamc@692 64
adamc@692 65
adamc@692 66 // Dynamic tree management
adamc@692 67
adamc@690 68 function populate(node) {
adamc@690 69 var s = node.signal;
adamc@690 70 var oldSources = node.sources;
adamc@690 71 var sr = s();
adamc@690 72 var newSources = sr.sources;
adamc@690 73
adamc@690 74 for (var sp = oldSources; sp; sp = sp.next)
adamc@690 75 if (!member(sp.data, newSources))
adamc@690 76 sp.data.dyns = remove(node, sp.data.dyns);
adamc@690 77
adamc@690 78 for (var sp = newSources; sp; sp = sp.next)
adamc@690 79 if (!member(sp.data, oldSources))
adamc@690 80 sp.data.dyns = cons(node, sp.data.dyns);
adamc@690 81
adamc@690 82 node.sources = newSources;
adamc@690 83 node.recreate(sr.data);
adamc@579 84 }
adamc@574 85
adamc@580 86 function sc(v) {
adamc@690 87 return {data : v, dyns : null};
adamc@580 88 }
adamc@580 89 function sv(s, v) {
adamc@690 90 s.data = v;
adamc@690 91 for (var ls = s.dyns; ls; ls = ls.next)
adamc@690 92 if (!ls.dead)
adamc@690 93 populate(ls.data);
adamc@580 94 }
adamc@601 95 function sg(s) {
adamc@690 96 return s.data;
adamc@601 97 }
adamc@579 98
adamc@580 99 function ss(s) {
adamc@690 100 return function() { return {sources : cons(s, null), data : s.data } };
adamc@580 101 }
adamc@580 102 function sr(v) {
adamc@690 103 return function() { return {sources : null, data : v } };
adamc@580 104 }
adamc@580 105 function sb(x,y) {
adamc@690 106 return function() {
adamc@690 107 var xr = x();
adamc@690 108 var yr = y(xr.data)();
adamc@690 109 return {sources : union(xr.sources, yr.sources), data : yr.data};
adamc@690 110 };
adamc@580 111 }
adamc@571 112
adamc@604 113 function lastParent() {
adamc@604 114 var pos = document;
adamc@604 115
adamc@600 116 while (pos.lastChild && pos.lastChild.nodeType == 1)
adamc@600 117 pos = pos.lastChild;
adamc@600 118
adamc@600 119 return pos.parentNode;
adamc@600 120 }
adamc@600 121
adamc@604 122 function addNode(node) {
adamc@604 123 if (thisScript) {
adamc@604 124 thisScript.parentNode.appendChild(node);
adamc@604 125 thisScript.parentNode.removeChild(thisScript);
adamc@604 126 } else
adamc@604 127 lastParent().appendChild(node);
adamc@603 128 }
adamc@603 129
adamc@690 130 var thisScript = null;
adamc@690 131
adamc@604 132 function runScripts(node) {
adamc@604 133 var savedScript = thisScript;
adamc@603 134
adamc@692 135 var scripts = node.getElementsByTagName("script"), scriptsCopy = [];
adamc@604 136 var len = scripts.length;
adamc@646 137 for (var i = 0; i < len; ++i)
adamc@646 138 scriptsCopy[i] = scripts[i];
adamc@604 139 for (var i = 0; i < len; ++i) {
adamc@646 140 thisScript = scriptsCopy[i];
adamc@604 141 eval(thisScript.textContent);
adamc@604 142 }
adamc@604 143
adamc@604 144 thisScript = savedScript;
adamc@603 145 }
adamc@603 146
adamc@603 147
adamc@692 148 // Dynamic tree entry points
adamc@692 149
adamc@692 150 var dynDepth = 0;
adamc@692 151
adamc@571 152 function dyn(s) {
adamc@571 153 var x = document.createElement("span");
adamc@690 154 x.dead = false;
adamc@690 155 x.signal = s;
adamc@690 156 x.sources = null;
adamc@690 157 x.recreate = function(v) {
adamc@692 158 ++dynDepth;
adamc@692 159
adamc@690 160 var spans = x.getElementsByTagName("span");
adamc@690 161 for (var i = 0; i < spans.length; ++i) {
adamc@690 162 var span = spans[i];
adamc@690 163 span.dead = true;
adamc@690 164 for (var ls = span.sources; ls; ls = ls.next)
adamc@690 165 ls.data.dyns = remove(span, ls.data.dyns);
adamc@690 166 }
adamc@690 167
adamc@693 168 x.innerHTML = flatten(v);
adamc@690 169 runScripts(x);
adamc@692 170
adamc@692 171 if (--dynDepth == 0)
adamc@692 172 clearClosures();
adamc@690 173 };
adamc@690 174 populate(x);
adamc@604 175 addNode(x);
adamc@571 176 }
adamc@582 177
adamc@598 178 function inp(t, s) {
adamc@598 179 var x = document.createElement(t);
adamc@690 180 x.dead = false;
adamc@690 181 x.signal = ss(s);
adamc@690 182 x.sources = null;
adamc@690 183 x.recreate = function(v) { if (x.value != v) x.value = v; };
adamc@690 184 populate(x);
adamc@604 185 addNode(x);
adamc@598 186 x.onkeyup = function() { sv(s, x.value) };
adamc@606 187 return x;
adamc@598 188 }
adamc@598 189
adamc@692 190
adamc@692 191 // Basic string operations
adamc@692 192
adamc@597 193 function eh(x) {
adamc@597 194 return x.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;");
adamc@597 195 }
adamc@597 196
adamc@582 197 function ts(x) { return x.toString() }
adamc@586 198 function bs(b) { return (b ? "True" : "False") }
adamc@586 199
adamc@649 200 function pi(s) {
adamc@649 201 var r = parseInt(s);
adamc@649 202 if (r.toString() == s)
adamc@649 203 return r;
adamc@649 204 else
adamc@649 205 throw "Can't parse int: " + s;
adamc@649 206 }
adamc@649 207
adamc@649 208 function pfl(s) {
adamc@649 209 var r = parseFloat(s);
adamc@649 210 if (r.toString() == s)
adamc@649 211 return r;
adamc@649 212 else
adamc@649 213 throw "Can't parse float: " + s;
adamc@649 214 }
adamc@649 215
adamc@692 216 function uf(s) {
adamc@692 217 return escape(s).replace(new RegExp ("/", "g"), "%2F");
adamc@691 218 }
adamc@691 219
adamc@692 220 function uu(s) {
adamc@692 221 return unescape(s).replace(new RegExp ("\\+", "g"), " ");
adamc@692 222 }
adamc@692 223
adamc@692 224
adamc@692 225 // Error handling
adamc@692 226
adamc@669 227 function whine(msg) {
adamc@669 228 alert(msg);
adamc@669 229 throw msg;
adamc@669 230 }
adamc@669 231
adamc@649 232 function pf() {
adamc@669 233 whine("Pattern match failure");
adamc@649 234 }
adamc@589 235
adamc@603 236
adamc@692 237 // Remote calls
adamc@609 238
adamc@668 239 var client_id = 0;
adamc@668 240 var client_pass = 0;
adamc@668 241 var url_prefix = "/";
adamc@673 242 var timeout = 60;
adamc@668 243
adamc@668 244 function getXHR(uri)
adamc@609 245 {
adamc@609 246 try {
adamc@609 247 return new XMLHttpRequest();
adamc@609 248 } catch (e) {
adamc@609 249 try {
adamc@609 250 return new ActiveXObject("Msxml2.XMLHTTP");
adamc@609 251 } catch (e) {
adamc@609 252 try {
adamc@609 253 return new ActiveXObject("Microsoft.XMLHTTP");
adamc@609 254 } catch (e) {
adamc@609 255 throw "Your browser doesn't seem to support AJAX.";
adamc@609 256 }
adamc@609 257 }
adamc@609 258 }
adamc@609 259 }
adamc@609 260
adamc@668 261 function requestUri(xhr, uri) {
adamc@668 262 xhr.open("GET", uri, true);
adamc@668 263
adamc@668 264 if (client_id != 0) {
adamc@668 265 xhr.setRequestHeader("UrWeb-Client", client_id.toString());
adamc@668 266 xhr.setRequestHeader("UrWeb-Pass", client_pass.toString());
adamc@668 267 }
adamc@668 268
adamc@668 269 xhr.send(null);
adamc@668 270 }
adamc@668 271
adamc@613 272 function rc(uri, parse, k) {
adamc@609 273 var xhr = getXHR();
adamc@609 274
adamc@609 275 xhr.onreadystatechange = function() {
adamc@612 276 if (xhr.readyState == 4) {
adamc@612 277 var isok = false;
adamc@612 278
adamc@612 279 try {
adamc@612 280 if (xhr.status == 200)
adamc@612 281 isok = true;
adamc@612 282 } catch (e) { }
adamc@612 283
adamc@612 284 if (isok)
adamc@613 285 k(parse(xhr.responseText));
adamc@649 286 else {
adamc@669 287 whine("Error querying remote server!");
adamc@649 288 }
adamc@612 289 }
adamc@609 290 };
adamc@609 291
adamc@668 292 requestUri(xhr, uri);
adamc@609 293 }
adamc@667 294
adamc@667 295 function path_join(s1, s2) {
adamc@667 296 if (s1.length > 0 && s1[s1.length-1] == '/')
adamc@667 297 return s1 + s2;
adamc@667 298 else
adamc@667 299 return s1 + "/" + s2;
adamc@667 300 }
adamc@667 301
adamc@670 302 var channels = [];
adamc@670 303
adamc@670 304 function newQueue() {
adamc@670 305 return { front : null, back : null };
adamc@670 306 }
adamc@670 307 function enqueue(q, v) {
adamc@670 308 if (q.front == null) {
adamc@670 309 q.front = cons(v, null);
adamc@670 310 q.back = q.front;
adamc@670 311 } else {
adamc@670 312 var node = cons(v, null);
adamc@690 313 q.back.next = node;
adamc@670 314 q.back = node;
adamc@670 315 }
adamc@670 316 }
adamc@670 317 function dequeue(q) {
adamc@670 318 if (q.front == null)
adamc@670 319 return null;
adamc@670 320 else {
adamc@690 321 var r = q.front.data;
adamc@690 322 q.front = q.front.next;
adamc@670 323 if (q.front == null)
adamc@670 324 q.back = null;
adamc@670 325 return r;
adamc@670 326 }
adamc@670 327 }
adamc@670 328
adamc@670 329 function newChannel() {
adamc@670 330 return { msgs : newQueue(), listeners : newQueue() };
adamc@670 331 }
adamc@670 332
adamc@667 333 function listener() {
adamc@668 334 var uri = path_join(url_prefix, ".msgs");
adamc@667 335 var xhr = getXHR();
adamc@673 336 var tid, orsc, onTimeout;
adamc@673 337
adamc@673 338 var connect = function () {
adamc@673 339 xhr.onreadystatechange = orsc;
adamc@673 340 tid = window.setTimeout(onTimeout, timeout * 500);
adamc@673 341 requestUri(xhr, uri);
adamc@673 342 }
adamc@673 343
adamc@673 344 orsc = function() {
adamc@667 345 if (xhr.readyState == 4) {
adamc@673 346 window.clearTimeout(tid);
adamc@673 347
adamc@667 348 var isok = false;
adamc@667 349
adamc@667 350 try {
adamc@667 351 if (xhr.status == 200)
adamc@667 352 isok = true;
adamc@667 353 } catch (e) { }
adamc@667 354
adamc@668 355 if (isok) {
adamc@669 356 var lines = xhr.responseText.split("\n");
adamc@669 357 if (lines.length < 2)
adamc@690 358 return; // throw "Empty message from remote server";
adamc@669 359
adamc@669 360 for (var i = 0; i+1 < lines.length; i += 2) {
adamc@670 361 var chn = lines[i];
adamc@670 362 var msg = lines[i+1];
adamc@670 363
adamc@670 364 if (chn < 0)
adamc@670 365 whine("Out-of-bounds channel in message from remote server");
adamc@670 366
adamc@670 367 var ch;
adamc@670 368
adamc@670 369 if (chn >= channels.length || channels[chn] == null) {
adamc@670 370 ch = newChannel();
adamc@670 371 channels[chn] = ch;
adamc@670 372 } else
adamc@670 373 ch = channels[chn];
adamc@670 374
adamc@670 375 var listener = dequeue(ch.listeners);
adamc@670 376 if (listener == null) {
adamc@670 377 enqueue(ch.msgs, msg);
adamc@670 378 } else {
adamc@670 379 listener(msg);
adamc@670 380 }
adamc@669 381 }
adamc@669 382
adamc@673 383 connect();
adamc@668 384 }
adamc@667 385 else {
adamc@679 386 /*try {
adamc@672 387 whine("Error querying remote server for messages! " + xhr.status);
adamc@679 388 } catch (e) { }*/
adamc@667 389 }
adamc@667 390 }
adamc@667 391 };
adamc@667 392
adamc@673 393 onTimeout = function() {
adamc@673 394 xhr.abort();
adamc@673 395 connect();
adamc@673 396 };
adamc@673 397
adamc@673 398 connect();
adamc@667 399 }
adamc@670 400
adamc@670 401 function rv(chn, parse, k) {
adamc@682 402 if (chn == null)
adamc@682 403 return;
adamc@682 404
adamc@670 405 if (chn < 0)
adamc@670 406 whine("Out-of-bounds channel receive");
adamc@670 407
adamc@670 408 var ch;
adamc@670 409
adamc@670 410 if (chn >= channels.length || channels[chn] == null) {
adamc@670 411 ch = newChannel();
adamc@670 412 channels[chn] = ch;
adamc@670 413 } else
adamc@670 414 ch = channels[chn];
adamc@670 415
adamc@670 416 var msg = dequeue(ch.msgs);
adamc@670 417 if (msg == null) {
adamc@670 418 enqueue(ch.listeners, function(msg) { k(parse(msg))(null); });
adamc@670 419 } else {
adamc@670 420 k(parse(msg))(null);
adamc@670 421 }
adamc@670 422 }
adamc@693 423
adamc@693 424
adamc@693 425 // App-specific code
adamc@693 426