summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjmoenig <jens@moenig.org>2014-11-21 16:55:25 +0100
committerjmoenig <jens@moenig.org>2014-11-21 16:55:25 +0100
commit9e91a93ac029056adae89e7b8e07558b47f9634f (patch)
treeee0f93561552e794927f885ccb28177777db1ebd
parentf2d0c2eba5d6b012fc3fb1167c82b64b2f8ed447 (diff)
downloadsnap-9e91a93ac029056adae89e7b8e07558b47f9634f.tar.gz
snap-9e91a93ac029056adae89e7b8e07558b47f9634f.zip
Fix "stop this block"’s lexical awareness
“stop this block” when used inside a custom block definition now always returns out of the lexically enclosing script (the definition), even if it is used inside other nested, C-shaped custom blocks in the definition code. Previously it only stopped the nearest encompassing “for” block, now it always stops the block whose definition it is in. I don’t expect this fix to break any existing projects.
-rw-r--r--blocks.js40
-rwxr-xr-xhistory.txt4
-rw-r--r--threads.js65
3 files changed, 74 insertions, 35 deletions
diff --git a/blocks.js b/blocks.js
index 82ef601..e56d0ed 100644
--- a/blocks.js
+++ b/blocks.js
@@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2014-November-17';
+modules.blocks = '2014-November-21';
var SyntaxElementMorph;
@@ -395,7 +395,9 @@ SyntaxElementMorph.prototype.allInputs = function () {
SyntaxElementMorph.prototype.allEmptySlots = function () {
// answer empty input slots of all children excluding myself,
- // but omit those in nested rings (lambdas) and JS-Function primitives
+ // but omit those in nested rings (lambdas) and JS-Function primitives.
+ // Used by the evaluator when binding implicit formal parameters
+ // to empty input slots
var empty = [];
if (!(this instanceof RingMorph) &&
(this.selector !== 'reportJSFunction')) {
@@ -410,19 +412,24 @@ SyntaxElementMorph.prototype.allEmptySlots = function () {
return empty;
};
-SyntaxElementMorph.prototype.allReportBlocks = function () {
- // answer report blocks of all children including myself,
- // but omit those in nested rings (lambdas)
- if (this.selector === 'doReport') {return [this]; }
- var reports = [];
- if (!(this instanceof RingMorph)) {
- this.children.forEach(function (morph) {
- if (morph.allReportBlocks) {
- reports = reports.concat(morph.allReportBlocks());
- }
- });
+SyntaxElementMorph.prototype.tagExitBlocks = function (stopTag, isCommand) {
+ // tag 'report' and 'stop this block' blocks of all children including
+ // myself, with either a stopTag (for "stop" blocks) or an indicator of
+ // being inside a command block definition, but omit those in nested
+ // rings (lambdas. Used by the evaluator when entering a procedure
+ if (this.selector === 'doReport') {
+ this.partOfCustomCommand = isCommand;
+ } else if (this.selector === 'doStopThis') {
+ this.exitTag = stopTag;
+ } else {
+ if (!(this instanceof RingMorph)) {
+ this.children.forEach(function (morph) {
+ if (morph.tagExitBlocks) {
+ morph.tagExitBlocks(stopTag, isCommand);
+ }
+ });
+ }
}
- return reports;
};
SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
@@ -3186,9 +3193,10 @@ BlockMorph.prototype.snap = function () {
bottomBlock() - answer the bottom block of my stack
blockSequence() - answer an array of blocks starting with myself
- and the following "lexical awareness" indicator:
+ and the following "lexical awareness" indicators:
partOfCustomCommand - temporary bool set by the evaluator
+ exitTag - temporary string or number set by the evaluator
*/
// CommandBlockMorph inherits from BlockMorph:
@@ -3206,6 +3214,8 @@ function CommandBlockMorph() {
CommandBlockMorph.prototype.init = function () {
CommandBlockMorph.uber.init.call(this);
this.setExtent(new Point(200, 100));
+ this.partOfCustomCommand = false;
+ this.exitTag = null;
};
// CommandBlockMorph enumerating:
diff --git a/history.txt b/history.txt
index 0faac6b..c468c99 100755
--- a/history.txt
+++ b/history.txt
@@ -2333,3 +2333,7 @@ ______
* Threads: fix ‘line’ option in ‘split’ block for Windows files, thanks, @brianharvey!
* Morphic: fix slider range 1, thanks, @tonychenr !
* translation update, thanks, Manuel!
+
+141121
+------
+* Threads, Blocks: Fix STOP THIS BLOCK’s lexical awareness
diff --git a/threads.js b/threads.js
index 7d656c4..ab40460 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-21';
var ThreadManager;
var Process;
@@ -322,6 +322,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 +350,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();
@@ -765,12 +769,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 +955,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 +972,22 @@ Process.prototype.doReport = function (value) {
};
Process.prototype.doStopBlock = function () {
+ var target = this.context.expression.exitTag;
+ if (isNil(target)) {
+ return this.doStopCustomBlock();
+ }
+ while (this.context && this.context.tag !== target) {
+ if (this.context.expression === 'doStopWarping') {
+ this.doStopWarping();
+ } else {
+ this.popContext();
+ }
+ }
+};
+
+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();
@@ -1022,6 +1038,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 +1085,15 @@ 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;
- });
+ } 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 runnable with the current procedure count, so
+ // "stop this block" blocks can catch it
+ runnable.tag = this.procedureCount;
}
runnable.expression = runnable.expression.blockSequence();
}
@@ -2746,23 +2768,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(
@@ -2787,6 +2811,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 () {