diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,5 @@
+Version change log.
+
+=0.9.1= 
+Increased the upper bound on data-default in dependencies. See
+https://github.com/jswebtools/language-ecmascript/pull/5
diff --git a/language-ecmascript.cabal b/language-ecmascript.cabal
--- a/language-ecmascript.cabal
+++ b/language-ecmascript.cabal
@@ -1,5 +1,5 @@
 Name:           language-ecmascript
-Version:        0.9
+Version:        0.9.1
 Cabal-Version:	>= 1.10
 Copyright:      (c) 2007-2012 Brown University, (c) 2008-2010 Claudiu Saftoiu,
                 (c) 2012 Stevens Institute of Technology
@@ -10,8 +10,9 @@
 Maintainer:     Andrey Chudnov <oss@chudnov.com>
 Homepage:       http://github.com/jswebtools/language-ecmascript
 Bug-reports:    http://github.com/jswebtools/language-ecmascript/issues
-Stability:      provisional
-Tested-with:    GHC==7.0.4, GHC==7.4.1
+Stability:      experimental
+Tested-with:    GHC==7.4.1
+Extra-Source-Files: tests/parse-pretty/*.js, CHANGELOG
 Category:       Language
 Build-Type:     Simple
 Synopsis:       JavaScript analysis tools
@@ -20,7 +21,7 @@
   Includes a parser, pretty-printer, and basic building blocks for more
   sophisticated tools.
 
-  This package supercedes package WebBits.
+  This package used to be called WebBits.
 
 Source-repository head
    type: git
@@ -42,7 +43,7 @@
     containers >= 0.1,
     syb >= 0.1,
     uniplate >= 1.6 && <1.7,
-    data-default >= 0.4 && <0.5
+    data-default >= 0.4 && <0.6
   ghc-options:
     -fwarn-incomplete-patterns
   Exposed-Modules:
@@ -72,7 +73,7 @@
     directory,
     filepath,
     HUnit,
-    data-default >=0.4 && <0.5
+    data-default >=0.4 && <0.6
   Default-Extensions: DeriveDataTypeable, ScopedTypeVariables, DeriveFunctor, DeriveFoldable, DeriveTraversable, FlexibleContexts
   Default-Language: Haskell2010
   ghc-options:
diff --git a/tests/parse-pretty/add.js b/tests/parse-pretty/add.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/add.js
@@ -0,0 +1,1 @@
+1+2;;;;
diff --git a/tests/parse-pretty/do-while.js b/tests/parse-pretty/do-while.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/do-while.js
@@ -0,0 +1,3 @@
+do {
+  x = y;
+} while (element);
diff --git a/tests/parse-pretty/flapjax-fxinternal-22dec2008.js b/tests/parse-pretty/flapjax-fxinternal-22dec2008.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/flapjax-fxinternal-22dec2008.js
@@ -0,0 +1,2552 @@
+function flapjaxInit(options) {
+  // compress via http://alex.dojotoolkit.org/shrinksafe/
+  // make sure to change final eval call to use exported library renamed 
+  // name
+  
+  var defaultOptions = {
+    includeSynonyms: true,
+    exportMisc: true,
+    exportCore: true,
+    exportDOM: true,
+    exportDOMMisc: true,
+    exportWS: true,
+    hide: [ ],
+    show: [ ],
+    redefine: false,
+  };
+  
+  if (options === false) {
+    options = { hide: [ ], show: [ ] };
+  }
+  else if (options === true || !options) {
+    options = { };
+  }
+
+  // Fill in any missing options using the defaults.
+  for (var option in defaultOptions) {
+    if (options[option] === undefined) {
+      options[option] = defaultOptions[option];
+    }
+  };
+  
+  // Transform hide and show lists into tables
+  var hideArray = options.hide;
+  options.hide = { };
+  for (var i = 0; i < hideArray.length; i++) {
+    options.hide[hideArray[i]] = true;
+  }
+  var showArray = options.show;
+  options.show = { };
+  for (var i = 0; i < showArray.length; i++) {
+    options.show[showArray[i]] = true;
+  }
+  
+  var warn = function(s) {
+    /*if (console && console.warn) {
+      console.warn(s);
+    }*/
+  };
+   
+  
+  var flapjax = {     
+    version: 4, 
+    base: {}, //pulses
+    combinators: {}, // combinators yielding nodes
+	  behaviours: {},
+    dom: {} // dom convenience methods and combinators
+  };
+  
+  var d = flapjax.dom;
+  
+	flapjax.pub = {util:flapjax};
+  
+	var annotate = function(fn,names,protoArg,protoObjs,protoNames) {
+		for(var i=0; i<names.length;i++) {
+			flapjax.pub[names[i]] = fn;
+		}
+		if(protoArg != undefined) {
+			var pf = function() {
+				var args = slice(arguments,0);
+				args.splice(protoArg,0,this);
+				return fn.apply(this,args);
+			}
+			for(var i=0; i<protoObjs.length; i++) {
+				for(var j=0; j<protoNames.length;j++) {
+					protoObjs[i][protoNames[j]] = pf;
+				}
+			}
+		}
+	}
+  
+  var exports = { };
+  var synonyms = { };
+
+  var misc = { }; /* Used as a symbol */
+  var core = { }; /* Used as a symbol */
+  var dom = { }; /* Used as a symbol */
+  var domMisc = { }; /* Used as a symbol */
+  var ws = { }; /* Used as a symbol */
+  
+  var fxExport = function(exportCategory,exportVal,
+                          exportName /* , synonyms */) {
+    if (!(exportCategory instanceof Object)) {
+      throw 'fxExport: category is ' + exportCategory;
+    };
+
+    if (typeof(exportName) != 'string') {
+      console.log(exportVal);
+      throw 'fxExport: name is ' + exportName;   
+    };
+
+    exportVal.__flapjaxCategory = exportCategory;
+    exports[exportName] = exportVal;
+    for (var i = 3; i < arguments.length; i++) {
+      exports[arguments[i]] = exportVal;
+      synonyms[arguments[i]] = true;
+    }
+  }
+  
+  var fxMethodSynonyms = function(obj,method) {
+    if (options.includeSynonyms === true) {
+      for (var i = 2; i < arguments.length; i++) {
+        obj.prototype[arguments[i]] = obj.prototype[method];
+      };
+    };
+  };
+  
+  //////////////////////////////////////////////////////////////////////////////
+  // Miscellaneous functions
+  
+	//credit 4umi
+	//slice: Array a * Integer * Integer -> Array a
+	var slice = function (arr, start, stop) {
+		var i, len = arr.length, r = [];
+		if( !stop ) { stop = len; }
+		if( stop < 0 ) { stop = len + stop; }
+		if( start < 0 ) { start = len - start; }
+		if( stop < start ) { i = start; start = stop; stop = i; }
+		for( i = 0; i < stop - start; i++ ) { r[i] = arr[start+i]; }
+		return r;
+	}
+  
+	var isEqual = function (a,b) {
+		return (a == b) ||
+			( (((typeof(a) == 'number') && isNaN(a)) || a == 'NaN') &&
+			  (((typeof(b) == 'number') && isNaN(b)) || b == 'NaN') );
+	};
+
+	//member: a * Array b -> Boolean
+	var member = function(elt, lst) {
+		for (var i = 0; i < lst.length; i++) { 
+			if (isEqual(lst[i], elt)) {return true;} 
+		}
+		return false;
+	};
+  
+	var zip = function(arrays) {
+		if (arrays.length == 0) return [];
+		var ret = [];
+		for(var i=0; i<arrays[0].length;i++) {
+			ret.push([]);
+			for(var j=0; j<arrays.length;j++) 
+				ret[i].push(arrays[j][i]);
+		}
+		return ret;
+	}
+
+	//map: (a * ... -> z) * [a] * ... -> [z]
+	var map = function (fn) {
+		var arrays = slice(arguments, 1);
+		if (arrays.length === 0) { return []; }
+		else if (arrays.length === 1) {
+			var ret = [];
+			for(var i=0; i<arrays[0].length; i++) {ret.push(fn(arrays[0][i]));}
+			return ret;
+		}
+		else {
+			var ret = zip(arrays);
+			var o = new Object();
+			for(var i=0; i<ret.length; i++) {ret[i] = fn.apply(o,ret[i]);}
+			return ret;
+		}
+	};
+	  
+	//filter: (a -> Boolean) * Array a -> Array a
+	var filter = function (predFn, arr) {
+		var res = [];
+		for (var i = 0; i < arr.length; i++) { 
+			if (predFn(arr[i])) { res.push(arr[i]); }
+		}
+		return res;
+	};
+	  
+  //fold: (a * .... * accum -> accum) * accum * [a] * ... -> accum
+  //fold over list(s), left to right
+  var fold = function(fn, init /* arrays */) {
+		var lists = slice(arguments, 2);
+		if (lists.length === 0) { return init; }
+		else if(lists.length === 1) {
+			var acc = init;
+			for(var i = 0; i < lists[0].length; i++) {
+				acc = fn(lists[0][i],acc);
+			}
+			return acc;
+		}
+		else {
+			var acc = init;
+			for (var i = 0; i < lists[0].length; i++) {
+				var args = map( function (lst) { return lst[i];}, 
+		          lists);
+				args.push(acc);
+				acc = fn.apply({}, args);
+			}
+			return acc;
+		}
+  };
+	  
+  //foldR: (a * .... * accum -> accum) * accum * [a] * ... -> accum
+	//fold over list(s), right to left, fold more memory efficient (left to right)
+	var foldR = function (fn, init /* arrays */) {
+    var lists = slice(arguments, 2);
+		if (lists.length === 0) { return init; }
+		else if(lists.length === 1) {
+			var acc = init;
+			for(var i=lists[0].length - 1; i > -1; i--)
+				acc = fn(lists[0][i],acc);
+			return acc;
+		}
+		else {
+			var acc = init;
+			for (var i = lists[0].length - 1; i > -1; i--) {
+				var args = map( function (lst) { return lst[i];}, 
+		          lists);
+				args.push(acc);
+				acc = fn.apply({}, args);
+			}
+			return acc;     
+		}
+	};
+  
+  var pushAll = function(destArray,srcArray) {
+    for (var i = 0; i < srcArray.length; i++) {
+      destArray.push(srcArray[i]);
+    }
+    return destArray;
+  };
+	  
+  fxExport(misc,slice,'slice');
+  fxExport(misc,member,'member');
+  fxExport(misc,map,'map');
+  fxExport(misc,map,'forEach');
+  fxExport(misc,filter,'filter');
+  fxExport(misc,fold,'fold');
+  fxExport(misc,foldR,'foldR');
+  
+  //////////////////////////////////////////////////////////////////////////////
+  // Flapjax core
+  
+  //Pulse: Stamp * Path * Obj
+  var Pulse = function (stamp, value) {
+    this.stamp = stamp;
+    this.value = value;
+  };
+
+
+  //Probably can optimize as we expect increasing insert runs etc
+  var PQ = function () {
+    var ctx = this;
+    ctx.val = [];
+    this.insert = function (kv) {
+			ctx.val.push(kv);
+			var kvpos = ctx.val.length-1;
+			while(kvpos > 0 && kv.k < ctx.val[Math.floor((kvpos-1)/2)].k) {
+				var oldpos = kvpos;
+				kvpos = Math.floor((kvpos-1)/2);
+				ctx.val[oldpos] = ctx.val[kvpos];
+				ctx.val[kvpos] = kv;
+			}
+    };
+    this.isEmpty = function () { 
+      return ctx.val.length === 0; 
+    };
+    this.pop = function () {
+			if(ctx.val.length == 1) {
+				return ctx.val.pop();
+			}
+			var ret = ctx.val.shift();
+			ctx.val.unshift(ctx.val.pop());
+			var kvpos = 0;
+			var kv = ctx.val[0];
+			while(1) {
+				var leftChild = (kvpos*2+1 < ctx.val.length ? ctx.val[kvpos*2+1].k : kv.k+1);
+				var rightChild = (kvpos*2+2 < ctx.val.length ? ctx.val[kvpos*2+2].k : kv.k+1);
+				if(leftChild > kv.k && rightChild > kv.k)
+					break;
+				else if(leftChild < rightChild) {
+					ctx.val[kvpos] = ctx.val[kvpos*2+1];
+					ctx.val[kvpos*2+1] = kv;
+					kvpos = kvpos*2+1;
+				}
+				else {
+					ctx.val[kvpos] = ctx.val[kvpos*2+2];
+					ctx.val[kvpos*2+2] = kv;
+					kvpos = kvpos*2+2;
+				}
+			}
+			return ret;
+		};
+  };
+  
+  var lastRank = 0;
+  var stamp = 1;
+  var nextStamp = function () { return ++stamp; };
+  
+  //propagatePulse: Pulse * Array Node -> 
+  //Send the pulse to each node 
+  var propagatePulse = function (pulse, node) {
+    var queue = new PQ(); //topological queue for current timestep
+	queue.insert({k:node.rank,n:node,v:pulse});
+	while(!(queue.isEmpty())) {
+		var qv = queue.pop();
+		qv.n.updater(function(nextPulse) {
+			for(var i=0; i<qv.n.sendsTo.length;i++)
+				queue.insert({k:qv.n.sendsTo[i].rank,n:qv.n.sendsTo[i],v:nextPulse});
+		},new Pulse(qv.v.stamp,qv.v.value));
+	}
+  };
+  
+  //Event: Array Node b * ( (Pulse a -> Void) * Pulse b -> Void)
+  var EventStream = function (nodes,updater) {
+    this.updater = updater;
+    
+    this.sendsTo = []; //forward link
+    
+    for (var i = 0; i < nodes.length; i++) {
+      nodes[i].sendsTo.push(this);
+    }
+    
+    this.rank = ++lastRank;
+  };
+	EventStream.prototype = new Object();
+  
+  fxExport(core,EventStream,'EventStream');
+  
+  
+  //createNode: Array Node a * ( (Pulse b ->) * (Pulse a) -> Void) -> Node b
+  var createNode = function (nodes, updater) {
+		return new EventStream(nodes,updater);
+	};
+  
+  //attachListenerNode: Node * Node -> Voids
+  //flow from node to dependent
+  //note: does not add flow as counting for rank nor updates parent ranks
+  var attachListener = function (node, dependent) {
+    if (!(node instanceof EventStream)) { throw 'attachListenenerNode: expects event as first arg';} //SAFETY
+    if (!(dependent instanceof EventStream)) { throw 'attachListenenerNode: expects event as second arg';} //SAFETY
+    
+    node.sendsTo.push(dependent);
+		if(node.rank > dependent.rank) {
+			var lowest = lastRank+1;
+			var q = [dependent];
+			while(q.length) {
+				var cur = q.splice(0,1)[0];
+				cur.rank = ++lastRank;
+				q = q.concat(cur.sendsTo);
+			}
+		}
+  };
+  
+  //removeListenerNode: Node * Node -> Boolean
+  //remove flow from node to dependent
+  //note: does not remove flow as counting for rank nor updates parent ranks
+  var removeListener = function (node, dependent)
+  {
+    if (!(node instanceof EventStream)) { throw 'removeListenerNode: expects event as first arg';} //SAFETY
+    if (!(dependent instanceof EventStream)) { throw 'removeListenenerNode: expects event as second arg';} //SAFETY
+    
+    var foundSending = false;
+    for (var i = 0; i < node.sendsTo.length && !foundSending; i++) {
+      if (node.sendsTo[i] == dependent) {
+        node.sendsTo.splice(i, 1);
+        foundSending = true;
+      }
+    }
+    
+    return foundSending;
+  };
+  
+  // An internal_e is a node that simply propagates all pulses it receives.  It's used internally by various 
+  // combinators.
+  var internal_e = function(dependsOn) {
+    return createNode(dependsOn || [ ],function(send,pulse) { send(pulse); });
+  }
+  
+  var zero_e = function() {
+    return createNode([],function(send,pulse) {
+        throw ('zero_e : received a value; zero_e should not receive a value; the value was ' + pulse.value);
+    });
+  };
+  fxExport(core,zero_e,'zero_e');
+  
+  var one_e = function(val) {
+    var sent = false;
+    var evt = createNode([],function(send,pulse) {
+      if (sent) {
+        throw ('one_e : received an extra value');
+      }
+      sent = true;
+      send(pulse);
+    });
+    window.setTimeout(function() { sendEvent(evt,val); },0);
+    return evt;
+  };
+  fxExport(core,one_e,'one_e');
+  
+  // a.k.a. mplus; merge_e(e1,e2) == merge_e(e2,e1)
+  var merge_e = function() {
+    if (arguments.length == 0) {
+      return zero_e();
+    }
+    else {
+      var deps = slice(arguments,0);
+      return internal_e(deps);
+    }
+  };
+  fxExport(core,merge_e,'merge_e');
+
+  EventStream.prototype.merge_e = function() {
+    var deps = slice(arguments,0);
+    deps.push(this);
+    return internal_e(deps);
+  };
+  fxMethodSynonyms(EventStream,'merge_e','merge');
+
+  EventStream.prototype.constant_e = function(constantValue) {
+    return createNode([this],function(send,pulse) {
+      pulse.value = constantValue;
+      send(pulse);
+    });
+  };
+  fxMethodSynonyms(EventStream,'constant_e','constant','replaceValue_e','replaceValue');
+  
+  var constant_e = function(e) { return e.constant_e(); };
+  fxExport(core,constant_e,'constant_e','replaceValue_e');
+  
+	var createTimeSyncNode = function(nodes) {
+		var nqs = map(function(n) {
+				var qpulse = [];
+				return {q:qpulse,v:createNode([n],function(s,p) {qpulse.push(p.value); s(p);},nodes)};
+    },nodes);
+		return createNode(
+      map(function(n) {return n.v;},nqs), function(s,p) {
+        var allfull = fold(function(n,acc) {return n.q.length && acc;},true,nqs);
+        if(allfull) {
+          p.value = map(function(n) {return n.q.shift();},nqs);
+          s(p);
+      }});
+	};
+  
+	//This is up here so we can add things to its prototype that are in flapjax.combinators
+  var Behaviour = function (event, init, updater) {
+    if (!(event instanceof EventStream)) { 
+      throw 'Behaviour: expected event as second arg'; 
+    }
+    
+    var behave = this;
+    this.last = init;
+    
+    //sendEvent to this might impact other nodes that depend on this event
+    //sendBehaviour defaults to this one
+    this.underlyingRaw = event;
+    
+    //unexposed, sendEvent to this will only impact dependents of this behaviour
+    this.underlying =
+    createNode(
+      [event], 
+	  (updater ? 
+	   function (s, p) {behave.last = updater(p.value); p.value = behave.last; s(p);} : 
+	   function (s, p) {behave.last = p.value;  s(p);}));
+  };
+	Behaviour.prototype = new Object();
+  fxExport(core,Behaviour,'Behaviour');
+  fxExport(core,Behaviour,'Behavior');
+  
+  var receiver_e = function() {
+    var evt = internal_e();
+    evt.sendEvent = function(value) {
+      propagatePulse(new Pulse(nextStamp(), value),evt);
+    };
+    return evt;
+  };
+  fxExport(core,receiver_e,'receiver_e');
+  
+  //note that this creates a new timestamp and new event queue
+  var sendEvent = function (node, value) {
+    if (!(node instanceof EventStream)) { throw 'sendEvent: expected Event as first arg'; } //SAFETY
+    
+    propagatePulse(new Pulse(nextStamp(), value),node);
+  };
+  fxExport(core,sendEvent,'sendEvent');
+  synonyms['sendEvent'] = true; // Hack: sendEvent simply should not be exported; calling it a synonym deprecates it.
+  
+  // bind_e :: EventStream a * (a -> EventStream b) -> EventStream b
+  EventStream.prototype.bind_e = function(k) {
+    /* m.sendsTo result_e
+     * result_e.sendsTo prev_e
+     * prev_e.sendsTo return_e
+     */
+    var m = this;
+    var prev_e = false;
+    
+    var out_e = createNode([],function(send,pulse) { send(pulse); });
+    out_e.name = "bind out_e";
+    
+    var in_e = createNode([m], function (send,pulse) {
+      if (prev_e) { 
+        removeListener(prev_e,out_e);
+      }
+      prev_e = k(pulse.value);
+      if (prev_e instanceof EventStream) {
+        attachListener(prev_e,out_e);
+      }
+      else {
+        throw "bind_e : expected EventStream";
+      }
+    });
+    in_e.name = "bind in_e";
+    
+    return out_e;
+  };
+
+  /* Could be written as:
+   *
+   * e.bind_e(function(v) { return one_e(f(v)); })
+   */
+  EventStream.prototype.lift_e = function(f) {
+    if (!(f instanceof Function)) {
+      throw ('lift_e : expected a function as the first argument; received ' + f);
+    };
+    
+    return createNode([this],function(send,pulse) {
+      pulse.value = f(pulse.value);
+      send(pulse);
+    });
+  };
+  fxMethodSynonyms(EventStream,'lift_e',
+    'transform_e','map_e','apply_e','lift','transform','map','apply');
+  
+  EventStream.prototype.not_e = function() { return this.lift_e(function(v) { return !v; }); };
+  fxMethodSynonyms(EventStream,'not_e','not');
+  
+  var not_e = function(e) { return e.not_e(); };
+  fxExport(core,not_e,'not_e','not');
+  
+  EventStream.prototype.filter_e = function(pred) {
+    if (!(pred instanceof Function)) {
+      throw ('filter_e : expected predicate; received ' + pred);
+    };
+    
+    // Can be a bind_e
+    return createNode([this],
+      function(send,pulse) {
+        if (pred(pulse.value)) { send(pulse); }
+    });
+  };
+  fxMethodSynonyms(EventStream,'filter_e','filter');
+  
+  var filter_e = function(e,p) { return e.filter_e(p); };
+  fxExport(core,filter_e,'filter_e');
+  
+  // Fires just once.
+  EventStream.prototype.once_e = function() {
+    var done = false;
+    // Alternately: this.collect_e(0,\n v -> (n+1,v)).filter_e(\(n,v) -> n == 1).lift_e(fst)
+    return createNode([this],function(send,pulse) {
+      if (!done) { done = true; send(pulse); }
+    });
+  };
+  fxMethodSynonyms(EventStream,'once_e','once')
+  
+  var once_e = function(e) { return e.once_e(); };
+  fxExport(core,once_e,'once_e');
+  
+  EventStream.prototype.skipFirst_e = function() {
+    var skipped = false;
+    return createNode([this],function(send,pulse) {
+      if (skipped)
+        { send(pulse); }
+      else
+        { skipped = true; }
+    });
+  };
+  fxMethodSynonyms(EventStream,'skipFirst_e','skipFirst');
+  
+  var skipFirst_e = function(e) { return e.skipFirst_e(); };
+  fxExport(core,skipFirst_e,'skipFirst_e');
+  
+  EventStream.prototype.collect_e = function(init,fold) {
+    var acc = init;
+    return this.lift_e(
+      function (n) {
+        var next = fold(n, acc);
+        acc = next;
+        return next;
+      });
+  };
+  fxMethodSynonyms(EventStream,'collect_e','collect','transformWithMemory');
+  
+  var collect_e = function(e,i,f) { return e.collect_e(i,f); };
+  fxExport(core,collect_e,'collect_e');
+  
+  // a.k.a. join
+  EventStream.prototype.switch_e = function() {
+    return this.bind_e(function(v) { return v; });
+  };
+  fxMethodSynonyms(EventStream,'switch_e','forwardLatest');
+  
+  var switch_e = function(e) { return e.switch_e(); };
+  fxExport(core,switch_e,'switch_e');
+  
+  EventStream.prototype.if_e = function(thenE,elseE) {
+    var testStamp = -1;
+    var testValue = false;
+    
+    createNode([this],function(_,pulse) { testStamp = pulse.stamp; testValue = pulse.value; });
+    
+    return merge_e(
+      createNode([thenE],function(send,pulse) { if (testValue && (testStamp == pulse.stamp)) { send(pulse); } }),
+      createNode([elseE],function(send,pulse) { if (!testValue && (testStamp == pulse.stamp)) { send(pulse); } }));
+  };
+  fxMethodSynonyms(EventStream,'if_e','choose_e','choose');
+  
+  var if_e = function(test,thenE,elseE) {
+    if (test instanceof EventStream)
+      { return test.if_e(thenE,elseE); }
+    else
+      { return test ? thenE : elseE; }
+  };
+  fxExport(core,if_e,'if_e','choose_e');
+      
+  var cond_e = function (/*. predValArrays */) {
+    var predValArrays = slice(arguments, 0);
+    var acc = zero_e();
+    for (var i = predValArrays.length - 1; i > -1; i--) {
+      acc = if_e(predValArrays[i][0],predValArrays[i][1], acc);
+    }
+    return acc; 
+  };
+  fxExport(core,cond_e,'cond_e');
+  
+  var and_e = function (/* . nodes */) {
+    var nodes = slice(arguments, 0);
+    
+    var acc = (nodes.length > 0)? 
+    nodes[nodes.length - 1] : one_e(true);
+    
+    for (var i = nodes.length - 2; i > -1; i--) {
+      acc = if_e(
+        nodes[i], 
+        acc, 
+        nodes[i].constant_e(false));
+    }
+    return acc;
+  };
+  fxExport(core,and_e,'and_e');
+  
+  EventStream.prototype.and_e = function( /* others */ ) {
+    var deps = [this].concat(slice(arguments,0));
+    return and_e.apply(this,deps);
+  };
+  fxMethodSynonyms(EventStream,'and_e','and');
+  
+  var or_e = function () {
+    var nodes = slice(arguments, 0);
+    var acc = (nodes.length > 2)? 
+    nodes[nodes.length - 1] : one_e(false); 
+    for (var i = nodes.length - 2; i > -1; i--) {
+      acc = if_e(
+        nodes[i],
+        nodes[i],
+        acc);
+    }
+    return acc;
+  };
+  fxExport(core,or_e,'or_e');
+  
+  EventStream.prototype.or_e = function(/*others*/) {
+    var deps = [this].concat(slice(arguments,0));
+    return or_e.apply(this,deps);
+  };
+  fxMethodSynonyms(EventStream,'or_e','or');
+  
+  var delayStatic_e = function (event, time) {
+    
+    var resE = internal_e();
+    
+    createNode(
+      [event],
+      function (s, p) { 
+        setTimeout( 
+          function () { sendEvent(resE, p.value);}, 
+      time ); });
+    
+    return resE;
+  };
+  
+  //delay_e: Event a * [Behaviour] Number ->  Event a
+  EventStream.prototype.delay_e = function (time) {
+    var event = this;
+    
+    if (time instanceof Behaviour) {
+      
+      var receiverEE = internal_e();
+      var link = 
+      {
+        from: event, 
+        towards: delayStatic_e(event, valueNow(time))
+      };
+      
+			//TODO: Change semantics such that we are always guaranteed to get an event going out?
+      var switcherE = 
+      createNode(
+        [changes(time)],
+        function (s, p) {
+          removeListener(link.from, link.towards); 
+          link =
+          {
+            from: event, 
+            towards: delayStatic_e(event, p.value)
+          };
+          sendEvent(receiverEE, link.towards);
+        });
+      
+      var resE = receiverEE.switch_e();
+      
+      sendEvent(switcherE, valueNow(time));
+      return resE;
+      
+        } else { return delayStatic_e(event, time); }
+  };
+  fxMethodSynonyms(EventStream,'delay_e','delay');
+  
+  var delay_e = function(sourceE,interval) {
+    return sourceE.delay_e(interval);
+  };
+  fxExport(core,delay_e,'delay_e');
+  
+  //lift_e: ([Event] (. Array a -> b)) . Array [Event] a -> [Event] b
+  var lift_e = function (fn /*, [node0 | val0], ...*/) {
+    //      if (!(fn instanceof Function)) { throw 'lift_e: expected fn as second arg'; } //SAFETY
+    
+    var valsOrNodes = slice(arguments, 0);
+    //selectors[i]() returns either the node or real val, optimize real vals
+    var selectors = [];
+    var selectI = 0;
+    var nodes = [];
+    for (var i = 0; i < valsOrNodes.length; i++) {
+      if (valsOrNodes[i] instanceof EventStream) {
+        nodes.push(valsOrNodes[i]);
+        selectors.push( 
+          (function(ii) {
+              return function(realArgs) { 
+                return realArgs[ii];
+              };
+          })(selectI));
+        selectI++;
+      } else {
+        selectors.push( 
+          (function(aa) { 
+              return function () {
+                return aa;
+              }; 
+          })(valsOrNodes[i]));
+      } 
+    }
+    
+    var context = this;
+		var nofnodes = slice(selectors,1);
+    
+    if (nodes.length === 0) {
+      return one_e(fn.apply(context, valsOrNodes));
+    } else if ((nodes.length === 1) && (fn instanceof Function)) {
+      return nodes[0].lift_e(
+        function () {
+          var args = arguments;
+          return fn.apply(
+            context, 
+            map(function (s) {return s(args);}, nofnodes));
+        });
+    } else if (nodes.length === 1) {
+      return fn.lift_e(
+        function (v) {
+          var args = arguments;
+          return v.apply(
+            context, 
+            map(function (s) {return s(args);}, nofnodes));
+        });                
+    } else if (fn instanceof Function) {
+      return createTimeSyncNode(nodes).lift_e(
+        function (arr) {
+          return fn.apply(
+            this,
+            map(function (s) { return s(arr); }, nofnodes));
+        });
+    } else if (fn instanceof EventStream) {
+      return createTimeSyncNode(nodes).lift_e(
+        function (arr) {
+          return arr[0].apply(
+            this, 
+            map(function (s) {return s(arr); }, nofnodes));
+        });
+        } else {throw 'unknown lift_e case';}
+  };
+  fxExport(core,lift_e,'lift_e','transform_e','map_e','apply_e');
+  
+  EventStream.prototype.snapshot_e = function (valueB) {
+    return createNode(
+      [this],
+      function (s, p) {
+        p.value = valueNow(valueB);
+        s(p);
+      }
+      ); 
+  };
+  fxMethodSynonyms(EventStream,'snapshot_e','snapshot','takeSnapshot');
+
+  var snapshot_e = function(triggerE,valueB) {
+    return triggerE.snapshot_e(valueB);
+  };
+  fxExport(core,snapshot_e,'snapshot_e');
+
+  EventStream.prototype.filterRepeats_e = function(optStart) {
+    var hadFirst = optStart === undefined ? false : true;
+    var prev = optStart;
+  
+    return this.filter_e(function (v) {
+      if (!hadFirst || !(isEqual(prev,v))) {
+        hadFirst = true;
+        prev = v;
+        return true;
+      }
+      else {
+        return false;
+      }
+    });
+  };
+  fxMethodSynonyms(EventStream,'filterRepeats_e','filterRepeats');
+
+  var filterRepeats_e = function(sourceE,optStart) {
+    return sourceE.filterRepeats_e(optStart);
+  };
+  fxExport(core,filterRepeats_e,'filterRepeats_e','filterRepeats');
+
+  //credit Pete Hopkins
+  var calmStatic_e = function (triggerE, time) {
+		var out = internal_e();
+    createNode(
+      [triggerE],
+      function() {
+        var towards = null;
+        return function (s, p) {
+          if (towards !== null) { clearTimeout(towards); }
+          towards = setTimeout( function () { towards = null; sendEvent(out,p.value) }, time );
+        };
+      }());
+		return out;
+  };
+  
+  //calm_e: Event a * [Behaviour] Number -> Event a
+  EventStream.prototype.calm_e = function(time) {
+    if (time instanceof Behaviour) {
+			var out = internal_e();
+      createNode(
+        [this],
+        function() {
+          var towards = null;
+          return function (s, p) {
+            if (towards !== null) { clearTimeout(towards); }
+            towards = setTimeout( function () { towards = null; sendEvent(out,p.value) }, valueNow(time));
+          };
+        }());
+			return out;
+    } else {
+      return calmStatic_e(this,time);       
+    }
+  };
+  fxMethodSynonyms(EventStream,'calm_e','calm');
+  
+  var calm_e = function(sourceE,interval) {
+    return sourceE.calm_e(interval);
+  };
+  fxExport(core,calm_e,'calm_e');
+  
+  EventStream.prototype.blind_e = function (time) {
+    return createNode(
+      [this],
+      function () {
+        var intervalFn = 
+        time instanceof Behaviour?
+        function () { return valueNow(time); }
+        : function () { return time; };
+        var lastSent = (new Date()).getTime() - intervalFn() - 1;
+        return function (s, p) {
+          var curTime = (new Date()).getTime();
+          if (curTime - lastSent > intervalFn()) {
+            lastSent = curTime;
+            s(p);
+          }
+        };
+      }());
+  };
+  fxMethodSynonyms(EventStream,'blind_e','blind');
+  
+  var blind_e = function(sourceE,interval) {
+    return sourceE.blind_e(interval);
+  };
+  fxExport(core,blind_e,'blind_e');
+ 
+  EventStream.prototype.hold = function(init) {
+    return new Behaviour(this,init);
+  };
+  fxMethodSynonyms(EventStream,'hold','startsWith','toBehavior');
+
+  var hold = function(e,init) {
+    if (!(e instanceof EventStream)) {
+      throw 'hold: expected EventStream; received ' + e;
+    }
+    return e.hold(init); 
+  };
+  fxExport(core,hold,'hold');
+  
+  Behaviour.prototype.valueNow = function() {
+    return this.last;
+  };
+  var valueNow = function(behavior) { return behavior.valueNow(); };
+  fxExport(core,valueNow,'valueNow');
+
+  Behaviour.prototype.changes = function() {
+    return this.underlying;
+  };
+  fxMethodSynonyms(Behaviour,'changes','toEvent');
+  
+  var changes = function (behave) { return behave.changes(); }
+  fxExport(core,changes,'changes');
+  
+  Behaviour.prototype.switch_b = function() {
+    var behaviourCreatorsB = this;
+    var init = valueNow(behaviourCreatorsB);
+    
+    var prevSourceE = null;
+    
+    var receiverE = new internal_e();
+    
+		//XXX could result in out-of-order propagation! Fix!
+    var makerE = 
+    createNode(
+      [changes(behaviourCreatorsB)],
+      function (_, p) {
+        if (!(p.value instanceof Behaviour)) { throw 'switch_b: expected Behaviour as value of Behaviour of first argument'; } //SAFETY
+        if (prevSourceE != null) {
+          removeListener(prevSourceE, receiverE);
+        }
+        
+        prevSourceE = changes(p.value);
+        attachListener(prevSourceE, receiverE);
+        
+        sendEvent(receiverE, valueNow(p.value));
+      });
+    
+    if (init instanceof Behaviour) {
+      sendEvent(makerE, init);
+    }
+    
+    return hold(
+      receiverE,
+      init instanceof Behaviour? valueNow(init) : init);
+  };
+  fxMethodSynonyms(Behaviour,'switch_b','forwardLatest');
+  
+  var switch_b = function (b) { return b.switch_b(); };
+  fxExport(core,switch_b,'switch_b');
+  
+  //TODO test, signature
+  var timer_b = function(interval) {
+    return hold(timer_e(interval), (new Date()).getTime());
+  };
+  fxExport(dom,timer_b,'timer_b','asTimer_b');
+  
+  //TODO test, signature
+  var delayStatic_b = function (triggerB, time, init) {
+    return hold(delayStatic_e(changes(triggerB), time), init);
+  };
+  
+  //TODO test, signature
+  Behaviour.prototype.delay_b = function (time, init) {
+    var triggerB = this;
+    if (time instanceof Behaviour) {
+      return hold(
+        delay_e(
+          changes(triggerB), 
+          time),
+        arguments.length > 3 ? init : valueNow(triggerB));
+    } else {
+      return delayStatic_b(
+        triggerB, 
+        time,
+        arguments.length > 3 ? init : valueNow(triggerB));
+    }
+  };
+  fxMethodSynonyms(Behaviour,'delay_b','delay');
+  
+  var delay_b = function(srcB, timeB, init) { 
+    return srcB.delay_b(timeB,init); 
+  };
+  fxExport(core,delay_b,'delay_b');
+  
+  //artificially send a pulse to underlying event node of a behaviour
+  //note: in use, might want to use a receiver node as a proxy or an identity map
+  Behaviour.prototype.sendBehaviour = function(val) {
+    sendEvent(this.underlyingRaw,val);
+  };
+  Behaviour.prototype.sendBehavior = Behaviour.prototype.sendBehaviour;
+  
+  var sendBehaviour = function (b,v) { b.sendBehaviour(v); };
+  fxExport(core,sendBehaviour,'sendBehaviour');
+  fxExport(core,sendBehaviour,'sendBehavior');
+
+  Behaviour.prototype.if_b = function(trueB,falseB) {
+    var testB = this;
+    //TODO auto conversion for behaviour funcs
+    if (!(trueB instanceof Behaviour)) { trueB = constant_b(trueB); }
+    if (!(falseB instanceof Behaviour)) { falseB = constant_b(falseB); }
+    return lift_b(function(te,t,f) { return te ? t : f; },testB,trueB,falseB);
+  };
+  fxMethodSynonyms(Behaviour,'if_b','choose_b','choose');
+  
+  var if_b = function(test,cons,altr) {
+    if (!(test instanceof Behaviour)) { test = constant_b(test); };
+    
+    return test.if_b(cons,altr);
+  };
+  fxExport(core,if_b,'if_b','choose_b');
+  
+
+  //cond_b: . [Behaviour boolean, Behaviour a] -> Behaviour a
+  var cond_b = function (/* . pairs */ ) {
+    var pairs = slice(arguments, 0);
+	return lift_b.apply({},[function() {
+			for(var i=0;i<pairs.length;i++) {
+				if(arguments[i]) return arguments[pairs.length+i];
+			}
+			return undefined;
+		}].concat(map(function(pair) {return pair[0];},pairs).concat(map(function(pair) {return pair[1];},pairs))));
+  };
+  fxExport(core,cond_b,'cond_b');
+  
+  //TODO optionally append to objects
+  //createConstantB: a -> Behaviour a
+  var constant_b = function (val) {
+    return new Behaviour(internal_e(), val);
+  };
+  fxExport(core,constant_b,'constant_b','receiver_b');
+  
+	var lift_b = function (fn /* . behaves */) {
+    var args = slice(arguments, 1);
+
+    //dependencies
+    var constituentsE =
+      map(changes,
+      filter(function (v) { return v instanceof Behaviour; },
+        arguments));
+    
+    //calculate new vals
+    var getCur = function (v) {
+      return v instanceof Behaviour ? v.last : v;
+    };
+    
+    var ctx = this;
+    var getRes = function () {
+      return getCur(fn).apply(ctx, map(getCur, args));
+    };
+	
+    if(constituentsE.length == 1) {
+      return new Behaviour(constituentsE[0],getRes(),getRes);
+    }
+      
+    //gen/send vals @ appropriate time
+    var prevStamp = -1;
+    var mid = createNode(constituentsE, function (s, p) {
+        if (p.stamp != prevStamp) {
+          prevStamp = p.stamp;
+          s(p);
+    }});
+    return new Behaviour(mid,getRes(),getRes);
+  };
+  fxExport(core,lift_b,'lift_b','transform_b','apply_b');
+  
+  Behaviour.prototype.lift_b = function(/* args */) {
+    var args= slice(arguments,0).concat([this]);
+    return lift_b.apply(this,args);
+  };
+  fxMethodSynonyms(Behaviour,'lift_b','transform_b','apply_b','transform',
+    'lift');
+  
+  var and_b = function (/* . behaves */) {
+	return lift_b.apply({},[function() {
+			for(var i=0; i<arguments.length; i++) {if(!arguments[i]) return false;}
+			return true;
+	}].concat(slice(arguments,0)));
+  };
+  fxExport(core, and_b, 'and_b');
+  
+  Behaviour.prototype.and_b = function() {
+    return and_b([this].concat(arguments));
+  };
+  fxMethodSynonyms(Behaviour,'and_b','and');
+  
+  var or_b = function (/* . behaves */ ) {
+	return lift_b.apply({},[function() {
+			for(var i=0; i<arguments.length; i++) {if(arguments[i]) return true;}
+			return false;
+	}].concat(slice(arguments,0)));
+  };
+  fxExport(core,or_b,'or_b');
+  
+  Behaviour.prototype.or_b = function () {
+    return or_b([this].concat(arguments));
+  };
+  fxMethodSynonyms(Behaviour, 'or_b', 'or');
+  
+  Behaviour.prototype.not_b = function() {
+    return this.lift_b(function(v) { return !v; });
+  };
+  fxMethodSynonyms(Behaviour,'not_b','not');
+  
+  var not_b = function(b) { return b.not_b(); };
+  fxExport(core,not_b,'not_b');
+  
+  Behaviour.prototype.blind_b = function (intervalB) {
+    return changes(this).blind_e(intervalB).hold(this.valueNow());
+  };
+  fxMethodSynonyms(Behaviour,'blind_b','blind');
+  
+  var blind_b = function(srcB,intervalB) {
+    return srcB.blind_b(intervalB);
+  };
+  fxExport(core,blind_b,'blind_b');
+  
+  Behaviour.prototype.calm_b = function (intervalB) {
+    return this.changes().calm_e(intervalB).hold(this.valueNow());
+  };
+  fxMethodSynonyms(Behaviour,'calm_b','calm');
+  
+  var calm_b = function (srcB,intervalB) { 
+    return srcB.calm_b(intervalB);
+  };
+  fxExport(core,calm_b,'calm_b');
+    
+  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+  // DOM Utilities
+  
+	//credit Scott Andrew
+	//usage: addEvent(myDomObj, "mouseover", event->void )
+	//warning: do not use 'this' as meaning depends on browser (myDomObj vs window)
+	//addEvent: Dom * String DomEventEnum * (DomEvent -> a) -> Void
+	var addEvent = function (obj, evType, fn) {
+		//TODO encode mouseleave evt, formalize new evt interface
+    
+		if (obj.addEventListener) {
+      obj.addEventListener(evType, fn, false); //TODO true fails on Opera
+      return true;
+		} else if (obj.attachEvent) {
+      //some reason original had code bloat here
+      return obj.attachEvent("on"+evType, fn); 
+		} else {
+      return false; 
+		}
+	};
+  fxExport(domMisc,addEvent,'addEvent');
+
+  // The Flapjax library does not use this function  
+	//credit Dustin Diaz 
+	//note: node/tag optional
+	//getElementsByClass: Regexp CSSSelector * Dom * String DomNodeEnum -> Array Dom
+	var getElementsByClass = function (searchClass, node, tag) {
+		var classElements = [];
+		if ( (node === null) || (node === undefined) ) { node = document; }
+		if ( (tag === null) || (tag === undefined) ) { tag = '*'; }
+		var els = node.getElementsByTagName(tag);
+		var elsLen = els.length;
+		var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
+		for (var i = 0, j = 0; i < elsLen; i++) {
+      if ( pattern.test(els[i].className) ) {
+        classElements.push(els[i]);
+      }
+		}
+		return classElements;
+	};
+  fxExport(domMisc,getElementsByClass,'getElementsByClass','$$');
+  
+	//assumes IDs already preserved
+	//swapDom: (Dom a U String) [* (Dom b U String)] -> Dom a
+	var swapDom = function(replaceMe, withMe) {
+		if ((replaceMe === null) || (replaceMe === undefined)) { throw ('swapDom: expected dom node or id, received: ' + replaceMe); } //SAFETY
+    
+		var replaceMeD = getObj(replaceMe);
+		if (!(replaceMeD.nodeType > 0)) { throw ('swapDom expected a Dom node as first arg, received ' + replaceMeD); } //SAFETY
+    
+		if (withMe) {
+      var withMeD = getObj(withMe);
+      if (!(withMeD.nodeType > 0)) { throw 'swapDom: can only swap with a DOM object'; } //SAFETY
+      try {
+        if(withMeD != replaceMeD) replaceMeD.parentNode.replaceChild(withMeD, replaceMeD);
+      } catch (e) {
+        throw('swapDom error in replace call: withMeD: ' + withMeD + ', replaceMe Parent: ' + replaceMeD + ', ' + e + ', parent: ' + replaceMeD.parentNode);                    
+      }
+		} else {
+      replaceMeD.parentNode.removeChild(replaceMeD); //TODO isolate child and set innerHTML to "" to avoid psuedo-leaks?
+		}
+		return replaceMeD;
+	};
+  fxExport(domMisc,swapDom,'swapDom');
+  
+	//getObj: String U Dom -> Dom
+	//throws 
+	//  'getObj: expects a Dom obj or Dom id as first arg'
+	//  'getObj: flapjax: cannot access object'
+	//  'getObj: no obj to get
+	//also known as '$'
+	//TODO Maybe alternative?
+	var getObj = function (name) {
+		if (typeof(name) == 'object') { return name; }
+		else if ((typeof(name) == 'null') || (typeof(name) == 'undefined')) {
+      throw 'getObj: expects a Dom obj or Dom id as first arg';
+		} else {
+      
+      var res = 
+			document.getElementById ? document.getElementById(name) :
+			document.all ? document.all[name] :
+			document.layers ? document.layers[name] :
+			(function(){ throw 'getObj: flapjax: cannot access object';})();
+      if ((res === null) || (res === undefined)) { 
+        throw ('getObj: no obj to get: ' + name); 
+      }
+      return res;
+		}
+	};
+  fxExport(domMisc,getObj,'getObj');
+  fxExport(domMisc,getObj,'$');
+  
+  //helper to reduce obj look ups
+  //getDynObj: domNode . Array (id) -> domObj
+  //obj * [] ->  obj
+  //obj * ['position'] ->  obj
+  //obj * ['style', 'color'] ->  obj.style
+  var getMostDom = function (domObj, indices) {
+    var acc = getObj(domObj);
+    if ( (indices === null) || (indices === undefined) || (indices.length < 1)) {
+      return acc;
+    } else {
+      for (var i = 0; i < indices.length - 1; i++) {
+        acc = acc[indices[i]];
+      }
+      return acc;
+    }       
+  };
+  
+  var getDomVal = function (domObj, indices) {
+    var val = getMostDom(domObj, indices);
+    if (indices && indices.length > 0) {
+      val = val[indices[indices.length - 1]];
+    }
+    return val;
+  };
+  
+  //TODO: manual timer management stinks.
+  // TODO: Name turn off or somethin
+  var ___timerID = 0;
+  var __getTimerId = function () { return ++___timerID; };    
+  var timerDisablers = [];
+  
+  var disableTimerNode = function (node) { timerDisablers[node.__timerId](); };
+  
+  var disableTimer = function (v) {
+    if (v instanceof Behaviour) { 
+      disableTimerNode(v.underlyingRaw); 
+    } else if (v instanceof EventStream) {
+      disableTimerNode(v);
+    }
+  };
+  fxExport(dom,disableTimer,'disableTimer');
+  
+  var createTimerNodeStatic = function (interval) {
+    var node = internal_e();
+    node.__timerId = __getTimerId();
+    var fn = function () { sendEvent(node, (new Date()).getTime());};
+    var timer = setInterval(fn, interval);
+    timerDisablers[node.__timerId] = function () {clearInterval(timer); };
+    return node;
+  };
+  
+  var timer_e = function (interval) {
+    if (interval instanceof Behaviour) {
+      var receiverE = internal_e();
+      
+      //the return
+      var res = receiverE.switch_e();
+      
+      //keep track of previous timer to disable it
+      var prevE = createTimerNodeStatic(valueNow(interval));
+      
+      //init
+      sendEvent(receiverE, prevE);
+      
+      //interval changes: collect old timer
+      createNode(
+        [changes(interval)],
+        function (_, p) {
+          disableTimerNode(prevE); 
+          prevE = createTimerNodeStatic(p.value);
+          sendEvent(receiverE, prevE);
+        });
+      
+      res.__timerId = __getTimerId();
+      timerDisablers[res.__timerId] = function () {
+        disableTimerNode[prevE]();
+        return;
+      };
+      
+      return res;
+    } else {
+      return createTimerNodeStatic(interval);
+    }
+  };
+  fxExport(dom,timer_e,'timer_e','asTimer_e');
+
+	d.TagB = function(tagName,args) {
+		this.resE = internal_e();
+		this.currentTag = document.createElement(tagName);
+		this.extractParameters(args);
+		this.insertChildrenNodes();
+		this.styleHooks = [];
+		this.styleChangedE = internal_e();
+		var ctx = this;
+    this.styleChangedE.lift_e(function(_) {
+				var oldTag = ctx.currentTag;
+				ctx.currentTag = document.createElement(tagName);
+        while (oldTag.firstChild) {
+          ctx.currentTag.appendChild(oldTag.removeChild(oldTag.firstChild));
+				}
+				while(ctx.styleHooks.length) removeListener(ctx.styleHooks.pop(),ctx.styleChangedE);
+        ctx.enstyle(ctx.currentTag,ctx.attribs);
+        sendEvent(ctx.resE, ctx.currentTag);
+		});
+		this.enstyle(this.currentTag,this.attribs);
+    this.resB = hold(this.resE, this.currentTag);
+	};
+	d.TagB.prototype = {
+    // Array [[Behaviour] Object *] [[Behaviour] Array] [Behaviour] Dom U String U undefined
+    //   --> {attribs: Array [Behaviour] Object, arrs: Array [Behaviour] Array [Behaviour] Dom }
+    // split an arguments array into:
+    //   1. arrs: (coalesced, and possibly time varying) arrays of dom objects 
+    //   2. attribs: attribute objects
+		extractParameters: function(args) {
+			this.arrs = [];
+			var attribs = [];
+      
+			var curarr = [];
+			this.arrs.push(curarr);
+			for (var i = 0; i < args.length; i++) {
+				if (args[i] instanceof Behaviour) {
+					var vn = valueNow(args[i]);
+					if (vn instanceof Array) {	        
+						this.arrs.push(args[i]);
+						curarr = [];
+						this.arrs.push(curarr);
+					} else {
+						if ( ((typeof(vn) == 'object') && (vn.nodeType == 1)) ||
+              (typeof(vn) == 'string') || (vn == undefined)) {
+            curarr.push(args[i]);
+              } else if (typeof(vn) == 'object') {
+                attribs.push(args[i]);
+              }
+              else { throw ('createParameterizedTagB: unknown behaviour argument argument ' + i); } //SAFETY
+					}
+				} else {
+					if (args[i] instanceof Array) {
+						var arr = args[i];
+						for (var j = 0; j < arr.length; j++) { curarr.push(arr[j]); }
+					} else {
+						var vn = args[i];
+						if ( ((typeof(vn) == 'object') && (vn.nodeType == 1)) ||
+              (typeof(vn) == 'string') || (vn == undefined)) {
+						curarr.push(args[i]);
+              } else if (typeof(vn) == 'object') {
+                attribs.push(args[i]);
+              }
+					}
+				}
+			};
+			if(attribs.length > 1) throw ('createParameterizedTagB ' + tagName + ': more than one attribute (' + attribs.length + ')');
+			this.attribs = attribs.length > 0 ? attribs[0] : {};
+		},
+		insertChildrenNodes: function() {
+			var ctx = this;
+      
+			function quickNode(e) { 
+        if ((typeof(e) == 'object') && (e.nodeType))
+					return e; 
+        else if ( e == undefined )
+					return document.createTextNode(''); 
+        else 
+          return document.createTextNode(e); 
+      }
+      
+			function unBehaviourize(arr) {
+				return map(function(n) {return (n instanceof Behaviour) ? valueNow(n) : n;},arr)
+			}
+      
+			var lnodes = map(function() {return [];},this.arrs);
+			var arrLastVals = map(unBehaviourize,unBehaviourize(this.arrs));
+      
+			var arrChangesE = internal_e();
+			var nodeChangesE = internal_e();
+			
+			function attachNodes(i,arr) {
+				for(var j=0;j<arr.length;j++) {
+					var cnode = arr[j];
+					if(cnode instanceof Behaviour) {
+						var newnode = (function(jj) {
+                return changes(cnode).lift_e(function(n) {return {index:i,jdex:jj,val:n};});
+						})(j);
+						lnodes[i].push(newnode);
+						attachListener(newnode,nodeChangesE);
+						cnode = valueNow(cnode);
+					}
+				}
+			}
+      
+			var childChangesE = merge_e(
+				// Behaviour arrays change
+				arrChangesE.lift_e(function(ai) {
+            var i = ai.index;
+            var newarr = ai.val;
+            while(lnodes[i].length) {
+              var ln = lnodes[i].pop();
+              removeListener(ln,nodeChangesE);
+            }
+            var newvals = map(function(n) {return quickNode(n);},unBehaviourize(newarr));
+            for(var j=0;j<arrLastVals[i].length;j++)
+				try {
+					ctx.currentTag.removeChild(arrLastVals[i][j]);
+				}
+				catch(e) {}
+            if(newvals.length) {
+              var nextNode = null;
+              for(ii = i+1; ii < arrLastVals.length && !(arrLastVals[ii].length) ; ii++);
+              if(ii < arrLastVals.length) nextNode = arrLastVals[ii][0];
+              for(var j=0; j<newvals.length; j++) ctx.currentTag.insertBefore(newvals[j],nextNode);
+            }
+            arrLastVals[i] = newvals;
+            attachNodes(i,newarr);
+            return ctx.currentTag;
+				}),
+				// Behaviour nodes change
+				nodeChangesE.lift_e(function(ni) {
+            var i = ni.index;
+            var j = ni.jdex;
+            var newnode = quickNode(ni.val);
+            swapDom(arrLastVals[i][j],newnode);
+            arrLastVals[i][j] = newnode;
+            return ctx.currentTag;
+				}));
+			childChangesE.lift_e(function(cc) {sendEvent(ctx.resE,cc);});
+      
+			for(var i=0; i<this.arrs.length;i++) {
+				for(var j=0; j<arrLastVals[i].length; j++) {
+					arrLastVals[i][j] = quickNode(arrLastVals[i][j]);
+					this.currentTag.appendChild(arrLastVals[i][j]);
+				}
+				if(this.arrs[i] instanceof Behaviour) {
+					attachNodes(i,valueNow(this.arrs[i]));
+					var newnode = (function(ii) {return changes(ctx.arrs[ii]).lift_e(function(na) {return {index:ii,val:na};});})(i);
+					attachListener(newnode,arrChangesE);
+				}
+				else {
+					attachNodes(i,this.arrs[i]);
+				}
+			}
+		},
+		enstyle: function(obj,vals) {
+			//build & record hook if dynamic collection
+			if (vals instanceof Behaviour) {
+				if (!(typeof(valueNow(vals)) == 'object')) { throw 'enstyle: expected object literal as behaviour value'; } //SAFETY
+				this.styleHooks.push(changes(vals));
+				attachListener(changes(vals),this.styleChangedE);
+			}
+			var valsV = vals instanceof Behaviour ? valueNow(vals) : vals;
+			if (typeof(valsV) == 'object') {
+				for (var i in valsV) {
+					if (!(Object.prototype) || !(Object.prototype[i])) {
+						this.enstyleProperty(obj,valsV, i);
+					}
+				}
+			} 
+			else { throw 'enstyle: expected object literals'; } //SAFETY
+		},
+    enstyleProperty: function (obj, vals, i) {
+      if (vals[i] instanceof Behaviour) {
+        if (typeof(valueNow(vals[i])) == 'object') {
+          this.enstyle(obj[i], vals[i]);
+        }
+				else {
+          obj[i] = valueNow(vals[i]);
+					changes(vals[i]).lift_e(function(v) {obj[i] = v;});
+				}
+			}
+			else {
+        if (typeof(vals[i]) == 'object') {
+          this.enstyle(obj[i], vals[i]);
+        } else {
+          obj[i] = vals[i];
+        }
+      }
+    }
+	};
+  
+	d.createParameterizedTagB = function(tagName) {
+		return new d.TagB(tagName,slice(arguments,1)).resB;
+	}
+  
+  d.enstyleStaticProperty = function (obj, props, index) {
+    if (typeof(props[index]) == 'object') {
+      for (var i in props[index]) {
+        if (!(Object.prototype) || !(Object.prototype[i])) {
+          d.enstyleStaticProperty(obj[index], props[index], i);
+        }
+      }
+    } else {
+      obj[index] = props[index];
+			if (index == 'checked')	obj['defaultChecked'] = props[index]; /* TODO: this should maybe be elsewhere? */
+			if (index == 'selected') obj['defaultSelected'] = props[index]; /* TODO: this should maybe be elsewhere? */
+    }
+  };
+  
+  d.staticTagMaker = function (tagName) {
+    
+    return function () {
+      
+      var tagD = document.createElement(tagName);
+      if (!(tagD.nodeType > 0)) { throw (tagName + ': invalid tag name'); } //SAFETY
+      
+      //partition input
+      
+      //          if (arguments[1] === null || arguments[1] === undefined) { arguments[1] = {}; }
+      var attribs = [];
+      for (var i = 0; i < arguments.length; i++) {
+        if (arguments[i] instanceof Array) {
+          for (var j = 0; j < arguments[i].length; j++) {
+            if (arguments[i][j]) {
+              tagD.appendChild(
+                (typeof(arguments[i][j]) == 'object' &&
+                  arguments[i][j].nodeType > 0)?
+                arguments[i][j] :
+                document.createTextNode(arguments[i][j]));
+            }
+          }
+        } else if (!arguments[i]) {
+          //ignore
+        } else if ((typeof(arguments[i]) == 'object') && 
+          (arguments[i].nodeType > 0)) {
+        tagD.appendChild(arguments[i]);
+          } else if (typeof(arguments[i]) == 'object') {
+            attribs.push(arguments[i]);
+          } else {
+            tagD.appendChild(document.createTextNode(arguments[i]));                    
+          }
+      }
+      
+      if (attribs.length == 1) { 
+        for (var k in attribs[0]) {
+          if (!(Object.prototype) || !(Object.prototype[k])) {
+            d.enstyleStaticProperty(tagD, attribs[0], k);   
+          }
+        }
+      } else if (attribs.length >  0) { 
+        throw 'static enstyle: expected object literals'; //SAFETY
+      } /* else {
+      alert('no attribs on: ' + tagName);
+      } */
+      
+      
+      return tagD;
+    };
+  };
+  
+  
+  
+  var generatedTags = 
+  [ "a", "b", "blockquote", "br", "button", "canvas", "div", "fieldset", 
+  "form", "font", "h1", "h2", "h3", "h4", "hr", "img", "iframe", "input", 
+  "label", "legend", "li", "ol", "optgroup", "option", 
+  "p", "pre", "select", "span", "strong", "table", "tbody", 
+  "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul" ];
+  
+  map(
+    function (tagName) { 
+      
+      var upper = tagName.toUpperCase();
+      
+      //d.<TAG>B
+      d[upper + 'B'] = function () { 
+        return d.createParameterizedTagB.apply(this, [tagName].concat(slice(arguments,0)));
+      };          
+			annotate(d[upper+'B'],[upper+'B']);
+      
+      //d.<TAG>
+      d[upper] = d.staticTagMaker(tagName);  //faster, simple
+      /*              function () {
+      // 8/16/06 - leo - arguments bug
+      // more forgiving: allows reactive children (just doesn't propagate them)
+      var args = [b.maybeEmpty].concat(slice(arguments, 0)); 
+      return valueNow(d[upper + 'B'].apply(this, args));
+      }; */
+			annotate(d[upper],[upper]);
+    },
+    generatedTags);
+  
+  //TEXTB: Behaviour a -> Behaviour Dom TextNode    
+  d.TEXTB = function (strB) {
+    //      if (!(strB instanceof Behaviour || typeof(strB) == 'string')) { throw 'TEXTB: expected Behaviour as second arg'; } //SAFETY
+    if (!(strB instanceof Behaviour)) { strB = constant_b(strB); }
+    
+    return hold(
+      changes(strB).lift_e(
+        function (txt) { return document.createTextNode(txt); }),
+      document.createTextNode(valueNow(strB)));
+  };
+	annotate(d.TEXTB,['TEXTB']);
+  
+  var TEXT = function (str) {
+    return document.createTextNode(str);
+  };
+  fxExport(dom,TEXT,'TEXT');
+  
+  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+  // Reactive DOM
+  
+  //tagRec: Array (EventName a) * 
+  //      ( .Array (Event a) * Array (Event a) -> Behaviour Dom) -> Behaviour Dom
+  var tagRec = function (eventNames, maker) {
+    if (!(eventNames instanceof Array)) { throw 'tagRec: expected array of event names as first arg'; } //SAFETY
+    if (!(maker instanceof Function)) { throw 'tagRec: expected function as second arg'; } //SAFETY
+    
+    var numEvents = eventNames.length;
+    var internals = [ ];
+    var switches = [ ];
+    for (var i = 0; i < numEvents; i++) {
+      internals[i] = internal_e();
+      switches[i] = internals[i].switch_e();
+    };
+    
+    var domB = maker.apply(this,switches);
+    
+    var prevValue;
+    
+    var interceptE = createNode([domB.changes()],function (_,p) {
+      if (isEqual(p.value,prevValue)) { return; }
+      
+      prevValue = p.value;
+      for (var i = 0; i < numEvents; i++) {
+        sendEvent(internals[i],extractEvent_e(prevValue,eventNames[i]));
+      };
+    });
+    
+    sendEvent(interceptE,domB.valueNow());
+    
+    return domB;
+  };
+  fxExport(dom,tagRec,'tagRec','nodeWithOwnEvents');
+  
+  //extractEventStatic_e: Dom * String -> Event
+  var extractEventStatic_e = function (domObj, eventName) {
+    if (!eventName) { throw 'extractEvent_e : no event name specified'; }
+    if (!domObj) { throw 'extractEvent_e : no DOM element specified'; }
+    
+    domObj = getObj(domObj);
+    
+    var primEventE = internal_e();
+    addEvent(domObj,eventName,function(evt) {
+       sendEvent(primEventE, evt || window.event);
+       // Important for IE; false would prevent things like a checkbox actually
+       // checking.
+       return true;
+    });
+    
+    return primEventE;
+  };
+
+  //extractEvent_e: [Behaviour] Dom * String -> Event
+  var extractEvent_e = function (domB, eventName) {
+    if (!(domB instanceof Behaviour)) {
+      return extractEventStatic_e(domB,eventName);
+    }
+    else {
+      var domE = domB.changes();
+      
+      var eventEE = domE.lift_e(function(dom) {
+        return extractEventStatic_e(dom,eventName);
+      });
+      
+      var resultE = eventEE.switch_e();
+      
+      sendEvent(domE,domB.valueNow());
+      
+      return resultE;
+    };
+  };
+  fxExport(dom,extractEvent_e,'extractEvent_e','$EVT');
+  
+  //extractEvents_e: 
+  //      [Behaviour] Dom  
+  //      . Array String
+  //      -> Event
+  // ex: extractEvents_e(m, 'body', 'mouseover', 'mouseout')
+  d.extractEvents_e = function (domObj /* . eventNames */) {
+    var eventNames = slice(arguments, 1);
+    
+    var events = map(
+      function (eventName) {
+        return extractEvent_e(domObj, eventName); 
+      },
+      eventNames.length === 0 ? [] : eventNames);
+    
+    return merge_e.apply(this, events);
+  };
+  annotate(d.extractEvents_e,['extractEvents_e'],0,[Behaviour.prototype],['extractEvents_e']);
+  
+  //value of dom form object during trigger
+  d.extractValueOnEvent_e = function (triggerE, domObj) {
+    if (!(triggerE instanceof EventStream)) { throw 'extractValueOnEvent_e: expected Event as first arg'; } //SAFETY
+    
+    return changes(d.extractValueOnEvent_b.apply(this, arguments));
+    
+  };
+  annotate(d.extractValueOnEvent_e,['extractValueOnEvent_e']);
+  
+  //extractDomFieldOnEvent_e: Event * Dom U String . Array String -> Event a
+  d.extractDomFieldOnEvent_e = function (triggerE, domObj /* . indices */) {
+    if (!(triggerE instanceof EventStream)) { throw 'extractDomFieldOnEvent_e: expected Event as first arg'; } //SAFETY
+    var indices = slice(arguments, 2);
+    var res =
+    triggerE.lift_e(
+      function () { return getDomVal(domObj, indices); });
+    return res;
+  };
+  
+  d.extractValue_e = function (domObj) {
+    return changes(d.extractValue_b.apply(this, arguments));
+  };
+  annotate(d.extractValue_e,['extractValue_e','$E']);
+  
+  //extractValueOnEvent_b: Event * DOM -> Behaviour
+  // value of a dom form object, polled during trigger
+  d.extractValueOnEvent_b = function (triggerE, domObj) {
+    return d.extractValueStatic_b(domObj, triggerE);
+  };
+  annotate(d.extractValueOnEvent_b,['extractValueOnEvent_b']);
+  
+  //extractValueStatic_b: DOM [ * Event ] -> Behaviour a
+  //If no trigger for extraction is specified, guess one
+  d.extractValueStatic_b = function (domObj, triggerE) {
+    
+    var objD;
+    try {
+      objD = getObj(domObj);
+	    //This is for IE
+	    if(typeof(domObj) == 'string' && objD.id != domObj) {
+	    	throw 'Make a radio group';
+	    }
+    } catch (e) {
+      objD = {type: 'radio-group', name: domObj};
+    }
+    
+    var getter; // get value at any current point in time
+    
+    
+    switch (objD.type)  {
+      
+      //TODO: checkbox.value instead of status?
+    case 'checkbox': 
+      
+      return hold(
+        filterRepeats_e(
+          d.extractDomFieldOnEvent_e(
+            triggerE ? triggerE : 
+            d.extractEvents_e(
+              objD, 
+              'click', 'keyup', 'change'),
+            objD,
+            'checked'),objD.checked),
+        objD.checked);
+      
+      case 'select-one':
+        
+        getter = function (_) {                         
+          return objD.selectedIndex > -1 ? 
+          (objD.options[objD.selectedIndex].value ?
+            objD.options[objD.selectedIndex].value :
+            objD.options[objD.selectedIndex].innerText)
+			    : undefined;
+        };
+        
+        return hold(
+          filterRepeats_e(
+              (triggerE ? triggerE :
+              d.extractEvents_e(
+                objD,
+                'click', 'keyup', 'change')).lift_e(getter)),getter(),
+          getter());
+        
+        case 'select-multiple':
+          //TODO ryan's cfilter adapted for equality check
+          
+          getter = function (_) {
+            var res = [];
+            for (var i = 0; i < objD.options.length; i++) {
+              if (objD.options[i].selected) {
+                res.push(objD.options[i].value ? objD.options[i].value : objD.options[i].innerText);
+              }
+            }
+            return res;
+          };
+          
+          
+          return hold(
+              (triggerE ? triggerE : 
+              d.extractEvents_e(
+                objD,
+                'click', 'keyup', 'change')).lift_e(getter),
+            getter());
+          
+          case 'text':
+          case 'textarea':
+          case 'hidden':
+          case 'password':
+            
+            return hold(
+              filterRepeats_e(
+                d.extractDomFieldOnEvent_e(
+                  triggerE ? triggerE :
+                  d.extractEvents_e(
+                    objD, 
+                    'click', 'keyup', 'change'),
+                  objD,
+                  'value'),objD.value),
+              objD.value);
+            
+            case 'button': //same as above, but don't filter repeats
+              
+              return hold(
+                d.extractDomFieldOnEvent_e(
+                  triggerE ? triggerE :
+                  d.extractEvents_e(
+                    objD, 
+                    'click', 'keyup', 'change'),
+                  objD,
+                  'value'),
+                objD.value);
+              
+              
+              case 'radio': 
+              case 'radio-group':
+                
+                //TODO returns value of selected button, but if none specified,
+                //      returns 'on', which is ambiguous. could return index,
+                //      but that is probably more annoying
+                
+                var radiosAD = filter(
+                  function (elt) { 
+                    return (elt.type == 'radio') &&
+                    (elt.getAttribute('name') == objD.name); 
+                  },
+                  document.getElementsByTagName('input'));
+                
+                getter = 
+                objD.type == 'radio' ?
+                
+                function (_) {
+                  return objD.checked;
+                } :
+                
+                function (_) {
+                  for (var i = 0; i < radiosAD.length; i++) {
+                    if (radiosAD[i].checked) {
+                      return radiosAD[i].value; 
+                    }
+                  }
+                  return undefined; //TODO throw exn? 
+                };
+                
+                var actualTriggerE = triggerE ? triggerE :
+                merge_e.apply(
+                  this,
+                  map(
+                    function (radio) { 
+                      return d.extractEvents_e(
+                        radio, 
+                    'click', 'keyup', 'change'); },
+                      radiosAD));
+                
+                return hold(
+                  filterRepeats_e(
+                      actualTriggerE.lift_e(getter),getter()),
+                  getter());
+                
+                default:
+                  
+                  throw ('extractValueStatic_b: unknown value type "' + objD.type + '"');
+    }
+  };
+  
+  d.extractValue_b = function (domObj) {
+    if (domObj instanceof Behaviour) {
+      return lift_b(function (dom) { return d.extractValueStatic_b(dom); },
+                    domObj)
+             .switch_b();
+    } else {
+      return d.extractValueStatic_b(domObj);
+    }
+  };
+	annotate(d.extractValue_b,['extractValue_b','$B'],0,[Behaviour.prototype],['extractValue_b','$B']);
+  
+  //into[index] = deepValueNow(from) via descending from object and mutating each field
+  d.deepStaticUpdate = function (into, from, index) {
+    var fV = (from instanceof Behaviour)? valueNow(from) : from;
+    if (typeof(fV) == 'object') {
+      for (var i in fV) {
+        if (!(Object.prototype) || !(Object.prototype[i])) {
+          d.deepStaticUpdate(index? into[index] : into, fV[i], i);
+        }
+      }
+    } else {
+      var old = into[index];
+      into[index] = fV;
+    }
+  };
+  
+  //note: no object may be time varying, just the fields
+  //into[index] = from
+  //only updates on changes
+  d.deepDynamicUpdate = function (into, from, index) {
+    var fV = (from instanceof Behaviour)? valueNow(from) : from;
+    if (typeof(fV) == 'object') {
+      if (from instanceof Behaviour) {
+        throw 'deepDynamicUpdate: dynamic collections not supported';
+      }
+      for (var i in fV) {
+        if (!(Object.prototype) || !(Object.prototype[i])) {
+          d.deepDynamicUpdate(index? into[index] : into, fV[i], i);
+        }
+      }
+    } else {
+      if (from instanceof Behaviour) {
+        createNode(
+          [changes(from)],
+          function (s, p) {
+            if (index) { 
+              var old = into[index];
+              into[index] = p.value;
+            }
+            else { into = p.value; } //TODO notify topE?
+          });
+      }
+    }
+  };
+  
+	
+  d.insertValue = function (val, domObj /* . indices */) {
+    var indices = slice(arguments, 2);
+    var parent = getMostDom(domObj, indices);
+    d.deepStaticUpdate(parent, val, indices ? indices[indices.length - 1] : undefined);    	
+  };
+  annotate(d.insertValue,['insertValue']);
+  
+  //TODO convenience method (default to firstChild nodeValue) 
+  d.insertValueE = function (triggerE, domObj /* . indices */) {
+    if (!(triggerE instanceof EventStream)) { throw 'insertValueE: expected Event as first arg'; } //SAFETY
+    
+    var indices = slice(arguments, 2);
+    var parent = getMostDom(domObj, indices);
+    
+      triggerE.lift_e(function (v) {
+        d.deepStaticUpdate(parent, v, indices? indices[indices.length - 1] : undefined);
+      });
+  };
+  annotate(d.insertValueE,['insertValueE'],0,[EventStream.prototype],['insertValueE']);
+  
+  //insertValueB: Behaviour * domeNode . Array (id) -> void
+  //TODO notify adapter of initial state change?
+  d.insertValueB = function (triggerB, domObj /* . indices */) { 
+    
+    var indices = slice(arguments, 2);
+    var parent = getMostDom(domObj, indices);
+    
+    
+    //NOW
+    d.deepStaticUpdate(parent, triggerB, indices ? indices[indices.length - 1] : undefined);
+    
+    //LATER
+    d.deepDynamicUpdate(parent, triggerB, indices? indices[indices.length -1] : undefined);
+    
+  };
+  annotate(d.insertValueB,['insertValueB'],0,[Behaviour.prototype],['insertValueB']);
+  
+  //TODO copy dom event call backs of original to new? i don't thinks so
+  //  complication though: registration of call backs should be scoped
+  d.insertDomE = function (triggerE, domObj) {
+    
+    if (!(triggerE instanceof EventStream)) { throw 'insertDomE: expected Event as first arg'; } //SAFETY
+    
+    var objD = getObj(domObj);
+    
+    var res = triggerE.lift_e(
+      function (newObj) {
+        //TODO safer check
+        if (!((typeof(newObj) == 'object') && (newObj.nodeType == 1))) { 
+          newObj = d.SPAN({}, newObj);
+        }
+        swapDom(objD, newObj);
+        objD = newObj;
+        return newObj; // newObj;
+      });
+    
+    return res;
+  };
+  annotate(d.insertDomE,['insertDomE'],0,[EventStream.prototype],['insertDomE']);
+  
+  //insertDom: dom 
+  //          * dom 
+  //          [* (null | undefined | 'over' | 'before' | 'after' | 'leftMost' | 'rightMost' | 'beginning' | 'end']
+  //          -> void
+  // TODO: for consistency, switch replaceWithD, hookD argument order
+  d.insertDom = function (hookD, replaceWithD, optPosition) {
+    switch (optPosition)
+    {
+    case undefined:
+    case null:
+    case 'over':
+      swapDom(hookD,replaceWithD);
+      break;
+    case 'before':  
+      hookD.parentNode.insertBefore(replaceWithD, hookD);
+      break;
+    case 'after':
+      if (hookD.nextSibling) {
+        hookD.parentNode.insertBefore(replaceWithD, hookD.nextSibling);
+      } else {
+        hookD.parentNode.appendChild(replaceWithD);
+      }
+      break;
+    case 'leftMost':
+      if (hookD.parentNode.firstChild) { 
+        hookD.parentNode.insertBefore(
+          replaceWithD, 
+          hookD.parentNode.firstChild);
+                } else { hookD.parentNode.appendChild(replaceWithD); }
+                break;
+              case 'rightMost':
+                hookD.parentNode.appendChild(replaceWithD);
+                break;
+              case 'beginning':
+                if (hookD.firstChild) { 
+                  hookD.insertBefore(
+                    replaceWithD, 
+                    hookD.firstChild);
+                } else { hookD.appendChild(replaceWithD); }
+                break;
+              case 'end':
+                hookD.appendChild(replaceWithD);
+                break;
+              default:
+                throw ('domInsert: unknown position: ' + optPosition);
+    }
+  };
+  
+  //insertDom: dom 
+  //          * dom U String domID 
+  //          [* (null | undefined | 'over' | 'before' | 'after' | 'leftMost' | 'rightMost' | 'beginning' | 'end']
+  //          -> void
+  d.insertDomClean = function (replaceWithD, hook, optPosition) {
+    //TODO span of textnode instead of textnode?
+    d.insertDom(
+	    getObj(hook), 
+	    ((typeof(replaceWithD) == 'object') && (replaceWithD.nodeType > 0)) ? replaceWithD :
+      document.createTextNode(replaceWithD),	    
+	    optPosition);     	    
+  };
+  annotate(d.insertDomClean,['insertDom']);
+  
+  //TODO test
+  //insertDomB: 
+  //      [Behaviour] String U Dom 
+  //      [* ( id U null U undefined ) 
+  //          [* ('before' U 'after' U 'leftMost' U 'rightMost' U 'over' U 'beginning' U 'end')]]
+  //      -> Behaviour a
+  //if optID not specified, id must be set in init val of trigger
+  //if position is not specified, default to 'over'
+  //performs initial swap onload    
+  d.insertDomB = function (initTriggerB, optID, optPosition) {
+    
+    if (!(initTriggerB instanceof Behaviour)) { 
+      initTriggerB = constant_b(initTriggerB);
+    }
+    
+    var triggerB = 
+    lift_b(
+      function (d) { 
+        if ((typeof(d) == 'object') && (d.nodeType >  0)) {
+          return d;
+        } else {
+          var res = document.createElement('span'); //TODO createText instead
+          res.appendChild(document.createTextNode(d));
+          return res;
+        }
+      },
+      initTriggerB);
+    
+    var initD = valueNow(triggerB);
+    if (!((typeof(initD) == 'object') && (initD.nodeType == 1))) { throw ('insertDomB: initial value conversion failed: ' + initD); } //SAFETY	
+    
+    d.insertDom(
+      optID === null || optID === undefined ? getObj(initD.getAttribute('id')) : getObj(optID), 
+      initD, 
+      optPosition);
+    
+    var resB = hold(
+      d.insertDomE(
+        changes(triggerB),
+        initD), 
+      initD);
+    
+    return resB;
+  };
+  annotate(d.insertDomB,['insertDomB'],0,[Behaviour.prototype],['insertDomB']);
+  
+  
+  d.extractId_b = function (id, start)
+  {
+    return hold(
+      createNode( start instanceof Behaviour? [changes(start)] :
+        [],
+        function (s, p) {
+          p.value = getObj(id);
+          s(p);
+        }),
+      getObj(id));
+  };
+  annotate(d.insertDomB,['extractId_b'],1,[Behaviour.prototype],['extractId_b']);
+  
+  var mouse_e = function(elem) {
+    return extractEvent_e(elem,'mousemove')
+    .lift_e(function(evt) {
+        if (evt.pageX | evt.pageY) {
+          return { left: evt.pageX, top: evt.pageY };
+        }
+        else if (evt.clientX || evt.clientY) {
+          return { left : evt.clientX + document.body.scrollLeft,
+                   top: evt.clientY + document.body.scrollTop };
+        }
+        else {
+          return { left: 0, top: 0 };
+        }
+    });
+  };
+  
+  var mouse_b = function(elem) {
+    return mouse_e(elem).hold({ left: 0, top: 0 });
+  }
+  fxExport(dom,mouse_b,'mouse_b');
+  
+  var mouseLeft_b = function(elem) {
+    return lift_b(function(v) { return v.left; },mouse_b(elem));
+  };
+  fxExport(dom,mouseLeft_b,'mouseLeft_b');
+  
+  var mouseTop_b = function(elem) {
+    return mouse_b(elem).lift_b(function(v) { return v.top; });
+  };
+  
+  fxExport(dom,mouseTop_b,'mouseTop_b');
+  
+  var clicks_e = function(elem) {
+    return extractEvent_e(elem,'click');
+  };
+  fxExport(dom,clicks_e,'clicks_e');
+  
+  //////////////////////////////////////////////////////////////////////////////
+  // Combinators for web services
+  
+  
+	//credit Matt White
+	var getURLParam = function (param) {
+    var lparam = param.toLowerCase();
+    var aReturn = [];
+    var strHref = window.location.href;
+    var endstr = (strHref.indexOf('#') > -1) ? strHref.indexOf('#') : strHref.length;
+    if ( strHref.indexOf("?") > -1 ) {
+      var strQueryString = strHref.substring(strHref.indexOf("?")+1,endstr);
+      map(function(qp) {
+          var eq = qp.indexOf('=');
+          var qname = qp.substr(0,eq+1).toLowerCase();
+          if(qname == lparam+"=") aReturn.push(decodeURIComponent(qp.substr(eq+1)));
+      },strQueryString.split("&"));
+    }
+    if (aReturn.length == 0) return undefined;
+    else if(aReturn.length == 1) return aReturn[0];
+    else return aReturn;
+	};
+  fxExport(domMisc,getURLParam,'getURLParam','$URL');
+  
+	//credit Quirksmode
+	//readCookie: String -> String U Undefined
+	var readCookie = function (name) {
+    var nameEQ = name + "=";
+    var ca = document.cookie.split(';');
+    for (var i=0; i < ca.length; i++) {
+      var co = ca[i];
+      while (co.charAt(0) == ' ') { co = co.substring(1, co.length); }
+      if (co.indexOf(nameEQ) === 0) { 
+        return co.substring(nameEQ.length, co.length);
+      }
+    }
+    return undefined;       
+	};
+  fxExport(domMisc,readCookie,'readCookie');
+  
+  //========== dynamic scripts ==========
+  var scriptCounter = 0;
+  var deleteScript = function (scriptID) {
+    var scriptD = getObj(scriptID);
+    scriptD.parentNode.removeChild(scriptD); //TODO isolate child and set innerHTML to "" to avoid psuedo-leaks?
+  };
+  
+  // optional fn/param that gets polled until parm is defined
+  var runScript = function (url, fn, param) {
+    var script = document.createElement("script");
+    script.src = url;
+    var scriptID = 'scriptFnRPC' + scriptCounter++;
+    script.setAttribute('id', scriptID);
+    document.getElementsByTagName("head").item(0).appendChild(script);
+    var timer = {};
+    var check = 
+    function () {
+      eval("try { if (" + param + "!== undefined) {var stat = " + param + ";}} catch (e) {}");
+      if (stat !== undefined) {
+        eval(param + " = undefined;");
+        clearInterval(timer.timer);
+        clearInterval(timer.timeout);
+        if (fn instanceof Function) {
+          fn(stat); 
+        }
+        deleteScript(scriptID);
+      }
+    };
+    timer.timer = setInterval(check, 3500);
+    timer.timeout = 
+    setTimeout( 
+      function () { 
+        try { clearInterval(timer.timer); }
+        catch (e) {}
+      },
+      5000); //TODO make parameter?
+  };
+  
+  // Node {url, globalArg} -> Node a
+  //load script @ url and poll until param is set, then pass it along
+  var evalForeignScriptVal_e = function(urlArg_e) {
+    var result = receiver_e();
+    urlArg_e.lift_e(function(urlArg) {
+        runScript(urlArg.url,
+          function(val) { result.sendEvent(val); }, 
+          urlArg.globalArg);
+    });
+    
+    return result;
+  };
+  fxExport(ws,evalForeignScriptVal_e,'evalForeignScriptVal_e');
+  
+  var ajaxRequest = function(method,url,body,async,useFlash,callback) {
+    var xhr;
+    if (useFlash) {
+      xhr = new FlashXMLHttpRequest();
+      xhr.onload = function() { callback(xhr); };
+    }
+    else if (window.XMLHttpRequest && !(window.ActiveXObject)) {
+      xhr = new window.XMLHttpRequest();
+      xhr.onload = function() { callback(xhr); };
+    }
+    else if (window.ActiveXObject) {
+      try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
+      catch(e) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
+      
+      xhr.onreadystatechange = function() {
+        if (xhr.readyState == 4) { callback(xhr); }
+      };
+    };
+    
+    xhr.open(method,url,async);
+    
+    if (method == 'POST') {
+      xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
+    }
+    
+    xhr.send(body);
+    return xhr;
+  };
+
+  var encodeREST = function(obj) {
+    var str = "";
+    for (var field in obj) {
+      if (typeof(obj[field]) !== 'function') { // skips functions in the object
+        if (str != '') { str += '&'; }
+        str += field + '=' + encodeURIComponent(obj[field]);
+      }
+    }
+    return str;
+  };
+
+  // From json.org
+  var parseJSON = function(str) {
+    try {
+        return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
+                str.replace(/"(\\.|[^"\\])*"/g, ''))) &&
+            eval('(' + str + ')');
+    } catch (e) {
+        throw 'cannot parse JSON string: ' + e;
+    }
+  };
+
+  var toJSONString = (function() {
+    var m = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        };
+    var s = {
+      array: function (x) {
+        var a = ['['], b, f, i, l = x.length, v;
+        for (i = 0; i < l; i += 1) {
+          v = x[i];
+          f = s[typeof v];
+          if (f) {
+            v = f(v);
+            if (typeof v == 'string') {
+              if (b) {
+                a[a.length] = ',';
+              }
+              a[a.length] = v;
+              b = true;
+            }
+          }
+        }
+        a[a.length] = ']';
+        return a.join('');
+      },
+      'boolean': function (x) {
+        return String(x);
+      },
+      'null': function (x) {
+        return "null";
+      },
+      number: function (x) {
+        return isFinite(x) ? String(x) : 'null';
+      },
+      object: function (x) {
+        if (x) {
+          if (x instanceof Array) {
+            return s.array(x);
+          }
+          var a = ['{'], b, f, i, v;
+          for (i in x) {
+            v = x[i];
+            f = s[typeof v];
+            if (f) {
+              v = f(v);
+              if (typeof v == 'string') {
+                if (b) {
+                  a[a.length] = ',';
+                }
+                a.push(s.string(i), ':', v);
+                b = true;
+              }
+            }
+          }
+          a[a.length] = '}';
+          return a.join('');
+        }
+        return 'null';
+      },
+      string: function (x) {
+        if (/["\\\x00-\x1f]/.test(x)) {
+          x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+              var c = m[b];
+              if (c) {
+                return c;
+              }
+              c = b.charCodeAt();
+              return '\\u00' +
+              Math.floor(c / 16).toString(16) +
+              (c % 16).toString(16);
+          });
+        }
+        return '"' + x + '"';
+      }
+    };
+    return function(val) {
+      var f = s[typeof val];
+      if (f) { return f(val); }
+      else { throw 'parseJSON: unknown object type: ' + (typeof val); }
+    };
+  })();
+   
+  var serverRequest_e = function(useFlash,requestE) {
+    var response_e = receiver_e();
+    
+    requestE.lift_e(function (obj) {
+        var body = '';
+        var method = 'GET';
+        var url = obj.url;
+        
+        var reqType = obj.request ? obj.request : (obj.fields ? 'post' : 'get');
+        if (obj.request == 'get') {
+          url += "?" + encodeREST(obj.fields);
+          body = '';
+          method = 'GET';
+        } else if (obj.request == 'post') {
+          body = toJSONString(obj.fields); 
+          method = 'POST';
+        } else if (obj.request == 'rawPost') {
+          body = obj.body;
+          method = 'POST';
+        }
+        else if (obj.request == 'rest') {
+          body = encodeREST(obj.fields);
+          method = 'POST';
+        }
+        else {
+          raise ("Invalid request type: " + obj.request);
+        }
+        
+        var async = obj.async;
+        
+        var xhr;
+        
+        // Branch on the response type to determine how to parse it
+        if (obj.response == 'json') {
+          xhr = ajaxRequest(method,url,body,async,useFlash,
+            function(xhr) {
+              response_e.sendEvent(parseJSON(xhr.responseText)); 
+            });
+        }
+        else if (obj.response == 'xml') {
+          ajaxRequest(method,url,body,async,useFlash,
+            function(xhr) {
+              response_e.sendEvent(xhr.responseXML);
+            });
+        }
+        else if (obj.response == 'plain' || !obj.response) {
+          ajaxRequest(method,url,body,async,useFlash,
+            function(xhr) {
+              response_e.sendEvent(xhr.responseText);
+          });
+        }
+        else {
+          raise('Unknown response format: ' + obj.response);
+        }
+    });
+    
+    return response_e;
+  };
+  
+  var getWebServiceObject_e = function(requestE) {
+    return serverRequest_e(false,requestE);
+  };
+  fxExport(ws,getWebServiceObject_e,'getWebServiceObject_e');
+  
+  var getForeignWebServiceObject_e = function(requestE) {
+    return serverRequest_e(true,requestE);
+  };
+  fxExport(ws,getForeignWebServiceObject_e,'getForeignWebServiceObject_e');
+
+  var cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return { left: valueL, top: valueT };
+ };
+fxExport(domMisc,cumulativeOffset,'cumulativeOffset');
+
+  ///////// OPTIONALLY EXPORT PUBLIC FUNCTIONS TO GLOBAL NAMESPACE
+  if (!options.hideAll) {
+    for (var zz in flapjax.pub) {
+      if (!options[zz]) {
+        window[zz] = flapjax.pub[zz]; // use eval instead so as to achieve Flash compatibility
+      }
+    }
+  }
+  
+  for (var id in exports) {
+    if (window[id]) {
+      if (options.redefine === false) {
+        warn('window.' + id + ' is already defined; Flapjax will not ' +
+             'redefine it.');
+      }
+      else {
+        warn('Flapjax is redefining window.' + id + '.');
+      }
+    }
+
+    if (window[id] && options.redefine === false) {
+      // Don't redefine id
+    }
+    else if (options.show && options.hide && options.show[id] === true && 
+        options.hide[id] === true) {
+      throw('flapjaxInit: invalid options: ' + id + " specified as 'show' and 'hide'"); 
+    }
+    else if (options.show && options.show[id]) {
+      window[id] = exports[id];
+    }
+    else if (options.hide && options.hide[id]) {
+      // do nothing -- don't export
+    }
+    // Default behavior--check if it is a synonym first ...
+    else if (!synonyms[id] || options.includeSynonyms) {
+      // ... then export if its of the right category
+      if (options.exportMisc && exports[id].__flapjaxCategory === misc) {
+        window[id] = exports[id];
+      }
+      else if (options.exportDOMMisc && exports[id].__flapjaxCategory === domMisc) {
+        window[id] = exports[id];
+      }
+      else if (options.exportDOM && exports[id].__flapjaxCategory === dom) {
+        window[id] = exports[id];
+      }
+      else if (options.exportCore && exports[id].__flapjaxCategory === core) {
+        window[id] = exports[id];
+      }
+      else if (options.exportWS && exports[id].__flapjaxCategory === ws) {
+        window[id] = exports[id];
+      }
+    }
+  }
+  
+  return exports;
+}
diff --git a/tests/parse-pretty/neg.js b/tests/parse-pretty/neg.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/neg.js
@@ -0,0 +1,2 @@
+var foo = -x;
+var bar = +y;
diff --git a/tests/parse-pretty/numbers.js b/tests/parse-pretty/numbers.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/numbers.js
@@ -0,0 +1,2 @@
+var x = 23;
+var y = 23.0;
diff --git a/tests/parse-pretty/object-lit-1.js b/tests/parse-pretty/object-lit-1.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/object-lit-1.js
@@ -0,0 +1,4 @@
+var x = {  // avoid ambiguity with BlockStmt
+  foo : 23, 
+  bar : 50 
+}
diff --git a/tests/parse-pretty/object-lit-2.js b/tests/parse-pretty/object-lit-2.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/object-lit-2.js
@@ -0,0 +1,4 @@
+var x = {  // avoid ambiguity with BlockStmt
+  foo : 23, 
+  bar : 50, 
+} 
diff --git a/tests/parse-pretty/prefix-chain.js b/tests/parse-pretty/prefix-chain.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/prefix-chain.js
@@ -0,0 +1,4 @@
+var w = typeof typeof 34;
+var x = ! -- x; // derived from jQuery
+var y = ! !x; // derived from Prototype
+var z = !! -- x;
diff --git a/tests/parse-pretty/strings.js b/tests/parse-pretty/strings.js
new file mode 100644
--- /dev/null
+++ b/tests/parse-pretty/strings.js
@@ -0,0 +1,6 @@
+var x = '\b';
+var z = {
+  '\b': '\\b' // lhs is a PropString, not a StringLit, but must be printed
+              //  as a StringLit
+};
+var w = "\\s";
