X-Git-Url: https://git.distorted.org.uk/~mdw/dep-ui/blobdiff_plain/ac26861cd2097171f4f61adf7277af93c7094b16..55be9d83928fa05d864282eb8e5513535906ffe9:/dep.js?ds=sidebyside
diff --git a/dep.js b/dep.js
index 4376b89..f9a9a18 100644
--- a/dep.js
+++ b/dep.js
@@ -18,14 +18,14 @@
* 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 () { 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 -------------------------------------------------*/
-} })();
+})();