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@670: adamc@690: 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@692: adamc@692: function newClosure(f) { adamc@692: var n = closures.length; adamc@692: closures[n] = f; adamc@692: return n; adamc@692: } adamc@692: adamc@692: function cr(n) { adamc@692: return closures[n](); adamc@692: } adamc@692: adamc@692: function flatten(tr) { adamc@693: if (tr.cat1 != null) adamc@693: return flatten(tr.cat1) + flatten(tr.cat2); adamc@693: else if (tr.closure != null) adamc@693: return "cr(" + newClosure(tr.closure) + ")"; adamc@693: else adamc@692: return tr; adamc@692: } adamc@692: adamc@692: function clearClosures() { adamc@692: closures = []; adamc@692: } 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@690: var sr = s(); adamc@690: var newSources = sr.sources; adamc@690: adamc@690: for (var sp = oldSources; sp; sp = sp.next) adamc@690: if (!member(sp.data, newSources)) adamc@690: sp.data.dyns = remove(node, sp.data.dyns); adamc@690: adamc@690: for (var sp = newSources; sp; sp = sp.next) adamc@690: if (!member(sp.data, oldSources)) adamc@690: sp.data.dyns = cons(node, sp.data.dyns); adamc@690: adamc@690: node.sources = newSources; adamc@690: node.recreate(sr.data); 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@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@604: eval(thisScript.textContent); 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@690: x.recreate = function(v) { adamc@692: ++dynDepth; 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@690: } adamc@690: adamc@693: x.innerHTML = flatten(v); adamc@690: runScripts(x); adamc@692: adamc@692: if (--dynDepth == 0) adamc@692: clearClosures(); adamc@690: }; adamc@690: populate(x); adamc@604: addNode(x); adamc@571: } adamc@582: adamc@598: function inp(t, s) { adamc@598: var x = document.createElement(t); adamc@690: x.dead = false; adamc@690: x.signal = ss(s); adamc@690: x.sources = null; adamc@690: x.recreate = function(v) { if (x.value != v) x.value = v; }; adamc@690: populate(x); adamc@604: addNode(x); adamc@598: x.onkeyup = function() { sv(s, x.value) }; adamc@606: return x; adamc@598: } adamc@598: adamc@692: adamc@692: // Basic string operations adamc@692: adamc@597: function eh(x) { adamc@597: 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@649: function pi(s) { adamc@649: var r = parseInt(s); adamc@649: if (r.toString() == s) adamc@649: return r; adamc@649: else adamc@649: throw "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@649: throw "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@692: // Error handling adamc@692: adamc@669: function whine(msg) { adamc@669: alert(msg); adamc@669: throw msg; adamc@669: } adamc@669: adamc@649: function pf() { adamc@669: whine("Pattern match failure"); adamc@649: } adamc@589: adamc@603: adamc@692: // Remote calls adamc@609: adamc@668: var client_id = 0; 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@668: function requestUri(xhr, uri) { adamc@668: xhr.open("GET", uri, true); adamc@668: adamc@668: if (client_id != 0) { adamc@668: xhr.setRequestHeader("UrWeb-Client", client_id.toString()); adamc@668: xhr.setRequestHeader("UrWeb-Pass", client_pass.toString()); adamc@668: } adamc@668: adamc@668: xhr.send(null); adamc@668: } adamc@668: adamc@613: function rc(uri, parse, k) { 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@612: if (isok) adamc@613: k(parse(xhr.responseText)); adamc@649: else { adamc@669: whine("Error querying remote server!"); adamc@649: } adamc@612: } adamc@609: }; adamc@609: adamc@668: requestUri(xhr, uri); 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@673: requestUri(xhr, uri); 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@669: if (lines.length < 2) adamc@690: return; // throw "Empty message from remote server"; 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@670: listener(msg); adamc@670: } adamc@669: } adamc@669: adamc@673: connect(); adamc@668: } adamc@667: else { adamc@679: /*try { adamc@672: whine("Error querying remote server for messages! " + xhr.status); adamc@679: } catch (e) { }*/ 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@670: k(parse(msg))(null); adamc@670: } adamc@670: } adamc@693: adamc@693: adamc@693: // App-specific code adamc@693: