diff options
| author | Michael Ball <cycomachead@gmail.com> | 2014-11-21 03:08:41 -0800 |
|---|---|---|
| committer | Michael Ball <cycomachead@gmail.com> | 2014-11-21 03:08:41 -0800 |
| commit | 3e8146d043b1f1dc6f37e0a8cbd2e5d3dafb90c7 (patch) | |
| tree | 0a36d8b40781b5c3adc38c382cd866bbd7499598 /threads.js | |
| parent | dbf2e6665b4ff7ef8cb863bcfef5939117a2f795 (diff) | |
| parent | f2d0c2eba5d6b012fc3fb1167c82b64b2f8ed447 (diff) | |
| download | snap-yow-3e8146d043b1f1dc6f37e0a8cbd2e5d3dafb90c7.tar.gz snap-yow-3e8146d043b1f1dc6f37e0a8cbd2e5d3dafb90c7.zip | |
Merge in recent changes (split block improvements #619)
Diffstat (limited to 'threads.js')
| -rw-r--r-- | threads.js | 253 |
1 files changed, 118 insertions, 135 deletions
@@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2014-October-01'; +modules.threads = '2014-November-20'; var ThreadManager; var Process; @@ -136,7 +136,8 @@ ThreadManager.prototype.toggleProcess = function (block) { ThreadManager.prototype.startProcess = function ( block, isThreadSafe, - exportResult + exportResult, + callback ) { var active = this.findProcess(block), top = block.topBlock(), @@ -149,7 +150,7 @@ ThreadManager.prototype.startProcess = function ( this.removeTerminatedProcesses(); } top.addHighlight(); - newProc = new Process(block.topBlock()); + newProc = new Process(block.topBlock(), callback); newProc.exportResult = exportResult; this.processes.push(newProc); return newProc; @@ -225,8 +226,9 @@ ThreadManager.prototype.removeTerminatedProcesses = function () { var remaining = []; this.processes.forEach(function (proc) { if (!proc.isRunning() && !proc.errorFlag && !proc.isDead) { - proc.topBlock.removeHighlight(); - + if (proc.topBlock instanceof BlockMorph) { + proc.topBlock.removeHighlight(); + } if (proc.prompter) { proc.prompter.destroy(); if (proc.homeContext.receiver.stopTalking) { @@ -235,18 +237,22 @@ ThreadManager.prototype.removeTerminatedProcesses = function () { } if (proc.topBlock instanceof ReporterBlockMorph) { - if (proc.homeContext.inputs[0] instanceof List) { - proc.topBlock.showBubble( - new ListWatcherMorph( - proc.homeContext.inputs[0] - ), - proc.exportResult - ); + if (proc.onComplete instanceof Function) { + proc.onComplete(proc.homeContext.inputs[0]); } else { - proc.topBlock.showBubble( - proc.homeContext.inputs[0], - proc.exportResult - ); + if (proc.homeContext.inputs[0] instanceof List) { + proc.topBlock.showBubble( + new ListWatcherMorph( + proc.homeContext.inputs[0] + ), + proc.exportResult + ); + } else { + proc.topBlock.showBubble( + proc.homeContext.inputs[0], + proc.exportResult + ); + } } } } else { @@ -295,9 +301,9 @@ ThreadManager.prototype.findProcess = function (block) { are children receiver object (sprite) to which the process applies, cached from the top block - context the Context describing the current state + context the Context describing the current state of this process - homeContext stores information relevant to the whole process, + homeContext stores information relevant to the whole process, i.e. its receiver, result etc. isPaused boolean indicating whether to pause readyToYield boolean indicating whether to yield control to @@ -305,15 +311,17 @@ ThreadManager.prototype.findProcess = function (block) { readyToTerminate boolean indicating whether the stop method has been called isDead boolean indicating a terminated clone process - timeout msecs after which to force yield - lastYield msecs when the process last yielded - errorFlag boolean indicating whether an error was encountered + timeout msecs after which to force yield + lastYield msecs when the process last yielded + errorFlag boolean indicating whether an error was encountered prompter active instance of StagePrompterMorph httpRequest active instance of an HttpRequest or null pauseOffset msecs between the start of an interpolated operation and when the process was paused exportResult boolean flag indicating whether a picture of the top block along with the result bubble shoud be exported + onComplete an optional callback function to be executed when + the process is done */ Process.prototype = {}; @@ -321,7 +329,7 @@ Process.prototype.contructor = Process; Process.prototype.timeout = 500; // msecs after which to force yield Process.prototype.isCatchingErrors = true; -function Process(topBlock) { +function Process(topBlock, onComplete) { this.topBlock = topBlock || null; this.readyToYield = false; @@ -338,6 +346,7 @@ function Process(topBlock) { this.pauseOffset = null; this.frameCount = 0; this.exportResult = false; + this.onComplete = onComplete || null; if (topBlock) { this.homeContext.receiver = topBlock.receiver(); @@ -435,7 +444,6 @@ Process.prototype.pauseStep = function () { Process.prototype.evaluateContext = function () { var exp = this.context.expression; - this.frameCount += 1; if (exp instanceof Array) { return this.evaluateSequence(exp); @@ -586,7 +594,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 +604,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 +612,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 +629,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 +682,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 +765,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 +797,7 @@ Process.prototype.evaluate = function ( var outer = new Context(null, null, context.outerContext), runnable, extra, + exit, parms = args.asArray(), i, value; @@ -798,23 +813,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) { @@ -849,8 +858,9 @@ Process.prototype.evaluate = function ( } else if (context.emptySlots !== 1) { throw new Error( - 'expecting ' + context.emptySlots + ' input(s), ' - + 'but getting ' + parms.length + localize('expecting') + ' ' + context.emptySlots + ' ' + + localize('input(s), but getting') + ' ' + + parms.length ); } } @@ -858,6 +868,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; + } } }; @@ -867,6 +889,9 @@ Process.prototype.fork = function (context, args) { 'continuations cannot be forked' ); } + if (!(context instanceof Context)) { + throw new Error('expecting a ring but getting ' + context); + } var outer = new Context(null, null, context.outerContext), runnable = new Context(null, @@ -879,8 +904,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) { @@ -915,8 +938,9 @@ Process.prototype.fork = function (context, args) { } else if (context.emptySlots !== 1) { throw new Error( - 'expecting ' + context.emptySlots + ' input(s), ' - + 'but getting ' + parms.length + localize('expecting') + ' ' + context.emptySlots + ' ' + + localize('input(s), but getting') + ' ' + + parms.length ); } } @@ -933,68 +957,42 @@ 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) { + if (this.context.expression.partOfCustomCommand) { + return this.doStopBlock(); + } + while (this.context && this.context.expression !== 'exitReporter') { if (this.context.expression === 'doStopWarping') { this.doStopWarping(); } else { this.popContext(); } } - if (this.context && this.context.isImplicitLambda) { + return value; +}; + +Process.prototype.doStopBlock = function () { + while (this.context && !this.context.isCustomBlock) { 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(); -}; - -// 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 +1015,7 @@ Process.prototype.evaluateCustomBlock = function () { args = new List(this.context.inputs), parms = args.asArray(), runnable, + exit, extra, i, value, @@ -1024,7 +1023,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,16 +1031,12 @@ Process.prototype.evaluateCustomBlock = function () { this.context.parentContext, context.expression, outer, - outer.receiver, - true // is custom block + outer.receiver ); + runnable.isCustomBlock = true; extra = new Context(runnable, 'doYield'); - this.context.parentContext = extra; - runnable.isLambda = true; - runnable.isCustomBlock = true; - // passing parameters if any were passed if (parms.length > 0) { @@ -1063,6 +1058,21 @@ Process.prototype.evaluateCustomBlock = function () { } if (runnable.expression instanceof CommandBlockMorph) { + // 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; + } else { // mark all REPORT blocks as being part of a custom command + runnable.expression.allReportBlocks().forEach(function (rb) { + rb.partOfCustomCommand = true; + }); + } runnable.expression = runnable.expression.blockSequence(); } }; @@ -1148,7 +1158,7 @@ Process.prototype.doShowVar = function (varName) { if (isGlobal || target.owner) { label = name; } else { - label = name + ' (temporary)'; + label = name + ' ' + localize('(temporary)'); } watcher = new WatcherMorph( label, @@ -1308,16 +1318,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 +1333,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 +1348,6 @@ Process.prototype.doIfElse = function () { } } if (this.context) { - this.context.isLambda = isLambda; - this.context.isImplicitLambda = isImplicitLambda; this.context.isCustomBlock = isCustomBlock; } @@ -1420,8 +1422,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 +1438,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 +1518,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(); }; @@ -2142,7 +2128,7 @@ Process.prototype.reportTextSplit = function (string, delimiter) { str = (string || '').toString(); switch (this.inputOption(delimiter)) { case 'line': - // Entirely Unicode Compliant Line Splitting (Platform independent) + // Unicode Compliant Line Splitting (Platform independent) // http://www.unicode.org/reports/tr18/#Line_Boundaries del = /\r\n|[\n\v\f\r\x85\u2028\u2029]/; break; @@ -2779,8 +2765,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 */ @@ -2805,22 +2789,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 () { @@ -2876,6 +2856,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; @@ -3007,9 +2990,9 @@ VariableFrame.prototype.find = function (name) { var frame = this.silentFind(name); if (frame) {return frame; } throw new Error( - 'a variable of name \'' + localize('a variable of name \'') + name - + '\'\ndoes not exist in this context' + + localize('\'\ndoes not exist in this context') ); }; @@ -3074,9 +3057,9 @@ VariableFrame.prototype.getVar = function (name) { return ''; } throw new Error( - 'a variable of name \'' + localize('a variable of name \'') + name - + '\'\ndoes not exist in this context' + + localize('\'\ndoes not exist in this context') ); }; |
