Fix reporting out of nested custom C-shaped blocks

REPORT now reports to the nearest lexical element expecting an input
(which may not be the block holding the REPORT statement, this lets you
REPORT out of nested FOR loops).
STOP THIS BLOCK behaves as it used to.
If you’ve been using REPORT instead of STOP THIS BLOCK, you should
migrate.
pull/3/merge
jmoenig 2014-11-14 12:49:01 +01:00
rodzic 7b96be6c40
commit b36a358173
3 zmienionych plików z 80 dodań i 109 usunięć

Wyświetl plik

@ -2309,3 +2309,11 @@ ______
141008
------
* Objects: fixed #608, #610
141106
------
* Morphic: Enable mouseMove events with right button pressed
141114
------
* Threads, Store: Fix reporting out of nested custom C-shaped blocks

Wyświetl plik

@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
// Global stuff ////////////////////////////////////////////////////////
modules.store = '2014-October-01';
modules.store = '2014-November-14';
// XML_Serializer ///////////////////////////////////////////////////////
@ -1824,9 +1824,8 @@ Context.prototype.toXML = function (serializer) {
return '';
}
return serializer.format(
'<context% ~><inputs>%</inputs><variables>%</variables>' +
'<context ~><inputs>%</inputs><variables>%</variables>' +
'%<receiver>%</receiver>%</context>',
this.isLambda ? ' lambda="lambda"' : '',
this.inputs.reduce(
function (xml, input) {
return xml + serializer.format('<input>$</input>', input);

Wyświetl plik

@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
modules.threads = '2014-October-01';
modules.threads = '2014-November-15';
var ThreadManager;
var Process;
@ -586,7 +586,6 @@ Process.prototype.evaluateInput = function (input) {
) || (input instanceof CSlotMorph && !input.isStatic)) {
// I know, this still needs yet to be done right....
ans = this.reify(ans, new List());
ans.isImplicitLambda = true;
}
}
}
@ -597,8 +596,6 @@ Process.prototype.evaluateInput = function (input) {
Process.prototype.evaluateSequence = function (arr) {
var pc = this.context.pc,
outer = this.context.outerContext,
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock;
if (pc === (arr.length - 1)) { // tail call elimination
this.context = new Context(
@ -607,8 +604,6 @@ Process.prototype.evaluateSequence = function (arr) {
this.context.outerContext,
this.context.receiver
);
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock;
} else {
if (pc >= arr.length) {
@ -626,8 +621,8 @@ Process.prototype.evaluateSequence = function (arr) {
Caution: we cannot just revert to this version of the method, because to make
tail call elimination work many tweaks had to be done to various primitives.
For the most part these tweaks are about schlepping the outer context (for
the variable bindings) and the isLambda flag along, and are indicated by a
short comment in the code. But to really revert would take a good measure
the variable bindings) and the isCustomBlock flag along, and are indicated
by a short comment in the code. But to really revert would take a good measure
of trial and error as well as debugging. In the developers file archive there
is a version of threads.js dated 120119(2) which basically resembles the
last version before introducing tail call optimization on 120123.
@ -679,6 +674,11 @@ Process.prototype.doYield = function () {
}
};
Process.prototype.exitReporter = function () {
// catch-tag for REPORT and STOP BLOCK primitives
this.popContext();
};
// Process Exception Handling
Process.prototype.handleError = function (error, element) {
@ -757,9 +757,15 @@ 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);
};
Process.prototype.evaluate = function (
context,
@ -783,6 +789,7 @@ Process.prototype.evaluate = function (
var outer = new Context(null, null, context.outerContext),
runnable,
extra,
exit,
parms = args.asArray(),
i,
value;
@ -798,23 +805,17 @@ Process.prototype.evaluate = function (
);
extra = new Context(runnable, 'doYield');
/*
Note: if the context's expression is a ReporterBlockMorph,
the extra context gets popped off immediately without taking
effect (i.e. it doesn't yield within evaluating a stack of
nested reporters)
*/
// Note: if the context's expression is a ReporterBlockMorph,
// the extra context gets popped off immediately without taking
// effect (i.e. it doesn't yield within evaluating a stack of
// nested reporters)
if (isCommand || (context.expression instanceof ReporterBlockMorph)) {
if (context.expression instanceof ReporterBlockMorph) {
this.context.parentContext = extra;
} else {
this.context.parentContext = runnable;
}
runnable.isLambda = true;
runnable.isImplicitLambda = context.isImplicitLambda;
runnable.isCustomBlock = false;
// assign parameters if any were passed
if (parms.length > 0) {
@ -858,6 +859,18 @@ Process.prototype.evaluate = function (
if (runnable.expression instanceof CommandBlockMorph) {
runnable.expression = runnable.expression.blockSequence();
// insert a reporter exit tag for the
// CALL SCRIPT primitive variant
if (!isCommand) {
exit = new Context(
runnable.parentContext,
'exitReporter',
outer,
outer.receiver
);
runnable.parentContext = exit;
}
}
};
@ -879,8 +892,6 @@ Process.prototype.fork = function (context, args) {
stage = this.homeContext.receiver.parentThatIsA(StageMorph),
proc = new Process();
runnable.isLambda = true;
// assign parameters if any were passed
if (parms.length > 0) {
@ -933,68 +944,39 @@ Process.prototype.fork = function (context, args) {
stage.threads.processes.push(proc);
};
Process.prototype.doReport = function (value, isCSlot) {
while (this.context && !this.context.isLambda) {
Process.prototype.doReport = function (value) {
while (this.context && this.context.expression !== 'exitReporter') {
if (this.context.expression === 'doStopWarping') {
this.doStopWarping();
} else {
this.popContext();
}
}
if (this.context && this.context.isImplicitLambda) {
if (this.context.expression === 'doStopWarping') {
this.doStopWarping();
} else {
this.popContext();
}
return this.doReport(value, true);
}
if (this.context && this.context.isCustomBlock) {
// now I'm back at the custom block sequence.
// advance my pc to my expression's length
this.context.pc = this.context.expression.length - 1;
}
if (isCSlot) {
if (this.context &&
this.context.parentContext &&
this.context.parentContext.expression instanceof Array) {
this.popContext();
}
}
return value;
};
Process.prototype.doStopBlock = function () {
this.doReport();
while (this.context && !this.context.isCustomBlock) {
if (this.context.expression === 'doStopWarping') {
this.doStopWarping();
} else {
this.popContext();
}
}
};
// Process evaluation variants, commented out for now (redundant)
/*
Process.prototype.doRunWithInputList = function (context, args) {
// provide an extra selector for the palette
return this.doRun(context, args);
};
Process.prototype.evaluateWithInputList = function (context, args) {
// provide an extra selector for the palette
return this.evaluate(context, args);
};
Process.prototype.forkWithInputList = function (context, args) {
// provide an extra selector for the palette
return this.fork(context, args);
};
*/
// Process continuations primitives
Process.prototype.doCallCC = function (aContext) {
this.evaluate(aContext, new List([this.context.continuation()]));
Process.prototype.doCallCC = function (aContext, isReporter) {
this.evaluate(
aContext,
new List([this.context.continuation()]),
!isReporter
);
};
Process.prototype.reportCallCC = function (aContext) {
this.doCallCC(aContext);
this.doCallCC(aContext, true);
};
Process.prototype.runContinuation = function (aContext, args) {
@ -1017,6 +999,7 @@ Process.prototype.evaluateCustomBlock = function () {
args = new List(this.context.inputs),
parms = args.asArray(),
runnable,
exit,
extra,
i,
value,
@ -1024,7 +1007,7 @@ Process.prototype.evaluateCustomBlock = function () {
if (!context) {return null; }
outer = new Context();
outer.receiver = this.context.receiver; // || this.homeContext.receiver;
outer.receiver = this.context.receiver;
outer.variables.parentFrame = outer.receiver ?
outer.receiver.variables : null;
@ -1032,15 +1015,11 @@ Process.prototype.evaluateCustomBlock = function () {
this.context.parentContext,
context.expression,
outer,
outer.receiver,
true // is custom block
outer.receiver
);
extra = new Context(runnable, 'doYield');
this.context.parentContext = extra;
runnable.isLambda = true;
runnable.isCustomBlock = true;
extra = new Context(runnable, 'doYield');
this.context.parentContext = extra;
// passing parameters if any were passed
if (parms.length > 0) {
@ -1064,6 +1043,18 @@ Process.prototype.evaluateCustomBlock = function () {
if (runnable.expression instanceof CommandBlockMorph) {
runnable.expression = runnable.expression.blockSequence();
// insert a reporter exit tag for the
// CALL SCRIPT primitive variant
if (this.context.expression.definition.type !== 'command') {
exit = new Context(
runnable.parentContext,
'exitReporter',
outer,
outer.receiver
);
runnable.parentContext = exit;
}
}
};
@ -1308,16 +1299,12 @@ Process.prototype.reportListContainsItem = function (list, element) {
Process.prototype.doIf = function () {
var args = this.context.inputs,
outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock;
this.popContext();
if (args[0]) {
if (args[1]) {
this.pushContext(args[1].blockSequence(), outer);
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock;
}
}
@ -1327,8 +1314,6 @@ Process.prototype.doIf = function () {
Process.prototype.doIfElse = function () {
var args = this.context.inputs,
outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock;
this.popContext();
@ -1344,8 +1329,6 @@ Process.prototype.doIfElse = function () {
}
}
if (this.context) {
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock;
}
@ -1420,8 +1403,6 @@ Process.prototype.doStopOthers = function (choice) {
Process.prototype.doWarp = function (body) {
// execute my contents block atomically (more or less)
var outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock,
stage;
@ -1438,13 +1419,8 @@ Process.prototype.doWarp = function (body) {
stage.fps = 0; // variable frame rate
}
}
this.pushContext('doYield');
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock;
if (!this.isAtomic) {
this.pushContext('doStopWarping');
}
@ -1523,28 +1499,19 @@ Process.prototype.doForever = function (body) {
Process.prototype.doRepeat = function (counter, body) {
var block = this.context.expression,
outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock;
if (counter < 1) { // was '=== 0', which caused infinite loops on non-ints
return null;
}
this.popContext();
this.pushContext(block, outer);
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock;
this.context.addInput(counter - 1);
this.pushContext('doYield');
if (body) {
this.pushContext(body.blockSequence());
}
this.pushContext();
};
@ -2775,8 +2742,6 @@ Process.prototype.reportFrameCount = function () {
startValue initial value for interpolated operations
activeAudio audio buffer for interpolated operations, don't persist
activeNote audio oscillator for interpolated ops, don't persist
isLambda marker for return ops
isImplicitLambda marker for return ops
isCustomBlock marker for return ops
emptySlots caches the number of empty slots for reification
*/
@ -2801,22 +2766,18 @@ function Context(
this.startTime = null;
this.activeAudio = null;
this.activeNote = null;
this.isLambda = false; // marks the end of a lambda
this.isImplicitLambda = false; // marks the end of a C-shaped slot
this.isCustomBlock = false; // marks the end of a custom block's stack
this.emptySlots = 0; // used for block reification
}
Context.prototype.toString = function () {
var pref = this.isLambda ? '\u03BB-' : '',
expr = this.expression;
var expr = this.expression;
if (expr instanceof Array) {
if (expr.length > 0) {
expr = '[' + expr[0] + ']';
}
}
return pref + 'Context >> ' + expr + ' ' + this.variables;
return 'Context >> ' + expr + ' ' + this.variables;
};
Context.prototype.image = function () {
@ -2872,6 +2833,9 @@ Context.prototype.continuation = function () {
} else {
return new Context(null, 'doStop');
}
if (cont.expression === 'exitReporter') {
return cont.continuation();
}
cont = cont.copyForContinuation();
cont.isContinuation = true;
return cont;