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.
pull/3/merge
jmoenig 2014-11-21 16:55:25 +01:00
rodzic f2d0c2eba5
commit 9e91a93ac0
3 zmienionych plików z 74 dodań i 35 usunięć

Wyświetl plik

@ -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:

Wyświetl plik

@ -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 BLOCKs lexical awareness

Wyświetl plik

@ -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 () {