From e57f5d29d9c0dacd9cdfc76eeb218a276e88b13f Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 17:43:31 -0500 Subject: [PATCH 01/16] trying to get infrastructure on exclusive locks --- js-assembler/runtime-src/runtime.js | 75 ++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 8e66cad..ff9e4f6 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -161,6 +161,69 @@ defaultCurrentPrintImplementation); + + ////////////////////////////////////////////////////////////////////// + + // Exclusive Locks. Even though JavaScript is a single-threaded + // evaluator, we still have a need to create exclusive regions + // of evaluation, since we might inadvertantly access some state + // with two computations, with use of setTimeout. + var ExclusiveLock = function() { + this.locked = false; // (U false string) + this.waiters = []; + }; + + // makeRandomNonce: -> string + // Creates a randomly-generated nonce. + ExclusiveLock.makeRandomNonce = function() { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var LEN = 32; + var result = []; + var i; + for (i = 0; i < LEN; i++) { + result.push(chars.charAt(Math.floor(Math.random() * chars.length))); + } + return result.join(''); + }; + + ExclusiveLock.prototype.acquire = function(id, onAcquire) { + var that = this; + if (id === undefined) { + id = ExclusiveLock.makeRandomNonce(); + } + // Allow for re-entrancy if the id is the same as the + // entity who is locking. + if (this.locked === false || this.locked === id) { + this.locked = id; + onAcquire.call( + this, + // NOTE: the caller must release the lock or else deadlock. + function() { + setTimeout( + function() { + var waiter; + if (that.locked === false) { + throw new Error( + "Internal error: trying to unlock the lock, but already unlocked"); + } + that.locked = false; + if (that.waiters.length > 0) { + waiter = that.waiters.shift(); + that.acquire(waiter.id, waiter.onAcquire); + }, + 0); + }); + } else { + this.waiters.push({ id: id, + onAcquire: onAcquire } ); + } + }; + ////////////////////////////////////////////////////////////////////// + + + + + //////////////////////////////////////////////////////////////////////] // The MACHINE @@ -229,8 +292,9 @@ }; this.primitives = Primitives; + this.exclusiveLock = new ExclusiveLock(); }; - + // Try to get the continuation mark key used for procedure application tracing. var getTracedAppKey = function(MACHINE) { @@ -379,6 +443,8 @@ var scheduleTrampoline = function(MACHINE, f) { setTimeout( function() { + + // FIXME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! return MACHINE.trampoline(f); }, 0); @@ -415,6 +481,13 @@ }; + // WARNING WARNING WARNING + // + // Make sure to get an exclusive lock before jumping into trampoline. + // Otherwise, Bad Things will happen. + // + // e.g. machine.lock.acquire(function() { machine.trampoline... machine.lock.release();}); + Machine.prototype.trampoline = function(initialJump, noJumpingOff) { var thunk = initialJump; var startTime = (new Date()).valueOf(); From fa5cb557ba8ccf05671df49086bdb852e3802ed9 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 18:03:53 -0500 Subject: [PATCH 02/16] trying to get the exclusive lock logic in place --- js-assembler/runtime-src/baselib-functions.js | 12 +++++++++-- js-assembler/runtime-src/runtime.js | 21 +++++++++++-------- version.rkt | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index e22ac93..f0a15aa 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -144,8 +144,8 @@ var oldVal = MACHINE.v; var oldArgcount = MACHINE.a; var oldProc = MACHINE.p; - var oldErrorHandler = MACHINE.params['currentErrorHandler']; + var afterGoodInvoke = function (MACHINE) { plt.runtime.PAUSE( function (restart) { @@ -186,8 +186,15 @@ MACHINE.a = oldArgcount; MACHINE.p = oldProc; fail(e); + }; - MACHINE.trampoline(v.label); + + MACHINE.exclusiveLock.acquire( + "js-as-closure", + function(releaseLock) { + releaseLock(); + MACHINE.trampoline(v.label); + }); }; return f; }; @@ -270,6 +277,7 @@ MACHINE.p = oldProc; fail(e); }; + MACHINE.trampoline(proc.label); } else { fail(baselib.exceptions.makeExnFail( diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index ff9e4f6..663f4fb 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -197,7 +197,7 @@ this.locked = id; onAcquire.call( this, - // NOTE: the caller must release the lock or else deadlock. + // NOTE: the caller must release the lock or else deadlock! function() { setTimeout( function() { @@ -441,13 +441,16 @@ var recomputeMaxNumBouncesBeforeYield; var scheduleTrampoline = function(MACHINE, f) { - setTimeout( - function() { - - // FIXME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - return MACHINE.trampoline(f); - }, - 0); + MACHINE.exclusiveLock.acquire( + 'scheduleTrampoline', + function(release) { + setTimeout( + function() { + release(); + return MACHINE.trampoline(f); + }, + 0); + }); }; // Creates a restarting function, that reschedules f in a context @@ -486,7 +489,7 @@ // Make sure to get an exclusive lock before jumping into trampoline. // Otherwise, Bad Things will happen. // - // e.g. machine.lock.acquire(function() { machine.trampoline... machine.lock.release();}); + // e.g. machine.lock.acquire('id', function(release) { machine.trampoline... release();}); Machine.prototype.trampoline = function(initialJump, noJumpingOff) { var thunk = initialJump; diff --git a/version.rkt b/version.rkt index 865b3e0..96261f3 100644 --- a/version.rkt +++ b/version.rkt @@ -7,4 +7,4 @@ (provide version) (: version String) -(define version "1.96") +(define version "1.97") From efb978b52feafba077cae435a38c7c5981c7c5a4 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 18:05:31 -0500 Subject: [PATCH 03/16] correcting typos --- js-assembler/runtime-src/runtime.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 663f4fb..0f17a02 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -210,8 +210,9 @@ if (that.waiters.length > 0) { waiter = that.waiters.shift(); that.acquire(waiter.id, waiter.onAcquire); - }, - 0); + } + }, + 0); }); } else { this.waiters.push({ id: id, From 6904222c69d981f91d4b97fe3a9e9b42814382eb Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 18:43:25 -0500 Subject: [PATCH 04/16] starting to do something --- js-assembler/runtime-src/baselib-functions.js | 246 +++++++++--------- js-assembler/runtime-src/runtime.js | 32 +-- version.rkt | 2 +- 3 files changed, 143 insertions(+), 137 deletions(-) diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index f0a15aa..b95e4a5 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -128,70 +128,69 @@ var coerseClosureToJavaScript = function (v, MACHINE) { var f = function (succ, fail) { - succ = succ || function () {}; - fail = fail || function () {}; - - if (!(baselib.arity.isArityMatching(v.racketArity, arguments.length - 2))) { - var msg = baselib.format.format( - "arity mismatch: ~s expected ~s argument(s) but received ~s", - [v.displayName, v.racketArity, arguments.length - 2]); - return fail(new baselib.exceptions.RacketError( - msg, - baselib.exceptions.makeExnFailContractArity(msg, - MACHINE.captureContinuationMarks()))); - } - - var oldVal = MACHINE.v; - var oldArgcount = MACHINE.a; - var oldProc = MACHINE.p; - var oldErrorHandler = MACHINE.params['currentErrorHandler']; - - var afterGoodInvoke = function (MACHINE) { - plt.runtime.PAUSE( - function (restart) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - var returnValue = MACHINE.v; - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - succ(returnValue); - }); - }; - afterGoodInvoke.mvr = function (MACHINE) { - plt.runtime.PAUSE( - function (restart) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - var returnValues = [MACHINE.v], i; - for (i = 0; i < MACHINE.a - 1; i++) { - returnValues.push(MACHINE.e.pop()); - } - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - succ.apply(null, returnValues); - }); - }; - - MACHINE.c.push( - new baselib.frames.CallFrame(afterGoodInvoke, v)); - MACHINE.a = arguments.length - 2; - var i; - for (i = 0; i < arguments.length - 2; i++) { - MACHINE.e.push(arguments[arguments.length - 1 - i]); - } - MACHINE.p = v; - MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - fail(e); - - }; - MACHINE.exclusiveLock.acquire( "js-as-closure", function(releaseLock) { + succ = succ || function () {}; + fail = fail || function () {}; + + if (!(baselib.arity.isArityMatching(v.racketArity, arguments.length - 2))) { + var msg = baselib.format.format( + "arity mismatch: ~s expected ~s argument(s) but received ~s", + [v.displayName, v.racketArity, arguments.length - 2]); + return fail(new baselib.exceptions.RacketError( + msg, + baselib.exceptions.makeExnFailContractArity(msg, + MACHINE.captureContinuationMarks()))); + } + + var oldVal = MACHINE.v; + var oldArgcount = MACHINE.a; + var oldProc = MACHINE.p; + var oldErrorHandler = MACHINE.params['currentErrorHandler']; + + var afterGoodInvoke = function (MACHINE) { + plt.runtime.PAUSE( + function (restart) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + var returnValue = MACHINE.v; + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + succ(returnValue); + }); + }; + afterGoodInvoke.mvr = function (MACHINE) { + plt.runtime.PAUSE( + function (restart) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + var returnValues = [MACHINE.v], i; + for (i = 0; i < MACHINE.a - 1; i++) { + returnValues.push(MACHINE.e.pop()); + } + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + succ.apply(null, returnValues); + }); + }; + + MACHINE.c.push( + new baselib.frames.CallFrame(afterGoodInvoke, v)); + MACHINE.a = arguments.length - 2; + var i; + for (i = 0; i < arguments.length - 2; i++) { + MACHINE.e.push(arguments[arguments.length - 1 - i]); + } + MACHINE.p = v; + MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + fail(e); + + }; releaseLock(); MACHINE.trampoline(v.label); }); @@ -223,69 +222,76 @@ // internallCallDuringPause: call a Racket procedure and get its results. // The use assumes the machine is in a running-but-paused state. var internalCallDuringPause = function (MACHINE, proc, success, fail) { - var i; - var oldArgcount, oldVal, oldProc, oldErrorHandler; - if (! baselib.arity.isArityMatching(proc.racketArity, arguments.length - 4)) { - var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", - [proc.displayName, proc.racketArity, arguments.length - 4]); - return fail(baselib.exceptions.makeExnFailContractArity(msg, - MACHINE.captureContinuationMarks())); - } + MACHINE.exclusiveLock.acquire( + "internal call during pause", + function(releaseLock) { - if (isClosure(proc)) { - oldVal = MACHINE.v; - oldArgcount = MACHINE.a; - oldProc = MACHINE.p; + var i; + var oldArgcount, oldVal, oldProc, oldErrorHandler; + if (! baselib.arity.isArityMatching(proc.racketArity, arguments.length - 4)) { + var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", + [proc.displayName, proc.racketArity, arguments.length - 4]); + return fail(baselib.exceptions.makeExnFailContractArity(msg, + MACHINE.captureContinuationMarks())); + } - oldErrorHandler = MACHINE.params['currentErrorHandler']; - var afterGoodInvoke = function (MACHINE) { - plt.runtime.PAUSE(function (restart) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - var returnValue = MACHINE.v; - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - success(returnValue); - }); - }; - afterGoodInvoke.mvr = function (MACHINE) { - plt.runtime.PAUSE(function (restart) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - var returnValues = [MACHINE.v]; - var i; - for (i = 0; i < MACHINE.a - 1; i++) { - returnValues.push(MACHINE.e.pop()); + if (isClosure(proc)) { + oldVal = MACHINE.v; + oldArgcount = MACHINE.a; + oldProc = MACHINE.p; + + oldErrorHandler = MACHINE.params['currentErrorHandler']; + var afterGoodInvoke = function (MACHINE) { + plt.runtime.PAUSE(function (restart) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + var returnValue = MACHINE.v; + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + success(returnValue); + }); + }; + afterGoodInvoke.mvr = function (MACHINE) { + plt.runtime.PAUSE(function (restart) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + var returnValues = [MACHINE.v]; + var i; + for (i = 0; i < MACHINE.a - 1; i++) { + returnValues.push(MACHINE.e.pop()); + } + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + success.apply(null, returnValues); + }); + }; + + MACHINE.c.push( + new baselib.frames.CallFrame(afterGoodInvoke, proc)); + MACHINE.a = arguments.length - 4; + for (i = 0; i < arguments.length - 4; i++) { + MACHINE.e.push(arguments[arguments.length - 1 - i]); } - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - success.apply(null, returnValues); - }); - }; + MACHINE.p = proc; + MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + fail(e); + }; - MACHINE.c.push( - new baselib.frames.CallFrame(afterGoodInvoke, proc)); - MACHINE.a = arguments.length - 4; - for (i = 0; i < arguments.length - 4; i++) { - MACHINE.e.push(arguments[arguments.length - 1 - i]); - } - MACHINE.p = proc; - MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - fail(e); - }; - - MACHINE.trampoline(proc.label); - } else { - fail(baselib.exceptions.makeExnFail( - baselib.format.format( - "Not a procedure: ~e", - proc), - MACHINE.captureContinuationMarks())); - } + releaseLock(); + MACHINE.trampoline(proc.label); + + } else { + fail(baselib.exceptions.makeExnFail( + baselib.format.format( + "Not a procedure: ~e", + proc), + MACHINE.captureContinuationMarks())); + } + }); }; diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 0f17a02..07c65b2 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -195,11 +195,11 @@ // entity who is locking. if (this.locked === false || this.locked === id) { this.locked = id; - onAcquire.call( - this, - // NOTE: the caller must release the lock or else deadlock! + setTimeout( function() { - setTimeout( + onAcquire.call( + that, + // NOTE: the caller must release the lock or else deadlock! function() { var waiter; if (that.locked === false) { @@ -211,9 +211,9 @@ waiter = that.waiters.shift(); that.acquire(waiter.id, waiter.onAcquire); } - }, - 0); - }); + }); + }, + 0); } else { this.waiters.push({ id: id, onAcquire: onAcquire } ); @@ -442,16 +442,16 @@ var recomputeMaxNumBouncesBeforeYield; var scheduleTrampoline = function(MACHINE, f) { - MACHINE.exclusiveLock.acquire( - 'scheduleTrampoline', - function(release) { - setTimeout( - function() { + setTimeout( + function() { + MACHINE.exclusiveLock.acquire( + 'scheduleTrampoline', + function(release) { release(); - return MACHINE.trampoline(f); - }, - 0); - }); + MACHINE.trampoline(f); + }); + }, + 0); }; // Creates a restarting function, that reschedules f in a context diff --git a/version.rkt b/version.rkt index 96261f3..10e1aaf 100644 --- a/version.rkt +++ b/version.rkt @@ -7,4 +7,4 @@ (provide version) (: version String) -(define version "1.97") +(define version "1.98") From ccdb0c88f658d73f28129781902455ba6a77833a Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 19:00:56 -0500 Subject: [PATCH 05/16] before --- js-assembler/runtime-src/runtime.js | 6 +++--- version.rkt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 07c65b2..398fb9e 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -441,13 +441,14 @@ // var recomputeMaxNumBouncesBeforeYield; - var scheduleTrampoline = function(MACHINE, f) { + var scheduleTrampoline = function(MACHINE, f, before) { setTimeout( function() { MACHINE.exclusiveLock.acquire( 'scheduleTrampoline', function(release) { release(); + if (before) { before(); } MACHINE.trampoline(f); }); }, @@ -460,8 +461,7 @@ var makeRestartFunction = function(MACHINE) { var oldArgcount = MACHINE.a; return function(f) { - MACHINE.a = oldArgcount; - return scheduleTrampoline(MACHINE, f); + return scheduleTrampoline(MACHINE, f, function() { MACHINE.a = oldArgcount; }); }; }; diff --git a/version.rkt b/version.rkt index 10e1aaf..750354d 100644 --- a/version.rkt +++ b/version.rkt @@ -7,4 +7,4 @@ (provide version) (: version String) -(define version "1.98") +(define version "1.100") From 46f314ea3563cbfdf7a41f1f04aad05dfc69c81f Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 20:04:03 -0500 Subject: [PATCH 06/16] chasing a double-release --- js-assembler/runtime-src/baselib-functions.js | 39 ++++++++++++------- js-assembler/runtime-src/runtime.js | 21 ++++++---- version.rkt | 2 +- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index b95e4a5..6576b84 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -128,16 +128,24 @@ var coerseClosureToJavaScript = function (v, MACHINE) { var f = function (succ, fail) { + var args = []; + var i; + for (i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + MACHINE.exclusiveLock.acquire( "js-as-closure", function(releaseLock) { succ = succ || function () {}; fail = fail || function () {}; + + releaseLock(); - if (!(baselib.arity.isArityMatching(v.racketArity, arguments.length - 2))) { + if (!(baselib.arity.isArityMatching(v.racketArity, args.length - 2))) { var msg = baselib.format.format( "arity mismatch: ~s expected ~s argument(s) but received ~s", - [v.displayName, v.racketArity, arguments.length - 2]); + [v.displayName, v.racketArity, args.length - 2]); return fail(new baselib.exceptions.RacketError( msg, baselib.exceptions.makeExnFailContractArity(msg, @@ -177,10 +185,10 @@ MACHINE.c.push( new baselib.frames.CallFrame(afterGoodInvoke, v)); - MACHINE.a = arguments.length - 2; + MACHINE.a = args.length - 2; var i; - for (i = 0; i < arguments.length - 2; i++) { - MACHINE.e.push(arguments[arguments.length - 1 - i]); + for (i = 0; i < args.length - 2; i++) { + MACHINE.e.push(args[args.length - 1 - i]); } MACHINE.p = v; MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { @@ -191,7 +199,6 @@ fail(e); }; - releaseLock(); MACHINE.trampoline(v.label); }); }; @@ -222,15 +229,20 @@ // internallCallDuringPause: call a Racket procedure and get its results. // The use assumes the machine is in a running-but-paused state. var internalCallDuringPause = function (MACHINE, proc, success, fail) { - MACHINE.exclusiveLock.acquire( + var args = []; + var i; + for (i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + MACHINE.exclusiveLock.acquire( "internal call during pause", function(releaseLock) { - + releaseLock(); var i; var oldArgcount, oldVal, oldProc, oldErrorHandler; - if (! baselib.arity.isArityMatching(proc.racketArity, arguments.length - 4)) { + if (! baselib.arity.isArityMatching(proc.racketArity, args.length - 4)) { var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", - [proc.displayName, proc.racketArity, arguments.length - 4]); + [proc.displayName, proc.racketArity, args.length - 4]); return fail(baselib.exceptions.makeExnFailContractArity(msg, MACHINE.captureContinuationMarks())); } @@ -268,9 +280,9 @@ MACHINE.c.push( new baselib.frames.CallFrame(afterGoodInvoke, proc)); - MACHINE.a = arguments.length - 4; - for (i = 0; i < arguments.length - 4; i++) { - MACHINE.e.push(arguments[arguments.length - 1 - i]); + MACHINE.a = args.length - 4; + for (i = 0; i < args.length - 4; i++) { + MACHINE.e.push(args[args.length - 1 - i]); } MACHINE.p = proc; MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { @@ -281,7 +293,6 @@ fail(e); }; - releaseLock(); MACHINE.trampoline(proc.label); } else { diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 398fb9e..9045c05 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -191,29 +191,36 @@ if (id === undefined) { id = ExclusiveLock.makeRandomNonce(); } + + var alreadyReleased = false; + // Allow for re-entrancy if the id is the same as the // entity who is locking. if (this.locked === false || this.locked === id) { this.locked = id; - setTimeout( + onAcquire.call( + that, + // releaseLock function() { - onAcquire.call( - that, - // NOTE: the caller must release the lock or else deadlock! + setTimeout( function() { var waiter; + if (alreadyReleased) { + throw new Error( + "Internal error: trying to release the lock, but already released"); + } if (that.locked === false) { throw new Error( "Internal error: trying to unlock the lock, but already unlocked"); } that.locked = false; + alreadyReleased = true; if (that.waiters.length > 0) { waiter = that.waiters.shift(); that.acquire(waiter.id, waiter.onAcquire); } - }); - }, - 0); + }, 0); + }); } else { this.waiters.push({ id: id, onAcquire: onAcquire } ); diff --git a/version.rkt b/version.rkt index 750354d..f14d333 100644 --- a/version.rkt +++ b/version.rkt @@ -7,4 +7,4 @@ (provide version) (: version String) -(define version "1.100") +(define version "1.101") From 31ac4282765f834bb865683ec39b9e4ae5603db1 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Sun, 4 Dec 2011 20:40:44 -0500 Subject: [PATCH 07/16] trying to get the exclusive lock code right --- js-assembler/runtime-src/baselib-functions.js | 21 ++++++----- js-assembler/runtime-src/runtime.js | 35 ++++++++++--------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index 6576b84..c6323b6 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -138,14 +138,12 @@ "js-as-closure", function(releaseLock) { succ = succ || function () {}; - fail = fail || function () {}; - - releaseLock(); - + fail = fail || function () {}; if (!(baselib.arity.isArityMatching(v.racketArity, args.length - 2))) { var msg = baselib.format.format( "arity mismatch: ~s expected ~s argument(s) but received ~s", [v.displayName, v.racketArity, args.length - 2]); + releaseLock(); return fail(new baselib.exceptions.RacketError( msg, baselib.exceptions.makeExnFailContractArity(msg, @@ -197,8 +195,8 @@ MACHINE.a = oldArgcount; MACHINE.p = oldProc; fail(e); - - }; + }; + releaseLock(); MACHINE.trampoline(v.label); }); }; @@ -234,17 +232,17 @@ for (i = 0; i < arguments.length; i++) { args.push(arguments[i]); } - MACHINE.exclusiveLock.acquire( + MACHINE.exclusiveLock.acquire( "internal call during pause", function(releaseLock) { - releaseLock(); var i; var oldArgcount, oldVal, oldProc, oldErrorHandler; if (! baselib.arity.isArityMatching(proc.racketArity, args.length - 4)) { var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", [proc.displayName, proc.racketArity, args.length - 4]); - return fail(baselib.exceptions.makeExnFailContractArity(msg, - MACHINE.captureContinuationMarks())); + releaseLock(); + fail(baselib.exceptions.makeExnFailContractArity(msg, + MACHINE.captureContinuationMarks())); } if (isClosure(proc)) { @@ -292,10 +290,11 @@ MACHINE.p = oldProc; fail(e); }; - + releaseLock(); MACHINE.trampoline(proc.label); } else { + releaseLock(); fail(baselib.exceptions.makeExnFail( baselib.format.format( "Not a procedure: ~e", diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 9045c05..779e18c 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -202,24 +202,25 @@ that, // releaseLock function() { - setTimeout( - function() { - var waiter; - if (alreadyReleased) { - throw new Error( - "Internal error: trying to release the lock, but already released"); - } - if (that.locked === false) { - throw new Error( - "Internal error: trying to unlock the lock, but already unlocked"); - } - that.locked = false; - alreadyReleased = true; - if (that.waiters.length > 0) { - waiter = that.waiters.shift(); + var waiter; + if (alreadyReleased) { + throw new Error( + "Internal error: trying to release the lock, but already released"); + } + if (that.locked === false) { + throw new Error( + "Internal error: trying to unlock the lock, but already unlocked"); + } + that.locked = false; + alreadyReleased = true; + if (that.waiters.length > 0) { + waiter = that.waiters.shift(); + setTimeout( + function() { that.acquire(waiter.id, waiter.onAcquire); - } - }, 0); + }, + 0); + } }); } else { this.waiters.push({ id: id, From 7f0fe7516b6078e34349e5dbb782aaa2cec5ae12 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Fri, 9 Dec 2011 18:50:16 -0500 Subject: [PATCH 08/16] adding a lock around the trampoline --- Makefile | 4 +- js-assembler/runtime-src/runtime.js | 211 +++++++++++++++------------- 2 files changed, 112 insertions(+), 103 deletions(-) diff --git a/Makefile b/Makefile index f1510a8..507fbf3 100644 --- a/Makefile +++ b/Makefile @@ -45,8 +45,8 @@ cs019-doc: setup: - raco setup --no-docs -P dyoo whalesong.plt 1 11 + raco setup --no-docs -P dyoo whalesong.plt 1 12 planet-link: - raco planet link dyoo whalesong.plt 1 11 . + raco planet link dyoo whalesong.plt 1 12 . diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 779e18c..dbd4c69 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -114,7 +114,7 @@ var ArityAtLeast = baselib.arity.ArityAtLeast; var makeArityAtLeast = baselib.arity.makeArityAtLeast; var isArityMatching = baselib.arity.isArityMatching; - + var testArgument = baselib.check.testArgument; var testArity = baselib.check.testArity; @@ -138,17 +138,17 @@ var defaultCurrentPrintImplementation = function (MACHINE) { - if(--MACHINE.cbt < 0) { - throw defaultCurrentPrintImplementation; + if(--MACHINE.cbt < 0) { + throw defaultCurrentPrintImplementation; } var oldArgcount = MACHINE.a; var elt = MACHINE.e[MACHINE.e.length - 1]; - var outputPort = + var outputPort = MACHINE.params.currentOutputPort; if (elt !== VOID) { outputPort.writeDomNode( - MACHINE, + MACHINE, toDomNode(elt, MACHINE.params['print-mode'])); outputPort.writeDomNode(MACHINE, toDomNode("\n", 'display')); } @@ -223,7 +223,7 @@ } }); } else { - this.waiters.push({ id: id, + this.waiters.push({ id: id, onAcquire: onAcquire } ); } }; @@ -260,7 +260,7 @@ 'currentDisplayer': function(MACHINE, domNode) { $(domNode).appendTo(document.body); }, - + // currentErrorDisplayer: DomNode -> Void // currentErrorDisplayer is responsible for displaying errors to the browser. 'currentErrorDisplayer': function(MACHINE, domNode) { @@ -268,7 +268,7 @@ }, 'currentInspector': baselib.inspectors.DEFAULT_INSPECTOR, - + 'currentOutputPort': new StandardOutputPort(), 'currentErrorPort': new StandardErrorPort(), 'currentInputPort': new StandardInputPort(), @@ -278,9 +278,9 @@ MACHINE, toDomNode(exn, MACHINE.params['print-mode'])); }, - + 'currentNamespace': {}, - + // These parameters control how often // control yields back to the browser // for response. The implementation is a @@ -333,7 +333,7 @@ return MACHINE.c.slice(i + 1, MACHINE.c.length - skip); } - } + } raise(MACHINE, new Error("captureControl: unable to find tag " + tag)); }; @@ -341,20 +341,20 @@ // restoreControl clears the control stack (up to, but not including the // prompt tagged by tag), and then appends the rest of the control frames. - // At the moment, the rest of the control frames is assumed to be in the + // At the moment, the rest of the control frames is assumed to be in the // top of the environment. Machine.prototype.restoreControl = function(tag) { var MACHINE = this; var i; for (i = MACHINE.c.length - 1; i >= 0; i--) { if (MACHINE.c[i].tag === tag) { - MACHINE.c = + MACHINE.c = MACHINE.c.slice(0, i+1).concat( MACHINE.e[MACHINE.e.length - 1]); return; } } - raise(MACHINE, new Error("restoreControl: unable to find tag " + tag)); + raise(MACHINE, new Error("restoreControl: unable to find tag " + tag)); }; @@ -382,11 +382,11 @@ var lst = NULL; var i; for (i = 0; i < length; i++) { - lst = makePair(MACHINE.e[MACHINE.e.length - depth - length + i], + lst = makePair(MACHINE.e[MACHINE.e.length - depth - length + i], lst); } MACHINE.e.splice(MACHINE.e.length - depth - length, - length, + length, lst); MACHINE.a = MACHINE.a - length + 1; }; @@ -420,8 +420,8 @@ if (control[i].marks.length !== 0) { kvLists.push(control[i].marks); } - - if (tracedCalleeKey !== null && + + if (tracedCalleeKey !== null && control[i] instanceof CallFrame && control[i].p !== null) { kvLists.push([[tracedCalleeKey, control[i].p]]); @@ -429,7 +429,7 @@ } return new baselib.contmarks.ContinuationMarkSet(kvLists); }; - + @@ -451,24 +451,24 @@ var scheduleTrampoline = function(MACHINE, f, before) { setTimeout( - function() { + function() { MACHINE.exclusiveLock.acquire( 'scheduleTrampoline', - function(release) { + function(release) { release(); if (before) { before(); } - MACHINE.trampoline(f); + MACHINE.trampoline(f); }); }, 0); }; // Creates a restarting function, that reschedules f in a context - // with the old argcount in place. + // with the old argcount in place. // Meant to be used only by the trampoline. var makeRestartFunction = function(MACHINE) { var oldArgcount = MACHINE.a; - return function(f) { + return function(f) { return scheduleTrampoline(MACHINE, f, function() { MACHINE.a = oldArgcount; }); }; }; @@ -499,79 +499,88 @@ // Otherwise, Bad Things will happen. // // e.g. machine.lock.acquire('id', function(release) { machine.trampoline... release();}); - Machine.prototype.trampoline = function(initialJump, noJumpingOff) { - var thunk = initialJump; - var startTime = (new Date()).valueOf(); - this.cbt = STACK_LIMIT_ESTIMATE; - this.params.numBouncesBeforeYield = - this.params.maxNumBouncesBeforeYield; - this.running = true; - - while(true) { - try { - thunk(this); - break; - } catch (e) { - // There are a few kinds of things that can get thrown - // during racket evaluation: - // - // functions: this gets thrown if the Racket code - // realizes that the number of bounces has grown too - // large. The thrown function represents a restarter - // function. The running flag remains true. - // - // Pause: causes the machine evaluation to pause, with - // the expectation that it will restart momentarily. - // The running flag on the machine will remain true. - // - // HaltError: causes evaluation to immediately halt. - // We schedule the onHalt function of the HaltError to - // call afterwards. The running flag on the machine - // is set to false. - // - // Everything else: otherwise, we send the exception value - // to the current error handler and exit. - // The running flag is set to false. - if (typeof(e) === 'function') { - thunk = e; - this.cbt = STACK_LIMIT_ESTIMATE; - - - // If we're running an a model that prohibits - // jumping off the trampoline, continue. - if (noJumpingOff) { - continue; - } - - if (this.params.numBouncesBeforeYield-- < 0) { - recomputeMaxNumBouncesBeforeYield( - this, - (new Date()).valueOf() - startTime); - scheduleTrampoline(this, thunk); - return; - } - } else if (e instanceof Pause) { - var restart = makeRestartFunction(this); - e.onPause(restart); - return; - } else if (e instanceof HaltError) { - this.running = false; - e.onHalt(this); - return; - } else { - // General error condition: just exit out - // of the trampoline and call the current error handler. - this.running = false; - this.params.currentErrorHandler(this, e); - return; - } - } - } - this.running = false; var that = this; - this.params.currentSuccessHandler(this); - return; + + that.exclusiveLock.acquire( + 'trampoline', + function(release) { + var thunk = initialJump; + var startTime = (new Date()).valueOf(); + that.cbt = STACK_LIMIT_ESTIMATE; + that.params.numBouncesBeforeYield = + that.params.maxNumBouncesBeforeYield; + that.running = true; + + while(true) { + try { + thunk(that); + break; + } catch (e) { + // There are a few kinds of things that can get thrown + // during racket evaluation: + // + // functions: this gets thrown if the Racket code + // realizes that the number of bounces has grown too + // large. The thrown function represents a restarter + // function. The running flag remains true. + // + // Pause: causes the machine evaluation to pause, with + // the expectation that it will restart momentarily. + // The running flag on the machine will remain true. + // + // HaltError: causes evaluation to immediately halt. + // We schedule the onHalt function of the HaltError to + // call afterwards. The running flag on the machine + // is set to false. + // + // Everything else: otherwise, we send the exception value + // to the current error handler and exit. + // The running flag is set to false. + if (typeof(e) === 'function') { + thunk = e; + that.cbt = STACK_LIMIT_ESTIMATE; + + + // If we're running an a model that prohibits + // jumping off the trampoline, continue. + if (noJumpingOff) { + continue; + } + + if (that.params.numBouncesBeforeYield-- < 0) { + recomputeMaxNumBouncesBeforeYield( + that, + (new Date()).valueOf() - startTime); + scheduleTrampoline(that, thunk); + release(); + return; + } + } else if (e instanceof Pause) { + var restart = makeRestartFunction(that); + e.onPause(restart); + release(); + return; + } else if (e instanceof HaltError) { + that.running = false; + e.onHalt(that); + release(); + return; + } else { + // General error condition: just exit out + // of the trampoline and call the current error handler. + that.running = false; + that.params.currentErrorHandler(that, e); + release(); + return; + } + } + } + that.running = false; + that.params.currentSuccessHandler(that); + release(); + return; + }); }; // recomputeGas: state number -> number @@ -579,13 +588,13 @@ // We'd like to see a delay of DESIRED_DELAY_BETWEEN_BOUNCES so // that we get MACHINE.params.desiredYieldsPerSecond bounces per // second. - var DESIRED_DELAY_BETWEEN_BOUNCES = + var DESIRED_DELAY_BETWEEN_BOUNCES = (1000 / MACHINE.params.desiredYieldsPerSecond); var ALPHA = 50; var delta = (ALPHA * ((DESIRED_DELAY_BETWEEN_BOUNCES - - observedDelay) / + observedDelay) / DESIRED_DELAY_BETWEEN_BOUNCES)); - MACHINE.params.maxNumBouncesBeforeYield = + MACHINE.params.maxNumBouncesBeforeYield = Math.max(MACHINE.params.maxNumBouncesBeforeYield + delta, 1); }; @@ -619,7 +628,7 @@ - + @@ -765,7 +774,7 @@ exports['installPrimitiveProcedure'] = installPrimitiveProcedure; exports['makePrimitiveProcedure'] = makePrimitiveProcedure; exports['Primitives'] = Primitives; - + exports['ready'] = ready; // Private: the runtime library will set this flag to true when // the library has finished loading. @@ -780,7 +789,7 @@ exports['ModuleRecord'] = ModuleRecord; exports['VariableReference'] = VariableReference; exports['ContinuationPromptTag'] = ContinuationPromptTag; - exports['DEFAULT_CONTINUATION_PROMPT_TAG'] = + exports['DEFAULT_CONTINUATION_PROMPT_TAG'] = DEFAULT_CONTINUATION_PROMPT_TAG; exports['NULL'] = NULL; exports['VOID'] = VOID; From 23d4c27c2b61185d1f653a1aaabb17b8f6af2975 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Fri, 9 Dec 2011 20:40:10 -0500 Subject: [PATCH 09/16] There is something very suspicious happening with makeRestartFunction where the env and control are being munged, but I haven't been able to trace where yet. --- js-assembler/runtime-src/runtime.js | 174 +++++++++++++++------------- version.rkt | 2 +- 2 files changed, 94 insertions(+), 82 deletions(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index dbd4c69..7f1b403 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -194,9 +194,7 @@ var alreadyReleased = false; - // Allow for re-entrancy if the id is the same as the - // entity who is locking. - if (this.locked === false || this.locked === id) { + if (this.locked === false) { this.locked = id; onAcquire.call( that, @@ -455,9 +453,8 @@ MACHINE.exclusiveLock.acquire( 'scheduleTrampoline', function(release) { - release(); if (before) { before(); } - MACHINE.trampoline(f); + MACHINE._trampoline(f, false, release); }); }, 0); @@ -468,8 +465,16 @@ // Meant to be used only by the trampoline. var makeRestartFunction = function(MACHINE) { var oldArgcount = MACHINE.a; + var oldEnv = MACHINE.e.slice(); + var oldControl = MACHINE.c.slice(); return function(f) { - return scheduleTrampoline(MACHINE, f, function() { MACHINE.a = oldArgcount; }); + MACHINE.exclusiveLock.acquire(undefined, + function(release) { + MACHINE.a = oldArgcount; + MACHINE.e = oldEnv; + MACHINE.c = oldControl; + MACHINE._trampoline(f, false, release); + }); }; }; @@ -505,84 +510,91 @@ that.exclusiveLock.acquire( 'trampoline', function(release) { - var thunk = initialJump; - var startTime = (new Date()).valueOf(); - that.cbt = STACK_LIMIT_ESTIMATE; - that.params.numBouncesBeforeYield = - that.params.maxNumBouncesBeforeYield; - that.running = true; - - while(true) { - try { - thunk(that); - break; - } catch (e) { - // There are a few kinds of things that can get thrown - // during racket evaluation: - // - // functions: this gets thrown if the Racket code - // realizes that the number of bounces has grown too - // large. The thrown function represents a restarter - // function. The running flag remains true. - // - // Pause: causes the machine evaluation to pause, with - // the expectation that it will restart momentarily. - // The running flag on the machine will remain true. - // - // HaltError: causes evaluation to immediately halt. - // We schedule the onHalt function of the HaltError to - // call afterwards. The running flag on the machine - // is set to false. - // - // Everything else: otherwise, we send the exception value - // to the current error handler and exit. - // The running flag is set to false. - if (typeof(e) === 'function') { - thunk = e; - that.cbt = STACK_LIMIT_ESTIMATE; - - - // If we're running an a model that prohibits - // jumping off the trampoline, continue. - if (noJumpingOff) { - continue; - } - - if (that.params.numBouncesBeforeYield-- < 0) { - recomputeMaxNumBouncesBeforeYield( - that, - (new Date()).valueOf() - startTime); - scheduleTrampoline(that, thunk); - release(); - return; - } - } else if (e instanceof Pause) { - var restart = makeRestartFunction(that); - e.onPause(restart); - release(); - return; - } else if (e instanceof HaltError) { - that.running = false; - e.onHalt(that); - release(); - return; - } else { - // General error condition: just exit out - // of the trampoline and call the current error handler. - that.running = false; - that.params.currentErrorHandler(that, e); - release(); - return; - } - } - } - that.running = false; - that.params.currentSuccessHandler(that); - release(); - return; + that._trampoline(initialJump, noJumpingOff, release); }); }; + Machine.prototype._trampoline = function(initialJump, noJumpingOff, release) { + var that = this; + var thunk = initialJump; + var startTime = (new Date()).valueOf(); + that.cbt = STACK_LIMIT_ESTIMATE; + that.params.numBouncesBeforeYield = + that.params.maxNumBouncesBeforeYield; + that.running = true; + + while(true) { + try { + thunk(that); + break; + } catch (e) { + // There are a few kinds of things that can get thrown + // during racket evaluation: + // + // functions: this gets thrown if the Racket code + // realizes that the number of bounces has grown too + // large. The thrown function represents a restarter + // function. The running flag remains true. + // + // Pause: causes the machine evaluation to pause, with + // the expectation that it will restart momentarily. + // The running flag on the machine will remain true. + // + // HaltError: causes evaluation to immediately halt. + // We schedule the onHalt function of the HaltError to + // call afterwards. The running flag on the machine + // is set to false. + // + // Everything else: otherwise, we send the exception value + // to the current error handler and exit. + // The running flag is set to false. + if (typeof(e) === 'function') { + thunk = e; + that.cbt = STACK_LIMIT_ESTIMATE; + + + // If we're running an a model that prohibits + // jumping off the trampoline, continue. + if (noJumpingOff) { + continue; + } + + if (that.params.numBouncesBeforeYield-- < 0) { + recomputeMaxNumBouncesBeforeYield( + that, + (new Date()).valueOf() - startTime); + scheduleTrampoline(that, thunk); + release(); + return; + } + } else if (e instanceof Pause) { + var restart = makeRestartFunction(that); + e.onPause(restart); + release(); + return; + } else if (e instanceof HaltError) { + that.running = false; + e.onHalt(that); + release(); + return; + } else { + // General error condition: just exit out + // of the trampoline and call the current error handler. + that.running = false; + that.params.currentErrorHandler(that, e); + release(); + return; + } + } + } + that.running = false; + that.params.currentSuccessHandler(that); + release(); + return; + + }; + + // recomputeGas: state number -> number recomputeMaxNumBouncesBeforeYield = function(MACHINE, observedDelay) { // We'd like to see a delay of DESIRED_DELAY_BETWEEN_BOUNCES so diff --git a/version.rkt b/version.rkt index f14d333..72acb46 100644 --- a/version.rkt +++ b/version.rkt @@ -7,4 +7,4 @@ (provide version) (: version String) -(define version "1.101") +(define version "1.102") From 4573dc701ee24988f0dd3f1d6358a2570af9b1c7 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Fri, 9 Dec 2011 22:01:44 -0500 Subject: [PATCH 10/16] Trying to introduce mechanism for locking the evaluator, but I'm still running into several messy issues. --- js-assembler/runtime-src/baselib-functions.js | 140 +++++++++--------- js-assembler/runtime-src/baselib-modules.js | 23 ++- js-assembler/runtime-src/runtime.js | 28 +--- 3 files changed, 93 insertions(+), 98 deletions(-) diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index c6323b6..a7abd7d 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -196,8 +196,8 @@ MACHINE.p = oldProc; fail(e); }; - releaseLock(); - MACHINE.trampoline(v.label); + + MACHINE._trampoline(v.label, false, releaseLock); }); }; return f; @@ -225,85 +225,85 @@ // internallCallDuringPause: call a Racket procedure and get its results. - // The use assumes the machine is in a running-but-paused state. + // The use assumes the machine is in a running-but-paused state, where the + // lock is still in effect. The lock will continue to be in effect + // after coming back from the internal call. var internalCallDuringPause = function (MACHINE, proc, success, fail) { var args = []; var i; for (i = 0; i < arguments.length; i++) { args.push(arguments[i]); } - MACHINE.exclusiveLock.acquire( - "internal call during pause", - function(releaseLock) { - var i; - var oldArgcount, oldVal, oldProc, oldErrorHandler; - if (! baselib.arity.isArityMatching(proc.racketArity, args.length - 4)) { - var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", - [proc.displayName, proc.racketArity, args.length - 4]); - releaseLock(); - fail(baselib.exceptions.makeExnFailContractArity(msg, - MACHINE.captureContinuationMarks())); - } - if (isClosure(proc)) { - oldVal = MACHINE.v; - oldArgcount = MACHINE.a; - oldProc = MACHINE.p; + var i; + var oldArgcount, oldVal, oldProc, oldErrorHandler; + if (! baselib.arity.isArityMatching(proc.racketArity, args.length - 4)) { + var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", + [proc.displayName, proc.racketArity, args.length - 4]); + fail(baselib.exceptions.makeExnFailContractArity(msg, + MACHINE.captureContinuationMarks())); + } - oldErrorHandler = MACHINE.params['currentErrorHandler']; - var afterGoodInvoke = function (MACHINE) { - plt.runtime.PAUSE(function (restart) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - var returnValue = MACHINE.v; - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - success(returnValue); - }); - }; - afterGoodInvoke.mvr = function (MACHINE) { - plt.runtime.PAUSE(function (restart) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - var returnValues = [MACHINE.v]; - var i; - for (i = 0; i < MACHINE.a - 1; i++) { - returnValues.push(MACHINE.e.pop()); - } - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - success.apply(null, returnValues); - }); - }; + if (! isClosure(proc)) { + fail(baselib.exceptions.makeExnFail( + baselib.format.format( + "Not a procedure: ~e", + proc), + MACHINE.captureContinuationMarks())); - MACHINE.c.push( - new baselib.frames.CallFrame(afterGoodInvoke, proc)); - MACHINE.a = args.length - 4; - for (i = 0; i < args.length - 4; i++) { - MACHINE.e.push(args[args.length - 1 - i]); - } - MACHINE.p = proc; - MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - MACHINE.v = oldVal; - MACHINE.a = oldArgcount; - MACHINE.p = oldProc; - fail(e); - }; - releaseLock(); - MACHINE.trampoline(proc.label); - - } else { - releaseLock(); - fail(baselib.exceptions.makeExnFail( - baselib.format.format( - "Not a procedure: ~e", - proc), - MACHINE.captureContinuationMarks())); - } + } + + oldVal = MACHINE.v; + oldArgcount = MACHINE.a; + oldProc = MACHINE.p; + + oldErrorHandler = MACHINE.params['currentErrorHandler']; + var afterGoodInvoke = function (MACHINE) { + plt.runtime.PAUSE(function (restart) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + var returnValue = MACHINE.v; + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + success(returnValue); }); - }; + }; + afterGoodInvoke.mvr = function (MACHINE) { + plt.runtime.PAUSE(function (restart) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + var returnValues = [MACHINE.v]; + var i; + for (i = 0; i < MACHINE.a - 1; i++) { + returnValues.push(MACHINE.e.pop()); + } + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + success.apply(null, returnValues); + }); + }; + MACHINE.c.push( + new baselib.frames.CallFrame(afterGoodInvoke, proc)); + MACHINE.a = args.length - 4; + for (i = 0; i < args.length - 4; i++) { + MACHINE.e.push(args[args.length - 1 - i]); + } + MACHINE.p = proc; + MACHINE.params['currentErrorHandler'] = function (MACHINE, e) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + MACHINE.v = oldVal; + MACHINE.a = oldArgcount; + MACHINE.p = oldProc; + fail(e); + }; + MACHINE._trampoline(proc.label, + false, + function() { + // The lock should still being held, so we don't + // automatically unlock control. + }); + }; diff --git a/js-assembler/runtime-src/baselib-modules.js b/js-assembler/runtime-src/baselib-modules.js index 15aa6af..b005891 100644 --- a/js-assembler/runtime-src/baselib-modules.js +++ b/js-assembler/runtime-src/baselib-modules.js @@ -62,19 +62,28 @@ if (this.isInvoked) { succ(); } else { - MACHINE.params['currentErrorHandler'] = function (MACHINE, anError) { - MACHINE.params['currentErrorHandler'] = oldErrorHandler; - fail(MACHINE, anError); - }; - MACHINE.c.push(new plt.baselib.frames.CallFrame(afterGoodInvoke, null)); if (isInternal) { + MACHINE.params['currentErrorHandler'] = function (MACHINE, anError) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + fail(MACHINE, anError); + }; + MACHINE.c.push(new plt.baselib.frames.CallFrame(afterGoodInvoke, null)); throw that.label; } else { - MACHINE.trampoline(that.label); + MACHINE.exclusiveLock.acquire( + undefined, + function(release) { + MACHINE.params['currentErrorHandler'] = function (MACHINE, anError) { + MACHINE.params['currentErrorHandler'] = oldErrorHandler; + fail(MACHINE, anError); + }; + MACHINE.c.push(new plt.baselib.frames.CallFrame(afterGoodInvoke, null)); + MACHINE._trampoline(that.label, false, release); + }); } } }; - + exports.ModuleRecord = ModuleRecord; diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 7f1b403..3863fe9 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -447,15 +447,10 @@ // var recomputeMaxNumBouncesBeforeYield; - var scheduleTrampoline = function(MACHINE, f, before) { + var scheduleTrampoline = function(MACHINE, f, release) { setTimeout( function() { - MACHINE.exclusiveLock.acquire( - 'scheduleTrampoline', - function(release) { - if (before) { before(); } - MACHINE._trampoline(f, false, release); - }); + MACHINE._trampoline(f, false, release); }, 0); }; @@ -463,18 +458,11 @@ // Creates a restarting function, that reschedules f in a context // with the old argcount in place. // Meant to be used only by the trampoline. - var makeRestartFunction = function(MACHINE) { + var makeRestartFunction = function(MACHINE, release) { var oldArgcount = MACHINE.a; - var oldEnv = MACHINE.e.slice(); - var oldControl = MACHINE.c.slice(); return function(f) { - MACHINE.exclusiveLock.acquire(undefined, - function(release) { - MACHINE.a = oldArgcount; - MACHINE.e = oldEnv; - MACHINE.c = oldControl; - MACHINE._trampoline(f, false, release); - }); + MACHINE.a = oldArgcount; + MACHINE._trampoline(f, false, release); }; }; @@ -563,14 +551,12 @@ recomputeMaxNumBouncesBeforeYield( that, (new Date()).valueOf() - startTime); - scheduleTrampoline(that, thunk); - release(); + scheduleTrampoline(that, thunk, release); return; } } else if (e instanceof Pause) { - var restart = makeRestartFunction(that); + var restart = makeRestartFunction(that, release); e.onPause(restart); - release(); return; } else if (e instanceof HaltError) { that.running = false; From ac499e24f6891fde1b69141988d861292b780bad Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Fri, 9 Dec 2011 22:31:36 -0500 Subject: [PATCH 11/16] locking internalCall --- js-assembler/runtime-src/runtime.js | 44 +++++++++++++++++++++++++---- web-world/js-impl.js | 21 +++++++------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 3863fe9..6dc6720 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -458,11 +458,14 @@ // Creates a restarting function, that reschedules f in a context // with the old argcount in place. // Meant to be used only by the trampoline. - var makeRestartFunction = function(MACHINE, release) { + var makeRestartFunction = function(MACHINE, release, pauseLock) { var oldArgcount = MACHINE.a; return function(f) { - MACHINE.a = oldArgcount; - MACHINE._trampoline(f, false, release); + pauseLock.acquire(function(pauseReleaseLock) { + MACHINE.a = oldArgcount; + MACHINE._trampoline(f, false, release); + pauseReleaseLock(); + }); }; }; @@ -555,8 +558,39 @@ return; } } else if (e instanceof Pause) { - var restart = makeRestartFunction(that, release); - e.onPause(restart); + var pauseLock = new ExclusiveLock(); + var oldArgcount = that.a; + var restarted = false; + var restart = function(f) { + pauseLock.acquire(function(releasePauseLock) { + restarted = true; + that.a = oldArgcount; + that._trampoline(f, false, release); + releasePauseLock(); + }); + }; + var internalCall = function(proc, success, fail) { + if (restarted) { + return; + } + var args = []; + for (i = 3; i < arguments.length; i++) { + args.push(arguments[i]); + } + pauseLock.acquire(function(release) { + var newSuccess = function() { + success.apply(null, arguments); + release(); + }; + var newFail = function() { + fail.apply(null, arguments); + release(); + }; + baselib.functions.internalCallDuringPause.apply( + null, [that, proc, newSuccess, newFail].concat(args)); + }); + }; + e.onPause(restart, internalCall); return; } else if (e instanceof HaltError) { that.running = false; diff --git a/web-world/js-impl.js b/web-world/js-impl.js index 69ce92c..61989b3 100644 --- a/web-world/js-impl.js +++ b/web-world/js-impl.js @@ -1171,7 +1171,7 @@ MACHINE.params.currentOutputPort = find(handlers, isWithOutputToHandler).outputPort; } - PAUSE(function(restart) { + PAUSE(function(restart, internalCall) { var onCleanRestart, onMessyRestart, startEventHandlers, stopEventHandlers, startEventHandler, stopEventHandler, @@ -1260,7 +1260,7 @@ var onGoodWorldUpdate = function(newWorld) { world = newWorld; - stopWhen(MACHINE, + stopWhen(internalCall, world, mockView, function(shouldStop) { @@ -1274,14 +1274,14 @@ fail); }; if (plt.baselib.arity.isArityMatching(racketWorldCallback.racketArity, 3)) { - racketWorldCallback(MACHINE, + racketWorldCallback(internalCall, world, mockView, data, onGoodWorldUpdate, fail); } else { - racketWorldCallback(MACHINE, + racketWorldCallback(internalCall, world, mockView, onGoodWorldUpdate, @@ -1299,7 +1299,7 @@ // update, and have to do it from scratch. var nonce = Math.random(); var originalMockView = view.getMockAndResetFocus(nonce); - toDraw(MACHINE, + toDraw(internalCall, world, originalMockView, function(newMockView) { @@ -1334,15 +1334,14 @@ }; var wrapFunction = function(proc) { - var f = function(MACHINE) { + var f = function(internalCall) { var success = arguments[arguments.length - 2]; var fail = arguments[arguments.length - 1]; var args = [].slice.call(arguments, 1, arguments.length - 2); - return plt.baselib.functions.internalCallDuringPause.apply(null, - [MACHINE, - proc, - success, - fail].concat(args)); + return internalCall.apply(null, + [proc, + success, + fail].concat(args)); }; f.racketArity = proc.racketArity; return f; From bc5204f28e4c67ead10698cb560efe6a080fae27 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Fri, 9 Dec 2011 22:36:58 -0500 Subject: [PATCH 12/16] fixing typos --- js-assembler/runtime-src/runtime.js | 53 ++++++++++++++++------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 6dc6720..518b392 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -461,11 +461,13 @@ var makeRestartFunction = function(MACHINE, release, pauseLock) { var oldArgcount = MACHINE.a; return function(f) { - pauseLock.acquire(function(pauseReleaseLock) { - MACHINE.a = oldArgcount; - MACHINE._trampoline(f, false, release); - pauseReleaseLock(); - }); + pauseLock.acquire( + undefined, + function(pauseReleaseLock) { + MACHINE.a = oldArgcount; + MACHINE._trampoline(f, false, release); + pauseReleaseLock(); + }); }; }; @@ -562,14 +564,17 @@ var oldArgcount = that.a; var restarted = false; var restart = function(f) { - pauseLock.acquire(function(releasePauseLock) { - restarted = true; - that.a = oldArgcount; - that._trampoline(f, false, release); - releasePauseLock(); - }); + pauseLock.acquire( + undefined, + function(releasePauseLock) { + restarted = true; + that.a = oldArgcount; + that._trampoline(f, false, release); + releasePauseLock(); + }); }; var internalCall = function(proc, success, fail) { + var i; if (restarted) { return; } @@ -577,18 +582,20 @@ for (i = 3; i < arguments.length; i++) { args.push(arguments[i]); } - pauseLock.acquire(function(release) { - var newSuccess = function() { - success.apply(null, arguments); - release(); - }; - var newFail = function() { - fail.apply(null, arguments); - release(); - }; - baselib.functions.internalCallDuringPause.apply( - null, [that, proc, newSuccess, newFail].concat(args)); - }); + pauseLock.acquire( + undefined, + function(release) { + var newSuccess = function() { + success.apply(null, arguments); + release(); + }; + var newFail = function() { + fail.apply(null, arguments); + release(); + }; + baselib.functions.internalCallDuringPause.apply( + null, [that, proc, newSuccess, newFail].concat(args)); + }); }; e.onPause(restart, internalCall); return; From 1baa212d02e127b089d0015dd3db327530afcbbc Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Fri, 9 Dec 2011 22:59:15 -0500 Subject: [PATCH 13/16] defensive: when the machine fails during an internal call, try getting the environment and control in a good state. --- js-assembler/runtime-src/baselib-functions.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index a7abd7d..937523c 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -236,7 +236,7 @@ } var i; - var oldArgcount, oldVal, oldProc, oldErrorHandler; + var oldArgcount, oldVal, oldProc, oldErrorHandler, oldControlLength, oldEnvLength; if (! baselib.arity.isArityMatching(proc.racketArity, args.length - 4)) { var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s", [proc.displayName, proc.racketArity, args.length - 4]); @@ -256,6 +256,8 @@ oldVal = MACHINE.v; oldArgcount = MACHINE.a; oldProc = MACHINE.p; + oldControlLength = MACHINE.c.length; + oldEnvLength = MACHINE.e.length; oldErrorHandler = MACHINE.params['currentErrorHandler']; var afterGoodInvoke = function (MACHINE) { @@ -295,6 +297,8 @@ MACHINE.v = oldVal; MACHINE.a = oldArgcount; MACHINE.p = oldProc; + MACHINE.c.length = oldControlLength; + MACHINE.e.length = oldEnvLength; fail(e); }; MACHINE._trampoline(proc.label, From 68de55b170da8cbf703050b48ac642ba41a67805 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Mon, 12 Dec 2011 12:34:01 -0500 Subject: [PATCH 14/16] adding references to the static resources --- js-assembler/runtime-src/runtime.js | 2 +- scribblings/manual.scrbl | 37 ++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index 518b392..8da5eaa 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -188,7 +188,7 @@ ExclusiveLock.prototype.acquire = function(id, onAcquire) { var that = this; - if (id === undefined) { + if (!id) { id = ExclusiveLock.makeRandomNonce(); } diff --git a/scribblings/manual.scrbl b/scribblings/manual.scrbl index c4507a9..cff8017 100644 --- a/scribblings/manual.scrbl +++ b/scribblings/manual.scrbl @@ -114,30 +114,41 @@ Prerequisites: at least @link["http://racket-lang.org/"]{Racket Here are a collection of programs that use the @emph{web-world} library described later in this document: @itemize[ -@item{@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.html"]{attr-animation.html} [@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.rkt"]{src}] Uses @racket[update-view-attr] and @racket[on-tick] to perform a simple color animation.} +@item{@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.html"]{attr-animation.html} +[@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/attr-animation/index.html"]{index.html} + @link["http://hashcollision.org/whalesong/examples/attr-animation/style.css"]{style.css}] +Uses @racket[update-view-attr] and @racket[on-tick] to perform a simple color animation.} -@item{@link["http://hashcollision.org/whalesong/examples/boid/boid.html"]{boid.html} [@link["http://hashcollision.org/whalesong/examples/boid/boid.rkt"]{src}] Uses @racket[update-view-css] and @racket[on-tick] to perform an animation of a flock of @link["http://en.wikipedia.org/wiki/Boids"]{boids}.} +@item{@link["http://hashcollision.org/whalesong/examples/boid/boid.html"]{boid.html} +[@link["http://hashcollision.org/whalesong/examples/boid/boid.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/boid/index.html"]{index.html}] Uses @racket[update-view-css] and @racket[on-tick] to perform an animation of a flock of @link["http://en.wikipedia.org/wiki/Boids"]{boids}.} @item{@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.html"]{dwarves.html} -[@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/dwarves/index.html"]{index.html}] Uses @racket[view-show] and @racket[view-hide] to manipulate a view. Click on a dwarf to make them hide. } @item{@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.html"]{dwarves-with-remove.html} -[@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/index.html"]{index.html}] Uses @racket[view-focus?] and @racket[view-remove] to see if a dwarf should be removed from the view. } @item{@link["http://hashcollision.org/whalesong/examples/field/field.html"]{field.html} -[@link["http://hashcollision.org/whalesong/examples/field/field.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/field/field.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/field/index.html"]{index.html}] Uses @racket[view-bind] to read a text field, and @racket[update-view-text] to change the text content of an element. } @item{@link["http://hashcollision.org/whalesong/examples/phases/phases.html"]{phases.html} -[@link["http://hashcollision.org/whalesong/examples/phases/phases.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/phases/phases.rkt"]{src} +@link["http://hashcollision.org/whalesong/examples/phases/index1.html"]{index1.html} +@link["http://hashcollision.org/whalesong/examples/phases/index2.html"]{index2.html}] Switches out one view entirely in place of another. Different views can correspond to phases in a program. } @@ -148,24 +159,28 @@ Uses @racket[on-tick] to show a timer counting up. } @item{@link["http://hashcollision.org/whalesong/examples/redirected/redirected.html"]{redirected.html} -[@link["http://hashcollision.org/whalesong/examples/redirected/redirected.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/redirected/redirected.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/redirected/index.html"]{index.html}] Uses @racket[on-tick] to show a timer counting up, and also uses @racket[open-output-element] to pipe side-effecting @racket[printf]s to a hidden @tt{div}. } @item{@link["http://hashcollision.org/whalesong/examples/todo/todo.html"]{todo.html} -[@link["http://hashcollision.org/whalesong/examples/todo/todo.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/todo/todo.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/todo/index.html"]{index.html}] A simple TODO list manager. } @item{@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.html"]{where-am-i.html} -[@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/where-am-i/index.html"]{index.html}] Uses @racket[on-location-change] and @racket[on-mock-location-change] to demonstrate location services. } @item{@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/hot-cross-buns.html"]{hot-cross-buns.html} -[@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/hot-cross-buns.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/hot-cross-buns.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/hot-cross-buns/index.html"]{index.html}] Demonstrates use of checkboxes. Uses @racket[view-has-attr?] to see if a checkbox has been checked, and @racket[remove-view-attr] to change the @emph{checked} attribute when the user wants to reset the page. @@ -214,7 +229,7 @@ If you want to use Whalesong, run the following to create the @filepath{whalesong} launcher: @codeblock|{ #lang racket/base -(require (planet dyoo/whalesong:1:11/make-launcher)) +(require (planet dyoo/whalesong:1:12/make-launcher)) }| This may take a few minutes, as Racket is compiling Whalesong, its dependencies, and its documentation. When it finally finishes, From e19c26f9d19328c7b68178ad09f227e84df47d02 Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Tue, 13 Dec 2011 15:23:17 -0500 Subject: [PATCH 15/16] more docs --- scribblings/manual.scrbl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scribblings/manual.scrbl b/scribblings/manual.scrbl index cff8017..2fbed39 100644 --- a/scribblings/manual.scrbl +++ b/scribblings/manual.scrbl @@ -154,7 +154,8 @@ Switches out one view entirely in place of another. Different views can corresp @item{@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.html"]{tick-tock.html} -[@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.rkt"]{src}] +[@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.rkt"]{src} + @link["http://hashcollision.org/whalesong/examples/tick-tock/index.html"]{index.html}] Uses @racket[on-tick] to show a timer counting up. } From a75c3d6c85730b3e3faa2f4b520c17846ccf5ebd Mon Sep 17 00:00:00 2001 From: Danny Yoo Date: Tue, 13 Dec 2011 15:23:59 -0500 Subject: [PATCH 16/16] adding a form to make it easier to do bulk binds --- web-world/main.rkt | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/web-world/main.rkt b/web-world/main.rkt index f74db08..2e38ce6 100644 --- a/web-world/main.rkt +++ b/web-world/main.rkt @@ -2,8 +2,28 @@ (require "impl.rkt" "helpers.rkt" - "event.rkt") + "event.rkt" + (for-syntax racket/base)) (provide (all-from-out "impl.rkt") (all-from-out "helpers.rkt") - (all-from-out "event.rkt")) \ No newline at end of file + (all-from-out "event.rkt")) + +(provide view-bind*) + + +;; A syntactic form to make it more convenient to focus and bind multiple things +;; (view-bind* a-view +;; [id type function] +;; [id type function] ...) +(define-syntax (view-bind* stx) + (syntax-case stx () + [(_ a-view [a-selector a-type a-function] ...) + (foldl (lambda (a-selector a-type a-function a-view-stx) + #'(view-bind (view-focus #,a-view-stx #,a-selector) + #,a-type + #,a-function)) + #'a-view + (syntax->list #'(a-selector ...)) + (syntax->list #'(a-type ...)) + (syntax->list #'(a-function ...)))])) \ No newline at end of file