X-Git-Url: https://git.distorted.org.uk/~mdw/dep-ui/blobdiff_plain/6cfd4191b11af6db92fd970b7b6f9e2f9c876d83..55be9d83928fa05d864282eb8e5513535906ffe9:/dep.js diff --git a/dep.js b/dep.js index 9dd42b4..f9a9a18 100644 --- a/dep.js +++ b/dep.js @@ -18,7 +18,7 @@ * 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 . + * along with this program; if not, see . */ var DEP = { }; (function () { @@ -47,6 +47,34 @@ 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. @@ -90,7 +118,7 @@ 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. @@ -150,7 +178,7 @@ function Dep(value, maybefunc) { func = value; f |= F_QUEUED; } else { - val = value; + val = value === undefined ? BAD : value; func = null; f |= F_VALUE | F_DEPS; } @@ -207,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(); @@ -227,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; @@ -259,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 () { @@ -308,7 +333,6 @@ Dep.prototype = { */ var queued = this.__flags & F_QUEUED; - var e; var me = this; // Update us with the given VALUE. @@ -325,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 () { @@ -374,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; @@ -424,7 +444,7 @@ Dep.prototype = { 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; @@ -449,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; @@ -463,12 +483,13 @@ function recompute_pending() { d.__flags = f | F_DEPS; } } - } finally { + }, function () { while (PENDING.length) { d = PENDING.shift(); d._value = BAD; } - } + STATE = old_state; + }); } function with_frozen(body, delay) { @@ -477,14 +498,13 @@ function with_frozen(body, delay) { * 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': @@ -497,7 +517,9 @@ function with_frozen(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'); @@ -508,10 +530,11 @@ function with_frozen(body, delay) { op = DELAYED.shift(); op(); } - } finally { + }, function () { DELAYED = old_delayed; PENDING = old_pending; - } + STATE = old_state; + }); break; } }