summaryrefslogtreecommitdiff
path: root/threads.js
diff options
context:
space:
mode:
Diffstat (limited to 'threads.js')
-rw-r--r--threads.js90
1 files changed, 66 insertions, 24 deletions
diff --git a/threads.js b/threads.js
index cd83af6..1782984 100644
--- a/threads.js
+++ b/threads.js
@@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
-modules.threads = '2014-November-20';
+modules.threads = '2014-November-24';
var ThreadManager;
var Process;
@@ -100,8 +100,18 @@ function snapEquals(a, b) {
var x = +a,
y = +b,
+ i,
specials = [true, false, ''];
+ // "zum Schneckengang verdorben, was Adlerflug geworden wäre"
+ // collecting edge-cases that somebody complained about
+ // on Github. Folks, take it easy and keep it fun, okay?
+ // Shit like this is patently ugly and slows Snap down. Tnx!
+ for (i = 9; i <= 13; i += 1) {
+ specials.push(String.fromCharCode(i));
+ }
+ specials.push(String.fromCharCode(160));
+
// check for special values before coercing to numbers
if (isNaN(x) || isNaN(y) ||
[a, b].some(function (any) {return contains(specials, any) ||
@@ -110,7 +120,7 @@ function snapEquals(a, b) {
y = b;
}
- // handle text comparision case-insensitive.
+ // handle text comparison case-insensitive.
if (isString(x) && isString(y)) {
return x.toLowerCase() === y.toLowerCase();
}
@@ -322,6 +332,9 @@ ThreadManager.prototype.findProcess = function (block) {
block along with the result bubble shoud be exported
onComplete an optional callback function to be executed when
the process is done
+ procedureCount number counting procedure call entries,
+ used to tag custom block calls, so "stop block"
+ invocations can catch them
*/
Process.prototype = {};
@@ -347,6 +360,7 @@ function Process(topBlock, onComplete) {
this.frameCount = 0;
this.exportResult = false;
this.onComplete = onComplete || null;
+ this.procedureCount = 0;
if (topBlock) {
this.homeContext.receiver = topBlock.receiver();
@@ -684,7 +698,7 @@ Process.prototype.doYield = function () {
Process.prototype.exitReporter = function () {
// catch-tag for REPORT and STOP BLOCK primitives
- this.popContext();
+ this.handleError(new Error("missing 'report' statement in reporter"));
};
// Process Exception Handling
@@ -765,12 +779,6 @@ Process.prototype.reportJSFunction = function (parmNames, body) {
);
};
-/*
-Process.prototype.doRun = function (context, args, isCustomBlock) {
- return this.evaluate(context, args, true, isCustomBlock);
-};
-*/
-
Process.prototype.doRun = function (context, args) {
return this.evaluate(context, args, true);
};
@@ -957,9 +965,11 @@ Process.prototype.fork = function (context, args) {
stage.threads.processes.push(proc);
};
+// Process "return" primitives
+
Process.prototype.doReport = function (value) {
if (this.context.expression.partOfCustomCommand) {
- return this.doStopBlock();
+ return this.doStopCustomBlock();
}
while (this.context && this.context.expression !== 'exitReporter') {
if (this.context.expression === 'doStopWarping') {
@@ -972,6 +982,24 @@ Process.prototype.doReport = function (value) {
};
Process.prototype.doStopBlock = function () {
+ var target = this.context.expression.exitTag;
+ if (isNil(target)) {
+ return this.doStopCustomBlock();
+ }
+ while (this.context &&
+ (isNil(this.context.tag) || (this.context.tag > target))) {
+ if (this.context.expression === 'doStopWarping') {
+ this.doStopWarping();
+ } else {
+ this.popContext();
+ }
+ }
+ this.pushContext();
+};
+
+Process.prototype.doStopCustomBlock = function () {
+ // fallback solution for "report" blocks inside
+ // custom command definitions and untagged "stop" blocks
while (this.context && !this.context.isCustomBlock) {
if (this.context.expression === 'doStopWarping') {
this.doStopWarping();
@@ -1010,7 +1038,8 @@ Process.prototype.runContinuation = function (aContext, args) {
// Process custom block primitives
Process.prototype.evaluateCustomBlock = function () {
- var context = this.context.expression.definition.body,
+ var caller = this.context.parentContext,
+ context = this.context.expression.definition.body,
declarations = this.context.expression.definition.declarations,
args = new List(this.context.inputs),
parms = args.asArray(),
@@ -1022,6 +1051,7 @@ Process.prototype.evaluateCustomBlock = function () {
outer;
if (!context) {return null; }
+ this.procedureCount += 1;
outer = new Context();
outer.receiver = this.context.receiver;
outer.variables.parentFrame = outer.receiver ?
@@ -1068,10 +1098,19 @@ Process.prototype.evaluateCustomBlock = function () {
outer.receiver
);
runnable.parentContext = exit;
- } else { // mark all REPORT blocks as being part of a custom command
- runnable.expression.allReportBlocks().forEach(function (rb) {
- rb.partOfCustomCommand = true;
- });
+ this.popContext(); // don't yield when done
+ } else {
+ // tag all "stop this block" blocks with the current
+ // procedureCount as exitTag, and mark all "report" blocks
+ // as being inside a custom command definition
+ runnable.expression.tagExitBlocks(this.procedureCount, true);
+
+ // tag the caller with the current procedure count, so
+ // "stop this block" blocks can catch it, but only
+ // if the caller hasn't been tagged already
+ if (caller && !caller.tag) {
+ caller.tag = this.procedureCount;
+ }
}
runnable.expression = runnable.expression.blockSequence();
}
@@ -1269,7 +1308,7 @@ Process.prototype.doInsertInList = function (element, index, list) {
return null;
}
if (this.inputOption(index) === 'any') {
- idx = this.reportRandom(1, list.length());
+ idx = this.reportRandom(1, list.length() + 1);
}
if (this.inputOption(index) === 'last') {
idx = list.length() + 1;
@@ -2750,23 +2789,25 @@ Process.prototype.reportFrameCount = function () {
structure:
- parentContext the Context to return to when this one has
+ parentContext the Context to return to when this one has
been evaluated.
outerContext the Context holding my lexical scope
- expression SyntaxElementMorph, an array of blocks to evaluate,
+ expression SyntaxElementMorph, an array of blocks to evaluate,
null or a String denoting a selector, e.g. 'doYield'
receiver the object to which the expression applies, if any
- variables the current VariableFrame, if any
- inputs an array of input values computed so far
+ variables the current VariableFrame, if any
+ inputs an array of input values computed so far
(if expression is a BlockMorph)
- pc the index of the next block to evaluate
+ pc the index of the next block to evaluate
(if expression is an array)
- startTime time when the context was first evaluated
- startValue initial value for interpolated operations
+ startTime time when the context was first evaluated
+ startValue initial value for interpolated operations
activeAudio audio buffer for interpolated operations, don't persist
activeNote audio oscillator for interpolated ops, don't persist
isCustomBlock marker for return ops
- emptySlots caches the number of empty slots for reification
+ emptySlots caches the number of empty slots for reification
+ tag string or number to optionally identify the Context,
+ as a "return" target (for the "stop block" primitive)
*/
function Context(
@@ -2791,6 +2832,7 @@ function Context(
this.activeNote = null;
this.isCustomBlock = false; // marks the end of a custom block's stack
this.emptySlots = 0; // used for block reification
+ this.tag = null; // lexical catch-tag for custom blocks
}
Context.prototype.toString = function () {