diff options
Diffstat (limited to 'threads.js')
| -rw-r--r-- | threads.js | 292 |
1 files changed, 164 insertions, 128 deletions
@@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2014-November-24'; +modules.threads = '2015-June-25'; var ThreadManager; var Process; @@ -159,9 +159,11 @@ ThreadManager.prototype.startProcess = function ( active.stop(); this.removeTerminatedProcesses(); } - top.addHighlight(); newProc = new Process(block.topBlock(), callback); newProc.exportResult = exportResult; + if (!newProc.homeContext.receiver.isClone) { + top.addHighlight(); + } this.processes.push(newProc); return newProc; }; @@ -218,11 +220,10 @@ ThreadManager.prototype.resumeAll = function (stage) { }; ThreadManager.prototype.step = function () { -/* - run each process until it gives up control, skipping processes - for sprites that are currently picked up, then filter out any - processes that have been terminated -*/ + // run each process until it gives up control, skipping processes + // for sprites that are currently picked up, then filter out any + // processes that have been terminated + this.processes.forEach(function (proc) { if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) { proc.runStep(); @@ -235,7 +236,7 @@ ThreadManager.prototype.removeTerminatedProcesses = function () { // and un-highlight their scripts var remaining = []; this.processes.forEach(function (proc) { - if (!proc.isRunning() && !proc.errorFlag && !proc.isDead) { + if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) { if (proc.topBlock instanceof BlockMorph) { proc.topBlock.removeHighlight(); } @@ -338,7 +339,7 @@ ThreadManager.prototype.findProcess = function (block) { */ Process.prototype = {}; -Process.prototype.contructor = Process; +Process.prototype.constructor = Process; Process.prototype.timeout = 500; // msecs after which to force yield Process.prototype.isCatchingErrors = true; @@ -384,13 +385,13 @@ Process.prototype.isRunning = function () { // Process entry points Process.prototype.runStep = function () { -/* - a step is an an uninterruptable 'atom', it can consist - of several contexts, even of several blocks -*/ + // a step is an an uninterruptable 'atom', it can consist + // of several contexts, even of several blocks + if (this.isPaused) { // allow pausing in between atomic steps: return this.pauseStep(); } + this.readyToYield = false; while (!this.readyToYield && this.context @@ -459,6 +460,9 @@ Process.prototype.pauseStep = function () { Process.prototype.evaluateContext = function () { var exp = this.context.expression; this.frameCount += 1; + if (this.context.tag === 'exit') { + this.expectReport(); + } if (exp instanceof Array) { return this.evaluateSequence(exp); } @@ -482,7 +486,7 @@ Process.prototype.evaluateContext = function () { Process.prototype.evaluateBlock = function (block, argCount) { // check for special forms - if (contains(['reportOr', 'reportAnd'], block.selector)) { + if (contains(['reportOr', 'reportAnd', 'doReport'], block.selector)) { return this[block.selector](block); } @@ -548,33 +552,34 @@ Process.prototype.reportAnd = function (block) { } }; -/* - tail-call-elimination for reporters - ----------------------------------- - currently under construction, commented out for now - to activate add 'doReport' to the list of special forms - selectors in evaluateBlock() and comment out / remove - the current 'doReport' primitive in the "return" - section of the code - Process.prototype.doReport = function (block) { - -// if (this.context.expression.partOfCustomCommand) { -// return this.doStopCustomBlock(); -// } - var outer = this.context.outerContext; - while (this.context && this.context.expression !== 'exitReporter') { - if (this.context.expression === 'doStopWarping') { - this.doStopWarping(); - } else { - this.popContext(); + if (this.context.expression.partOfCustomCommand) { + this.doStopCustomBlock(); + this.popContext(); + } else { + while (this.context && this.context.tag !== 'exit') { + if (this.context.expression === 'doStopWarping') { + this.doStopWarping(); + } else { + this.popContext(); + } + } + if (this.context) { + if (this.context.expression === 'expectReport') { + // pop off inserted top-level exit context + this.popContext(); + } else { + // un-tag and preserve original caller + this.context.tag = null; + } } } - this.popContext(); + // in any case evaluate (and ignore) + // the input, because it could be + // and HTTP Request for a hardware extension this.pushContext(block.inputs()[0], outer); }; -*/ // Process: Non-Block evaluation @@ -724,9 +729,8 @@ Process.prototype.doYield = function () { } }; -Process.prototype.exitReporter = function () { - // catch-tag for REPORT and STOP BLOCK primitives - this.handleError(new Error("missing 'report' statement in reporter")); +Process.prototype.expectReport = function () { + this.handleError(new Error("reporter didn't report")); }; // Process Exception Handling @@ -831,9 +835,9 @@ Process.prototype.evaluate = function ( } var outer = new Context(null, null, context.outerContext), - runnable, - extra, + caller = this.context.parentContext, exit, + runnable, parms = args.asArray(), i, value; @@ -847,17 +851,11 @@ Process.prototype.evaluate = function ( outer, context.receiver ); - 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) + this.context.parentContext = runnable; if (context.expression instanceof ReporterBlockMorph) { - this.context.parentContext = extra; - } else { - this.context.parentContext = runnable; + // auto-"warp" nested reporters + this.readyToYield = (Date.now() - this.lastYield > this.timeout); } // assign parameters if any were passed @@ -904,17 +902,22 @@ 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; + if (caller) { + // tag caller, so "report" can catch it later + caller.tag = 'exit'; + } else { + // top-level context, insert a tagged exit context + // which "report" can catch later + exit = new Context( + runnable.parentContext, + 'expectReport', + outer, + outer.receiver + ); + exit.tag = 'exit'; + runnable.parentContext = exit; + } } } }; @@ -993,21 +996,7 @@ 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.doStopCustomBlock(); - } - while (this.context && this.context.expression !== 'exitReporter') { - if (this.context.expression === 'doStopWarping') { - this.doStopWarping(); - } else { - this.popContext(); - } - } - return value; -}; +// Process stopping blocks primitives Process.prototype.doStopBlock = function () { var target = this.context.expression.exitTag; @@ -1073,7 +1062,6 @@ Process.prototype.evaluateCustomBlock = function () { parms = args.asArray(), runnable, exit, - extra, i, value, outer; @@ -1092,8 +1080,7 @@ Process.prototype.evaluateCustomBlock = function () { outer.receiver ); runnable.isCustomBlock = true; - extra = new Context(runnable, 'doYield'); - this.context.parentContext = extra; + this.context.parentContext = runnable; // passing parameters if any were passed if (parms.length > 0) { @@ -1115,32 +1102,43 @@ 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') { + // tag return target + if (this.context.expression.definition.type !== 'command') { + if (caller) { + // tag caller, so "report" can catch it later + caller.tag = 'exit'; + } else { + // top-level context, insert a tagged exit context + // which "report" can catch later exit = new Context( runnable.parentContext, - 'exitReporter', + 'expectReport', outer, outer.receiver ); + exit.tag = 'exit'; runnable.parentContext = exit; - } 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); + } + // auto-"warp" nested reporters + this.readyToYield = (Date.now() - this.lastYield > this.timeout); + } 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 the caller with the current procedure count, so - // "stop this block" blocks can catch it, but only - // if the caller hasn't been tagged already - if (caller && !caller.tag) { - caller.tag = this.procedureCount; - } + // tag the caller with the current procedure count, so + // "stop this block" blocks can catch it, but only + // if the caller hasn't been tagged already + if (caller && !caller.tag) { + caller.tag = this.procedureCount; + } + // yield commands unless explicitly "warped" + if (!this.isAtomic) { + this.readyToYield = true; } - runnable.expression = runnable.expression.blockSequence(); } + runnable.expression = runnable.expression.blockSequence(); }; // Process variables primitives @@ -1155,13 +1153,16 @@ Process.prototype.doDeclareVariables = function (varNames) { Process.prototype.doSetVar = function (varName, value) { var varFrame = this.context.variables, name = varName; - if (name instanceof Context) { if (name.expression.selector === 'reportGetVar') { - name = name.expression.blockSpec; + name.variables.setVar( + name.expression.blockSpec, + value + ); + return; } } - varFrame.setVar(name, value); + varFrame.setVar(name, value, this.blockReceiver()); }; Process.prototype.doChangeVar = function (varName, value) { @@ -1170,10 +1171,14 @@ Process.prototype.doChangeVar = function (varName, value) { if (name instanceof Context) { if (name.expression.selector === 'reportGetVar') { - name = name.expression.blockSpec; + name.variables.changeVar( + name.expression.blockSpec, + value + ); + return; } } - varFrame.changeVar(name, value); + varFrame.changeVar(name, value, this.blockReceiver()); }; Process.prototype.reportGetVar = function () { @@ -1325,6 +1330,8 @@ Process.prototype.doDeleteFromList = function (index, list) { } if (this.inputOption(index) === 'last') { idx = list.length(); + } else if (isNaN(+this.inputOption(index))) { + return null; } list.remove(idx); }; @@ -1683,6 +1690,27 @@ Process.prototype.reportMap = function (reporter, list) { } }; +Process.prototype.doForEach = function (upvar, list, script) { + // perform a script for each element of a list, assigning the + // current iteration's element to a variable with the name + // specified in the "upvar" parameter, so it can be referenced + // within the script. Uses the context's - unused - fourth + // element as temporary storage for the current list index + + if (isNil(this.context.inputs[3])) {this.context.inputs[3] = 1; } + var index = this.context.inputs[3]; + this.context.outerContext.variables.addVar(upvar); + this.context.outerContext.variables.setVar( + upvar, + list.at(index) + ); + if (index > list.length()) {return; } + this.context.inputs[3] += 1; + this.pushContext('doYield'); + this.pushContext(); + this.evaluate(script, new List(), true); +}; + // Process interpolated primitives Process.prototype.doWait = function (secs) { @@ -1788,6 +1816,7 @@ Process.prototype.doAsk = function (data) { isStage = this.blockReceiver() instanceof StageMorph, activePrompter; + stage.keysPressed = {}; if (!this.prompter) { activePrompter = detect( stage.children, @@ -1907,7 +1936,7 @@ Process.prototype.reportTypeOf = function (thing) { if (thing === true || (thing === false)) { return 'Boolean'; } - if (!isNaN(parseFloat(thing))) { + if (!isNaN(+thing)) { return 'number'; } if (isString(thing)) { @@ -2093,14 +2122,14 @@ Process.prototype.reportMonadic = function (fname, n) { case 'ln': result = Math.log(x); break; - case 'log': - result = 0; + case 'log': // base 10 + result = Math.log(x) / Math.LN10; break; case 'e^': result = Math.exp(x); break; case '10^': - result = 0; + result = Math.pow(10, x); break; default: nop(); @@ -2156,6 +2185,9 @@ Process.prototype.reportJoinWords = function (aList) { // Process string ops Process.prototype.reportLetter = function (idx, string) { + if (string instanceof List) { // catch a common user error + return ''; + } var i = +(idx || 0), str = (string || '').toString(); return str[i - 1] || ''; @@ -2372,6 +2404,7 @@ Process.prototype.objectTouchingObject = function (thisObj, name) { var myself = this, those, stage, + box, mouse; if (this.inputOption(name) === 'mouse-pointer') { @@ -2383,9 +2416,14 @@ Process.prototype.objectTouchingObject = function (thisObj, name) { } else { stage = thisObj.parentThatIsA(StageMorph); if (stage) { - if (this.inputOption(name) === 'edge' && - !stage.bounds.containsRectangle(thisObj.bounds)) { - return true; + if (this.inputOption(name) === 'edge') { + box = thisObj.bounds; + if (!thisObj.costume && thisObj.penBounds) { + box = thisObj.penBounds.translateBy(thisObj.position()); + } + if (!stage.bounds.containsRectangle(box)) { + return true; + } } if (this.inputOption(name) === 'pen trails' && thisObj.isTouching(stage.penTrailsMorph())) { @@ -2523,6 +2561,7 @@ Process.prototype.reportContextFor = function (context, otherObj) { if (result.outerContext) { result.outerContext = copy(result.outerContext); result.outerContext.receiver = otherObj; + result.outerContext.variables.parentFrame = otherObj.variables; } return result; }; @@ -2601,31 +2640,30 @@ Process.prototype.reportTimer = function () { }; // Process Dates and times in Snap -// Map block options to built-in functions -var dateMap = { - 'year' : 'getFullYear', - 'month' : 'getMonth', - 'date': 'getDate', - 'day of week' : 'getDay', - 'hour' : 'getHours', - 'minute' : 'getMinutes', - 'second' : 'getSeconds', - 'time in milliseconds' : 'getTime' -}; - Process.prototype.reportDate = function (datefn) { - var inputFn = this.inputOption(datefn), - currDate = new Date(), - func = dateMap[inputFn], - result = currDate[func](); + var currDate, func, result, + inputFn = this.inputOption(datefn), + // Map block options to built-in functions + dateMap = { + 'year' : 'getFullYear', + 'month' : 'getMonth', + 'date': 'getDate', + 'day of week' : 'getDay', + 'hour' : 'getHours', + 'minute' : 'getMinutes', + 'second' : 'getSeconds', + 'time in milliseconds' : 'getTime' + }; if (!dateMap[inputFn]) { return ''; } + currDate = new Date(); + func = dateMap[inputFn]; + result = currDate[func](); // Show months as 1-12 and days as 1-7 if (inputFn === 'month' || inputFn === 'day of week') { result += 1; } - return result; }; @@ -2923,12 +2961,10 @@ Context.prototype.continuation = function () { } else if (this.parentContext) { cont = this.parentContext; } else { - return new Context(null, 'doStop'); - } - if (cont.expression === 'exitReporter') { - return cont.continuation(); + return new Context(null, 'doYield'); } cont = cont.copyForContinuation(); + cont.tag = null; cont.isContinuation = true; return cont; }; |
