adamc@692: // Lists adamc@692: adamc@580: function cons(v, ls) { adamc@690: return { next : ls, data : v }; adamc@690: } adamc@690: function concat(ls1, ls2) { adamc@690: return (ls1 ? cons(ls1.data, concat(ls1.next, ls2)) : ls2); adamc@690: } adamc@690: function member(x, ls) { adamc@690: for (; ls; ls = ls.next) adamc@690: if (ls.data == x) adamc@690: return true; adamc@690: return false; adamc@690: } adamc@690: function remove(x, ls) { adamc@690: return (ls ? (ls.data == x ? ls.next : cons(ls.data, remove(x, ls.next))) : null); adamc@690: } adamc@690: function union(ls1, ls2) { adamc@690: return (ls1 ? (member(ls1.data, ls2) ? union(ls1.next, ls2) : cons(ls1.data, union(ls1.next, ls2))) : ls2); adamc@580: } adamc@703: function length(ls) { adamc@703: return (ls ? 1 + length(ls.next) : 0); adamc@703: } adamc@670: adamc@690: adamc@729: // Error handling adamc@729: adamc@729: function whine(msg) { adamc@729: alert(msg); adamc@729: throw msg; adamc@729: } adamc@729: adamc@810: function pf(loc) { adamc@835: throw ("Pattern match failure (" + loc + ")"); adamc@729: } adamc@729: adamc@798: function runHandlers(kind, ls, arg) { adamc@798: if (ls == null) adamc@798: alert(kind + ": " + arg); adamc@729: for (; ls; ls = ls.next) adamc@729: try { adamc@729: ls.data(arg)(null); adamc@729: } catch (v) { } adamc@729: } adamc@729: adamc@729: var errorHandlers = null; adamc@729: adamc@729: function onError(f) { adamc@729: errorHandlers = cons(f, errorHandlers); adamc@729: } adamc@729: adamc@729: function er(s) { adamc@798: runHandlers("Error", errorHandlers, s); adamc@729: throw {uw_error: s}; adamc@729: } adamc@729: adamc@729: var failHandlers = null; adamc@729: adamc@729: function onFail(f) { adamc@729: failHandlers = cons(f, failHandlers); adamc@729: } adamc@729: adamc@729: function doExn(v) { adamc@729: if (v == null || v.uw_error == null) { adamc@729: var s = (v == null ? "null" : v.toString()); adamc@798: runHandlers("Fail", failHandlers, s); adamc@729: } adamc@729: } adamc@729: adamc@729: var disconnectHandlers = null; adamc@729: adamc@729: function onDisconnect(f) { adamc@729: disconnectHandlers = cons(function (_){return f}, disconnectHandlers); adamc@729: } adamc@729: adamc@729: function discon() { adamc@798: runHandlers("Disconnect", disconnectHandlers, null); adamc@729: } adamc@729: adamc@729: var connectHandlers = null; adamc@729: adamc@729: function onConnectFail(f) { adamc@729: connectHandlers = cons(function (_){return f}, connectHandlers); adamc@729: } adamc@729: adamc@729: function conn() { adamc@798: runHandlers("Connect", connectHandlers, null); adamc@729: } adamc@729: adamc@729: var serverHandlers = null; adamc@729: adamc@729: function onServerError(f) { adamc@729: serverHandlers = cons(f, serverHandlers); adamc@729: } adamc@729: adamc@729: function servErr(s) { adamc@824: window.setTimeout(function () { runHandlers("Server", serverHandlers, s); }, 0); adamc@729: } adamc@729: adamc@729: adamc@692: // Embedding closures in XML strings adamc@692: adamc@693: function cs(f) { adamc@693: return {closure: f}; adamc@693: } adamc@693: adamc@693: function isWeird(v) { adamc@693: return v.closure != null || v.cat1 != null; adamc@693: } adamc@693: adamc@692: function cat(s1, s2) { adamc@693: if (isWeird(s1) || isWeird(s2)) adamc@693: return {cat1: s1, cat2: s2}; adamc@693: else adamc@692: return s1 + s2; adamc@692: } adamc@692: adamc@692: var closures = []; adamc@703: var freeClosures = null; adamc@692: adamc@692: function newClosure(f) { adamc@703: var n; adamc@703: if (freeClosures == null) { adamc@703: n = closures.length; adamc@703: } else { adamc@703: n = freeClosures.data; adamc@703: freeClosures = freeClosures.next; adamc@703: } adamc@692: closures[n] = f; adamc@692: return n; adamc@692: } adamc@692: adamc@703: function freeClosure(n) { adamc@703: closures[n] = null; adamc@703: freeClosures = cons(n, freeClosures); adamc@703: } adamc@703: adamc@692: function cr(n) { adamc@692: return closures[n](); adamc@692: } adamc@692: adamc@703: function flatten(cls, tr) { adamc@693: if (tr.cat1 != null) adamc@703: return flatten(cls, tr.cat1) + flatten(cls, tr.cat2); adamc@703: else if (tr.closure != null) { adamc@703: var cl = newClosure(tr.closure); adamc@703: cls.v = cons(cl, cls.v); adamc@703: return "cr(" + cl + ")"; adamc@703: } else adamc@692: return tr; adamc@692: } adamc@692: adamc@728: function flattenLocal(s) { adamc@728: var cls = {v : null}; adamc@728: var r = flatten(cls, s); adamc@728: for (cl = cls.v; cl != null; cl = cl.next) adamc@728: freeClosure(cl.data); adamc@728: return r; adamc@728: } adamc@728: adamc@692: adamc@692: adamc@692: // Dynamic tree management adamc@692: adamc@690: function populate(node) { adamc@690: var s = node.signal; adamc@690: var oldSources = node.sources; adamc@729: try { adamc@729: var sr = s(); adamc@729: var newSources = sr.sources; adamc@690: adamc@729: for (var sp = oldSources; sp; sp = sp.next) adamc@729: if (!member(sp.data, newSources)) adamc@729: sp.data.dyns = remove(node, sp.data.dyns); adamc@690: adamc@729: for (var sp = newSources; sp; sp = sp.next) adamc@729: if (!member(sp.data, oldSources)) adamc@729: sp.data.dyns = cons(node, sp.data.dyns); adamc@690: adamc@729: node.sources = newSources; adamc@729: node.recreate(sr.data); adamc@729: } catch (v) { adamc@729: doExn(v); adamc@729: } adamc@579: } adamc@574: adamc@580: function sc(v) { adamc@690: return {data : v, dyns : null}; adamc@580: } adamc@580: function sv(s, v) { adamc@690: s.data = v; adamc@690: for (var ls = s.dyns; ls; ls = ls.next) adamc@690: if (!ls.dead) adamc@690: populate(ls.data); adamc@580: } adamc@601: function sg(s) { adamc@690: return s.data; adamc@601: } adamc@579: adamc@580: function ss(s) { adamc@690: return function() { return {sources : cons(s, null), data : s.data } }; adamc@580: } adamc@580: function sr(v) { adamc@690: return function() { return {sources : null, data : v } }; adamc@580: } adamc@580: function sb(x,y) { adamc@690: return function() { adamc@690: var xr = x(); adamc@690: var yr = y(xr.data)(); adamc@690: return {sources : union(xr.sources, yr.sources), data : yr.data}; adamc@690: }; adamc@580: } adamc@571: adamc@604: function lastParent() { adamc@604: var pos = document; adamc@604: adamc@600: while (pos.lastChild && pos.lastChild.nodeType == 1) adamc@600: pos = pos.lastChild; adamc@600: adamc@600: return pos.parentNode; adamc@600: } adamc@600: adamc@604: function addNode(node) { adamc@604: if (thisScript) { adamc@604: thisScript.parentNode.appendChild(node); adamc@604: thisScript.parentNode.removeChild(thisScript); adamc@604: } else adamc@604: lastParent().appendChild(node); adamc@603: } adamc@603: adamc@812: function setHTML(html) { adamc@812: var x = document.createElement("span"); adamc@812: x.dead = false; adamc@812: x.signal = null; adamc@812: x.sources = null; adamc@812: x.closures = null; adamc@812: x.innerHTML = html; adamc@812: addNode(x); adamc@812: runScripts(x); adamc@812: } adamc@812: adamc@690: var thisScript = null; adamc@690: adamc@604: function runScripts(node) { adamc@604: var savedScript = thisScript; adamc@603: adamc@692: var scripts = node.getElementsByTagName("script"), scriptsCopy = []; adamc@604: var len = scripts.length; adamc@646: for (var i = 0; i < len; ++i) adamc@646: scriptsCopy[i] = scripts[i]; adamc@604: for (var i = 0; i < len; ++i) { adamc@646: thisScript = scriptsCopy[i]; adamc@729: try { adamc@729: eval(thisScript.textContent); adamc@729: } catch (v) { adamc@729: doExn(v); adamc@729: } adamc@604: } adamc@604: adamc@604: thisScript = savedScript; adamc@603: } adamc@603: adamc@603: adamc@692: // Dynamic tree entry points adamc@692: adamc@692: var dynDepth = 0; adamc@692: adamc@571: function dyn(s) { adamc@571: var x = document.createElement("span"); adamc@690: x.dead = false; adamc@690: x.signal = s; adamc@690: x.sources = null; adamc@703: x.closures = null; adamc@690: x.recreate = function(v) { adamc@703: for (var ls = x.closures; ls; ls = ls.next) adamc@703: freeClosure(ls.data); adamc@692: adamc@690: var spans = x.getElementsByTagName("span"); adamc@690: for (var i = 0; i < spans.length; ++i) { adamc@690: var span = spans[i]; adamc@690: span.dead = true; adamc@690: for (var ls = span.sources; ls; ls = ls.next) adamc@690: ls.data.dyns = remove(span, ls.data.dyns); adamc@703: for (var ls = span.closures; ls; ls = ls.next) adamc@703: freeClosure(ls.data); adamc@690: } adamc@690: adamc@703: var cls = {v : null}; adamc@703: x.innerHTML = flatten(cls, v); adamc@703: x.closures = cls.v; adamc@690: runScripts(x); adamc@690: }; adamc@703: addNode(x); adamc@690: populate(x); adamc@571: } adamc@582: adamc@817: function input(t, s, recreate) { adamc@598: var x = document.createElement(t); adamc@690: x.dead = false; adamc@690: x.signal = ss(s); adamc@690: x.sources = null; adamc@817: x.recreate = recreate(x); adamc@690: populate(x); adamc@604: addNode(x); adamc@816: adamc@816: return x; adamc@816: } adamc@816: adamc@816: function inp(s) { adamc@817: var x = input("input", s, function(x) { return function(v) { if (x.value != v) x.value = v; }; }); adamc@816: x.value = s.data; adamc@816: x.onkeyup = function() { sv(s, x.value) }; adamc@816: adamc@816: return x; adamc@816: } adamc@816: adamc@816: function sel(s, content) { adamc@817: var x = input("select", s, function(x) { return function(v) { if (x.value != v) x.value = v; }; }); adamc@816: x.innerHTML = content; adamc@816: x.value = s.data; adamc@835: if (x.value != s.data) adamc@835: sv(s, x.value); adamc@816: x.onchange = function() { sv(s, x.value) }; adamc@797: adamc@835: adamc@606: return x; adamc@598: } adamc@598: adamc@817: function chk(s) { adamc@817: var x = input("input", s, function(x) { return function(v) { if (x.checked != v) x.checked = v; }; }); adamc@817: x.type = "checkbox"; adamc@817: x.checked = s.data; adamc@817: x.onchange = function() { sv(s, x.checked) }; adamc@817: adamc@817: return x; adamc@817: } adamc@817: adamc@800: function addOnChange(x, f) { adamc@800: var old = x.onchange; adamc@800: x.onchange = function() { old(); f (); }; adamc@800: } adamc@800: adamc@692: adamc@692: // Basic string operations adamc@692: adamc@597: function eh(x) { adamc@800: if (x == null) adamc@800: return "NULL"; adamc@800: else adamc@800: return x.split("&").join("&").split("<").join("<").split(">").join(">"); adamc@597: } adamc@597: adamc@582: function ts(x) { return x.toString() } adamc@586: function bs(b) { return (b ? "True" : "False") } adamc@586: adamc@828: function sub(s, i) { return s[i]; } adamc@828: function suf(s, i) { return s.substring(i); } adamc@828: function slen(s) { return s.length; } adamc@829: function sidx(s, ch) { adamc@829: var r = s.indexOf(ch); adamc@829: if (r == -1) adamc@829: return null; adamc@829: else adamc@829: return r; adamc@829: } adamc@831: function sspn(s, chs) { adamc@831: for (var i = 0; i < s.length; ++i) adamc@831: if (chs.indexOf(s[i]) != -1) adamc@831: return i; adamc@831: adamc@831: return null; adamc@831: } adamc@829: function schr(s, ch) { adamc@829: var r = s.indexOf(ch); adamc@829: if (r == -1) adamc@829: return null; adamc@829: else adamc@829: return s.substring(r); adamc@829: } adamc@829: function ssub(s, start, len) { adamc@829: return s.substring(start, start+len); adamc@829: } adamc@821: adamc@649: function pi(s) { adamc@649: var r = parseInt(s); adamc@649: if (r.toString() == s) adamc@649: return r; adamc@649: else adamc@729: er("Can't parse int: " + s); adamc@649: } adamc@649: adamc@649: function pfl(s) { adamc@649: var r = parseFloat(s); adamc@649: if (r.toString() == s) adamc@649: return r; adamc@649: else adamc@729: er("Can't parse float: " + s); adamc@649: } adamc@649: adamc@692: function uf(s) { adamc@692: return escape(s).replace(new RegExp ("/", "g"), "%2F"); adamc@691: } adamc@691: adamc@692: function uu(s) { adamc@692: return unescape(s).replace(new RegExp ("\\+", "g"), " "); adamc@692: } adamc@692: adamc@692: adamc@603: adamc@692: // Remote calls adamc@609: adamc@703: var client_id = null; adamc@668: var client_pass = 0; adamc@668: var url_prefix = "/"; adamc@673: var timeout = 60; adamc@668: adamc@668: function getXHR(uri) adamc@609: { adamc@609: try { adamc@609: return new XMLHttpRequest(); adamc@609: } catch (e) { adamc@609: try { adamc@609: return new ActiveXObject("Msxml2.XMLHTTP"); adamc@609: } catch (e) { adamc@609: try { adamc@609: return new ActiveXObject("Microsoft.XMLHTTP"); adamc@609: } catch (e) { adamc@609: throw "Your browser doesn't seem to support AJAX."; adamc@609: } adamc@609: } adamc@609: } adamc@609: } adamc@609: adamc@736: var sig = null; adamc@736: adamc@736: function requestUri(xhr, uri, needsSig) { adamc@668: xhr.open("GET", uri, true); adamc@668: adamc@703: if (client_id != null) { adamc@668: xhr.setRequestHeader("UrWeb-Client", client_id.toString()); adamc@668: xhr.setRequestHeader("UrWeb-Pass", client_pass.toString()); adamc@668: } adamc@668: adamc@736: if (needsSig) { adamc@736: if (sig == null) adamc@736: whine("Missing cookie signature!"); adamc@736: adamc@736: xhr.setRequestHeader("UrWeb-Sig", sig); adamc@736: } adamc@736: adamc@668: xhr.send(null); adamc@668: } adamc@668: adamc@736: function rc(uri, parse, k, needsSig) { adamc@728: uri = flattenLocal(uri); adamc@609: var xhr = getXHR(); adamc@609: adamc@609: xhr.onreadystatechange = function() { adamc@612: if (xhr.readyState == 4) { adamc@612: var isok = false; adamc@612: adamc@612: try { adamc@612: if (xhr.status == 200) adamc@612: isok = true; adamc@612: } catch (e) { } adamc@612: adamc@729: if (isok) { adamc@729: try { adamc@729: k(parse(xhr.responseText)); adamc@729: } catch (v) { adamc@729: doExn(v); adamc@729: } adamc@729: } else { adamc@729: conn(); adamc@649: } adamc@612: } adamc@609: }; adamc@609: adamc@736: requestUri(xhr, uri, needsSig); adamc@609: } adamc@667: adamc@667: function path_join(s1, s2) { adamc@667: if (s1.length > 0 && s1[s1.length-1] == '/') adamc@667: return s1 + s2; adamc@667: else adamc@667: return s1 + "/" + s2; adamc@667: } adamc@667: adamc@670: var channels = []; adamc@670: adamc@670: function newQueue() { adamc@670: return { front : null, back : null }; adamc@670: } adamc@670: function enqueue(q, v) { adamc@670: if (q.front == null) { adamc@670: q.front = cons(v, null); adamc@670: q.back = q.front; adamc@670: } else { adamc@670: var node = cons(v, null); adamc@690: q.back.next = node; adamc@670: q.back = node; adamc@670: } adamc@670: } adamc@670: function dequeue(q) { adamc@670: if (q.front == null) adamc@670: return null; adamc@670: else { adamc@690: var r = q.front.data; adamc@690: q.front = q.front.next; adamc@670: if (q.front == null) adamc@670: q.back = null; adamc@670: return r; adamc@670: } adamc@670: } adamc@670: adamc@670: function newChannel() { adamc@670: return { msgs : newQueue(), listeners : newQueue() }; adamc@670: } adamc@670: adamc@667: function listener() { adamc@668: var uri = path_join(url_prefix, ".msgs"); adamc@667: var xhr = getXHR(); adamc@673: var tid, orsc, onTimeout; adamc@673: adamc@673: var connect = function () { adamc@673: xhr.onreadystatechange = orsc; adamc@673: tid = window.setTimeout(onTimeout, timeout * 500); adamc@736: requestUri(xhr, uri, false); adamc@673: } adamc@673: adamc@673: orsc = function() { adamc@667: if (xhr.readyState == 4) { adamc@673: window.clearTimeout(tid); adamc@673: adamc@667: var isok = false; adamc@667: adamc@667: try { adamc@667: if (xhr.status == 200) adamc@667: isok = true; adamc@667: } catch (e) { } adamc@667: adamc@668: if (isok) { adamc@669: var lines = xhr.responseText.split("\n"); adamc@729: if (lines.length < 2) { adamc@729: discon(); adamc@729: return; adamc@729: } adamc@669: adamc@669: for (var i = 0; i+1 < lines.length; i += 2) { adamc@670: var chn = lines[i]; adamc@670: var msg = lines[i+1]; adamc@670: adamc@670: if (chn < 0) adamc@670: whine("Out-of-bounds channel in message from remote server"); adamc@670: adamc@670: var ch; adamc@670: adamc@670: if (chn >= channels.length || channels[chn] == null) { adamc@670: ch = newChannel(); adamc@670: channels[chn] = ch; adamc@670: } else adamc@670: ch = channels[chn]; adamc@670: adamc@670: var listener = dequeue(ch.listeners); adamc@670: if (listener == null) { adamc@670: enqueue(ch.msgs, msg); adamc@670: } else { adamc@728: try { adamc@728: listener(msg); adamc@728: } catch (v) { adamc@728: doExn(v); adamc@728: } adamc@670: } adamc@669: } adamc@669: adamc@673: connect(); adamc@668: } adamc@667: else { adamc@729: try { adamc@824: if (xhr.status != 0) adamc@824: servErr("Error querying remote server for messages: " + xhr.status); adamc@736: } catch (e) { servErr("Error querying remote server for messages"); } adamc@667: } adamc@667: } adamc@667: }; adamc@667: adamc@673: onTimeout = function() { adamc@673: xhr.abort(); adamc@673: connect(); adamc@673: }; adamc@673: adamc@673: connect(); adamc@667: } adamc@670: adamc@670: function rv(chn, parse, k) { adamc@682: if (chn == null) adamc@682: return; adamc@682: adamc@670: if (chn < 0) adamc@670: whine("Out-of-bounds channel receive"); adamc@670: adamc@670: var ch; adamc@670: adamc@670: if (chn >= channels.length || channels[chn] == null) { adamc@670: ch = newChannel(); adamc@670: channels[chn] = ch; adamc@670: } else adamc@670: ch = channels[chn]; adamc@670: adamc@670: var msg = dequeue(ch.msgs); adamc@670: if (msg == null) { adamc@670: enqueue(ch.listeners, function(msg) { k(parse(msg))(null); }); adamc@670: } else { adamc@728: try { adamc@728: k(parse(msg))(null); adamc@728: } catch (v) { adamc@728: doExn(v); adamc@728: } adamc@670: } adamc@670: } adamc@693: adamc@693: adamc@693: // App-specific code adamc@693: