// Copyright (c) 2009 Alexander Fritze
// For license details please contact <alex@croczilla.com>
// -------------------------------------------------------------------
//
// Oni v0.3
//
// An embedded structured concurrency language
// See http://www.croczilla.com/oni for details
//
// JavaScript-embedded version for web browsers
// Generated from oni.js.in.
//




(function () {


////////////////////////////////////////////////////////////////////////
// Helpers

// Binds a function to an object; the function will be executed with
// its 'this' variable set to 'obj':
function bind(f, obj) {
  return function() {
    return f.apply(obj, arguments);
  }
}

// create an array from a function's arguments object, starting at the
// i's parameter
function slice_args(a, /*[opt]*/ i) {
  return Array.prototype.slice.call(a, i);
}

var win = window;

// call 'fct' asynchronously
function callAsync(fct) {
  win.setTimeout(fct, 0);
}

function timeout(cont, duration_ms) {
  var id = win.setTimeout(function() { return cont([true, null]);}, duration_ms);
  return function() { win.clearTimeout(id); }
}

function getCurrentTimeMS() { return (new Date).getTime(); }

//----------------------------------------------------------------------
// Scheduler

function Scheduler(max_chain) {
  this.maxChain = max_chain;
  this.pendingFunctions = [];
  this.scheduled = false;
  this.executing = false;
}
Scheduler.prototype = {};

Scheduler.prototype.schedule = function(fct, append) {
  if (append) {
    this.pendingFunctions.push(fct);
  }
  else {
    // The 'normal' mode of operation is to prepend functions. We do
    // this for everything apart from external signals
    // (i.e. continuations from true async functions). In this way we
    // coax the scheduler into executing simple code in a
    // 'synchronous' way.
    this.pendingFunctions.unshift(fct);
  }
    
  if (!this.scheduled) {
    this.scheduled = true;
    callAsync(bind(Scheduler.prototype._next, this));
  }
};

Scheduler.prototype._next = function() {
  this.scheduled = false;
  this.executeChain();
};

Scheduler.prototype.executeChain = function() {
  // Prevent reentrancy; it happens when oni code calls other oni code
  // through Eval (which in turn can happen indirectly when
  // 'tunneling' oni code through 'normal' js code).
  // Reentrancy is bad, because it breaks proper sequencing.  
  // XXX Another way to solve this would be to give each Eval its own
  // scheduler.
  if (this.executing) return;
  this.executing = true;
  var chain = 0;
  while (this.pendingFunctions.length && ++chain < this.maxChain) {
    (this.pendingFunctions.shift())();
  }

  if (!this.scheduled && this.pendingFunctions.length) {
    this.scheduled = true;
    callAsync(bind(Scheduler.prototype._next, this));
  }
  this.executing = false;
};
  

var globalScheduler = new Scheduler(300);

////////////////////////////////////////////////////////////////////////
// Oni expression graph structure

// A node in the Oni expression graph. The nodes are functions (with
// node state such as children stored in closures), so that we can use
// function application '(...)' on them.

var ONI_OEN_TOKEN = {};
function markAsOEN(f) {
  f.__isOEN = ONI_OEN_TOKEN;
}
function isOEN(e) {
  return e && (e.__isOEN == ONI_OEN_TOKEN);
}

// Oni expression nodes support an 'execute(xc, env)' method,
// identified by passing in ONI_OEN_EXECUTE_TOKEN as first arg and xc,
// env as the following args.
var ONI_OEN_EXECUTE_TOKEN = {};

// helper to execute an expression node:
function executeOEN(oen, xc, env) {
  // selfquoting:
  if (!isOEN(oen)) oen = OEN_Quote(oen);
  oen(ONI_OEN_EXECUTE_TOKEN, xc, env);
}

// Oni expression nodes can be marked as 'call by need':
// XXX not honoured yet
var ONI_CALLBYNEED_TOKEN = {};
function markAsCallByNeed(f) {
  f.__isCallByNeed = ONI_CALLBYNEED_TOKEN;
}
function isCallByNeed(e) {
  return e && (e.__isCallByNeed == ONI_CALLBYNEED_TOKEN);
}

// Oni appliable node; these are expression graph nodes that support an
// 'fapply' method.

var ONI_OAN_TOKEN = {};
function markAsOAN(f) {
  // an OAN is also an OEN:
  f.__isOEN = ONI_OEN_TOKEN;
  f.__isOAN = ONI_OAN_TOKEN;
}
function isOAN(e) {
  return e && (e.__isOAN == ONI_OAN_TOKEN);
}

// Oni appliable nodes support an 'fapply(xc, env, pars)' method,
// invoked by passing in ONI_OAN_FAPPLY_TOKEN as first arg and xc,
// env, pars as the following args
var ONI_OAN_FAPPLY_TOKEN = {};

// helper for function application on an appliable expression node:
function fapplyOAN(oan, xc, env, pars) {
  oan(ONI_OAN_FAPPLY_TOKEN, xc, env, pars);
}


//----------------------------------------------------------------------
// Sequence node:
function OEN_Seq(exps) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Seq(exps, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Alternation node:
function OEN_Alt(exps) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Alt(exps, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Quote node:
function OEN_Quote(datum) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Quote(datum, arguments[1]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      //XXX do we need to quote here, or could we just unshift datum
      //directly?
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Apply node:
function OEN_Apply(fexp, args) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Apply(fexp, args, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// ALift node:
function OAN_ALift(async_f) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      // evaluate to self. XXX there must be a better way
      return new OXN_Quote(OAN_ALift(async_f), arguments[1]);
    }
    else if (arguments[0] == ONI_OAN_FAPPLY_TOKEN) {
      return new OXN_ALift_Apply(async_f, arguments[1], arguments[2], arguments[3]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOAN(f);
  return f;
}

//----------------------------------------------------------------------
// SLift node:
function OAN_SLift(sync_f) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      // evaluate to self: XXX there must be a better way
      return new OXN_Quote(OAN_SLift(sync_f), arguments[1]);
    }
    else if (arguments[0] == ONI_OAN_FAPPLY_TOKEN) {
      // Calls the synchronous js function 'sync_f'. If sync_f throws an
      // exception it will be converted into an Oni exception with label "error".
      // Simple implementation in terms of ALift:
      var async_f = function(/*cont, arg1, arg2, ... */) {
        var cont = arguments[0];
        var pars = slice_args(arguments, 1);
        var base = this;
        // XXX not quite correct; sync_f might be called after abort.
        // Should be ok though; ALift OXN prevents result from being published
        try { var rv;
              if (sync_f.apply) {
                rv = sync_f.apply(base, pars);
              }
              else {
                // workaround for IE, which doesn't implement
                // 'apply' for some native functions (such as alert)
                var command = "sync_f(";
                for (var i=0; i<pars.length; ++i) {
                  if (i!=0) command += ",";
                  command += "pars["+i+"]";
                }
                command += ")";
                rv = eval(command);
              }
              cont([true, rv], true); // 'true' param: makes sure SLift'ed functions get
                                      // executed in a synchronous section.
            }
        catch (e) { cont([false, e], true); }
      };
      return new OXN_ALift_Apply(async_f, arguments[1], arguments[2], arguments[3]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOAN(f);
  return f;
}

//----------------------------------------------------------------------
// Let node:
function OEN_Let(bindings, body_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Let(bindings, body_exp, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Get node:
function OEN_Get(var_name) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Get(var_name, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}





//----------------------------------------------------------------------
// Closure node:
function OAN_Closure(formals, body_exp, env) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      // evaluate to self: XXX there must be a better way
      // XXX can this ever be reached?
      return new OXN_Quote(OAN_Closure(formals, body_exp, env),
                           arguments[1]);
    }
    else if (arguments[0] == ONI_OAN_FAPPLY_TOKEN) {
      // The key to tail calls is not to generate an execution node
      // here, but just insert our saved (lexical) environment +
      // formals bindings into the call to body_exp.

      // create an environment with formals bound to args, and rooted
      // in the saved lexical environment:
      var new_env = new OENV(env);
      var args = arguments[3];
      for (var i=0; i< formals.length; ++i) {
        // xxx what to do when we have more/fewer args than formals?
        new_env.createBinding(formals[i], args[i]);
      }
      return executeOEN(body_exp, arguments[1], new_env);
    }
    else {
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      // function application chaining
      return Apply.apply(this, aargs);
    }
  };
  markAsOAN(f);
  return f;
}

//----------------------------------------------------------------------
// Lambda node:
function OEN_Lambda(formals, body_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Lambda(formals, body_exp, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Throw node:
function OEN_Throw(tag_exp, val_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Throw(tag_exp, val_exp, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      // XXX this is pretty pointless since Throw never returns
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Catch node:
function OEN_Catch(tag_exp, body_exp, handler_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Catch(tag_exp, body_exp, handler_exp,
                           arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// If node:
function OEN_If(test_exp, consequent_exp, alternative_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_If(test_exp, consequent_exp, alternative_exp,
                        arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Complete node:
function OEN_Complete(body_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Complete(body_exp, arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

//----------------------------------------------------------------------
// Bracket node:
function OEN_Bracket(acquire_exp, use_exp, release_exp) {
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      return new OXN_Bracket(acquire_exp, use_exp, release_exp,
                             arguments[1], arguments[2]);
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  return f;
}

////////////////////////////////////////////////////////////////////////
// Expression graph execution state

//----------------------------------------------------------------------
// Oni execution context

// execute an expression with a new execution context:
function fork(env, next, expr) {
  var xc = {  next    : next,
              current : null
           };
  executeOEN(expr, xc, env);
  return xc;
}

//----------------------------------------------------------------------
// Oni environments
function OENV(parent, bindings) {
  this.parent = parent;
  if (!bindings) bindings = {};
  this.bindings = bindings;
}
OENV.prototype = {
  createBinding : function(name, val) { this.bindings[name] = val; },
  setBinding : function(name, val) {
    var p = this;
    do {
      // IE doesn't implement hasOwnProperty on DOM elements (or so it looks).
      // force setting on root objects:
      if (!p.parent || p.bindings.hasOwnProperty(name)) {
        p.bindings[name] = val;
        return;
      }
    } while((p = p.parent) != null); 
  },
  getBinding : function(name) {
    if (!this.parent || this.bindings.hasOwnProperty(name))
      return this.bindings[name];
    else
      return this.parent.getBinding(name);
  }
};

//----------------------------------------------------------------------
// Oni return values
function ORV(value) {
  this.value = value;
}
ORV.prototype = {
  didThrow : function() { return isOniException(this.value); }
};

//----------------------------------------------------------------------
// Oni exceptions
function OXX(tag, value) {
  this.tag = tag;
  this.value = value;
}
OXX.prototype = { __isOXX : true };

function isOniException(e) {
  return e && (e.__isOXX == true);
}

//----------------------------------------------------------------------
// Oni execution node; this will be root prototype for all execution
// node types
function OXN(classname) {
  this.oxn_classname = classname;
}
OXN.prototype = {
  __isOXN: true,
  toString: function() { return "OXN<"+this.oxn_classname+">"; }
};

//----------------------------------------------------------------------
// Seq execution node:
function OXN_Seq(exps, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  this.env = env;
  
  this.exps = exps;
  
  // we're also the execution context for our subexecutions:
  this.next = this;
  this.current = null;
  
  this.i_exp = -1;
  this.exp_count = this.exps.length;
  var me = this;
  globalScheduler.schedule(function() { me.cont(me, new ORV(null)); });
}
OXN_Seq.prototype = new OXN("Seq");

OXN_Seq.prototype.cont = function(exiting_xc, rv) {
  if (this.aborted) {
    this.xc.next.cont(this.xc, new ORV(undefined));
    this.clear();
    return;
  }
  
  if (rv.didThrow()) {
    this.xc.next.cont(this.xc, rv);
    return;
  }

  var i = ++this.i_exp;
  if (i >= this.exp_count) {
    // should only reach this for empty Seq's
    this.xc.next.cont(this.xc, rv);
    this.clear();
  }
  else if (i+1 == this.exp_count) {
    // tail call
    executeOEN(this.exps[i], this.xc, this.env);
    this.clear();
  }
  else {
    // normal call
    executeOEN(this.exps[i], this, this.env);
  }
};

OXN_Seq.prototype.abort = function() {
  this.aborted = true;
  if (this.current) {
    this.current.abort();
  }
};

OXN_Seq.prototype.clear = function() {
  delete this.rv;
  delete this.xc;
  delete this.env;
  delete this.current;
};

//----------------------------------------------------------------------
// Alt execution node:
function OXN_Alt(exps, xc, env) {
  this.xc = xc;
  this.xc.current = this;

  this.fork_count = exps.length;
  this.forks = [];

  // fork backwards, so that we get the right evaluation order:
  for (var i=exps.length-1; i>=0; --i) {
    this.forks.push(fork(env, this, exps[i]));
  }
}
OXN_Alt.prototype = new OXN("Alt");

OXN_Alt.prototype.cont = function(exiting_context, rv) {
  if (rv.didThrow()) {
    this.xc.next.cont(this.xc, rv);
    return;
  }

  if (this.fork_count == this.forks.length) {
    // the first subexpression has returned.
    // remember its value. abort all others.
    for (var i=0; i<this.forks.length; ++i) {
      if (this.forks[i] == exiting_context) {
        this.rv = rv;
        this.forks[i].current = null;
      }
      else {
        this.forks[i].current.abort();
      }
    }
  }
  else {
    // another subexpression has returned.    
    // mark its context as null, so that we don't (potentially) send
    // obsolete abort signals.
    for (var i=0; i<this.forks.length; ++i)
      if (this.forks[i] == exiting_context) {
        this.forks[i].current = null;
        break;
      }
  }

  if (--this.fork_count == 0) {
    // all subexpressions are done.
    // return stored result (from first returning subexpression)
    this.xc.next.cont(this.xc, this.rv);
    this.clear();
  }
};

OXN_Alt.prototype.abort = function() {
  if (this.forks) {
    for (var i=0; i<this.forks.length; ++i) {
      if (this.forks[i].current)
        this.forks[i].current.abort();
    }
  }
};

OXN_Alt.prototype.clear = function() {
  delete this.xc;
  delete this.forks;
};

//----------------------------------------------------------------------
// ExceptionRoot execution node:
// A handler which will take care of unwinding the stack in return to 
// an 'abort' call. (exceptions traverse up the tree, are caught, and an
// abort call is issued down the tree.)

function OXN_ExceptionRoot(xc) {
  this.xc = xc;
  this.xc.current = this;
}
OXN_ExceptionRoot.prototype = new OXN("ExceptionRoot");

OXN_ExceptionRoot.prototype.cont = function() {
  this.xc.next.cont(this.xc, new ORV(undefined));
  delete this.xc;
}

OXN_ExceptionRoot.prototype.abort = function() {
  // assert this.xc 
  var cont = bind(this.cont, this);
  globalScheduler.schedule(cont);
}

//----------------------------------------------------------------------
// Quote execution node:
function OXN_Quote(datum, xc) {
  this.xc = xc;
  this.xc.current = this;
  
  this.datum = datum;
  var cont = bind(this.cont, this);
  globalScheduler.schedule(cont);
}
OXN_Quote.prototype = new OXN("Quote");

OXN_Quote.prototype.cont = function() {
  this.xc.next.cont(this.xc, new ORV(this.datum));
  this.clear();
};

OXN_Quote.prototype.abort = function() {
  this.datum = undefined;
};

OXN_Quote.prototype.clear = function() {
  if (!this.xc) return;
  delete this.xc;
  delete this.datum;
};

//----------------------------------------------------------------------
// Apply execution node:
function OXN_Apply(fexp, aexps, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  this.env = env;
  
  this.fork_count = 1 + aexps.length;
  this.forks = [];
  this.forks.push(fork(env, this, fexp));
  
  // fork backwards, so that we get the right evaluation order:
  for (var i=aexps.length-1; i>=0; --i) {
    this.forks.push(fork(env, this, aexps[i]));
    }
}
OXN_Apply.prototype = new OXN("Apply");

OXN_Apply.prototype.cont = function(exiting_context, rv) {
  if (rv.didThrow()) {
    this.xc.next.cont(this.xc, rv);
    return;
  }

  // replace context by result:
  for (var i=0; i<this.forks.length; ++i)
    if (this.forks[i] == exiting_context) {
      this.forks[i].current = null;
      this.forks[i].rv = rv;
      break;
    }
  // xxx assert that the context was found
  
  if (--this.fork_count == 0) {
    if (this.aborted) {
      this.xc.next.cont(this.xc, new ORV(undefined));
      this.clear();
      return;
    }
    
    var oan_rv = this.forks.shift().rv;
    var pars = [];
    // since we forked backwards above, we need to push backwards here:
    for (var i=this.forks.length-1; i>=0; --i) {
      pars.push(this.forks[i].rv.value);
    }
    
    // apply with parent context (tail call):
     fapplyOAN(oan_rv.value, this.xc, this.env, pars);
     this.clear();
  }
};

OXN_Apply.prototype.abort = function() {
  if (this.forks) {
    this.aborted = true;
    for (var i=0; i<this.forks.length; ++i) {
      if (this.forks[i].current)
        this.forks[i].current.abort();
    }
  }
};

OXN_Apply.prototype.clear = function() {
  delete this.xc;
  delete this.env;
  delete this.forks;
};
  
//----------------------------------------------------------------------
// ALift execution nodes:

// execution node for apply() calls:

// Calls the asynchronous js function 'async_f' which is
// expected to have the following signature:
//      abort_f async_f(cont, arg1, arg2, ...)
// - async_f must return an abort function 'abort_f' (can be null).
// - async_f must call 'cont' exactly 0 or 1 times, passing in a
//   return value structured in the following way:
//       [true, return_value]    : success, returning 'return_value'
//       [false, error_string]   : failure; oni will throw an exception
//                                 with tag "error" and value 'error_string'.
// - If Oni calls abort_f before async_f has called 'cont', any subsequent
//   call to 'cont' will be ignored. abort_f will be called exactly 0
//   or 1 times. abort_f will not be called after async_f has called 'cont'.
// - async_f will be executed with a 'this' object set to the current
//   environment.
function OXN_ALift_Apply(async_f, xc, env, args) {
  this.xc = xc;
  this.xc.current = this;

  var pars = [bind(this.cont, this)];
  pars = pars.concat(args);
  this.abort_f = async_f.apply(env, pars);
}
OXN_ALift_Apply.prototype = new OXN("ALift_Apply");

OXN_ALift_Apply.prototype.abort = function() {
  if (this.abort_f) {
    this.abort_f.apply(this.this_object);
    delete this.abort_f;
  }
  this.aborted = true;
  
  // arrange for our continuation to be called asynchronously:
  // (if the alifted js function didn't provide an abort_f, we might
  // call the continuation twice. but that's ok, we'll take care of it there)
  this.cont();
};

OXN_ALift_Apply.prototype.cont = function(rv, synchronous) {
  // The 'synchronous' parameter determines if we want to schedule
  // this return asap or append it to the end of the queue. See the
  // comments about scheduling simple code in a 'synchronous' way, in
  // the Scheduler and in OAN_SLift.
  
  if (this.aborted && !this.xc) {
    // a trailing callback from an uncancelable alifted function
    // -> ignore
    return;
  }
  var me = this;
  globalScheduler.schedule(function() { me.cont_inner(rv); }, synchronous != true);
};
  
OXN_ALift_Apply.prototype.cont_inner = function(rv) {
  if (this.aborted) {
    if (!this.xc) {
      // a trailing callback from an uncancelable alifted function
      // -> ignore
      return;
    }
    rv = new ORV(undefined);
  }
  else {
    // 'normal' return.
    // map result into ORV and to exception if appropriate:
    if (rv[0] == true)
      rv = new ORV(rv[1]);
    else {
      // install a handler which will take care of unwinding the stack when we
      // get the exception's corresponding 'abort' call:
      new OXN_ExceptionRoot(this.xc);
      rv = new ORV(new OXX("error", rv[1]));
    }
  }
  
  this.xc.next.cont(this.xc, rv);
  delete this.xc;
  delete this.abort_f;
  delete this.this_object;
};

//----------------------------------------------------------------------
// Let execution node:

function OXN_Let(bindings, body_exp, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  // bindings' value expressions will be executed in the new
  // environment, to support (mutual) recursion from closures:
  this.env = new OENV(env);
  
  this.body_exp = body_exp;
  
  // xxx this code is not reentrant. bindings' expressions must be async.
  this.fork_count = 0;
  this.forks = [];
  for (b in bindings) {
    ++this.fork_count;
    this.forks.push([b, fork(this.env, this, bindings[b])]);
  }   
}
OXN_Let.prototype = new OXN("Let");

OXN_Let.prototype.cont = function(exiting_context, rv) {
  if (rv.didThrow()) {
    this.xc.next.cont(this.xc, rv);
    return;
  }

  // insert value into environment; clear context:
  for (var i=0; i<this.forks.length; ++i) {
    if (this.forks[i][1] == exiting_context) {
      this.env.createBinding(this.forks[i][0], rv.value);
      this.forks[i][1].current = null;
      break;
    }
  }
  // xxx assert that context was found

  if (--this.fork_count == 0) {
    if (this.aborted) {
      this.xc.next.cont(this.xc, new ORV(undefined));
    }
    else {
      // value_exp has finished; now call body_exp with the new bindings &
      // the parent's xc (tail call):
      executeOEN(this.body_exp, this.xc, this.env);
    }
    this.clear();
  }
};

OXN_Let.prototype.abort = function() {
  if (this.forks) {
    this.aborted = true;
    for (var i=0; i<this.forks.length; ++i) {
      if (this.forks[i][1].current)
        this.forks[i][1].current.abort();
    }
  }
};

OXN_Let.prototype.clear = function() {
  delete this.xc;
  delete this.env;
  delete this.forks;
};

//----------------------------------------------------------------------
// Get execution node:
function OXN_Get(var_name, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  this.env = env;
  
  this.var_name = var_name;
  var cont = bind(this.cont, this);
  globalScheduler.schedule(cont);
}
OXN_Get.prototype = new OXN("Get");

OXN_Get.prototype.cont = function() {
  if (this.aborted) {
    this.xc.next.cont(this.xc, new ORV(undefined));
  }
  else {
    // XXX throw exception if value not bound?
    this.xc.next.cont(this.xc, new ORV(this.env.getBinding(this.var_name)));
  }
  this.clear();
};

OXN_Get.prototype.abort = function() {
  this.aborted = true;
};

OXN_Get.prototype.clear = function() {
  if (!this.xc) return;
  delete this.xc;
  delete this.env;
  delete this.var_name;
};






//----------------------------------------------------------------------
// Lambda execution node:
function OXN_Lambda(formals, body_exp, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  
  this.closure = OAN_Closure(formals, body_exp, env);
  var cont = bind(this.cont, this);
  globalScheduler.schedule(cont);
}
OXN_Lambda.prototype = new OXN("Lambda");

OXN_Lambda.prototype.cont = function() {
  this.xc.next.cont(this.xc, new ORV(this.closure));
  this.clear();
};

OXN_Lambda.prototype.abort = function() {
  this.closure = undefined;
}

OXN_Lambda.prototype.clear = function() {
  if (!this.xc) return;
  delete this.xc;
  delete this.closure;
};

//----------------------------------------------------------------------
// Throw execution node:
// executes tag_exp and val_exp in parallel, returns exception made from the two
// XXX no need for tag_exp to be an expression; we could rewrite this to use a
// literal directly
function OXN_Throw(tag_exp, val_exp, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  
  this.fork_count = 2;
  this.forks = [];
  this.forks.push(fork(env, this, tag_exp));
  this.forks.push(fork(env, this, val_exp));
}
OXN_Throw.prototype = new OXN("Throw");

OXN_Throw.prototype.cont = function(exiting_context, rv) {
  if (rv.didThrow()) {
    this.xc.next.cont(this.xc, rv);
    return;
  }

  // replace context by result:
  for (var i=0; i<this.forks.length; ++i)
    if (this.forks[i] == exiting_context) {
      this.forks[i].current = null;
      this.forks[i].rv = rv;
      break;
    }
  // xxx assert that the context was found

  if (--this.fork_count == 0) {
    if (this.aborted) {
      this.xc.next.cont(this.xc, new ORV(undefined));
    }
    else {
      // install a handler which will take care of unwinding the stack when we
      // get the exception's corresponding 'abort' call:
      new OXN_ExceptionRoot(this.xc);
      this.xc.next.cont(this.xc,
                        new ORV(new OXX(this.forks[0].rv.value,
                                        this.forks[1].rv.value)));
    }
    this.clear();
  }
};

OXN_Throw.prototype.abort = function() {
  if (this.forks) {
    this.aborted = true;
    for (var i=0; i<this.forks.length; ++i) {
      if (this.forks[i].current)
        this.forks[i].current.abort();
    }
  }
};

OXN_Throw.prototype.clear = function() {
  delete this.xc;
  delete this.forks;
};

//----------------------------------------------------------------------
// Catch execution node:
// execute tag_exp, then body_exp; catch any returns with tag_exp and
// execute handler_exp if an exception was caught
function OXN_Catch(tag_exp, body_exp, handler_exp, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  this.env = env;
  
  this.body_exp = body_exp;
  this.handler_exp = handler_exp;
  this.pending_exception = null;
  
  // we're the context for the execution of tag_exp, body_exp and
  // handler_exp:
  this.next = this;
  this.current = null;

  executeOEN(tag_exp, this, this.env);
}
OXN_Catch.prototype = new OXN("Catch");

OXN_Catch.prototype.cont = function(exiting_context, rv) {
  if (this.aborted) {
    // abort handling is the same for tag_exp & body_exp
    this.xc.next.cont(this.xc, new ORV(undefined));
    this.clear();
    return;
  }
  
  if (this.body_exp !== undefined) {
    // this is the tag_exp finishing
    if (rv.didThrow()) {
      this.xc.next.cont(this.xc, rv);
      return;
    }
    this.tag = rv.value;
    // we've got the tag, now execute the body:
    executeOEN(this.body_exp, this, this.env);
    delete this.body_exp;
  }
  else if (this.pending_handler) {
    // this is the handler_exp finishing    
    if (rv.didThrow()) {
      this.xc.next.cont(this.xc, rv);
      return;
    }
    // we assume that it returned a OAN on which we'll make a tail
    // call, passing in the exception's value:
    fapplyOAN(rv.value, this.xc, this.env, [this.pending_exception.value]);
     this.clear();
  }
  else if (this.pending_exception) {
    // the body_exp is returning from its abort-call.

    if (this.handler_exp !== undefined) {
      // we have a handler_exp that needs resolving
      executeOEN(this.handler_exp, this, this.env);
      this.pending_handler = true;
      delete this.handler_exp;
    }
    else {
      // no handler_exp... just return the exception's value:
      this.xc.next.cont(this.xc, new ORV(this.pending_exception.value));
      this.clear();
    }
  }
  else {
    // this is the body_exp finishing or excepting
    if (rv.didThrow()) {
      if (rv.value.tag != this.tag) {
        // exception isn't addressed to us -> pass through
        this.xc.next.cont(this.xc, rv);
        return;
      }

      // this is our exception. abort current execution, and pass up
      // result, or, if we have a handler_exp, resolve the exp and
      // pass the exception value through it:
      this.current.abort();
      this.current_aborted = true; 
      // keep hold of exception object:
      this.pending_exception = rv.value;
    }
    else {
      // no exception... return value of body_exp:
      this.xc.next.cont(this.xc, rv);
      this.clear();
    }
  }
};

OXN_Catch.prototype.abort = function() {
  if (this.current) {
    this.aborted = true;
    // only issue a downward abort if we haven't done so already by virtue of
    // catching an exception ourselves
    if (!this.current_aborted)
      this.current.abort();
  }
};

OXN_Catch.prototype.clear = function() {
  delete this.xc;
  delete this.env;
  delete this.current;
  delete this.body_exp;
  delete this.handler_exp;
  delete this.pending_exception;
  delete this.tag;
};

//----------------------------------------------------------------------
// If execution node:
function OXN_If(test_exp, consequent_exp, alternative_exp, xc, env) {
  this.xc = xc;
  this.xc.current = this;
  this.env = env;
  
  this.consequent_exp = consequent_exp;
  this.alternative_exp = alternative_exp;
  
  // we're also the context for the execution of test_exp:
  this.next = this;
  this.current = null;

  executeOEN(test_exp, this, this.env);
}
OXN_If.prototype = new OXN("If");

OXN_If.prototype.cont = function(exiting_context, rv) {
  if (this.aborted) {
    this.xc.next.cont(this.xc, new ORV(undefined));
  }
  else {
    if (rv.didThrow()) {
      this.xc.next.cont(this.xc, rv);
      return;
    }

    if (rv.value && this.consequent_exp !== undefined) {
      // tail call
      executeOEN(this.consequent_exp, this.xc, this.env);
    }
    else if (!rv.value && this.alternative_exp !== undefined) {
      // tail call
      executeOEN(this.alternative_exp, this.xc, this.env);
    }
    else {
      this.xc.next.cont(this.xc, rv);
    }
  }
  this.clear();
};

OXN_If.prototype.abort = function() {
  if (this.current) {
    this.aborted = true;
    this.current.abort();
  }
};

OXN_If.prototype.clear = function() {
  delete this.xc;
  delete this.env;
  delete this.current;
};

//----------------------------------------------------------------------
// 'Complete' execution node:
function OXN_Complete(body_exp, xc, env) {
  this.xc = xc;
  this.xc.current = this;

  // we're also the context for the execution of body_exp:
  this.next = this;
  this.current = null;

  executeOEN(body_exp, this, env);
}
OXN_Complete.prototype = new OXN("Complete");

OXN_Complete.prototype.cont = function(exiting_context, rv) {
  // this.aborted == the enclosing expression aborted us
  // this.thrown == the inner expression threw an exception (and OXN_Complete
  //                aborted it as a result)
  if (rv.didThrow()) {
    // Catch any exception:
    // ASSERT(!this.thrown, "multiple unexpected exceptions");
    // this.thrown = true;
    this.current.abort();
    // pass on the exception if we haven't been aborted ...
    if (!this.aborted) {
      this.xc.next.cont(this.xc, rv);
    }
    // else WARN("silent uncaught exception");
  }
  else {
    this.xc.next.cont(this.xc, this.aborted ? new ORV(undefined) : rv);
    delete this.xc;
    delete this.current;
  }
};

OXN_Complete.prototype.abort = function() {
  this.aborted = true;
  // Don't abort this.current. That's the whole point of OXN_Complete
  // after all.
};

//----------------------------------------------------------------------
// 'Bracket' execution node:
function OXN_Bracket(acquire_exp, use_exp, release_exp, xc, env) {
  this.xc = xc;
  this.env = env;
  this.xc.current = this;

  // we're also the context for the execution of acquire_exp, use_exp,
  // release_exp:
  this.next = this;
  this.current = null;

  this.use_exp = use_exp;
  this.release_exp = release_exp;

  executeOEN(acquire_exp, this, this.env);
}
OXN_Bracket.prototype = new OXN("Bracket");

OXN_Bracket.prototype.cont = function(exiting_context, rv) {
  // this.aborted == the enclosing expression aborted us
  // this.thrown == the inner acquire_exp or release_exp threw an
  // exception
  if (this.use_exp !== undefined) {
    // acquire_exp returning (or throwing)
    if (rv.didThrow()) {
      // Catch any exception:
      // ASSERT(!this.thrown, "multiple unexpected exceptions");
      this.thrown = true;
      this.current.abort();
      // pass on the exception if we haven't been aborted ...
      if (!this.aborted) {
        this.xc.next.cont(this.xc, rv);
      }
      // else WARN("silent uncaught exception");
    }
    else {
      // 'normal' return
      if (this.thrown) {
        // acquire_exp threw an exception. We're all done.
        this.xc.next.cont(this.xc, this.aborted ? new ORV(undefined) : rv);
        this.clear();
      }
      else {
        // continue in use_exp:
        executeOEN(this.use_exp, this, this.env);
        delete this.use_exp;
      }
    }
  }
  else if (this.release_exp !== undefined) {
    // use_exp returning (or throwing)
    if (rv.didThrow()) {
      // just propagate the exception; some enclosing expression will abort
      // use_exp
      this.xc.next.cont(this.xc, rv);
    }
    else {
      // 'normal' return
      // save return value for later:
      this.rv = rv;
      // continue in release_exp:
      executeOEN(this.release_exp, this, this.env);
      delete this.release_exp;
    }
  }
  else {
    //  release_exp returning (or throwing)
    if (rv.didThrow()) {
      // Catch any exception:
      // ASSERT(!this.trown, "multiple unexpected exceptions");
      this.thrown = true;
      this.current.abort();
      // pass on the exception if we haven't been aborted ...
      if (!this.aborted) {
        this.xc.next.cont(this.xc, rv);
      }
      // else WARN("silent uncaught exception");
    }
    else {
      // 'normal' return
      // return stored rv from use_exp
      this.xc.next.cont(this.xc, this.aborted ? new ORV(undefined) : this.rv);
      this.clear();
    }
  }
};

OXN_Bracket.prototype.abort = function() {
  // ASSERT (this.current)
  this.aborted = true;
  if (this.use_exp === undefined && this.release_exp !== undefined) {
    // we're in use_exp -> propagate abort downwards
    this.current.abort();
  }
};

OXN_Bracket.prototype.clear = function() {
  delete this.xc;
  delete this.env;
  delete this.current;
  delete this.acquire_exp;
  delete this.use_exp;
  delete this.release_exp;
};

//----------------------------------------------------------------------
// generic call by need 'values': (OVE - Oni Value Expression)
// XXX call-by-need semantics not implemented yet.

// valueobj must have members:
// - forceValue : function()
// - value : obj||undefined
// - isValueAvailable : boolean

// On wrapping a valueobj into an OVE_GenericValue, a member
// 'valueAvailable : function()' will be inserted into valueobj.

function OVE_GenericValue(valueobj) {
  var pendingOXNs = [];
  var readScheduled = false;

  function read() {
    readScheduled = false;
    if (!valueobj.isValueAvailable) {
      // shouldn't happen, but let's be defensive
      return;
    }
    var l = pendingOXNs;
    pendingOXNs = [];
    for (var i=0; i<l.length; ++i)
      if (!l[i].aborted)
        l[i].cont(valueobj.value);
  }
  
  valueobj.valueAvailable = function() {
    if (pendingOXNs.length && !readScheduled) {
      readScheduled = true;
      globalScheduler.schedule(read);
    }
  };
  
  var f = function() {
    if (arguments[0] == ONI_OEN_EXECUTE_TOKEN) {
      var oxn = new OXN_GenericValue(arguments[1]);
      pendingOXNs.push(oxn);
      if (!readScheduled) {
        if (valueobj.isValueAvailable) {
          readScheduled = true;
          globalScheduler.schedule(read);
        }
        else {
          valueobj.forceValue();
        }
      }
      return oxn;
    }
    else {
      // function application chaining
      var aargs = slice_args(arguments, 0);
      aargs.unshift(f);
      return Apply.apply(this, aargs);
    }
  };
  markAsOEN(f);
  markAsCallByNeed(f);
  return f;
}

// execution node:
function OXN_GenericValue(xc) {
  this.xc = xc;
  this.xc.current = this;
}
OXN_GenericValue.prototype = new OXN("GenericValue");

OXN_GenericValue.prototype.cont = function(rv) {
  this.xc.next.cont(this.xc, rv);
  delete this.xc;
};

OXN_GenericValue.prototype.abort = function() {
  this.aborted = true;
};

//----------------------------------------------------------------------
// Running an ONI expression:

function Eval(oen, bindings) {
  var valueObject = {};
  
  valueObject.forceValue = function() {
    // value is evaluated eagerly; nothing to force
  };

  valueObject.isValueAvailable = false;
  
  valueObject.setValue = function(val) {
    this.value = val;
    this.isValueAvailable = true;
    this.valueAvailable();
  };
  
  valueObject.cont = function(exiting_context, rv) {
    // dump("top level return:"+rv+"\n");
    if (rv.didThrow()) {
      exiting_context.current.abort();
      this.setValue(new ORV(new OXX("nested", rv.value)));
      // make sure exception gets reported in the console:
      callAsync(function() { throw "Uncaught Oni exception: '"+rv.value.tag+"', val:'"+rv.value.value+"'";  });
    }
    else {
      this.setValue(rv);
    }
    delete this.current;
  };
  
  valueObject.abort = function() {
    if (this.current) {
      this.current.abort();
    }
  };

  valueObject.current =  null;

  valueObject.next = valueObject;

  var ove = OVE_GenericValue(valueObject);
 
  executeOEN(oen, valueObject, new OENV(null, bindings));
  
  // start execution immediately:
  globalScheduler.executeChain();
  return ove;
};
this["Eval"] = Eval;

////////////////////////////////////////////////////////////////////////
// Surface syntax for expression graph construction


//----------------------------------------------------------------------
// oni_exp constructors:

// Seq : exp* -> oni_exp
function Seq(/* exp1, exp2, ... */) {
  return OEN_Seq(arguments);
}
this["Seq"] = Seq;

// Alt : exp* -> oni_exp
function Alt(/* exp1, exp2, ... */) {
  return OEN_Alt(arguments);
}
this["Alt"] = Alt;

// Quote : datum -> oni_exp
var Quote = OEN_Quote; 
this["Quote"] = Quote;

// Apply : exp, exp* -> oni_exp
function Apply(/* fexp, aexp1, aexp2, ... */) {
  return OEN_Apply(arguments[0], slice_args(arguments, 1));
}
this["Apply"] = Apply;

// If : exp, exp, exp? -> oni_exp
function If(/* test_exp, consequent_exp, alternative_exp? */) {
  return OEN_If(arguments[0], arguments[1], arguments[2]);
}
this["If"] = If;

// Complete : exp+ -> oni_exp
function Complete(/* exp+ */) {
  var exp = slice_args(arguments, 0);
  if (exp.length == 1)
    exp = exp[0];
  else
    exp = Seq.apply(this, exp);
  
  return OEN_Complete(exp);
}
this["Complete"] = Complete;

// Bracket : exp, exp, exp -> oni_exp
var Bracket = OEN_Bracket;
this["Bracket"] = Bracket;

// Let : bindings, exp+ -> oni_exp
function Let(/* bindings, exp+ */) {
  var bindings = arguments[0];
  var exp = slice_args(arguments, 1);
  if (exp.length == 1)
    exp = exp[0];
  else
    exp = Seq.apply(this, exp);
  
  return OEN_Let(bindings, exp);
}
this["Let"] = Let;

// Get : var_name -> oni_exp
var Get = OEN_Get;
this["Get"] = Get;





// Throw : tag, exp? -> oni_exp
function Throw(tag, val_exp) {
  return OEN_Throw(Quote(tag), val_exp);
};
this["Throw"] = Throw;

// Catch : [tag, exp?], exp+ -> oni_exp
// handler_exp, if provided, must resolve to an OAN.
function Catch(/*tag_handler, exp*/) {
  var tag_handler = arguments[0];
  var body_exp = slice_args(arguments, 1);
  if (body_exp.length == 1)
    body_exp = body_exp[0];
  else
    body_exp = Seq.apply(this, body_exp);
  
  return OEN_Catch(Quote(tag_handler[0]),
                   body_exp,
                   tag_handler[1] ? tag_handler[1] : undefined);
};
this["Catch"] = Catch;

// Loop : exp+ -> oni_exp
// Break : exp? -> oni_exp
// Continue : void -> oni_exp
var tag_Break = {};
var tag_Continue = {};
function Loop(/*exp+*/) {
  var exp = slice_args(arguments, 0);
  if (exp.length == 1)
    exp = exp[0];
  else
    exp = Seq.apply(this, exp);
  
  // XXX __loop__ shouldn't be visible in the environment 
  return Catch([tag_Break],
               Let({ "__loop__" : Lambda([],
                                         Catch([tag_Continue], exp),
                                         // XXX This ALift is here to throttle loops, so
                                         // that other strata get a fair chance at running.
                                         // (ALift callbacks will be scheduled after any
                                         // other callbacks)
                                         ALift(function(c){ c([true,1]); })(),
                                         Get("__loop__")() )
                   },
                   Get("__loop__")()));
}
function Break(exp) {
  return Throw(tag_Break, exp);
}
function Continue() {
  return Throw(tag_Continue);
}
this["Loop"] = Loop;
this["Break"] = Break;
this["Continue"] = Continue;

//----------------------------------------------------------------------
// oni_fexp constructors:

// ALift : async_f -> oni_fexp
var ALift = OAN_ALift;
this["ALift"] = ALift;

// SLift : sync_f -> oni_fexp
var SLift = OAN_SLift;
this["SLift"] = SLift;

// Lambda : [formals], exp+ -> oni_fexp
function Lambda(/* formals, exp+ */) {
  var formals = arguments[0];
  var body_exp = slice_args(arguments, 1);
  if (body_exp.length == 1)
    body_exp = body_exp[0];
  else
    body_exp = Seq.apply(this, body_exp);

  return OEN_Lambda(formals, body_exp);
}
this["Lambda"] = Lambda;

// Defun : [formals], exp+ -> oni_fexp
function Defun(/* formals, exp+ */) {
  var formals = arguments[0];
  var body_exp = slice_args(arguments, 1);
  if (body_exp.length == 1)
    body_exp = body_exp[0];
  else
    body_exp = Seq.apply(this, body_exp);
  
  return OAN_Closure(formals, body_exp, {});
}
this["Defun"] = Defun;

// Nop : oni_fexp
var Nop = SLift(function() { return null; });
this["Nop"] = Nop;

// Stop : oni_fexp
var Stop = ALift(function() { /* continuation never called */ });
this["Stop"] = Stop;

// Par : oni_fexp
var Par = Nop;
this["Par"] = Par;

////////////////////////////////////////////////////////////////////////
// Core library:


//----------------------------------------------------------------------
// Logical Operators:

var Not = SLift(function(a) { return !a; });
this["Not"] = Not;

//----------------------------------------------------------------------
// Relational Operators, Predicates:

var Equals = SLift(function(a, b) { return a == b; });
this["Equals"] = Equals;

var NEquals = SLift(function(a, b) { return a != b; });
this["NEquals"] = NEquals;

// returns true if obj is null or the empty list
var IsNull = SLift(function(obj) { return !obj || (obj.length === 0); });
this["IsNull"] = IsNull;

//----------------------------------------------------------------------
// Object Manipulation:

// construct a json object:
var Obj = SLift(function(/* path1, value1, ..., path_n, value_n */) {
  var obj = {};
  for (var i=0; i+1<arguments.length; i+=2) {
    var path = arguments[i];
    var ref = obj;
    if (path instanceof Array) {
      for (var e = 0; e<path.length-1; ++e) {
        if (!ref[path[e]]) ref[path[e]] = {};
        ref = ref[path[e]];
      }
      path = path[e];
    }
    if (ref[path]) throw("Duplicate member '"+path+"' in JSON Obj");
    ref[path] = arguments[i+1];
  }
  
  return obj;
});
this["Obj"] = Obj;

// retrieve a member from a json object:
var ObjMem = SLift(function(obj /*, member1, member2, ... */) {
  var rv = obj;
  for (var i=1; i<arguments.length; ++i) {
    rv = rv[arguments[i]];
  }
  return rv;
});
this["ObjMem"] = ObjMem;

//----------------------------------------------------------------------
// List Manipulation:

var emptylist = [];

var Car = SLift(function(lst) { return lst[0]; });
this["Car"] = Car;

var Cdr = SLift(function(lst) { return lst.length > 1 ? lst.slice(1) : emptylist; });
this["Cdr"] = Cdr;

var Cadr = SLift(function(lst) { return lst[1]; });
this["Cadr"] = Cadr;

var Caddr = SLift(function(lst) { return lst[2]; });
this["Caddr"] = Caddr;

var Cadddr = SLift(function(lst) { return lst[3]; });
this["Cadddr"] = Cadddr;

var ListRef = SLift(function(lst, i) { return lst[i]; });
this["ListRef"] = ListRef;

var ListTail = SLift(function(lst, i) { return lst.length > i ? lst.slice(i) : emptylist; });
this["ListTail"] = ListTail;

// behaves like r6rs 'member' function
var Member = SLift(function(obj, lst) {
  for (var i=0; i<lst.length; ++i) {
    if (lst[i] == obj)
      return lst.slice(i);
  }
  return false;
});
this["Member"] = Member;

// Create a list from the given arguments:
var List = SLift(function(/*objs*/) {
  return slice_args(arguments, 0);
});
this["List"] = List;

// Cons an object onto a list. (No support for dotted pairs!)
var Cons = SLift(function(obj, lst) {
  var res = [obj];
  return res.concat(lst);
});
this["Cons"] = Cons;

// removes all elements == obj from lst
var Remove = SLift(function(obj, lst) {
  var res = [];
  for (var i=0; i<lst.length; ++i)
    if (lst[i] != obj)
      res.push(lst[i]);
  return res;
});
this["Remove"] = Remove;
  
function GetCar(v) { return Car(Get(v)); }
this["GetCar"] = GetCar;

function GetCdr(v) { return Cdr(Get(v)); }
this["GetCdr"] = GetCdr;

function GetCadr(v) { return Cadr(Get(v)); }
this["GetCadr"] = GetCadr;

function GetCaddr(v) { return Caddr(Get(v)); }
this["GetCaddr"] = GetCaddr;

function GetCadddr(v) { return Cadddr(Get(v)); }
this["GetCadddr"] = GetCadddr;

//----------------------------------------------------------------------
// Mathematical Operators

var Add = SLift(function(a,b) { return a+b; });
this["Add"] = Add;

var Sub = SLift(function(a, b) { return a-b; });
this["Sub"] = Sub;

var Mul = SLift(function(a, b) { return a*b; });
this["Mul"] = Mul;

var Div = SLift(function(a, b) { return a/b; });
this["Div"] = Div;

var Random = SLift(function(ceil) { return Math.random()*ceil; });
this["Random"] = Random;

//----------------------------------------------------------------------
// Timing

// Sleep : oni_fexp
var Sleep = ALift(timeout);
this["Sleep"] = Sleep;

// Delay : exp, exp* -> oni_exp
function Delay(/* duration, exp* */) {
  var duration = arguments[0];
  var exps = slice_args(arguments, 1);
  exps.unshift(Apply(Sleep, duration));
  return Seq.apply(this, exps);
}
this["Delay"] = Delay;


var GetCurrentTimeMS = SLift(getCurrentTimeMS);

function Time(/*exp+*/) {
  var exp = slice_args(arguments, 0);
  if (exp.length == 1)
    exp = exp[0];
  else
    exp = Seq.apply(this, exp);

  // XXX __time__/__res__ shouldn't be visible in the environment
  return Let({ "__time__" : GetCurrentTimeMS() },
             Let({ "__res__" : exp },
                 List(Get("__res__"), Sub(GetCurrentTimeMS(),
                                          Get("__time__")))));
}
this["Time"] = Time;

//----------------------------------------------------------------------
// Atomic variables

function _AtomicVar(value) {
  this.value = value;
}
_AtomicVar.prototype = {};

_AtomicVar.prototype.read = function() { return this.value; };
_AtomicVar.prototype.write = function(value) { this.value = value; };
_AtomicVar.prototype.transform = function(xform_func) { return this.value = xform_func(this.value); };

// static API:
var AtomicVar = {};
AtomicVar.create = function(value) { return new _AtomicVar(value); };
AtomicVar.Create = SLift(AtomicVar.create);
AtomicVar.Read = SLift(function(v) { return v.read(); });
AtomicVar.Write = SLift(function(v, value) { v.write(value); });
AtomicVar.Transform = SLift(function(v, xform) { return v.transform(xform); });
this["AtomicVar"] = AtomicVar;

//////////////////////////////////////////////////////////////////////
// Signals

function _Signal() {
  this.listeners = [];
}
_Signal.prototype = {};

_Signal.prototype.notify = function(val) {
  var ls = this.listeners;
  this.listeners = [];
  for (var i=0; i<ls.length; ++i) {
    ls[i]([true, val]);
  }
};

_Signal.prototype.wait = function(cont) {
  this.listeners.push(cont);
  var me = this;
  return function() {
    for (var i=0; i<me.listeners.length; ++i) {
      if (me.listeners[i] == cont) {
        me.listeners.splice(i, 1);
        return;
      }
    }
  };
};

// static Signal API:
var Signal = {};
Signal.create = function() { return new _Signal(); };
Signal.Create = SLift(Signal.create);
Signal.Notify = SLift(function(s, val) { return s.notify(val);});
Signal.Wait = ALift(function(cont, s) { return s.wait(cont); });

this["Signal"] = Signal;

////////////////////////////////////////////////////////////////////////
// Barriers

function _Barrier(is_open) {
  this.is_open = is_open ? true : false;
  this.listeners = [];
}
_Barrier.prototype = {};

_Barrier.prototype.open = function() {
  this.is_open = true;
  var ls = this.listeners;
  this.listeners = [];
  for (var i=0; i<ls.length; ++i) {
    ls[i]([true, null]);
  }
};

_Barrier.prototype.close = function() {
  this.is_open = false;
};

_Barrier.prototype.pass = function(cont) {
  if (this.is_open)
    globalScheduler.schedule(function() { cont([true, null]); });
  else {
    this.listeners.push(cont);
    var me = this;
    return function() {
      for (var i=0; i<me.listeners.length; ++i) {
        if (me.listeners[i] == cont) {
          me.listeners.splice(i, 1);
          return;
        }
      }
    };
  }
};

// static Barrier API
var Barrier = {};
Barrier.create = function(is_open) { return new _Barrier(is_open); };
Barrier.Create = SLift(Barrier.create);
Barrier.Open = SLift(function(b) { return b.open();} );
Barrier.Close = SLift(function(b) { return b.close();});
Barrier.Pass = ALift(function(cont, b) { return b.pass(cont);});
  
this["Barrier"] = Barrier;

////////////////////////////////////////////////////////////////////////
// Channels

function _Channel() {
  this.puts = [];
  this.collects = [];
  this.availBarrier = Barrier.create(false);
  this.emptyBarrier = Barrier.create(true);    
}
_Channel.prototype = {};

_Channel.prototype.put = function(val, cont) {
  this.puts.push({ val: val, cont: cont });
  this.process();
};

_Channel.prototype.collect = function(cont) {
  this.collects.push(cont);
  this.process();
};

_Channel.prototype.process = function() {
  if (this.puts.length && this.collects.length) {
    var p = this.puts.shift();
    var c = this.collects.shift();
    c([true, p.val]);
    if (p.cont)
      p.cont([true,null]);
  }
  
  if (this.puts.length && !this.collects.length)
    this.availBarrier.open();
  else
    this.availBarrier.close();

  if (!this.puts.length)
    this.emptyBarrier.open();
  else
    this.emptyBarrier.close();
};

_Channel.prototype.waitAvail = function(cont) {
  return this.availBarrier.pass(cont);
};

_Channel.prototype.waitEmpty = function(cont) {
  return this.emptyBarrier.pass(cont);
};

// static Channel API:
var Channel = {};
Channel.create = function() { return new _Channel(); };
Channel.Create = SLift(Channel.create);
Channel.Put = ALift(function(cont, c, val) { return c.put(val, cont);});
Channel.Collect = ALift(function(cont, c) { return c.collect(cont);});
Channel.WaitAvail = ALift(function(cont, c) { return c.waitAvail(cont);}); 
Channel.WaitEmpty = ALift(function(cont, c) { return c.waitEmpty(cont);});

this["Channel"] = Channel;

})();
