dep.js (orelse): Fix typo in the documentation comment.
[dep-ui] / dep.js
diff --git a/dep.js b/dep.js
index 4376b89..f9a9a18 100644 (file)
--- a/dep.js
+++ b/dep.js
  * GNU Library General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
-var DEP = { }; (function () { with (DEP) {
+var DEP = { }; (function () {
 
 /*----- Utility functions and classes -------------------------------------*/
 
-DEP.dolist = function (list, func) {
+function dolist(list, func) {
   /* Apply FUNC to each element of LIST, discarding the results.
    *
    * JavaScript's looping iterates over indices rather than values, which is
@@ -36,6 +36,7 @@ DEP.dolist = function (list, func) {
   var i;
   for (i in list) func(list[i]);
 }
+DEP.dolist = dolist;
 
 function eql(x, y) {
   /* A standard equality function, suitable for detecting changes to `Dep'
@@ -46,22 +47,51 @@ function eql(x, y) {
   return x === y;
 }
 
+function try_finally(trythunk, finalthunk) {
+  /* Call the TRYTHUNK.  When TRYTHUNK exits (even if it's with an exception)
+   * then call FINALTHUNK.  If TRYTHUNK returned normally then return its
+   * value; otherwise continue with the non-local flow control.
+   *
+   * This is an unpleasant hack because V8 doesn't optimize functions which
+   * contain try/finally very well (despite this being an easy transformation
+   * for the compiler).
+   */
+
+  try { return trythunk(); }
+  finally { finalthunk(); }
+}
+
+function try_cleanup(trythunk, cleanthunk) {
+  /* Call the TRYTHUNK.  If TRYTHUNK returns normally, just return its value;
+   * otherwise call CLEANTHUNK and re-throw the exception.
+   *
+   * This is an unpleasant hack because V8 doesn't optimize functions which
+   * contain try/catch very well (despite this being an easy transformation
+   * for the compiler).
+   */
+
+  var e;
+  try { return trythunk(); }
+  catch (e) { cleanthunk(); throw e; }
+}
+
 /* A Tag is an object which is interesting only because of its identity, and
  * that the set of Tags is basically determined by the static structure of
  * the program.
  */
-DEP.Tag = function (what) {
+function Tag(what) {
   this.what = what;
 }
 Tag.prototype = {
   toString: function () { return '#{Tag ' + this.what + '}'; }
 }
+DEP.Tag = Tag;
 
 /* A Generation is like a Tag, except that it's expected that a program will
  * manufacture Generations repeatedly, and perhaps use them to determine
  * whether an object is `up-to-date' in some sense.
  */
-DEP.Generation = function (what) {
+function Generation(what) {
   this.what = what;
   this.seq = Generation._next++;
 }
@@ -88,15 +118,18 @@ var F_CHANGED = 4;                 // Changed in current phase.
 var F_RECOMPUTING = 8;                 // Currently being recomputed.
 var F_QUEUED = 16;                     // Queued for recomputation.
 
-var BAD = Tag('BAD')                   // Used for the value of `bad' deps.
+var BAD = new Tag('BAD');              // Used for the value of `bad' deps.
 
 var DELAYED = [];                      // Actions delayed by `with_frozen'.
 var PENDING = [];                      // Deps awaiting recomputation.
 
 /*----- Exceptions --------------------------------------------------------*/
 
-DEP.CircularDependency = new Tag('CircularDependency');
-DEP.BusyRecomputing = new Tag('BusyRecomputing');
+CircularDependency = new Tag('CircularDependency');
+DEP.CircularDependency = CircularDependency;
+
+BusyRecomputing = new Tag('BusyRecomputing');
+DEP.BusyRecomputing = BusyRecomputing;
 
 /*----- Main code ---------------------------------------------------------*/
 
@@ -112,7 +145,7 @@ DEP.BusyRecomputing = new Tag('BusyRecomputing');
  * causes that `Dep' to become bad in turn.
  */
 
-DEP.Dep = function (value, maybefunc) {
+function Dep(value, maybefunc) {
   /* Initialize a new `Dep' object.
    *
    * Handling of the arguments is a little fiddly.  If two arguments are
@@ -145,7 +178,7 @@ DEP.Dep = function (value, maybefunc) {
     func = value;
     f |= F_QUEUED;
   } else {
-    val = value;
+    val = value === undefined ? BAD : value;
     func = null;
     f |= F_VALUE | F_DEPS;
   }
@@ -177,6 +210,7 @@ DEP.Dep = function (value, maybefunc) {
   }
 }
 
+DEP.Dep = Dep;
 Dep._seq = 0;                          // Next sequence number to allocate.
 
 Dep.prototype = {
@@ -201,7 +235,7 @@ Dep.prototype = {
     s += ' #' + this._seq;
 
     // The value, or some kind of marker that it doesn't have one.
-    if (!(f & F_VALUE)) s += ' #{out-of-date}';
+    if (!(f & F_VALUE)) s += ' #{stale}';
     else if (v === BAD) s += ' #{bad}';
     else s += ' ' + v.toString();
 
@@ -221,7 +255,7 @@ Dep.prototype = {
      * If the receiver isn't up-to-date then we synthesize the flags.
      */
 
-    if (this.state === 'ready') return F_VALUE | F_DEPS;
+    if (STATE === 'ready') return F_VALUE | F_DEPS;
     else if (this._generation === GENERATION) return this.__flags;
     else if (this._value_function === null) return F_VALUE | F_DEPS;
     else return 0;
@@ -253,20 +287,17 @@ Dep.prototype = {
      */
 
     var old = EVALUATING;
-    var val, e;
+    var me = this;
+    var val;
 
     this._dependencies = { };
-    try {
-      EVALUATING = this;
-      try {
-       return this._value_function();
-      } catch (e) {
-       if (e === BAD) return BAD;
-       else throw e;
-      }
-    } finally {
+    return try_finally(function () {
+      EVALUATING = me;
+      return orelse(function () { return me._value_function(); },
+                   function () { return BAD; });
+    }, function() {
       EVALUATING = old;
-    }
+    });
   },
 
   _propagate: function () {
@@ -302,7 +333,6 @@ Dep.prototype = {
      */
 
     var queued = this.__flags & F_QUEUED;
-    var e;
     var me = this;
 
     // Update us with the given VALUE.
@@ -319,12 +349,8 @@ Dep.prototype = {
 
     // Try to recompute our value.  If that doesn't work then mark us as bad
     // and propagate that.
-    try {
-      return update(this._new_value());
-    } catch (e) {
-      update(BAD);
-      throw e;
-    }
+    return try_cleanup(function () { return update(me._new_value()); },
+                      function () { update(BAD); });
   },
 
   _force: function () {
@@ -368,12 +394,12 @@ Dep.prototype = {
 
     var val;
 
-    if (state === 'recomputing') {
+    if (STATE === 'recomputing') {
+      this._force();
       if (EVALUATING) {
        this._dependents[EVALUATING._seq] = EVALUATING;
        EVALUATING._dependencies[this._seq] = this;
       }
-      this._force();
     }
     val = this._value;
     if (val === BAD) throw BAD;
@@ -416,9 +442,9 @@ Dep.prototype = {
   }
 };
 
-DEP.orelse = function (thunk, errthunk) {
+function orelse(thunk, errthunk) {
   /* Call THUNK.  If it succeeds, then return its result.  If THUNK
-   * reads a bad dep then call ERRTHINK and return its result instead.
+   * reads a bad dep then call ERRTHUNK and return its result instead.
    */
 
   var e;
@@ -428,11 +454,13 @@ DEP.orelse = function (thunk, errthunk) {
     else throw e;
   }
 }
+DEP.orelse = orelse;
 
-DEP.bad = function () {
+function bad() {
   /* For use in a value-function: make the dep be bad. */
   throw BAD;
 }
+DEP.bad = bad;
 
 function recompute_pending() {
   /* Recompute any pending dependencies.
@@ -441,9 +469,9 @@ function recompute_pending() {
    */
 
   var d, f;
-
-  state = 'recomputing';
-  try {
+  var old_state = STATE;
+  STATE = 'recomputing';
+  try_finally(function () {
     while (PENDING.length) {
       d = PENDING.shift();
       f = d.__flags;
@@ -455,28 +483,28 @@ function recompute_pending() {
        d.__flags = f | F_DEPS;
       }
     }
-  } finally {
+  },  function () {
     while (PENDING.length) {
       d = PENDING.shift();
       d._value = BAD;
     }
-  }
+    STATE = old_state;
+  });
 }
 
-DEP.with_frozen = function (body, delay) {
+function with_frozen(body, delay) {
   /* Call BODY with dependencies frozen.
    *
    * If the BODY function changes any dep values (using D.set_value(...))
    * then dependents won't be updated until the BODY completes.
    *
-   * It's very
-   * bad to do this during a recomputation phase.  If DELAY is true, then the
-   * BODY is delayed until the recomputation completes; otherwise you get an
-   * exception.
+   * It's very bad to do this during a recomputation phase.  If DELAY is
+   * true, then the BODY is delayed until the recomputation completes;
+   * otherwise you get an exception.
    */
 
   var op, val;
-  var old_delayed, old_pending;
+  var old_delayed, old_pending, old_state;
 
   switch (STATE) {
   case 'frozen':
@@ -489,7 +517,9 @@ DEP.with_frozen = function (body, delay) {
   case 'ready':
     old_delayed = DELAYED;
     old_pending = PENDING;
-    try {
+    old_state = STATE;
+    STATE = "frozen";
+    try_finally(function () {
       DELAYED = [];
       PENDING = [];
       GENERATION = new Generation('dep-generation');
@@ -500,13 +530,15 @@ DEP.with_frozen = function (body, delay) {
        op = DELAYED.shift();
        op();
       }
-    } finally {
+    }, function () {
       DELAYED = old_delayed;
       PENDING = old_pending;
-    }
+      STATE = old_state;
+    });
     break;
   }
 }
+DEP.with_frozen = with_frozen;
 
 /*----- That's all, folks -------------------------------------------------*/
-} })();
+})();