From 9e91a93ac029056adae89e7b8e07558b47f9634f Mon Sep 17 00:00:00 2001 From: jmoenig Date: Fri, 21 Nov 2014 16:55:25 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20"stop=20this=20block"=E2=80=99s=20lexical?= =?UTF-8?q?=20awareness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “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. --- blocks.js | 40 ++++++++++++++++++++------------- history.txt | 4 ++++ threads.js | 65 ++++++++++++++++++++++++++++++++++++----------------- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/blocks.js b/blocks.js index 82ef6017..e56d0edc 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 0faac6b2..c468c990 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 7d656c43..ab40460c 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 () {