summaryrefslogtreecommitdiff
path: root/threads.js
diff options
context:
space:
mode:
Diffstat (limited to 'threads.js')
-rw-r--r--threads.js292
1 files changed, 164 insertions, 128 deletions
diff --git a/threads.js b/threads.js
index a6b824b..af899dc 100644
--- a/threads.js
+++ b/threads.js
@@ -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;
};