diff --git a/snap_stable.html b/snap_stable.html
new file mode 100755
index 00000000..0cc006b0
--- /dev/null
+++ b/snap_stable.html
@@ -0,0 +1,44 @@
+
+
+
+
+ Snap stable! Build Your Own Blocks 6.0.0 - beta -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/threads.js b/src/threads.js
index 9d6606c5..ef28d022 100644
--- a/src/threads.js
+++ b/src/threads.js
@@ -3540,21 +3540,19 @@ Process.prototype.reportTypeOf = function (thing) {
// Process math primtives - hyper-dyadic
-Process.prototype.hyperDyadic = function (baseOp, a, b, a_info, b_info) {
+Process.prototype.hyperDyadic = function (baseOp, a, b) {
// enable dyadic operations to be performed on lists and tables
- var len, i, result;
+ var len, a_info, b_info, i, result;
if (this.enableHyperOps) {
- a_info = a_info || this.examine(a);
- b_info = b_info || this.examine(b);
+ a_info = this.examine(a);
+ b_info = this.examine(b);
if (a_info.isScalar && b_info.isScalar &&
(a_info.rank !== b_info.rank)) {
// keep the shape of the higher rank
return this.hyperZip(
baseOp,
a_info.rank > b_info.rank ? a : a_info.scalar,
- b_info.rank > a_info.rank ? b : b_info.scalar,
- a_info.rank > b_info.rank ? a_info : null,
- b_info.rank > a_info.rank ? b_info : null
+ b_info.rank > a_info.rank ? b : b_info.scalar
);
}
if (a_info.rank > 1) {
@@ -3562,22 +3560,10 @@ Process.prototype.hyperDyadic = function (baseOp, a, b, a_info, b_info) {
if (a.length() !== b.length()) {
// test for special cased scalars in single-item lists
if (a_info.isScalar) {
- return this.hyperDyadic(
- baseOp,
- a_info.scalar,
- b,
- null,
- b_info
- );
+ return this.hyperDyadic(baseOp, a_info.scalar, b);
}
if (b_info.isScalar) {
- return this.hyperDyadic(
- baseOp,
- a,
- b_info.scalar,
- a_info,
- null
- );
+ return this.hyperDyadic(baseOp, a, b_info.scalar);
}
}
// zip both arguments ignoring out-of-bounds indices
@@ -3591,71 +3577,35 @@ Process.prototype.hyperDyadic = function (baseOp, a, b, a_info, b_info) {
return new List(result);
}
if (a_info.isScalar) {
- return this.hyperZip(
- baseOp,
- a_info.scalar,
- b,
- null,
- b_info
- );
+ return this.hyperZip(baseOp, a_info.scalar, b);
}
- return a.map(each => this.hyperDyadic(
- baseOp,
- each,
- b,
- null,
- b_info
- ));
+ return a.map(each => this.hyperDyadic(baseOp, each, b));
}
if (b_info.rank > 1) {
if (b_info.isScalar) {
- return this.hyperZip(
- baseOp,
- a,
- b_info.scalar,
- a_info,
- null
- );
+ return this.hyperZip(baseOp, a, b_info.scalar);
}
- return b.map(each => this.hyperDyadic(
- baseOp,
- a,
- each,
- a_info,
- null
- ));
+ return b.map(each => this.hyperDyadic(baseOp, a, each));
}
- return this.hyperZip(baseOp, a, b, a_info, b_info);
+ return this.hyperZip(baseOp, a, b);
}
return baseOp(a, b);
};
-Process.prototype.hyperZip = function (baseOp, a, b, a_info, b_info) {
+Process.prototype.hyperZip = function (baseOp, a, b) {
// enable dyadic operations to be performed on lists and tables
- var len, i, result;
- a_info = a_info || this.examine(a);
- b_info = b_info || this.examine(b);
+ var len, i, result,
+ a_info = this.examine(a),
+ b_info = this.examine(b);
if (a instanceof List) {
if (b instanceof List) {
if (a.length() !== b.length()) {
// test for special cased scalars in single-item lists
if (a_info.isScalar) {
- return this.hyperZip(
- baseOp,
- a_info.scalar,
- b,
- null,
- b_info
- );
+ return this.hyperZip(baseOp, a_info.scalar, b);
}
if (b_info.isScalar) {
- return this.hyperZip(
- baseOp,
- a,
- b_info.scalar,
- a_info,
- null
- );
+ return this.hyperZip(baseOp, a, b_info.scalar);
}
}
// zip both arguments ignoring out-of-bounds indices
@@ -3668,22 +3618,10 @@ Process.prototype.hyperZip = function (baseOp, a, b, a_info, b_info) {
}
return new List(result);
}
- return a.map(each => this.hyperZip(
- baseOp,
- each,
- b,
- null,
- b_info
- ));
+ return a.map(each => this.hyperZip(baseOp, each, b));
}
if (b instanceof List) {
- return b.map(each => this.hyperZip(
- baseOp,
- a,
- each,
- a_info,
- null
- ));
+ return b.map(each => this.hyperZip(baseOp, a, each));
}
return baseOp(a, b);
};
diff --git a/src/threads_uncached.js b/src/threads_uncached.js
deleted file mode 100644
index ef28d022..00000000
--- a/src/threads_uncached.js
+++ /dev/null
@@ -1,6756 +0,0 @@
-/*
-
- threads.js
-
- a tail call optimized blocks-based programming language interpreter
- based on morphic.js and blocks.js
- inspired by Scratch, Scheme and Squeak
-
- written by Jens Mönig
- jens@moenig.org
-
- Copyright (C) 2020 by Jens Mönig
-
- This file is part of Snap!.
-
- Snap! is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of
- the License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
-
- prerequisites:
- --------------
- needs blocks.js and objects.js
-
-
- toc
- ---
- the following list shows the order in which all constructors are
- defined. Use this list to locate code in this document:
-
- ThreadManager
- Process
- Context
- Variable
- VariableFrame
- JSCompiler
-
- credits
- -------
- John Maloney and Dave Feinberg designed the original Scratch evaluator
- Ivan Motyashov contributed initial porting from Squeak
-
-*/
-
-// Global stuff ////////////////////////////////////////////////////////
-
-/*global ArgMorph, BlockMorph, CommandBlockMorph, CommandSlotMorph, Morph, ZERO,
-MultiArgMorph, Point, ReporterBlockMorph, SyntaxElementMorph, contains, Costume,
-degrees, detect, nop, radians, ReporterSlotMorph, CSlotMorph, RingMorph, Sound,
-IDE_Morph, ArgLabelMorph, localize, XML_Element, hex_sha512, TableDialogMorph,
-StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, Map,
-isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, Color,
-TableFrameMorph, ColorSlotMorph, isSnapObject, newCanvas, Symbol, SVG_Costume*/
-
-modules.threads = '2020-June-22';
-
-var ThreadManager;
-var Process;
-var Context;
-var Variable;
-var VariableFrame;
-var JSCompiler;
-
-function snapEquals(a, b) {
- if (a instanceof List || (b instanceof List)) {
- if (a instanceof List && (b instanceof List)) {
- return a.equalTo(b);
- }
- return false;
- }
-
- var x = +a,
- y = +b,
- i,
- specials = [true, false, ''];
-
- // "zum Schneckengang verdorben, was Adlerflug geworden wäre"
- // collecting edge-cases that somebody complained about
- // on Github. Folks, take it easy and keep it fun, okay?
- // Shit like this is patently ugly and slows Snap down. Tnx!
- for (i = 9; i <= 13; i += 1) {
- specials.push(String.fromCharCode(i));
- }
- specials.push(String.fromCharCode(160));
-
- // check for special values before coercing to numbers
- if (isNaN(x) || isNaN(y) ||
- [a, b].some(any => contains(specials, any) ||
- (isString(any) && (any.indexOf(' ') > -1)))
- ) {
- x = a;
- y = b;
- }
-
- // handle text comparison case-insensitive.
- if (isString(x) && isString(y)) {
- return x.toLowerCase() === y.toLowerCase();
- }
-
- return x === y;
-}
-
-function invoke(
- action, // a BlockMorph or a Context, a reified ("ringified") block
- contextArgs, // optional List of arguments for the context, or null
- receiver, // sprite or environment, optional for contexts
- timeout, // msecs
- timeoutErrorMsg, // string
- suppressErrors, // bool
- callerProcess, // optional for JS-functions
- returnContext // bool
-) {
- // execute the given block or context synchronously without yielding.
- // Apply context (not a block) to a list of optional arguments.
- // Receiver (sprite, stage or environment), timeout etc. are optional.
- // If a timeout (in milliseconds) is specified, abort execution
- // after the timeout has been reached and throw an error.
- // SuppressErrors (bool) if non-timeout errors occurring in the
- // block are handled elsewhere.
- // This is highly experimental.
- // Caution: Kids, do not try this at home!
- // Use ThreadManager::startProcess with a callback instead
-
- var proc = new Process(),
- deadline = (timeout ? Date.now() + timeout : null);
-
- if (action instanceof Context) {
- if (receiver) { // optional
- action = proc.reportContextFor(receiver);
- }
- proc.initializeFor(action, contextArgs || new List());
- } else if (action instanceof BlockMorph) {
- proc.topBlock = action;
- if (receiver) {
- proc.homeContext = new Context();
- proc.homeContext.receiver = receiver;
- if (receiver.variables) {
- proc.homeContext.variables.parentFrame = receiver.variables;
- }
- } else {
- throw new Error('expecting a receiver but getting ' + receiver);
- }
- proc.context = new Context(
- null,
- action.blockSequence(),
- proc.homeContext
- );
- } else if (action.evaluate) {
- return action.evaluate();
- } else if (action instanceof Function) {
- return action.apply(
- receiver,
- contextArgs.asArray().concat(callerProcess)
- );
- } else {
- throw new Error('expecting a block or ring but getting ' + action);
- }
- if (suppressErrors) {
- proc.isCatchingErrors = false;
- }
- while (proc.isRunning()) {
- if (deadline && (Date.now() > deadline)) {
- throw (new Error(
- localize(
- timeoutErrorMsg ||
- "a synchronous Snap! script has timed out")
- )
- );
- }
- proc.runStep(deadline);
- }
- return returnContext ? proc.homeContext : proc.homeContext.inputs[0];
-}
-
-// ThreadManager ///////////////////////////////////////////////////////
-
-function ThreadManager() {
- this.processes = [];
- this.wantsToPause = false; // single stepping support
-}
-
-ThreadManager.prototype.pauseCustomHatBlocks = false;
-
-ThreadManager.prototype.toggleProcess = function (block, receiver) {
- var active = this.findProcess(block, receiver);
- if (active) {
- active.stop();
- } else {
- return this.startProcess(block, receiver, null, null, null, true);
- }
-};
-
-ThreadManager.prototype.startProcess = function (
- block,
- receiver,
- isThreadSafe,
- exportResult, // bool
- callback,
- isClicked,
- rightAway,
- atomic, // special option used (only) for "onStop" scripts
- variables // optional variable frame, used for WHEN hats
-) {
- var top = block.topBlock(),
- active = this.findProcess(top, receiver),
- glow,
- newProc;
- if (active) {
- if (isThreadSafe) {
- return active;
- }
- active.stop();
- active.canBroadcast = true; // broadcasts to fire despite reentrancy
- this.removeTerminatedProcesses();
- }
- newProc = new Process(top, receiver, callback, isClicked);
- newProc.exportResult = exportResult;
- newProc.isClicked = isClicked || false;
- newProc.isAtomic = atomic || false;
-
- // in case an optional variable frame has been passed,
- // copy it into the new outer context.
- // Relevance: When a predicate inside a generic WHEN hat block
- // publishes an upvar, this code makes the upvar accessible
- // to the script attached to the WHEN hat
- if (variables instanceof VariableFrame) {
- Object.keys(variables.vars).forEach(vName =>
- newProc.context.outerContext.variables.vars[vName] =
- variables.vars[vName]
- );
- }
-
- // show a highlight around the running stack
- // if there are more than one active processes
- // for a block, display the thread count
- // next to it
- glow = top.getHighlight();
- if (glow) {
- glow.threadCount = this.processesForBlock(top).length + 1;
- glow.updateReadout();
- } else {
- top.addHighlight();
- }
-
- this.processes.push(newProc);
- if (rightAway) {
- newProc.runStep();
- }
- return newProc;
-};
-
-ThreadManager.prototype.stopAll = function (excpt) {
- // excpt is optional
- this.processes.forEach(proc => {
- if (proc !== excpt) {
- proc.stop();
- }
- });
-};
-
-ThreadManager.prototype.stopAllForReceiver = function (rcvr, excpt) {
- // excpt is optional
- this.processes.forEach(proc => {
- if (proc.homeContext.receiver === rcvr && proc !== excpt) {
- proc.stop();
- if (rcvr.isTemporary) {
- proc.isDead = true;
- }
- }
- });
-};
-
-ThreadManager.prototype.stopAllForBlock = function (aTopBlock) {
- this.processesForBlock(aTopBlock, true).forEach(proc =>
- proc.stop()
- );
-};
-
-ThreadManager.prototype.stopProcess = function (block, receiver) {
- var active = this.findProcess(block, receiver);
- if (active) {
- active.stop();
- }
-};
-
-ThreadManager.prototype.pauseAll = function (stage) {
- this.processes.forEach(proc => proc.pause());
- if (stage) {
- stage.pauseAllActiveSounds();
- }
-};
-
-ThreadManager.prototype.isPaused = function () {
- return detect(
- this.processes,
- proc => proc.isPaused
- ) !== null;
-};
-
-ThreadManager.prototype.resumeAll = function (stage) {
- this.processes.forEach(proc => proc.resume());
- if (stage) {
- stage.resumeAllActiveSounds();
- }
-};
-
-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
-
- var isInterrupted;
- if (Process.prototype.enableSingleStepping) {
- this.processes.forEach(proc => {
- if (proc.isInterrupted) {
- proc.runStep();
- isInterrupted = true;
- } else {
- proc.lastYield = Date.now();
- }
- });
- this.wantsToPause = (Process.prototype.flashTime > 0.5);
- if (isInterrupted) {
- if (this.wantsToPause) {
- this.pauseAll();
- }
- return;
- }
- }
-
- this.processes.forEach(proc => {
- if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) {
- proc.runStep();
- }
- });
- this.removeTerminatedProcesses();
-};
-
-ThreadManager.prototype.removeTerminatedProcesses = function () {
- // and un-highlight their scripts
- var remaining = [],
- count;
- this.processes.forEach(proc => {
- var result,
- glow;
- if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) {
- if (proc.topBlock instanceof BlockMorph) {
- proc.unflash();
- // adjust the thread count indicator, if any
- count = this.processesForBlock(proc.topBlock).length;
- if (count) {
- glow = proc.topBlock.getHighlight() ||
- proc.topBlock.addHighlight();
- glow.threadCount = count;
- glow.updateReadout();
- } else {
- proc.topBlock.removeHighlight();
- }
- }
- if (proc.prompter) {
- proc.prompter.destroy();
- if (proc.homeContext.receiver.stopTalking) {
- proc.homeContext.receiver.stopTalking();
- }
- }
- if (proc.topBlock instanceof ReporterBlockMorph ||
- proc.isShowingResult) {
- result = proc.homeContext.inputs[0];
- if (proc.onComplete instanceof Function) {
- proc.onComplete(result);
- } else {
- if (result instanceof List) {
- proc.topBlock.showBubble(
- result.isTable() ?
- new TableFrameMorph(
- new TableMorph(result, 10)
- )
- : new ListWatcherMorph(result),
- proc.exportResult,
- proc.receiver
- );
- } else {
- proc.topBlock.showBubble(
- result,
- proc.exportResult,
- proc.receiver
- );
- }
- }
- } else if (proc.onComplete instanceof Function) {
- proc.onComplete();
- }
- } else {
- remaining.push(proc);
- }
- });
- this.processes = remaining;
-};
-
-ThreadManager.prototype.findProcess = function (block, receiver) {
- var top = block.topBlock();
- return detect(
- this.processes,
- each => each.topBlock === top && (each.receiver === receiver)
- );
-};
-
-ThreadManager.prototype.processesForBlock = function (block, only) {
- var top = only ? block : block.topBlock();
- return this.processes.filter(each =>
- each.topBlock === top &&
- each.isRunning() &&
- !each.isDead
- );
-};
-
-ThreadManager.prototype.doWhen = function (block, receiver, stopIt) {
- if (this.pauseCustomHatBlocks) {return; }
- if ((!block) || this.findProcess(block, receiver)) {
- return;
- }
- var pred = block.inputs()[0], world, test;
- if (block.removeHighlight()) {
- world = block.world();
- if (world) {
- world.hand.destroyTemporaries();
- }
- }
- if (stopIt) {return; }
- try {
- test = invoke(
- pred,
- null,
- receiver,
- 50, // timeout in msecs
- 'the predicate takes\ntoo long for a\ncustom hat block',
- true, // suppress errors => handle them right here instead
- null, // caller process for JS-functions
- true // return the whole home context instead of just he result
- );
- } catch (error) {
- block.addErrorHighlight();
- block.showBubble(
- error.name
- + '\n'
- + error.message
- );
- }
- // since we're asking for the whole context instead of just the result
- // of the computation, we need to look at the result-context's first
- // input to find out whether the condition is met
- if (test === true || (test && test.inputs && test.inputs[0] === true)) {
- this.startProcess(
- block,
- receiver,
- null, // isThreadSafe
- null, // exportResult
- null, // callback
- null, // isClicked
- true, // rightAway
- null, // atomic
- test.variables // make the test-context's variables available
- );
- }
-};
-
-ThreadManager.prototype.toggleSingleStepping = function () {
- Process.prototype.enableSingleStepping =
- !Process.prototype.enableSingleStepping;
- if (!Process.prototype.enableSingleStepping) {
- this.processes.forEach(proc => {
- if (!proc.isPaused) {
- proc.unflash();
- }
- });
- }
-};
-
-// Process /////////////////////////////////////////////////////////////
-
-/*
- A Process is what brings a stack of blocks to life. The process
- keeps track of which block to run next, evaluates block arguments,
- handles control structures, and so forth.
-
- The ThreadManager is the (passive) scheduler, telling each process
- when to run by calling its runStep() method. The runStep() method
- will execute some number of blocks, then voluntarily yield control
- so that the ThreadManager can run another process.
-
- The Scratch etiquette is that a process should yield control at the
- end of every loop iteration, and while it is running a timed command
- (e.g. "wait 5 secs") or a synchronous command (e.g. "broadcast xxx
- and wait"). Since Snap also has lambda and custom blocks Snap adds
- yields at the beginning of each non-atomic custom command block
- execution, and - to let users escape infinite loops and recursion -
- whenever the process runs into a timeout.
-
- a Process runs for a receiver, i.e. a sprite or the stage or any
- blocks-scriptable object that we'll introduce.
-
- structure:
-
- topBlock the stack's first block, of which all others
- are children
- receiver object (sprite) to which the process applies,
- cached from the top block
- instrument musical instrument type, cached from the receiver,
- so a single sprite can play several instruments
- at once
- context the Context describing the current state
- of this 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
- another process
- 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
- isFirstStep boolean indicating whether on first step - for clones
- 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
- isClicked boolean flag indicating whether the process was
- initiated by a user-click on a block
- isShowingResult boolean flag indicating whether a "report" command
- has been executed in a user-clicked process
- 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
- procedureCount number counting procedure call entries,
- used to tag custom block calls, so "stop block"
- invocations can catch them
- flashingContext for single stepping
- isInterrupted boolean, indicates intra-step flashing of blocks
- canBroadcast boolean, used to control reentrancy & "when stopped"
-*/
-
-Process.prototype = {};
-Process.prototype.constructor = Process;
-Process.prototype.timeout = 500; // msecs after which to force yield
-Process.prototype.isCatchingErrors = true;
-Process.prototype.enableHyperOps = true; // experimental hyper operations
-Process.prototype.enableLiveCoding = false; // experimental
-Process.prototype.enableSingleStepping = false; // experimental
-Process.prototype.enableCompiling = false; // experimental
-Process.prototype.flashTime = 0; // experimental
-// Process.prototype.enableJS = false;
-
-function Process(topBlock, receiver, onComplete, yieldFirst) {
- this.topBlock = topBlock || null;
- this.receiver = receiver;
- this.instrument = receiver ? receiver.instrument : null;
- this.readyToYield = false;
- this.readyToTerminate = false;
- this.isDead = false;
- this.isClicked = false;
- this.isShowingResult = false;
- this.errorFlag = false;
- this.context = null;
- this.homeContext = new Context(null, null, null, receiver);
- this.lastYield = Date.now();
- this.isFirstStep = true;
- this.isAtomic = false;
- this.prompter = null;
- this.httpRequest = null;
- this.isPaused = false;
- this.pauseOffset = null;
- this.frameCount = 0;
- this.exportResult = false;
- this.onComplete = onComplete || null;
- this.procedureCount = 0;
- this.flashingContext = null; // experimental, for single-stepping
- this.isInterrupted = false; // experimental, for single-stepping
- this.canBroadcast = true; // used to control "when I am stopped"
-
- if (topBlock) {
- this.homeContext.variables.parentFrame =
- this.homeContext.receiver.variables;
- this.context = new Context(
- null,
- topBlock.blockSequence(),
- this.homeContext
- );
- if (yieldFirst) {
- this.pushContext('doYield'); // highlight top block
- }
- }
-}
-
-// Process accessing
-
-Process.prototype.isRunning = function () {
- return (this.context !== null) && (!this.readyToTerminate);
-};
-
-// Process entry points
-
-Process.prototype.runStep = function (deadline) {
- // 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;
- this.isInterrupted = false;
-
- while (!this.readyToYield && !this.isInterrupted
- && this.context
- && (Date.now() - this.lastYield < this.timeout)
- ) {
- // also allow pausing inside atomic steps - for PAUSE block primitive:
- if (this.isPaused) {
- return this.pauseStep();
- }
- if (deadline && (Date.now() > deadline)) {
- if (this.isAtomic &&
- this.homeContext.receiver &&
- this.homeContext.receiver.endWarp) {
- this.homeContext.receiver.endWarp();
- }
- return;
- }
- this.evaluateContext();
- }
-
- this.lastYield = Date.now();
- this.isFirstStep = false;
-
- // make sure to redraw atomic things
- if (this.isAtomic &&
- this.homeContext.receiver &&
- this.homeContext.receiver.endWarp) {
- this.homeContext.receiver.endWarp();
- this.homeContext.receiver.startWarp();
- }
-
- if (this.readyToTerminate) {
- while (this.context) {
- this.popContext();
- }
- if (this.homeContext.receiver) {
- if (this.homeContext.receiver.endWarp) {
- // pen optimization
- this.homeContext.receiver.endWarp();
- }
- }
- }
-};
-
-Process.prototype.stop = function () {
- this.readyToYield = true;
- this.readyToTerminate = true;
- this.errorFlag = false;
- if (this.context) {
- this.context.stopMusic();
- }
- this.canBroadcast = false;
-};
-
-Process.prototype.pause = function () {
- if (this.readyToTerminate) {
- return;
- }
- this.isPaused = true;
- this.flashPausedContext();
- if (this.context && this.context.startTime) {
- this.pauseOffset = Date.now() - this.context.startTime;
- }
-};
-
-Process.prototype.resume = function () {
- if (!this.enableSingleStepping) {
- this.unflash();
- }
- this.isPaused = false;
- this.pauseOffset = null;
-};
-
-Process.prototype.pauseStep = function () {
- this.lastYield = Date.now();
- if (this.context && this.context.startTime) {
- this.context.startTime = this.lastYield - this.pauseOffset;
- }
-};
-
-// Process evaluation
-
-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);
- }
- if (exp instanceof MultiArgMorph) {
- return this.evaluateMultiSlot(exp, exp.inputs().length);
- }
- if (exp instanceof ArgLabelMorph) {
- return this.evaluateArgLabel(exp);
- }
- if (exp instanceof ArgMorph || exp.bindingID) {
- return this.evaluateInput(exp);
- }
- if (exp instanceof BlockMorph) {
- return this.evaluateBlock(exp, exp.inputs().length);
- }
- if (isString(exp)) {
- return this[exp].apply(this, this.context.inputs);
- }
- if (exp instanceof Variable) { // special case for empty reporter rings
- this.returnValueToParentContext(exp.value);
- }
- this.popContext(); // default: just ignore it
-};
-
-Process.prototype.evaluateBlock = function (block, argCount) {
- var rcvr, inputs,
- selector = block.selector;
-
- // check for special forms
- if (selector === 'reportOr' ||
- selector === 'reportAnd' ||
- selector === 'reportIfElse' ||
- selector === 'doReport') {
- if (this.isCatchingErrors) {
- try {
- return this[selector](block);
- } catch (error) {
- this.handleError(error, block);
- }
- } else {
- return this[selector](block);
- }
- }
-
- // first evaluate all inputs, then apply the primitive
- rcvr = this.context.receiver || this.receiver;
- inputs = this.context.inputs;
-
- if (argCount > inputs.length) {
- this.evaluateNextInput(block);
- } else {
- if (this.flashContext()) {return; } // yield to flash the block
- if (this[selector]) {
- rcvr = this;
- }
- if (this.isCatchingErrors) {
- try {
- this.returnValueToParentContext(
- rcvr[selector].apply(rcvr, inputs)
- );
- this.popContext();
- } catch (error) {
- this.handleError(error, block);
- }
- } else {
- this.returnValueToParentContext(
- rcvr[selector].apply(rcvr, inputs)
- );
- this.popContext();
- }
- }
-};
-
-// Process: Special Forms Blocks Primitives
-
-Process.prototype.reportOr = function (block) {
- var inputs = this.context.inputs;
-
- if (inputs.length < 1) {
- this.evaluateNextInput(block);
- } else if (inputs.length === 1) {
- // this.assertType(inputs[0], 'Boolean');
- if (inputs[0]) {
- if (this.flashContext()) {return; }
- this.returnValueToParentContext(true);
- this.popContext();
- } else {
- this.evaluateNextInput(block);
- }
- } else {
- // this.assertType(inputs[1], 'Boolean');
- if (this.flashContext()) {return; }
- this.returnValueToParentContext(inputs[1] === true);
- this.popContext();
- }
-};
-
-Process.prototype.reportAnd = function (block) {
- var inputs = this.context.inputs;
-
- if (inputs.length < 1) {
- this.evaluateNextInput(block);
- } else if (inputs.length === 1) {
- // this.assertType(inputs[0], 'Boolean');
- if (!inputs[0]) {
- if (this.flashContext()) {return; }
- this.returnValueToParentContext(false);
- this.popContext();
- } else {
- this.evaluateNextInput(block);
- }
- } else {
- // this.assertType(inputs[1], 'Boolean');
- if (this.flashContext()) {return; }
- this.returnValueToParentContext(inputs[1] === true);
- this.popContext();
- }
-};
-
-Process.prototype.doReport = function (block) {
- var outer = this.context.outerContext;
- if (this.flashContext()) {return; } // flash the block here, special form
- if (this.isClicked && (block.topBlock() === this.topBlock)) {
- this.isShowingResult = true;
- }
- if (block.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;
- }
- }
- }
- // in any case evaluate (and ignore)
- // the input, because it could be
- // an HTTP Request for a hardware extension
- this.pushContext(block.inputs()[0], outer);
- this.context.isCustomCommand = block.partOfCustomCommand;
-};
-
-// Process: Non-Block evaluation
-
-Process.prototype.evaluateMultiSlot = function (multiSlot, argCount) {
- // first evaluate all subslots, then return a list of their values
- var inputs = this.context.inputs,
- ans;
- if (multiSlot.bindingID) {
- if (this.isCatchingErrors) {
- try {
- ans = this.context.variables.getVar(multiSlot.bindingID);
- } catch (error) {
- this.handleError(error, multiSlot);
- }
- } else {
- ans = this.context.variables.getVar(multiSlot.bindingID);
- }
- this.returnValueToParentContext(ans);
- this.popContext();
- } else {
- if (argCount > inputs.length) {
- this.evaluateNextInput(multiSlot);
- } else {
- this.returnValueToParentContext(new List(inputs));
- this.popContext();
- }
- }
-};
-
-Process.prototype.evaluateArgLabel = function (argLabel) {
- // perform the ID function on an ArgLabelMorph element
- var inputs = this.context.inputs;
- if (inputs.length < 1) {
- this.evaluateNextInput(argLabel);
- } else {
- this.returnValueToParentContext(inputs[0]);
- this.popContext();
- }
-};
-
-Process.prototype.evaluateInput = function (input) {
- // evaluate the input unless it is bound to an implicit parameter
- var ans;
- if (this.flashContext()) {return; } // yield to flash the current argMorph
- if (input.bindingID) {
- if (this.isCatchingErrors) {
- try {
- ans = this.context.variables.getVar(input.bindingID);
- } catch (error) {
- this.handleError(error, input);
- }
- } else {
- ans = this.context.variables.getVar(input.bindingID);
- }
- } else {
- ans = input.evaluate();
- if (ans) {
- if (input.constructor === CommandSlotMorph ||
- input.constructor === ReporterSlotMorph ||
- (input instanceof CSlotMorph &&
- (!input.isStatic || input.isLambda))) {
- // I know, this still needs yet to be done right....
- ans = this.reify(ans, new List());
- }
- }
- }
- this.returnValueToParentContext(ans);
- this.popContext();
-};
-
-Process.prototype.evaluateSequence = function (arr) {
- var pc = this.context.pc,
- outer = this.context.outerContext,
- isCustomBlock = this.context.isCustomBlock;
- if (pc === (arr.length - 1)) { // tail call elimination
- this.context = new Context(
- this.context.parentContext,
- arr[pc],
- this.context.outerContext,
- this.context.receiver
- );
- this.context.isCustomBlock = isCustomBlock;
- } else {
- if (pc >= arr.length) {
- this.popContext();
- } else {
- this.context.pc += 1;
- this.pushContext(arr[pc], outer);
- }
- }
-};
-
-/*
-// version w/o tail call optimization:
---------------------------------------
-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 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.
-
-Process.prototype.evaluateSequence = function (arr) {
- var pc = this.context.pc;
- if (pc >= arr.length) {
- this.popContext();
- } else {
- this.context.pc += 1;
- this.pushContext(arr[pc]);
- }
-};
-*/
-
-Process.prototype.evaluateNextInput = function (element) {
- var nxt = this.context.inputs.length,
- args = element.inputs(),
- exp = args[nxt],
- sel = this.context.expression.selector,
- outer = this.context.outerContext; // for tail call elimination
-
- if (exp.isUnevaluated) {
- if (exp.isUnevaluated === true || exp.isUnevaluated()) {
- // just return the input as-is
- /*
- Note: we only reify the input here, if it's not an
- input to a reification primitive itself (THE BLOCK,
- THE SCRIPT), because those allow for additional
- explicit parameter bindings.
- */
- if (sel === 'reify' || sel === 'reportScript') {
- this.context.addInput(exp);
- } else {
- this.context.addInput(this.reify(exp, new List()));
- }
- } else {
- this.pushContext(exp, outer);
- }
- } else {
- this.pushContext(exp, outer);
- }
-};
-
-Process.prototype.doYield = function () {
- this.popContext();
- if (!this.isAtomic) {
- this.readyToYield = true;
- }
-};
-
-Process.prototype.expectReport = function () {
- this.handleError(new Error("reporter didn't report"));
-};
-
-// Process Exception Handling
-
-Process.prototype.handleError = function (error, element) {
- var m = element;
- this.stop();
- this.errorFlag = true;
- this.topBlock.addErrorHighlight();
- if (isNil(m) || isNil(m.world())) {m = this.topBlock; }
- m.showBubble(
- (m === element ? '' : 'Inside: ')
- + error.name
- + '\n'
- + error.message,
- this.exportResult,
- this.receiver
- );
-};
-
-Process.prototype.errorObsolete = function () {
- throw new Error('a custom block definition is missing');
-};
-
-// Process Lambda primitives
-
-Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) {
- var context = new Context(
- null,
- null,
- this.context ? this.context.outerContext : null
- ),
- i = 0;
-
- if (topBlock) {
- context.expression = this.enableLiveCoding ||
- this.enableSingleStepping ?
- topBlock : topBlock.fullCopy();
- context.expression.show(); // be sure to make visible if in app mode
-
- if (!isCustomBlock && !parameterNames.length()) {
- // mark all empty slots with an identifier
- context.expression.allEmptySlots().forEach(slot => {
- i += 1;
- if (slot instanceof MultiArgMorph) {
- slot.bindingID = Symbol.for('arguments');
- } else {
- slot.bindingID = i;
- }
- });
- // and remember the number of detected empty slots
- context.emptySlots = i;
- }
- } else {
- context.expression = this.enableLiveCoding ||
- this.enableSingleStepping ? [this.context.expression]
- : [this.context.expression.fullCopy()];
- }
-
- context.inputs = parameterNames.asArray();
- context.receiver
- = this.context ? this.context.receiver : this.receiver;
- context.origin = context.receiver; // for serialization
-
- return context;
-};
-
-Process.prototype.reportScript = function (parameterNames, topBlock) {
- return this.reify(topBlock, parameterNames);
-};
-
-Process.prototype.reifyScript = function (topBlock, parameterNames) {
- return this.reify(topBlock, parameterNames);
-};
-
-Process.prototype.reifyReporter = function (topBlock, parameterNames) {
- return this.reify(topBlock, parameterNames);
-};
-
-Process.prototype.reifyPredicate = function (topBlock, parameterNames) {
- return this.reify(topBlock, parameterNames);
-};
-
-Process.prototype.reportJSFunction = function (parmNames, body) {
- return Function.apply(
- null,
- parmNames.asArray().concat([body])
- );
-};
-
-Process.prototype.doRun = function (context, args) {
- return this.evaluate(context, args, true);
-};
-
-Process.prototype.evaluate = function (
- context,
- args,
- isCommand
-) {
- if (!context) {return null; }
- if (context instanceof Function) {
- // if (!this.enableJS) {
- // throw new Error('JavaScript is not enabled');
- // }
- return context.apply(
- this.blockReceiver(),
- args.asArray().concat([this])
- );
- }
- if (context.isContinuation) {
- return this.runContinuation(context, args);
- }
- if (!(context instanceof Context)) {
- throw new Error('expecting a ring but getting ' + context);
- }
-
- var outer = new Context(null, null, context.outerContext),
- caller = this.context.parentContext,
- exit,
- runnable,
- expr,
- parms = args.asArray(),
- i,
- value;
-
- if (!outer.receiver) {
- outer.receiver = context.receiver; // for custom blocks
- }
- runnable = new Context(
- this.context.parentContext,
- context.expression,
- outer,
- context.receiver
- );
- runnable.isCustomCommand = isCommand; // for short-circuiting HTTP requests
- this.context.parentContext = runnable;
-
- if (context.expression instanceof ReporterBlockMorph) {
- // auto-"warp" nested reporters
- this.readyToYield = (Date.now() - this.lastYield > this.timeout);
- }
-
- // assign arguments to parameters
-
- // assign the actual arguments list to the special
- // parameter ID Symbol.for('arguments'), to be used for variadic inputs
- outer.variables.addVar(Symbol.for('arguments'), args);
-
- // assign arguments that are actually passed
- if (parms.length > 0) {
-
- // assign formal parameters
- for (i = 0; i < context.inputs.length; i += 1) {
- value = 0;
- if (!isNil(parms[i])) {
- value = parms[i];
- }
- outer.variables.addVar(context.inputs[i], value);
- }
-
- // assign implicit parameters if there are no formal ones
- if (context.inputs.length === 0) {
- // in case there is only one input
- // assign it to all empty slots...
- if (parms.length === 1) {
- // ... unless it's an empty reporter ring,
- // in which special case it gets treated as the ID-function;
- // experimental feature jens is not at all comfortable with
- if (!context.emptySlots) {
- expr = context.expression;
- if (expr instanceof Array &&
- expr.length === 1 &&
- expr[0].selector &&
- expr[0].selector === 'reifyReporter' &&
- !expr[0].contents()) {
- runnable.expression = new Variable(parms[0]);
- }
- } else {
- for (i = 1; i <= context.emptySlots; i += 1) {
- outer.variables.addVar(i, parms[0]);
- }
- }
-
- // if the number of inputs matches the number
- // of empty slots distribute them sequentially
- } else if (parms.length === context.emptySlots) {
- for (i = 1; i <= parms.length; i += 1) {
- outer.variables.addVar(i, parms[i - 1]);
- }
-
- } else if (context.emptySlots !== 1) {
- throw new Error(
- localize('expecting') + ' ' + context.emptySlots + ' '
- + localize('input(s), but getting') + ' '
- + parms.length
- );
- }
- }
- }
-
- if (runnable.expression instanceof CommandBlockMorph) {
- runnable.expression = runnable.expression.blockSequence();
- if (!isCommand) {
- 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;
- }
- }
- }
-};
-
-Process.prototype.fork = function (context, args) {
- if (this.readyToTerminate) {return; }
- var proc = new Process(),
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- proc.instrument = this.instrument;
- proc.receiver = this.receiver;
- proc.initializeFor(context, args);
- // proc.pushContext('doYield');
- stage.threads.processes.push(proc);
-};
-
-Process.prototype.initializeFor = function (context, args) {
- // used by Process.fork() and global invoke()
- if (context.isContinuation) {
- throw new Error(
- '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,
- context.expression,
- outer
- ),
- parms = args.asArray(),
- i,
- value;
-
- // remember the receiver
- this.context = context.receiver;
-
- // assign arguments to parameters
-
- // assign the actual arguments list to the special
- // parameter ID Symbol.for('arguments'), to be used for variadic inputs
- outer.variables.addVar(Symbol.for('arguments'), args);
-
- // assign arguments that are actually passed
- if (parms.length > 0) {
-
- // assign formal parameters
- for (i = 0; i < context.inputs.length; i += 1) {
- value = 0;
- if (!isNil(parms[i])) {
- value = parms[i];
- }
- outer.variables.addVar(context.inputs[i], value);
- }
-
- // assign implicit parameters if there are no formal ones
- if (context.inputs.length === 0) {
- // in case there is only one input
- // assign it to all empty slots
- if (parms.length === 1) {
- for (i = 1; i <= context.emptySlots; i += 1) {
- outer.variables.addVar(i, parms[0]);
- }
-
- // if the number of inputs matches the number
- // of empty slots distribute them sequentially
- } else if (parms.length === context.emptySlots) {
- for (i = 1; i <= parms.length; i += 1) {
- outer.variables.addVar(i, parms[i - 1]);
- }
-
- } else if (context.emptySlots !== 1) {
- throw new Error(
- localize('expecting') + ' ' + context.emptySlots + ' '
- + localize('input(s), but getting') + ' '
- + parms.length
- );
- }
- }
- }
-
- if (runnable.expression instanceof CommandBlockMorph) {
- runnable.expression = runnable.expression.blockSequence();
- }
-
- this.homeContext = new Context(); // context.outerContext;
- this.homeContext.receiver = context.outerContext.receiver;
- this.topBlock = context.expression;
- this.context = runnable;
-};
-
-// Process stopping blocks primitives
-
-Process.prototype.doStopBlock = function () {
- var target = this.context.expression.exitTag;
- if (isNil(target)) {
- return this.doStopCustomBlock();
- }
- while (this.context &&
- (isNil(this.context.tag) || (this.context.tag > target))) {
- if (this.context.expression === 'doStopWarping') {
- this.doStopWarping();
- } else {
- this.popContext();
- }
- }
- this.pushContext();
-};
-
-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();
- } else {
- this.popContext();
- }
- }
-};
-
-// Process continuations primitives
-
-Process.prototype.doCallCC = function (aContext, isReporter) {
- this.evaluate(
- aContext,
- new List([this.context.continuation()]),
- !isReporter
- );
-};
-
-Process.prototype.reportCallCC = function (aContext) {
- this.doCallCC(aContext, true);
-};
-
-Process.prototype.runContinuation = function (aContext, args) {
- var parms = args.asArray();
-
- // determine whether the continuations is to show the result
- // in a value-balloon becuse the user has directly clicked on a reporter
- if (aContext.expression === 'expectReport' && parms.length) {
- this.stop();
- this.homeContext.inputs[0] = parms[0];
- return;
- }
-
- this.context.parentContext = aContext.copyForContinuationCall();
- // passing parameter if any was passed
- if (parms.length === 1) {
- this.context.parentContext.outerContext.variables.addVar(
- 1,
- parms[0]
- );
- }
-};
-
-// Process custom block primitives
-
-Process.prototype.evaluateCustomBlock = function () {
- var caller = this.context.parentContext,
- block = this.context.expression,
- method = block.isGlobal ? block.definition
- : this.blockReceiver().getMethod(block.semanticSpec),
- context = method.body,
- declarations = method.declarations,
- args = new List(this.context.inputs),
- parms = args.asArray(),
- runnable,
- exit,
- i,
- value,
- outer;
-
- if (!context) {return null; }
- this.procedureCount += 1;
- outer = new Context();
- outer.receiver = this.context.receiver;
-
- outer.variables.parentFrame = block.variables;
-
- // block (instance) var support, experimental:
- // only splice in block vars if any are defined, because block vars
- // can cause race conditions in global block definitions that
- // access sprite-local variables at the same time.
- if (method.variableNames.length) {
- block.variables.parentFrame = outer.receiver ?
- outer.receiver.variables : null;
- } else {
- // original code without block variables:
- outer.variables.parentFrame = outer.receiver ?
- outer.receiver.variables : null;
- }
-
- runnable = new Context(
- this.context.parentContext,
- context.expression,
- outer,
- outer.receiver
- );
- runnable.isCustomBlock = true;
- this.context.parentContext = runnable;
-
- // passing parameters if any were passed
- if (parms.length > 0) {
-
- // assign formal parameters
- for (i = 0; i < context.inputs.length; i += 1) {
- value = 0;
- if (!isNil(parms[i])) {
- value = parms[i];
- }
- outer.variables.addVar(context.inputs[i], value);
-
- // if the parameter is an upvar,
- // create a reference to the variable it points to
- if (declarations.get(context.inputs[i])[0] === '%upvar') {
- this.context.outerContext.variables.vars[value] =
- outer.variables.vars[context.inputs[i]];
- }
- }
- }
-
- // tag return target
- if (method.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,
- 'expectReport',
- outer,
- outer.receiver
- );
- exit.tag = 'exit';
- runnable.parentContext = exit;
- }
- // 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;
- }
- // yield commands unless explicitly "warped" or directly recursive
- if (!this.isAtomic && method.isDirectlyRecursive()) {
- this.readyToYield = true;
- }
- }
- runnable.expression = runnable.expression.blockSequence();
-};
-
-// Process variables primitives
-
-Process.prototype.doDeclareVariables = function (varNames) {
- var varFrame = this.context.outerContext.variables;
- varNames.asArray().forEach(name =>
- varFrame.addVar(name)
- );
-};
-
-Process.prototype.doSetVar = function (varName, value) {
- var varFrame = this.context.variables,
- name = varName;
- if (name instanceof Context) {
- if (name.expression.selector === 'reportGetVar') {
- name.variables.setVar(
- name.expression.blockSpec,
- value,
- this.blockReceiver()
- );
- return;
- }
- this.doSet(name, value);
- return;
- }
- if (name instanceof Array) {
- this.doSet(name, value);
- return;
- }
- varFrame.setVar(name, value, this.blockReceiver());
-};
-
-Process.prototype.doChangeVar = function (varName, value) {
- var varFrame = this.context.variables,
- name = varName;
-
- if (name instanceof Context) {
- if (name.expression.selector === 'reportGetVar') {
- name.variables.changeVar(
- name.expression.blockSpec,
- value,
- this.blockReceiver()
- );
- return;
- }
- }
- varFrame.changeVar(name, value, this.blockReceiver());
-};
-
-Process.prototype.reportGetVar = function () {
- // assumes a getter block whose blockSpec is a variable name
- return this.context.variables.getVar(
- this.context.expression.blockSpec
- );
-};
-
-Process.prototype.doShowVar = function (varName) {
- var varFrame = this.context.variables,
- stage,
- watcher,
- target,
- label,
- others,
- isGlobal,
- name = varName;
-
- if (name instanceof Context) {
- if (name.expression.selector === 'reportGetVar') {
- name = name.expression.blockSpec;
- } else {
- this.doChangePrimitiveVisibility(name.expression, false);
- return;
- }
- }
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- target = varFrame.silentFind(name);
- if (!target) {return; }
- // first try to find an existing (hidden) watcher
- watcher = detect(
- stage.children,
- morph => morph instanceof WatcherMorph &&
- morph.target === target &&
- morph.getter === name
- );
- if (watcher !== null) {
- watcher.show();
- watcher.fixLayout(); // re-hide hidden parts
- return;
- }
- // if no watcher exists, create a new one
- isGlobal = contains(
- this.homeContext.receiver.globalVariables().names(),
- varName
- );
- if (isGlobal || target.owner) {
- label = name;
- } else {
- label = name + ' ' + localize('(temporary)');
- }
- watcher = new WatcherMorph(
- label,
- SpriteMorph.prototype.blockColor.variables,
- target,
- name
- );
- watcher.setPosition(stage.position().add(10));
- others = stage.watchers(watcher.left());
- if (others.length > 0) {
- watcher.setTop(others[others.length - 1].bottom());
- }
- stage.add(watcher);
- watcher.fixLayout();
- }
- }
-};
-
-Process.prototype.doHideVar = function (varName) {
- // if no varName is specified delete all watchers on temporaries
- var varFrame = this.context.variables,
- stage,
- watcher,
- target,
- name = varName;
-
- if (name instanceof Context) {
- if (name.expression.selector === 'reportGetVar') {
- name = name.expression.blockSpec;
- } else {
- this.doChangePrimitiveVisibility(name.expression, true);
- return;
- }
- }
- if (!name) {
- this.doRemoveTemporaries();
- return;
- }
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- target = varFrame.find(name);
- watcher = detect(
- stage.children,
- morph => morph instanceof WatcherMorph &&
- morph.target === target &&
- morph.getter === name
- );
- if (watcher !== null) {
- if (watcher.isTemporary()) {
- watcher.destroy();
- } else {
- watcher.hide();
- }
- }
- }
- }
-};
-
-Process.prototype.doRemoveTemporaries = function () {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.watchers().forEach(watcher => {
- if (watcher.isTemporary()) {
- watcher.destroy();
- }
- });
- }
- }
-};
-
-// Process hiding and showing primitives primitives :-)
-
-Process.prototype.doChangePrimitiveVisibility = function (aBlock, hideIt) {
- var ide = this.homeContext.receiver.parentThatIsA(IDE_Morph),
- dict,
- cat;
- if (!ide || (aBlock.selector === 'evaluateCustomBlock')) {
- return;
- }
- if (hideIt) {
- StageMorph.prototype.hiddenPrimitives[aBlock.selector] = true;
- } else {
- delete StageMorph.prototype.hiddenPrimitives[aBlock.selector];
- }
- dict = {
- doWarp: 'control',
- reifyScript: 'operators',
- reifyReporter: 'operators',
- reifyPredicate: 'operators',
- doDeclareVariables: 'variables'
- };
- cat = dict[this.selector] || this.category;
- if (cat === 'lists') {cat = 'variables'; }
- ide.flushBlocksCache(cat);
- ide.refreshPalette();
-};
-
-// Process sprite inheritance primitives
-
-Process.prototype.doDeleteAttr = function (attrName) {
- var name = attrName,
- rcvr = this.blockReceiver();
- if (name instanceof Context) {
- if (name.expression.selector === 'reportGetVar') {
- name = name.expression.blockSpec;
- } else { // attribute
- name = {
- xPosition: 'x position',
- yPosition: 'y position',
- direction: 'direction',
- getCostumeIdx: 'costume #',
- size: 'size'
- }[name.expression.selector];
- if (!isNil(name)) {
- rcvr.inheritAttribute(name);
- }
- return; // error: cannot delete attribute...
- }
- }
- if (name instanceof Array) {
- return rcvr.inheritAttribute(this.inputOption(name));
- }
- if (contains(rcvr.inheritedVariableNames(true), name)) {
- rcvr.deleteVariable(name);
- }
-};
-
-// experimental message passing primitives
-
-Process.prototype.doTellTo = function (sprite, context, args) {
- this.doRun(
- this.reportAttributeOf(context, sprite),
- args
- );
-};
-
-Process.prototype.reportAskFor = function (sprite, context, args) {
- this.evaluate(
- this.reportAttributeOf(context, sprite),
- args
- );
-};
-
-// Process lists primitives
-
-Process.prototype.reportNewList = function (elements) {
- return elements;
-};
-
-Process.prototype.reportCONS = function (car, cdr) {
- this.assertType(cdr, 'list');
- return new List().cons(car, cdr);
-};
-
-Process.prototype.reportCDR = function (list) {
- this.assertType(list, 'list');
- return list.cdr();
-};
-
-Process.prototype.doAddToList = function (element, list) {
- this.assertType(list, 'list');
- if (list.type) {
- this.assertType(element, list.type);
- list = this.shadowListAttribute(list);
- }
- list.add(element);
-};
-
-Process.prototype.doDeleteFromList = function (index, list) {
- var idx = index;
- this.assertType(list, 'list');
- if (list.type) {
- list = this.shadowListAttribute(list);
- }
- if (this.inputOption(index) === 'all') {
- return list.clear();
- }
- if (index === '') {
- return null;
- }
- if (this.inputOption(index) === 'last') {
- idx = list.length();
- } else if (isNaN(+this.inputOption(index))) {
- return null;
- }
- list.remove(idx);
-};
-
-Process.prototype.doInsertInList = function (element, index, list) {
- var idx = index;
- this.assertType(list, 'list');
- if (list.type) {
- this.assertType(element, list.type);
- list = this.shadowListAttribute(list);
- }
- if (index === '') {
- return null;
- }
- if (this.inputOption(index) === 'any') {
- idx = this.reportBasicRandom(1, list.length() + 1);
- }
- if (this.inputOption(index) === 'last') {
- idx = list.length() + 1;
- }
- list.add(element, idx);
-};
-
-Process.prototype.doReplaceInList = function (index, list, element) {
- var idx = index;
- this.assertType(list, 'list');
- if (list.type) {
- this.assertType(element, list.type);
- list = this.shadowListAttribute(list);
- }
- if (index === '') {
- return null;
- }
- if (this.inputOption(index) === 'any') {
- idx = this.reportBasicRandom(1, list.length());
- }
- if (this.inputOption(index) === 'last') {
- idx = list.length();
- }
- list.put(element, idx);
-};
-
-Process.prototype.shadowListAttribute = function (list) {
- // private - check whether the list is an attribute that needs to be
- // shadowed. Use only on typed lists for performance.
- var rcvr;
- if (list.type === 'costume' || list.type === 'sound') {
- rcvr = this.blockReceiver();
- if (list === rcvr.costumes) {
- rcvr.shadowAttribute('costumes');
- list = rcvr.costumes;
- } else if (list === rcvr.sounds) {
- rcvr.shadowAttribute('sounds');
- list = rcvr.sounds;
- }
- }
- return list;
-};
-
-// Process accessing list elements - hyper dyadic
-
-Process.prototype.reportListItem = function (index, list) {
- var rank;
- this.assertType(list, 'list');
- if (index === '') {
- return '';
- }
- if (this.inputOption(index) === 'any') {
- return list.at(this.reportBasicRandom(1, list.length()));
- }
- if (this.inputOption(index) === 'last') {
- return list.at(list.length());
- }
- rank = this.rank(index);
- if (rank > 0 && this.enableHyperOps) {
- if (rank === 1) {
- if (index.isEmpty()) {
- return list.map(item => item);
- }
- return index.map(idx => list.at(idx));
- }
- return this.reportItems(index, list);
- }
- return list.at(index);
-};
-
-Process.prototype.reportItems = function (indices, list) {
- // This. This is it. The pinnacle of my programmer's life.
- // After days of roaming about my house and garden,
- // of taking showers and rummaging through the fridge,
- // of strumming the charango and the five ukuleles
- // sitting next to my laptop on my desk,
- // and of letting my mind wander far and wide,
- // to come up with this design, always thinking
- // "What would Brian do?".
- // And look, Ma, it's turned out all beautiful! -jens
-
- return makeSelector(
- this.rank(list),
- indices.cdr(),
- makeLeafSelector(indices.at(1))
- )(list);
-
- function makeSelector(rank, indices, next) {
- if (rank === 1) {
- return next;
- }
- return makeSelector(
- rank - 1,
- indices.cdr(),
- makeBranch(
- indices.at(1) || new List(),
- next
- )
- );
- }
-
- function makeBranch(indices, next) {
- return function(data) {
- if (indices.isEmpty()) {
- return data.map(item => next(item));
- }
- return indices.map(idx => next(data.at(idx)));
- };
- }
-
- function makeLeafSelector(indices) {
- return function (data) {
- if (indices.isEmpty()) {
- return data.map(item => item);
- }
- return indices.map(idx => data.at(idx));
- };
- }
-};
-
-// Process - other basic list accessors
-
-Process.prototype.reportListLength = function (list) {
- this.assertType(list, 'list');
- return list.length();
-};
-
-Process.prototype.reportListIndex = function(element, list) {
- this.assertType(list, 'list');
- return list.indexOf(element);
-};
-
-Process.prototype.reportListContainsItem = function (list, element) {
- this.assertType(list, 'list');
- return list.contains(element);
-};
-
-Process.prototype.reportListIsEmpty = function (list) {
- this.assertType(list, 'list');
- return list.isEmpty();
-};
-
-Process.prototype.doShowTable = function (list) {
- // experimental
- this.assertType(list, 'list');
- new TableDialogMorph(list).popUp(this.blockReceiver().world());
-};
-
-// Process non-HOF list primitives
-
-Process.prototype.reportNumbers = function (start, end) {
- // hyper-dyadic
- if (this.enableHyperOps) {
- return this.hyperDyadic(
- (strt, stp) => this.reportBasicNumbers(strt, stp),
- start,
- end
- );
- }
- return this.reportLinkedNumbers(start, end);
-};
-
-Process.prototype.reportBasicNumbers = function (start, end) {
- // answer a new arrayed list containing an linearly ascending progression
- // of integers beginning at start to end.
- var result, len, i,
- s = +start,
- e = +end,
- n = s;
-
- this.assertType(s, 'number');
- this.assertType(e, 'number');
-
- if (e > s) {
- len = Math.floor(e - s);
- result = new Array(len);
- for(i = 0; i <= len; i += 1) {
- result[i] = n;
- n += 1;
- }
- } else {
- len = Math.floor(s - e);
- result = new Array(len);
- for(i = 0; i <= len; i += 1) {
- result[i] = n;
- n -= 1;
- }
- }
- return new List(result);
-};
-
-Process.prototype.reportConcatenatedLists = function (lists) {
- var first, result, rows, row, rowIdx, cols, col;
- this.assertType(lists, 'list');
- if (lists.isEmpty()) {
- return lists;
- }
- first = lists.at(1);
- this.assertType(first, 'list');
- if (first.isLinked) { // link everything
- return this.concatenateLinkedLists(lists);
- }
-
- // in case the first sub-list is arrayed
- result = [];
- rows = lists.length();
- for (rowIdx = 1; rowIdx <= rows; rowIdx += 1) {
- row = lists.at(rowIdx);
- this.assertType(row, 'list');
- cols = row.length();
- for (col = 1; col <= cols; col += 1) {
- result.push(row.at(col));
- }
- }
- return new List(result);
-};
-
-Process.prototype.concatenateLinkedLists = function (lists) {
- var first;
- if (lists.isEmpty()) {
- return lists;
- }
- first = lists.at(1);
- this.assertType(first, 'list');
- if (lists.length() === 1) {
- return first;
- }
- if (first.isEmpty()) {
- return this.concatenateLinkedLists(lists.cdr());
- }
- return lists.cons(
- first.at(1),
- this.concatenateLinkedLists(
- lists.cons(
- first.cdr(),
- lists.cdr()
- )
- )
- );
-};
-
-// Process interpolated non-HOF list primitives
-
-Process.prototype.reportLinkedNumbers = function (start, end) {
- // answer a new linked list containing an linearly ascending progression
- // of integers beginning at start to end.
- // this is interpolated so it can handle big ranges of numbers
- // without blocking the UI
-
- var dta;
- if (this.context.accumulator === null) {
- this.assertType(start, 'number');
- this.assertType(end, 'number');
- this.context.accumulator = {
- target : new List(),
- end : null,
- idx : +start,
- step: +end > +start ? +1 : -1
- };
- this.context.accumulator.target.isLinked = true;
- this.context.accumulator.end = this.context.accumulator.target;
- }
- dta = this.context.accumulator;
- if (dta.step === 1 ? dta.idx > +end : dta.idx < +end) {
- dta.end.rest = new List();
- this.returnValueToParentContext(dta.target.cdr());
- return;
- }
- dta.end.rest = dta.target.cons(dta.idx);
- dta.end = dta.end.rest;
- dta.idx += dta.step;
- this.pushContext();
-};
-
-// Process conditionals primitives
-
-Process.prototype.doIf = function () {
- var args = this.context.inputs,
- outer = this.context.outerContext, // for tail call elimination
- isCustomBlock = this.context.isCustomBlock;
-
- // this.assertType(args[0], ['Boolean']);
- this.popContext();
- if (args[0]) {
- if (args[1]) {
- this.pushContext(args[1].blockSequence(), outer);
- this.context.isCustomBlock = isCustomBlock;
- }
- }
- this.pushContext();
-};
-
-Process.prototype.doIfElse = function () {
- var args = this.context.inputs,
- outer = this.context.outerContext, // for tail call elimination
- isCustomBlock = this.context.isCustomBlock;
-
- // this.assertType(args[0], ['Boolean']);
- this.popContext();
- if (args[0]) {
- if (args[1]) {
- this.pushContext(args[1].blockSequence(), outer);
- }
- } else {
- if (args[2]) {
- this.pushContext(args[2].blockSequence(), outer);
- } else {
- this.pushContext('doYield');
- }
- }
- if (this.context) {
- this.context.isCustomBlock = isCustomBlock;
- }
-
- this.pushContext();
-};
-
-Process.prototype.reportIfElse = function (block) {
- var inputs = this.context.inputs;
-
- if (inputs.length < 1) {
- this.evaluateNextInput(block);
- } else if (inputs.length > 1) {
- if (this.flashContext()) {return; }
- this.returnValueToParentContext(inputs.pop());
- this.popContext();
- } else {
- // this.assertType(inputs[0], ['Boolean']);
- if (inputs[0]) {
- this.evaluateNextInput(block);
- } else {
- inputs.push(null);
- this.evaluateNextInput(block);
- }
- }
-};
-
-// Process process related primitives
-
-Process.prototype.doStop = function () {
- this.stop();
-};
-
-Process.prototype.doStopAll = function () {
- var stage, ide;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- if (stage.enableCustomHatBlocks) {
- stage.threads.pauseCustomHatBlocks =
- !stage.threads.pauseCustomHatBlocks;
- } else {
- stage.threads.pauseCustomHatBlocks = false;
- }
- stage.stopAllActiveSounds();
- stage.threads.resumeAll(stage);
- stage.keysPressed = {};
- stage.runStopScripts();
- stage.threads.stopAll();
- if (stage.projectionSource) {
- stage.stopProjection();
- }
- stage.children.forEach(morph => {
- if (morph.stopTalking) {
- morph.stopTalking();
- }
- });
- stage.removeAllClones();
- }
- ide = stage.parentThatIsA(IDE_Morph);
- if (ide) {
- ide.controlBar.pauseButton.refresh();
- ide.controlBar.stopButton.refresh();
- }
- }
-};
-
-Process.prototype.doStopThis = function (choice) {
- switch (this.inputOption(choice)) {
- case 'all':
- this.doStopAll();
- break;
- case 'this script':
- this.doStop();
- break;
- case 'this block':
- this.doStopBlock();
- break;
- default:
- this.doStopOthers(choice);
- }
-};
-
-Process.prototype.doStopOthers = function (choice) {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- switch (this.inputOption(choice)) {
- case 'all but this script':
- stage.threads.stopAll(this);
- break;
- case 'other scripts in sprite':
- stage.threads.stopAllForReceiver(
- this.homeContext.receiver,
- this
- );
- break;
- default:
- nop();
- }
- }
- }
-};
-
-Process.prototype.doWarp = function (body) {
- // execute my contents block atomically (more or less)
- var outer = this.context.outerContext, // for tail call elimination
- isCustomBlock = this.context.isCustomBlock,
- stage;
-
- this.popContext();
-
- if (body) {
- if (this.homeContext.receiver) {
- if (this.homeContext.receiver.startWarp) {
- // pen optimization
- this.homeContext.receiver.startWarp();
- }
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.fps = 0; // variable frame rate
- }
- }
-
- // this.pushContext('doYield'); // no longer needed in Morphic2
- this.pushContext('popContext'); // instead we do this...
-
- if (this.context) {
- this.context.isCustomBlock = isCustomBlock;
- }
- if (!this.isAtomic) {
- this.pushContext('doStopWarping');
- }
- this.pushContext(body.blockSequence(), outer);
- this.isAtomic = true;
- }
- this.pushContext();
-};
-
-Process.prototype.doStopWarping = function () {
- var stage;
- this.popContext();
- this.isAtomic = false;
- if (this.homeContext.receiver) {
- if (this.homeContext.receiver.endWarp) {
- // pen optimization
- this.homeContext.receiver.endWarp();
- }
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.fps = stage.frameRate; // back to fixed frame rate
- }
- }
-};
-
-Process.prototype.reportIsFastTracking = function () {
- var ide;
- if (this.homeContext.receiver) {
- ide = this.homeContext.receiver.parentThatIsA(IDE_Morph);
- if (ide) {
- return ide.stage.isFastTracked;
- }
- }
- return false;
-};
-
-Process.prototype.doSetGlobalFlag = function (name, bool) {
- var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- name = this.inputOption(name);
- this.assertType(bool, 'Boolean');
- switch (name) {
- case 'turbo mode':
- this.doSetFastTracking(bool);
- break;
- case 'flat line ends':
- SpriteMorph.prototype.useFlatLineEnds = bool;
- break;
- case 'log pen vectors':
- StageMorph.prototype.enablePenLogging = bool;
- break;
- case 'video capture':
- if (bool) {
- this.startVideo(stage);
- } else {
- stage.stopProjection();
- }
- break;
- case 'mirror video':
- stage.mirrorVideo = bool;
- break;
- }
-};
-
-Process.prototype.reportGlobalFlag = function (name) {
- var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- name = this.inputOption(name);
- switch (name) {
- case 'turbo mode':
- return this.reportIsFastTracking();
- case 'flat line ends':
- return SpriteMorph.prototype.useFlatLineEnds;
- case 'log pen vectors':
- return StageMorph.prototype.enablePenLogging;
- case 'video capture':
- return !isNil(stage.projectionSource) &&
- stage.projectionLayer()
- .getContext('2d')
- .getImageData(0, 0, 1, 1)
- .data[3] > 0;
- case 'mirror video':
- return stage.mirrorVideo;
- default:
- return '';
- }
-};
-
-Process.prototype.doSetFastTracking = function (bool) {
- var ide;
- if (!this.reportIsA(bool, 'Boolean')) {
- return;
- }
- if (this.homeContext.receiver) {
- ide = this.homeContext.receiver.parentThatIsA(IDE_Morph);
- if (ide) {
- if (bool) {
- ide.startFastTracking();
- } else {
- ide.stopFastTracking();
- }
- }
- }
-};
-
-Process.prototype.doPauseAll = function () {
- var stage, ide;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.threads.pauseAll(stage);
- }
- ide = stage.parentThatIsA(IDE_Morph);
- if (ide) {ide.controlBar.pauseButton.refresh(); }
- }
-};
-
-// Process loop primitives
-
-Process.prototype.doForever = function (body) {
- this.context.inputs = []; // force re-evaluation of C-slot
- this.pushContext('doYield');
- if (body) {
- this.pushContext(body.blockSequence());
- }
- this.pushContext();
-};
-
-Process.prototype.doRepeat = function (counter, body) {
- var block = this.context.expression,
- outer = this.context.outerContext, // for tail call elimination
- 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.isCustomBlock = isCustomBlock;
- this.context.addInput(counter - 1);
- this.pushContext('doYield');
- if (body) {
- this.pushContext(body.blockSequence());
- }
- this.pushContext();
-};
-
-Process.prototype.doUntil = function (goalCondition, body) {
- // this.assertType(goalCondition, ['Boolean']);
- if (goalCondition) {
- this.popContext();
- this.pushContext('doYield');
- return null;
- }
- this.context.inputs = [];
- this.pushContext('doYield');
- if (body) {
- this.pushContext(body.blockSequence());
- }
- this.pushContext();
-};
-
-Process.prototype.doWaitUntil = function (goalCondition) {
- // this.assertType(goalCondition, ['Boolean']);
- if (goalCondition) {
- this.popContext();
- this.pushContext('doYield');
- return null;
- }
- this.context.inputs = [];
- this.pushContext('doYield');
- this.pushContext();
-};
-
-// Process interpolated iteration primitives
-
-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.
- // Distinguish between linked and arrayed lists.
-
- var next;
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = {
- source : list,
- remaining : list.length(),
- idx : 0
- };
- }
- if (this.context.accumulator.remaining === 0) {
- return;
- }
- this.context.accumulator.remaining -= 1;
- if (this.context.accumulator.source.isLinked) {
- next = this.context.accumulator.source.at(1);
- this.context.accumulator.source =
- this.context.accumulator.source.cdr();
- } else { // arrayed
- this.context.accumulator.idx += 1;
- next = list.at(this.context.accumulator.idx);
- }
- this.pushContext('doYield');
- this.pushContext();
- this.context.outerContext.variables.addVar(upvar);
- this.context.outerContext.variables.setVar(upvar, next);
- this.evaluate(script, new List([next]), true);
-};
-
-Process.prototype.doFor = function (upvar, start, end, script) {
- // perform a script for every integer step between start and stop,
- // assigning the current iteration index to a variable with the
- // name specified in the "upvar" parameter, so it can be referenced
- // within the script.
-
- var vars = this.context.outerContext.variables,
- dta = this.context.accumulator;
- if (dta === null) {
- this.assertType(start, 'number');
- this.assertType(end, 'number');
- dta = this.context.accumulator = {
- test : +start < +end ?
- (() => vars.getVar(upvar) > +end)
- : (() => vars.getVar(upvar) < +end),
- step : +start < +end ? 1 : -1,
- parms : new List() // empty parameters, reusable to avoid GC
- };
- vars.addVar(upvar);
- vars.setVar(upvar, Math.floor(+start));
- } else {
- vars.changeVar(upvar, dta.step);
- }
- if (dta.test()) {return; }
- this.pushContext('doYield');
- this.pushContext();
- this.evaluate(script, dta.parms, true);
-};
-
-// Process interpolated HOF primitives
-
-/*
- this.context.inputs:
- [0] - reporter
- [1] - list (original source)
- -----------------------------
- [2] - last reporter evaluation result
-
- these primitives used to store the accumulated data in the unused parts
- of the context's input-array. For reasons obscure to me this led to
- JS stack overflows when used on large lists (> 150 k items). As a remedy
- aggregations are now accumulated in the "accumulator" property slot
- of Context. Why this speeds up execution by orders of magnitude while
- "fixing" the stack-overflow issue eludes me. -Jens
-*/
-
-Process.prototype.reportMap = function (reporter, list) {
- // answer a new list containing the results of the reporter applied
- // to each value of the given list. Distinguish between linked and
- // arrayed lists.
- // if the reporter uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - element
- // #2 - optional | index
- // #3 - optional | source list
-
- var next, index, parms;
- if (list.isLinked) {
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = {
- source : list,
- idx : 1,
- target : new List(),
- end : null,
- remaining : list.length()
- };
- this.context.accumulator.target.isLinked = true;
- this.context.accumulator.end = this.context.accumulator.target;
- } else if (this.context.inputs.length > 2) {
- this.context.accumulator.end.rest = list.cons(
- this.context.inputs.pop()
- );
- this.context.accumulator.end = this.context.accumulator.end.rest;
- this.context.accumulator.idx += 1;
- this.context.accumulator.remaining -= 1;
- }
- if (this.context.accumulator.remaining === 0) {
- this.context.accumulator.end.rest = list.cons(
- this.context.inputs[2]
- ).cdr();
- this.returnValueToParentContext(
- this.context.accumulator.target.cdr()
- );
- return;
- }
- index = this.context.accumulator.idx;
- next = this.context.accumulator.source.at(1);
- this.context.accumulator.source = this.context.accumulator.source.cdr();
- } else { // arrayed
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = [];
- } else if (this.context.inputs.length > 2) {
- this.context.accumulator.push(this.context.inputs.pop());
- }
- if (this.context.accumulator.length === list.length()) {
- this.returnValueToParentContext(
- new List(this.context.accumulator)
- );
- return;
- }
- index = this.context.accumulator.length + 1;
- next = list.at(index);
- }
- this.pushContext();
- parms = [next];
- if (reporter.inputs.length > 1) {
- parms.push(index);
- }
- if (reporter.inputs.length > 2) {
- parms.push(list);
- }
- this.evaluate(reporter, new List(parms));
-};
-
-Process.prototype.reportKeep = function (predicate, list) {
- // Filter - answer a new list containing the items of the list for which
- // the predicate evaluates TRUE.
- // Distinguish between linked and arrayed lists.
- // if the predicate uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - element
- // #2 - optional | index
- // #3 - optional | source list
-
- var next, index, parms;
- if (list.isLinked) {
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = {
- source : list,
- idx: 1,
- target : new List(),
- end : null,
- remaining : list.length()
- };
- this.context.accumulator.target.isLinked = true;
- this.context.accumulator.end = this.context.accumulator.target;
- } else if (this.context.inputs.length > 2) {
- if (this.context.inputs.pop() === true) {
- this.context.accumulator.end.rest = list.cons(
- this.context.accumulator.source.at(1)
- );
- this.context.accumulator.end =
- this.context.accumulator.end.rest;
- }
- this.context.accumulator.remaining -= 1;
- this.context.accumulator.idx += 1;
- this.context.accumulator.source =
- this.context.accumulator.source.cdr();
- }
- if (this.context.accumulator.remaining === 0) {
- this.context.accumulator.end.rest = new List();
- this.returnValueToParentContext(
- this.context.accumulator.target.cdr()
- );
- return;
- }
- index = this.context.accumulator.idx;
- next = this.context.accumulator.source.at(1);
- } else { // arrayed
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = {
- idx : 0,
- target : []
- };
- } else if (this.context.inputs.length > 2) {
- if (this.context.inputs.pop() === true) {
- this.context.accumulator.target.push(
- list.at(this.context.accumulator.idx)
- );
- }
- }
- if (this.context.accumulator.idx === list.length()) {
- this.returnValueToParentContext(
- new List(this.context.accumulator.target)
- );
- return;
- }
- this.context.accumulator.idx += 1;
- index = this.context.accumulator.idx;
- next = list.at(index);
- }
- this.pushContext();
- parms = [next];
- if (predicate.inputs.length > 1) {
- parms.push(index);
- }
- if (predicate.inputs.length > 2) {
- parms.push(list);
- }
- this.evaluate(predicate, new List(parms));
-};
-
-Process.prototype.reportFindFirst = function (predicate, list) {
- // Find - answer the first item of the list for which
- // the predicate evaluates TRUE.
- // Distinguish between linked and arrayed lists.
- // if the predicate uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - element
- // #2 - optional | index
- // #3 - optional | source list
-
- var next, index, parms;
- if (list.isLinked) {
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = {
- source : list,
- idx : 1,
- remaining : list.length()
- };
- } else if (this.context.inputs.length > 2) {
- if (this.context.inputs.pop() === true) {
- this.returnValueToParentContext(
- this.context.accumulator.source.at(1)
- );
- return;
- }
- this.context.accumulator.remaining -= 1;
- this.context.accumulator.idx += 1;
- this.context.accumulator.source =
- this.context.accumulator.source.cdr();
- }
- if (this.context.accumulator.remaining === 0) {
- this.returnValueToParentContext('');
- return;
- }
- index = this.context.accumulator.idx;
- next = this.context.accumulator.source.at(1);
- } else { // arrayed
- if (this.context.accumulator === null) {
- this.assertType(list, 'list');
- this.context.accumulator = {
- idx : 0,
- current : null
- };
- } else if (this.context.inputs.length > 2) {
- if (this.context.inputs.pop() === true) {
- this.returnValueToParentContext(
- this.context.accumulator.current
- );
- return;
- }
- }
- if (this.context.accumulator.idx === list.length()) {
- this.returnValueToParentContext('');
- return;
- }
- this.context.accumulator.idx += 1;
- index = this.context.accumulator.idx;
- next = list.at(index);
- this.context.accumulator.current = next;
- }
- this.pushContext();
- parms = [next];
- if (predicate.inputs.length > 1) {
- parms.push(index);
- }
- if (predicate.inputs.length > 2) {
- parms.push(list);
- }
- this.evaluate(predicate, new List(parms));
-};
-
-Process.prototype.reportCombine = function (list, reporter) {
- // Fold - answer an aggregation of all list items from "left to right"
- // Distinguish between linked and arrayed lists.
- // if the reporter uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - accumulator
- // #2 - element
- // #3 - optional | index
- // #4 - optional | source list
-
- var next, current, index, parms;
- this.assertType(list, 'list');
- if (list.length() < 2) {
- this.returnValueToParentContext(list.length() ? list.at(1) : 0);
- return;
- }
- if (list.isLinked) {
- if (this.context.accumulator === null) {
- this.context.accumulator = {
- source : list.cdr(),
- idx : 1,
- target : list.at(1),
- remaining : list.length() - 1
- };
- } else if (this.context.inputs.length > 2) {
- this.context.accumulator.target = this.context.inputs.pop();
- this.context.accumulator.remaining -= 1;
- this.context.accumulator.idx += 1;
- this.context.accumulator.source =
- this.context.accumulator.source.cdr();
- }
- if (this.context.accumulator.remaining === 0) {
- this.returnValueToParentContext(this.context.accumulator.target);
- return;
- }
- next = this.context.accumulator.source.at(1);
- } else { // arrayed
- if (this.context.accumulator === null) {
- this.context.accumulator = {
- idx : 1,
- target : list.at(1)
- };
- } else if (this.context.inputs.length > 2) {
- this.context.accumulator.target = this.context.inputs.pop();
- }
- if (this.context.accumulator.idx === list.length()) {
- this.returnValueToParentContext(this.context.accumulator.target);
- return;
- }
- this.context.accumulator.idx += 1;
- next = list.at(this.context.accumulator.idx);
- }
- index = this.context.accumulator.idx;
- current = this.context.accumulator.target;
- this.pushContext();
- parms = [current, next];
- if (reporter.inputs.length > 2) {
- parms.push(index);
- }
- if (reporter.inputs.length > 3) {
- parms.push(list);
- }
- this.evaluate(reporter, new List(parms));
-};
-
-// Process interpolated primitives
-
-Process.prototype.doWait = function (secs) {
- if (!this.context.startTime) {
- this.context.startTime = Date.now();
- }
- if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
- if (!this.isAtomic && (secs === 0)) {
- // "wait 0 secs" is a plain "yield"
- // that can be overridden by "warp"
- this.readyToYield = true;
- }
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.doGlide = function (secs, endX, endY) {
- if (!this.context.startTime) {
- this.context.startTime = Date.now();
- this.context.startValue = new Point(
- this.blockReceiver().xPosition(),
- this.blockReceiver().yPosition()
- );
- }
- if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
- this.blockReceiver().gotoXY(endX, endY);
- return null;
- }
- this.blockReceiver().glide(
- secs * 1000,
- endX,
- endY,
- Date.now() - this.context.startTime,
- this.context.startValue
- );
-
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.doSayFor = function (data, secs) {
- if (!this.context.startTime) {
- this.context.startTime = Date.now();
- this.blockReceiver().bubble(data);
- }
- if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
- this.blockReceiver().stopTalking();
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.doThinkFor = function (data, secs) {
- if (!this.context.startTime) {
- this.context.startTime = Date.now();
- this.blockReceiver().doThink(data);
- }
- if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
- this.blockReceiver().stopTalking();
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.blockReceiver = function () {
- return this.context ? this.context.receiver || this.homeContext.receiver
- : this.homeContext.receiver || this.receiver;
-};
-
-// Process sound primitives (interpolated)
-
-Process.prototype.playSound = function (name) {
- if (name instanceof List) {
- return this.doPlaySoundAtRate(name, 44100);
- }
- return this.blockReceiver().doPlaySound(name);
-};
-
-Process.prototype.doPlaySoundUntilDone = function (name) {
- if (this.context.activeAudio === null) {
- this.context.activeAudio = this.playSound(name);
- }
- if (name === null || this.context.activeAudio.ended
- || this.context.activeAudio.terminated) {
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.doStopAllSounds = function () {
- var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.threads.processes.forEach(thread => {
- if (thread.context) {
- thread.context.stopMusic();
- if (thread.context.activeAudio) {
- thread.popContext();
- }
- }
- });
- stage.stopAllActiveSounds();
- }
-};
-
-Process.prototype.doPlaySoundAtRate = function (name, rate) {
- var sound, samples, ctx, gain, pan, source, rcvr;
-
- if (!(name instanceof List)) {
- sound = name instanceof Sound ? name
- : (typeof name === 'number' ? this.blockReceiver().sounds.at(name)
- : detect(
- this.blockReceiver().sounds.asArray(),
- s => s.name === name.toString()
- )
- );
- if (!sound.audioBuffer) {
- this.decodeSound(sound);
- return;
- }
- samples = this.reportGetSoundAttribute('samples', sound);
- } else {
- samples = name;
- }
-
- rcvr = this.blockReceiver();
- ctx = rcvr.audioContext();
- gain = rcvr.getGainNode();
- pan = rcvr.getPannerNode();
- source = this.encodeSound(samples, rate);
- rcvr.setVolume(rcvr.volume);
- source.connect(gain);
- if (pan) {
- gain.connect(pan);
- pan.connect(ctx.destination);
- rcvr.setPan(rcvr.pan);
- } else {
- gain.connect(ctx.destination);
- }
- source.pause = source.stop;
- source.ended = false;
- source.onended = () => source.ended = true;
- source.start();
- rcvr.parentThatIsA(StageMorph).activeSounds.push(source);
- return source;
-};
-
-Process.prototype.reportGetSoundAttribute = function (choice, soundName) {
- var sound = soundName instanceof Sound ? soundName
- : (typeof soundName === 'number' ?
- this.blockReceiver().sounds.at(soundName)
- : (soundName instanceof List ? this.encodeSound(soundName)
- : detect(
- this.blockReceiver().sounds.asArray(),
- s => s.name === soundName.toString()
- )
- )
- ),
- option = this.inputOption(choice);
-
- if (option === 'name') {
- return sound.name;
- }
-
- if (!sound.audioBuffer) {
- this.decodeSound(sound);
- return;
- }
-
- switch (option) {
- case 'samples':
- if (!sound.cachedSamples) {
- sound.cachedSamples = function (sound, untype) {
- var buf = sound.audioBuffer,
- result, i;
- if (buf.numberOfChannels > 1) {
- result = new List();
- for (i = 0; i < buf.numberOfChannels; i += 1) {
- result.add(new List(untype(buf.getChannelData(i))));
- }
- return result;
- }
- return new List(untype(buf.getChannelData(0)));
- } (sound, this.untype);
- }
- return sound.cachedSamples;
- case 'sample rate':
- return sound.audioBuffer.sampleRate;
- case 'duration':
- return sound.audioBuffer.duration;
- case 'length':
- return sound.audioBuffer.length;
- case 'number of channels':
- return sound.audioBuffer.numberOfChannels;
- default:
- return 0;
- }
-};
-
-Process.prototype.decodeSound = function (sound, callback) {
- // private - callback is optional and invoked with sound as argument
- var base64, binaryString, len, bytes, i, arrayBuffer, audioCtx;
-
- if (sound.audioBuffer) {
- return (callback || nop)(sound);
- }
- if (!sound.isDecoding) {
- base64 = sound.audio.src.split(',')[1];
- binaryString = window.atob(base64);
- len = binaryString.length;
- bytes = new Uint8Array(len);
- for (i = 0; i < len; i += 1) {
- bytes[i] = binaryString.charCodeAt(i);
- }
- arrayBuffer = bytes.buffer;
- audioCtx = Note.prototype.getAudioContext();
- sound.isDecoding = true;
- audioCtx.decodeAudioData(
- arrayBuffer,
- buffer => {
- sound.audioBuffer = buffer;
- sound.isDecoding = false;
- },
- err => {
- sound.isDecoding = false;
- this.handleError(err);
- }
- );
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.encodeSound = function (samples, rate) {
- // private
- var rcvr = this.blockReceiver(),
- ctx = rcvr.audioContext(),
- channels = (samples.at(1) instanceof List) ? samples.length() : 1,
- frameCount = (channels === 1) ?
- samples.length()
- : samples.at(1).length(),
- arrayBuffer = ctx.createBuffer(channels, frameCount, +rate || 44100),
- i,
- source;
-
- if (!arrayBuffer.copyToChannel) {
- arrayBuffer.copyToChannel = function (src, channel) {
- var buffer = this.getChannelData(channel);
- for (i = 0; i < src.length; i += 1) {
- buffer[i] = src[i];
- }
- };
- }
- if (channels === 1) {
- arrayBuffer.copyToChannel(
- Float32Array.from(samples.asArray()),
- 0,
- 0
- );
- } else {
- for (i = 0; i < channels; i += 1) {
- arrayBuffer.copyToChannel(
- Float32Array.from(samples.at(i + 1).asArray()),
- i,
- 0
- );
- }
- }
- source = ctx.createBufferSource();
- source.buffer = arrayBuffer;
- source.audioBuffer = source.buffer;
- return source;
-};
-
-// Process first-class sound creation from samples, interpolated
-
-Process.prototype.reportNewSoundFromSamples = function (samples, rate) {
- // this method inspired by: https://github.com/Jam3/audiobuffer-to-wav
- // https://www.russellgood.com/how-to-convert-audiobuffer-to-audio-file
-
- var audio, blob, reader;
-
- if (isNil(this.context.accumulator)) {
- this.assertType(samples, 'list'); // check only the first time
- this.context.accumulator = {
- audio: null
- };
- audio = new Audio();
- blob = new Blob(
- [
- this.audioBufferToWav(
- this.encodeSound(samples, rate || 44100).audioBuffer
- )
- ],
- {type: "audio/wav"}
- );
- reader = new FileReader();
- reader.onload = () => {
- audio.src = reader.result;
- this.context.accumulator.audio = audio;
- };
- reader.readAsDataURL(blob);
- }
- if (this.context.accumulator.audio) {
- return new Sound(
- this.context.accumulator.audio,
- this.blockReceiver().newSoundName(localize('sound'))
- );
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.audioBufferToWav = function (buffer, opt) {
- var numChannels = buffer.numberOfChannels,
- sampleRate = buffer.sampleRate,
- format = (opt || {}).float32 ? 3 : 1,
- bitDepth = format === 3 ? 32 : 16,
- result;
-
- function interleave(inputL, inputR) {
- var length = inputL.length + inputR.length,
- result = new Float32Array(length),
- index = 0,
- inputIndex = 0;
-
- while (index < length) {
- result[index++] = inputL[inputIndex];
- result[index++] = inputR[inputIndex];
- inputIndex += 1;
- }
- return result;
- }
-
- if (numChannels === 2) {
- result = interleave(
- buffer.getChannelData(0),
- buffer.getChannelData(1)
- );
- } else {
- result = buffer.getChannelData(0);
- }
- return this.encodeWAV(result, format, sampleRate, numChannels, bitDepth);
-};
-
-Process.prototype.encodeWAV = function (
- samples,
- format,
- sampleRate,
- numChannels,
- bitDepth
-) {
- var bytesPerSample = bitDepth / 8,
- blockAlign = numChannels * bytesPerSample,
- buffer = new ArrayBuffer(44 + samples.length * bytesPerSample),
- view = new DataView(buffer);
-
- function writeFloat32(output, offset, input) {
- for (var i = 0; i < input.length; i += 1, offset += 4) {
- output.setFloat32(offset, input[i], true);
- }
- }
-
- function floatTo16BitPCM(output, offset, input) {
- var i, s;
- for (i = 0; i < input.length; i += 1, offset += 2) {
- s = Math.max(-1, Math.min(1, input[i]));
- output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
- }
- }
-
- function writeString(view, offset, string) {
- for (var i = 0; i < string.length; i += 1) {
- view.setUint8(offset + i, string.charCodeAt(i));
- }
- }
-
- writeString(view, 0, 'RIFF'); // RIFF identifier
- // RIFF chunk length:
- view.setUint32(4, 36 + samples.length * bytesPerSample, true);
- writeString(view, 8, 'WAVE'); // RIFF type
- writeString(view, 12, 'fmt '); // format chunk identifier
- view.setUint32(16, 16, true); // format chunk length
- view.setUint16(20, format, true); // sample format (raw)
- view.setUint16(22, numChannels, true); // channel count
- view.setUint32(24, sampleRate, true); // sample rate
- // byte rate (sample rate * block align):
- view.setUint32(28, sampleRate * blockAlign, true);
- // block align (channel count * bytes per sample):
- view.setUint16(32, blockAlign, true);
- view.setUint16(34, bitDepth, true); // bits per sample
- writeString(view, 36, 'data'); // data chunk identifier
- // data chunk length:
- view.setUint32(40, samples.length * bytesPerSample, true);
- if (format === 1) { // Raw PCM
- floatTo16BitPCM(view, 44, samples);
- } else {
- writeFloat32(view, 44, samples);
- }
- return buffer;
-};
-
-// Process audio input (interpolated)
-
-Process.prototype.reportAudio = function (choice) {
- var stage = this.blockReceiver().parentThatIsA(StageMorph),
- selection = this.inputOption(choice);
- if (selection === 'resolution') {
- return stage.microphone.binSize();
- }
- if (selection === 'modifier') {
- return stage.microphone.modifier;
- }
- if (stage.microphone.isOn()) {
- switch (selection) {
- case 'volume':
- return stage.microphone.volume * 100;
- case 'frequency':
- return stage.microphone.pitch;
- case 'note':
- return stage.microphone.note;
- case 'samples':
- return new List(this.untype(stage.microphone.signals));
- case 'sample rate':
- return stage.microphone.audioContext.sampleRate;
- case 'output':
- return new List(this.untype(stage.microphone.output));
- case 'spectrum':
- return new List(this.untype(stage.microphone.frequencies));
- default:
- return null;
- }
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.untype = function (typedArray) {
- var len = typedArray.length,
- arr = new Array(len),
- i;
- for (i = 0; i < len; i += 1) {
- arr[i] = typedArray[i];
- }
- return arr;
-};
-
-Process.prototype.setMicrophoneModifier = function (modifier) {
- var stage = this.blockReceiver().parentThatIsA(StageMorph),
- invalid = [
- 'sprite',
- 'stage',
- 'list',
- 'costume',
- 'sound',
- 'number',
- 'text',
- 'Boolean'
- ];
- if (!modifier || contains(invalid, this.reportTypeOf(modifier))) {
- stage.microphone.modifier = null;
- stage.microphone.stop();
- return;
- }
- stage.microphone.modifier = modifier;
- stage.microphone.compiledModifier = this.reportCompiled(modifier, 1);
- stage.microphone.compilerProcess = this;
-};
-
-// Process user prompting primitives (interpolated)
-
-Process.prototype.doAsk = function (data) {
- var stage = this.homeContext.receiver.parentThatIsA(StageMorph),
- rcvr = this.blockReceiver(),
- isStage = rcvr instanceof StageMorph,
- isHiddenSprite = rcvr instanceof SpriteMorph && !rcvr.isVisible,
- activePrompter;
-
- stage.keysPressed = {};
- if (!this.prompter) {
- activePrompter = detect(
- stage.children,
- morph => morph instanceof StagePrompterMorph
- );
- if (!activePrompter) {
- if (!isStage && !isHiddenSprite) {
- rcvr.bubble(data, false, true);
- }
- this.prompter = new StagePrompterMorph(
- isStage || isHiddenSprite ? data : null
- );
- if (stage.scale < 1) {
- this.prompter.setWidth(stage.width() - 10);
- } else {
- this.prompter.setWidth(stage.dimensions.x - 20);
- }
- this.prompter.fixLayout();
- this.prompter.setCenter(stage.center());
- this.prompter.setBottom(stage.bottom() - this.prompter.border);
- stage.add(this.prompter);
- this.prompter.inputField.edit();
- stage.changed();
- }
- } else {
- if (this.prompter.isDone) {
- stage.lastAnswer = this.prompter.inputField.getValue();
- this.prompter.destroy();
- this.prompter = null;
- if (!isStage) {rcvr.stopTalking(); }
- return null;
- }
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.reportLastAnswer = function () {
- return this.homeContext.receiver.parentThatIsA(StageMorph).lastAnswer;
-};
-
-// Process URI retrieval (interpolated)
-
-Process.prototype.reportURL = function (url) {
- var response;
- if (!this.httpRequest) {
- // use the location protocol unless the user specifies otherwise
- if (url.indexOf('//') < 0 || url.indexOf('//') > 8) {
- if (location.protocol === 'file:') {
- // allow requests from locally loaded sources
- url = 'https://' + url;
- } else {
- url = location.protocol + '//' + url;
- }
- }
- this.httpRequest = new XMLHttpRequest();
- this.httpRequest.open("GET", url, true);
- // cache-control, commented out for now
- // added for Snap4Arduino but has issues with local robot servers
- // this.httpRequest.setRequestHeader('Cache-Control', 'max-age=0');
- this.httpRequest.send(null);
- if (this.context.isCustomCommand) {
- // special case when ignoring the result, e.g. when
- // communicating with a robot to control its motors
- this.httpRequest = null;
- return null;
- }
- } else if (this.httpRequest.readyState === 4) {
- response = this.httpRequest.responseText;
- this.httpRequest = null;
- return response;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-// Process event messages primitives
-
-Process.prototype.doBroadcast = function (message) {
- // messages are user-defined events, and by default global, same as in
- // Scratch. An experimental feature, messages can be sent to a single
- // sprite or to a list of sprites by using a 2-item list in the message
- // slot, where the first slot is a message text, and the second slot
- // its recipient(s), identified either by a single name or sprite, or by
- // a list of names or sprites (can be a heterogeneous list).
-
- var stage = this.homeContext.receiver.parentThatIsA(StageMorph),
- thisObj,
- msg = this.inputOption(message),
- trg,
- rcvrs,
- procs = [];
-
- if (!this.canBroadcast) {
- return [];
- }
- if (message instanceof List && (message.length() === 2)) {
- thisObj = this.blockReceiver();
- msg = message.at(1);
- trg = message.at(2);
- if (isSnapObject(trg)) {
- rcvrs = [trg];
- } else if (isString(trg)) {
- // assume the string to be the name of a sprite or the stage
- if (trg === stage.name) {
- rcvrs = [stage];
- } else {
- rcvrs = [this.getOtherObject(trg, thisObj, stage)];
- }
- } else if (trg instanceof List) {
- // assume all elements to be sprites or sprite names
- rcvrs = trg.itemsArray().map(each =>
- this.getOtherObject(each, thisObj, stage)
- );
- } else {
- return; // abort
- }
- } else { // global
- rcvrs = stage.children.concat(stage);
- }
- if (msg !== '') {
- stage.lastMessage = message; // the actual data structure
- rcvrs.forEach(morph => {
- if (isSnapObject(morph)) {
- morph.allHatBlocksFor(msg).forEach(block => {
- procs.push(stage.threads.startProcess(
- block,
- morph,
- stage.isThreadSafe
- ));
- });
- }
- });
- (stage.messageCallbacks[''] || []).forEach(callback =>
- callback(msg) // for "any" message, pass it along as argument
- );
- (stage.messageCallbacks[msg] || []).forEach(callback =>
- callback() // for a particular message
- );
- }
- return procs;
-};
-
-Process.prototype.doBroadcastAndWait = function (message) {
- if (!this.context.activeSends) {
- this.context.activeSends = this.doBroadcast(message);
- if (this.isRunning()) {
- this.context.activeSends.forEach(proc =>
- proc.runStep()
- );
- }
- }
- this.context.activeSends = this.context.activeSends.filter(proc =>
- proc.isRunning()
- );
- if (this.context.activeSends.length === 0) {
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.getLastMessage = function () {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- return stage.getLastMessage();
- }
- }
- return '';
-};
-
-Process.prototype.doSend = function (message, target) {
- var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- this.doBroadcast(
- new List(
- [
- message,
- target instanceof List ? target :
- target === stage.name ? new List([stage]) :
- new List([target])
- ]
- )
- );
-};
-
-// Process type inference
-
-Process.prototype.reportIsA = function (thing, typeString) {
- return this.reportTypeOf(thing) === this.inputOption(typeString);
-};
-
-Process.prototype.assertType = function (thing, typeString) {
- // make sure "thing" is a particular type or any of a number of types
- // and raise an error if not
- // use responsibly wrt performance implications
- var thingType = this.reportTypeOf(thing);
- if (thingType === typeString) {return true; }
- if (typeString instanceof Array && contains(typeString, thingType)) {
- return true;
- }
- throw new Error('expecting ' + typeString + ' but getting ' + thingType);
-};
-
-Process.prototype.assertAlive = function (thing) {
- if (thing && thing.isCorpse) {
- throw new Error('cannot operate on a deleted sprite');
- }
-};
-
-Process.prototype.reportTypeOf = function (thing) {
- // answer a string denoting the argument's type
- var exp;
- if (thing === null || (thing === undefined)) {
- return 'nothing';
- }
- if (thing === true || (thing === false)) {
- return 'Boolean';
- }
- if (thing instanceof List) {
- return 'list';
- }
- if (parseFloat(thing) === +thing) { // I hate this! -Jens
- return 'number';
- }
- if (isString(thing)) {
- return 'text';
- }
- if (thing instanceof SpriteMorph) {
- return 'sprite';
- }
- if (thing instanceof StageMorph) {
- return 'stage';
- }
- if (thing instanceof Costume) {
- return 'costume';
- }
- if (thing instanceof Sound) {
- return 'sound';
- }
- if (thing instanceof Context) {
- if (thing.expression instanceof RingMorph) {
- return thing.expression.dataType();
- }
- if (thing.expression instanceof ReporterBlockMorph) {
- if (thing.expression.isPredicate) {
- return 'predicate';
- }
- return 'reporter';
- }
-
- if (thing.expression instanceof Array) {
- exp = thing.expression[thing.pc || 0];
- if (exp.isPredicate) {
- return 'predicate';
- }
- if (exp instanceof RingMorph) {
- return exp.dataType();
- }
- if (exp instanceof ReporterBlockMorph) {
- return 'reporter';
- }
- if (exp instanceof CommandBlockMorph) {
- return 'command';
- }
- return 'reporter'; // 'ring';
- }
-
- if (thing.expression instanceof CommandBlockMorph) {
- return 'command';
- }
- return 'reporter'; // 'ring';
- }
- return 'undefined';
-};
-
-// Process math primtives - hyper-dyadic
-
-Process.prototype.hyperDyadic = function (baseOp, a, b) {
- // enable dyadic operations to be performed on lists and tables
- var len, a_info, b_info, i, result;
- if (this.enableHyperOps) {
- a_info = this.examine(a);
- b_info = this.examine(b);
- if (a_info.isScalar && b_info.isScalar &&
- (a_info.rank !== b_info.rank)) {
- // keep the shape of the higher rank
- return this.hyperZip(
- baseOp,
- a_info.rank > b_info.rank ? a : a_info.scalar,
- b_info.rank > a_info.rank ? b : b_info.scalar
- );
- }
- if (a_info.rank > 1) {
- if (b_info.rank > 1) {
- if (a.length() !== b.length()) {
- // test for special cased scalars in single-item lists
- if (a_info.isScalar) {
- return this.hyperDyadic(baseOp, a_info.scalar, b);
- }
- if (b_info.isScalar) {
- return this.hyperDyadic(baseOp, a, b_info.scalar);
- }
- }
- // zip both arguments ignoring out-of-bounds indices
- a = a.asArray();
- b = b.asArray();
- len = Math.min(a.length, b.length);
- result = new Array(len);
- for (i = 0; i < len; i += 1) {
- result[i] = this.hyperDyadic(baseOp, a[i], b[i]);
- }
- return new List(result);
- }
- if (a_info.isScalar) {
- return this.hyperZip(baseOp, a_info.scalar, b);
- }
- return a.map(each => this.hyperDyadic(baseOp, each, b));
- }
- if (b_info.rank > 1) {
- if (b_info.isScalar) {
- return this.hyperZip(baseOp, a, b_info.scalar);
- }
- return b.map(each => this.hyperDyadic(baseOp, a, each));
- }
- return this.hyperZip(baseOp, a, b);
- }
- return baseOp(a, b);
-};
-
-Process.prototype.hyperZip = function (baseOp, a, b) {
- // enable dyadic operations to be performed on lists and tables
- var len, i, result,
- a_info = this.examine(a),
- b_info = this.examine(b);
- if (a instanceof List) {
- if (b instanceof List) {
- if (a.length() !== b.length()) {
- // test for special cased scalars in single-item lists
- if (a_info.isScalar) {
- return this.hyperZip(baseOp, a_info.scalar, b);
- }
- if (b_info.isScalar) {
- return this.hyperZip(baseOp, a, b_info.scalar);
- }
- }
- // zip both arguments ignoring out-of-bounds indices
- a = a.asArray();
- b = b.asArray();
- len = Math.min(a.length, b.length);
- result = new Array(len);
- for (i = 0; i < len; i += 1) {
- result[i] = this.hyperZip(baseOp, a[i], b[i]);
- }
- return new List(result);
- }
- return a.map(each => this.hyperZip(baseOp, each, b));
- }
- if (b instanceof List) {
- return b.map(each => this.hyperZip(baseOp, a, each));
- }
- return baseOp(a, b);
-};
-
-Process.prototype.dimensions = function (data) {
- var dim = [],
- cur = data;
- while (cur instanceof List) {
- dim.push(cur.length());
- cur = cur.at(1);
- }
- return dim;
-};
-
-Process.prototype.isMatrix = function (data) {
- return this.rank(data) > 1;
-};
-
-Process.prototype.rank = function(data) {
- return this.dimensions(data).length;
-};
-
-Process.prototype.isScalar = function (data) {
- return this.dimensions.every(n => n === 1);
-};
-
-Process.prototype.scalar = function (data) {
- var cur = data;
- while (cur instanceof List) {
- cur = cur.at(1);
- }
- return cur;
-};
-
-Process.prototype.examine = function (data) {
- var cur = data,
- meta = {
- rank: 0,
- isScalar: true,
- scalar: null
- };
- while (cur instanceof List) {
- meta.rank += 1;
- if (cur.length() !== 1) {
- meta.isScalar = false;
- }
- cur = cur.at(1);
- }
- meta.scalar = cur;
- return meta;
-};
-
-// Process math primtives - arithmetic
-
-Process.prototype.reportSum = function (a, b) {
- return this.hyperDyadic(this.reportBasicSum, a, b);
-};
-
-Process.prototype.reportBasicSum = function (a, b) {
- return +a + (+b);
-};
-
-Process.prototype.reportDifference = function (a, b) {
- return this.hyperDyadic(this.reportBasicDifference, a, b);
-};
-
-Process.prototype.reportBasicDifference = function (a, b) {
- return +a - +b;
-};
-
-Process.prototype.reportProduct = function (a, b) {
- return this.hyperDyadic(this.reportBasicProduct, a, b);
-};
-
-Process.prototype.reportBasicProduct = function (a, b) {
- return +a * +b;
-};
-
-Process.prototype.reportQuotient = function (a, b) {
- return this.hyperDyadic(this.reportBasicQuotient, a, b);
-};
-
-Process.prototype.reportBasicQuotient = function (a, b) {
- return +a / +b;
-};
-
-Process.prototype.reportPower = function (a, b) {
- return this.hyperDyadic(this.reportBasicPower, a, b);
-};
-
-Process.prototype.reportBasicPower = function (a, b) {
- return Math.pow(+a, +b);
-};
-
-Process.prototype.reportModulus = function (a, b) {
- return this.hyperDyadic(this.reportBasicModulus, a, b);
-};
-
-Process.prototype.reportBasicModulus = function (a, b) {
- var x = +a,
- y = +b;
- return ((x % y) + y) % y;
-};
-
-Process.prototype.reportRandom = function (a, b) {
- return this.hyperDyadic(this.reportBasicRandom, a, b);
-};
-
-Process.prototype.reportBasicRandom = function (min, max) {
- var floor = +min,
- ceil = +max;
- if ((floor % 1 !== 0) || (ceil % 1 !== 0)) {
- return Math.random() * (ceil - floor) + floor;
- }
- return Math.floor(Math.random() * (ceil - floor + 1)) + floor;
-};
-
-// Process logic primitives - hyper-diadic / monadic where applicable
-
-Process.prototype.reportLessThan = function (a, b) {
- return this.hyperDyadic(this.reportBasicLessThan, a, b);
-};
-
-Process.prototype.reportBasicLessThan = function (a, b) {
- var x = +a,
- y = +b;
- if (isNaN(x) || isNaN(y)) {
- x = a;
- y = b;
- }
- return x < y;
-};
-
-Process.prototype.reportNot = function (bool) {
- if (this.enableHyperOps) {
- if (bool instanceof List) {
- return bool.map(each => this.reportNot(each));
- }
- }
- // this.assertType(bool, 'Boolean');
- return !bool;
-};
-
-Process.prototype.reportGreaterThan = function (a, b) {
- return this.hyperDyadic(this.reportBasicGreaterThan, a, b);
-};
-
-Process.prototype.reportBasicGreaterThan = function (a, b) {
- var x = +a,
- y = +b;
- if (isNaN(x) || isNaN(y)) {
- x = a;
- y = b;
- }
- return x > y;
-};
-
-Process.prototype.reportEquals = function (a, b) {
- return snapEquals(a, b);
-};
-
-Process.prototype.reportIsIdentical = function (a, b) {
- var tag = 'idTag';
- if (this.isImmutable(a) || this.isImmutable(b)) {
- return snapEquals(a, b);
- }
-
- function clear() {
- if (Object.prototype.hasOwnProperty.call(a, tag)) {
- delete a[tag];
- }
- if (Object.prototype.hasOwnProperty.call(b, tag)) {
- delete b[tag];
- }
- }
-
- clear();
- a[tag] = Date.now();
- if (b[tag] === a[tag]) {
- clear();
- return true;
- }
- clear();
- return false;
-};
-
-Process.prototype.isImmutable = function (obj) {
- // private
- var type = this.reportTypeOf(obj);
- return type === 'nothing' ||
- type === 'Boolean' ||
- type === 'text' ||
- type === 'number' ||
- type === 'undefined';
-};
-
-Process.prototype.reportBoolean = function (bool) {
- return bool;
-};
-
-// Process hyper-monadic primitives
-
-Process.prototype.reportRound = function (n) {
- if (this.enableHyperOps) {
- if (n instanceof List) {
- return n.map(each => this.reportRound(each));
- }
- }
- return Math.round(+n);
-};
-
-Process.prototype.reportMonadic = function (fname, n) {
- if (this.enableHyperOps) {
- if (n instanceof List) {
- return n.map(each => this.reportMonadic(fname, each));
- }
- }
-
- var x = +n,
- result = 0;
-
- switch (this.inputOption(fname)) {
- case 'abs':
- result = Math.abs(x);
- break;
- // case '\u2212': // minus-sign
- case 'neg':
- result = n * -1;
- break;
- case 'ceiling':
- result = Math.ceil(x);
- break;
- case 'floor':
- result = Math.floor(x);
- break;
- case 'sqrt':
- result = Math.sqrt(x);
- break;
- case 'sin':
- result = Math.sin(radians(x));
- break;
- case 'cos':
- result = Math.cos(radians(x));
- break;
- case 'tan':
- result = Math.tan(radians(x));
- break;
- case 'asin':
- result = degrees(Math.asin(x));
- break;
- case 'acos':
- result = degrees(Math.acos(x));
- break;
- case 'atan':
- result = degrees(Math.atan(x));
- break;
- case 'ln':
- result = Math.log(x);
- break;
- case 'log': // base 10
- result = Math.log10(x);
- break;
- case 'lg': // base 2
- result = Math.log2(x);
- break;
- case 'e^':
- result = Math.exp(x);
- break;
- case '10^':
- result = Math.pow(10, x);
- break;
- case '2^':
- result = Math.pow(2, x);
- break;
- default:
- nop();
- }
- return result;
-};
-
-// Process - non hyper-monadic text primitives
-
-Process.prototype.reportTextFunction = function (fname, string) {
- // currently in dev mode only, not hyper-monadic
- var x = (isNil(string) ? '' : string).toString(),
- result = '';
-
- switch (this.inputOption(fname)) {
- case 'encode URI':
- result = encodeURI(x);
- break;
- case 'decode URI':
- result = decodeURI(x);
- break;
- case 'encode URI component':
- result = encodeURIComponent(x);
- break;
- case 'decode URI component':
- result = decodeURIComponent(x);
- break;
- case 'XML escape':
- result = new XML_Element().escape(x);
- break;
- case 'XML unescape':
- result = new XML_Element().unescape(x);
- break;
- case 'hex sha512 hash':
- result = hex_sha512(x);
- break;
- default:
- nop();
- }
- return result;
-};
-
-Process.prototype.reportJoin = function (a, b) {
- var x = (isNil(a) ? '' : a).toString(),
- y = (isNil(b) ? '' : b).toString();
- return x.concat(y);
-};
-
-Process.prototype.reportJoinWords = function (aList) {
- if (aList instanceof List) {
- return aList.asText();
- }
- return (aList || '').toString();
-};
-
-// Process string ops - hyper-monadic/dyadic
-
-Process.prototype.reportLetter = function (idx, string) {
- return this.hyperDyadic(
- (ix, str) => this.reportBasicLetter(ix, str),
- idx,
- string
- );
-};
-
-Process.prototype.reportBasicLetter = function (idx, string) {
- var str, i;
-
- str = isNil(string) ? '' : string.toString();
- if (this.inputOption(idx) === 'any') {
- idx = this.reportBasicRandom(1, str.length);
- }
- if (this.inputOption(idx) === 'last') {
- idx = str.length;
- }
- i = +(idx || 0);
- return str[i - 1] || '';
-};
-
-Process.prototype.reportStringSize = function (data) {
- if (this.enableHyperOps) {
- if (data instanceof List) {
- return data.map(each => this.reportStringSize(each));
- }
- }
- if (data instanceof List) { // catch a common user error
- return data.length();
- }
- return isNil(data) ? 0 : data.toString().length;
-};
-
-Process.prototype.reportUnicode = function (string) {
- var str;
-
- if (this.enableHyperOps) {
- if (string instanceof List) {
- return string.map(each => this.reportUnicode(each));
- }
- str = isNil(string) ? '\u0000' : string.toString();
- if (str.length > 1) {
- return this.reportUnicode(new List(str.split('')));
- }
- } else {
- str = isNil(string) ? '\u0000' : string.toString();
- }
- if (str.codePointAt) { // support for Unicode in newer browsers.
- return str.codePointAt(0) || 0;
- }
- return str.charCodeAt(0) || 0;
-};
-
-Process.prototype.reportUnicodeAsLetter = function (num) {
- if (this.enableHyperOps) {
- if (num instanceof List) {
- return num.map(each => this.reportUnicodeAsLetter(each));
- }
- }
-
- var code = +(num || 0);
-
- if (String.fromCodePoint) { // support for Unicode in newer browsers.
- return String.fromCodePoint(code);
- }
- return String.fromCharCode(code);
-};
-
-Process.prototype.reportTextSplit = function (string, delimiter) {
- return this.hyperDyadic(
- (str, delim) => this.reportBasicTextSplit(str, delim),
- string,
- delimiter
- );
-};
-
-Process.prototype.reportBasicTextSplit = function (string, delimiter) {
- var types = ['text', 'number'],
- strType = this.reportTypeOf(string),
- delType = this.reportTypeOf(this.inputOption(delimiter)),
- str,
- del;
- if (!contains(types, strType)) {
- throw new Error('expecting text instead of a ' + strType);
- }
- if (!contains(types, delType)) {
- throw new Error('expecting a text delimiter instead of a ' + delType);
- }
- str = isNil(string) ? '' : string.toString();
- switch (this.inputOption(delimiter)) {
- case 'line':
- // 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;
- case 'tab':
- del = '\t';
- break;
- case 'cr':
- del = '\r';
- break;
- case 'word':
- case 'whitespace':
- str = str.trim();
- del = /\s+/;
- break;
- case 'letter':
- del = '';
- break;
- case 'csv':
- return this.parseCSV(string);
- case 'json':
- return this.parseJSON(string);
- /*
- case 'csv records':
- return this.parseCSVrecords(string);
- case 'csv fields':
- return this.parseCSVfields(string);
- */
- default:
- del = isNil(delimiter) ? '' : delimiter.toString();
- }
- return new List(str.split(del));
-};
-
-// Process - parsing primitives
-
-Process.prototype.parseCSV = function (text) {
- // try to address the kludge that Excel sometimes uses commas
- // and sometimes semi-colons as delimiters, try to find out
- // which makes more sense by examining the first line
- return this.rawParseCSV(text, this.guessDelimiterCSV(text));
-};
-
-Process.prototype.guessDelimiterCSV = function (text) {
- // assumes that the first line contains the column headers.
- // report the first delimiter for which parsing the header
- // yields more than a single field, otherwise default to comma
- var delims = [',', ';', '|', '\t'],
- len = delims.length,
- firstLine = text.split('\n')[0],
- i;
- for (i = 0; i < len; i += 1) {
- if (this.rawParseCSV(firstLine, delims[i]).length() > 1) {
- return delims[i];
- }
- }
- return delims[0];
-};
-
-Process.prototype.rawParseCSV = function (text, delim) {
- // RFC 4180
- // parse a csv table into a two-dimensional list.
- // if the table contains just a single row return it a one-dimensional
- // list of fields instead (for backwards-compatibility)
- var prev = '',
- fields = [''],
- records = [fields],
- col = 0,
- r = 0,
- esc = true,
- len = text.length,
- idx,
- char;
- delim = delim || ',';
- for (idx = 0; idx < len; idx += 1) {
- char = text[idx];
- if (char === '\"') {
- if (esc && char === prev) {
- fields[col] += char;
- }
- esc = !esc;
- } else if (char === delim && esc) {
- char = '';
- col += 1;
- fields[col] = char;
- } else if (char === '\r' && esc) {
- r += 1;
- records[r] = [''];
- fields = records[r];
- col = 0;
- } else if (char === '\n' && esc) {
- if (prev !== '\r') {
- r += 1;
- records[r] = [''];
- fields = records[r];
- col = 0;
- }
- } else {
- fields[col] += char;
- }
- prev = char;
- }
-
- // remove the last record, if it is empty
- if (records[records.length - 1].length === 1 &&
- records[records.length - 1][0] === '')
- {
- records.pop();
- }
-
- // convert arrays to Snap! Lists
- records = new List(
- records.map(row => new List(row))
- );
-
- // for backwards compatibility return the first row if it is the only one
- if (records.length() === 1) {
- return records.at(1);
- }
- return records;
-};
-
-Process.prototype.parseJSON = function (string) {
- // Bernat's original Snapi contribution
- function listify(jsonObject) {
- if (jsonObject instanceof Array) {
- return new List(
- jsonObject.map(function(eachElement) {
- return listify(eachElement);
- })
- );
- } else if (jsonObject instanceof Object) {
- return new List(
- Object.keys(jsonObject).map(function(eachKey) {
- return new List([
- eachKey,
- listify(jsonObject[eachKey])
- ]);
- })
- );
- } else {
- return jsonObject;
- }
- }
-
- return listify(JSON.parse(string));
-};
-
-// Process debugging
-
-Process.prototype.alert = function (data) {
- // debugging primitives only work in dev mode, otherwise they're nop
- var world;
- if (this.homeContext.receiver) {
- world = this.homeContext.receiver.world();
- if (world.isDevMode) {
- alert('Snap! ' + data.asArray());
- }
- }
-};
-
-Process.prototype.log = function (data) {
- // debugging primitives only work in dev mode, otherwise they're nop
- var world;
- if (this.homeContext.receiver) {
- world = this.homeContext.receiver.world();
- if (world.isDevMode) {
- console.log('Snap! ' + data.asArray());
- }
- }
-};
-
-// Process motion primitives
-
-Process.prototype.getOtherObject = function (name, thisObj, stageObj) {
- // private, find the sprite indicated by the given name
- // either onstage or in the World's hand
-
- // deal with first-class sprites
- if (isSnapObject(name)) {
- return name;
- }
-
- if (this.inputOption(name) === 'myself') {
- return thisObj;
- }
- var stage = isNil(stageObj) ?
- thisObj.parentThatIsA(StageMorph) : stageObj,
- thatObj = null;
- if (stage) {
- // find the corresponding sprite on the stage
- thatObj = detect(
- stage.children,
- morph => morph.name === name
- );
- if (!thatObj) {
- // check if the sprite in question is currently being
- // dragged around
- thatObj = detect(
- stage.world().hand.children,
- morph => morph instanceof SpriteMorph &&
- morph.name === name
- );
- }
- }
- return thatObj;
-};
-
-Process.prototype.getObjectsNamed = function (name, thisObj, stageObj) {
- // private, find all sprites and their clones indicated
- // by the given name either onstage or in the World's hand
-
- var stage = isNil(stageObj) ?
- thisObj.parentThatIsA(StageMorph) : stageObj,
- those = [];
-
- function check(obj) {
- return obj instanceof SpriteMorph && obj.isTemporary ?
- obj.cloneOriginName === name : obj.name === name;
- }
-
- if (stage) {
- // find the corresponding sprite on the stage
- those = stage.children.filter(check);
- if (!those.length) {
- // check if a sprite in question is currently being
- // dragged around
- those = stage.world().hand.children.filter(check);
- }
- }
- return those;
-};
-
-Process.prototype.setHeading = function (direction) {
- var thisObj = this.blockReceiver();
-
- if (thisObj) {
- if (this.inputOption(direction) === 'random') {
- direction = this.reportBasicRandom(1, 36000) / 100;
- }
- thisObj.setHeading(direction);
- }
-};
-
-Process.prototype.doFaceTowards = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj;
-
- if (thisObj) {
- if (this.inputOption(name) === 'center') {
- thisObj.faceToXY(0, 0);
- } else if (this.inputOption(name) === 'mouse-pointer') {
- thisObj.faceToXY(this.reportMouseX(), this.reportMouseY());
- } else if (this.inputOption(name) === 'random position') {
- thisObj.setHeading(this.reportBasicRandom(1, 36000) / 100);
- } else {
- if (name instanceof List) {
- thisObj.faceToXY(
- name.at(1),
- name.at(2)
- );
- return;
- }
- thatObj = this.getOtherObject(name, this.homeContext.receiver);
- if (thatObj) {
- thisObj.faceToXY(
- thatObj.xPosition(),
- thatObj.yPosition()
- );
- }
- }
- }
-};
-
-Process.prototype.doGotoObject = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj,
- stage;
-
- if (thisObj) {
- if (this.inputOption(name) === 'center') {
- thisObj.gotoXY(0, 0);
- } else if (this.inputOption(name) === 'mouse-pointer') {
- thisObj.gotoXY(this.reportMouseX(), this.reportMouseY());
- } else if (this.inputOption(name) === 'random position') {
- stage = thisObj.parentThatIsA(StageMorph);
- if (stage) {
- thisObj.setCenter(new Point(
- this.reportBasicRandom(stage.left(), stage.right()),
- this.reportBasicRandom(stage.top(), stage.bottom())
- ));
- }
- } else {
- if (name instanceof List) {
- thisObj.gotoXY(
- name.at(1),
- name.at(2)
- );
- return;
- }
- thatObj = this.getOtherObject(name, this.homeContext.receiver);
- if (thatObj) {
- thisObj.gotoXY(
- thatObj.xPosition(),
- thatObj.yPosition()
- );
- }
- }
- }
-};
-
-// Process layering primitives
-
-Process.prototype.goToLayer = function (name) {
- var option = this.inputOption(name),
- thisObj = this.blockReceiver();
- if (thisObj instanceof SpriteMorph) {
- if (option === 'front') {
- thisObj.comeToFront();
- } else if (option === 'back') {
- thisObj.goToBack();
- }
- }
-};
-
-// Process color primitives
-
-Process.prototype.setHSVA = function (name, num) {
- var options = ['hue', 'saturation', 'brightness', 'transparency'];
- this.blockReceiver().setColorComponentHSVA(
- options.indexOf(this.inputOption(name)),
- +num
- );
-};
-
-Process.prototype.changeHSVA = function (name, num) {
- var options = ['hue', 'saturation', 'brightness', 'transparency'];
- this.blockReceiver().changeColorComponentHSVA(
- options.indexOf(this.inputOption(name)),
- +num
- );
-};
-
-Process.prototype.setPenHSVA = Process.prototype.setHSVA;
-Process.prototype.changePenHSVA = Process.prototype.changeHSVA;
-Process.prototype.setBackgroundHSVA = Process.prototype.setHSVA;
-Process.prototype.changeBackgroundHSVA = Process.prototype.changeHSVA;
-
-// Process pasting primitives
-
-Process.prototype.doPasteOn = function (name, thisObj, stage) {
- // allow for lists of sprites and also check for temparary clones,
- // as in Scratch 2.0,
- var those;
- thisObj = thisObj || this.blockReceiver();
- stage = stage || thisObj.parentThatIsA(StageMorph);
- if (stage.name === name) {
- name = stage;
- }
- if (isSnapObject(name)) {
- return thisObj.pasteOn(name);
- }
- if (name instanceof List) { // assume all elements to be sprites
- those = name.itemsArray();
- } else {
- those = this.getObjectsNamed(name, thisObj, stage); // clones
- }
- those.forEach(each =>
- this.doPasteOn(each, thisObj, stage)
- );
-};
-
-// Process temporary cloning (Scratch-style)
-
-Process.prototype.createClone = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj;
-
- if (!name || this.readyToTerminate) {return; }
- if (thisObj) {
- if (this.inputOption(name) === 'myself') {
- thisObj.createClone(!this.isFirstStep);
- } else {
- thatObj = this.getOtherObject(name, thisObj);
- if (thatObj) {
- thatObj.createClone(!this.isFirstStep);
- }
- }
- }
-};
-
-Process.prototype.newClone = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj;
-
- if (!name) {return; }
- if (thisObj) {
- if (this.inputOption(name) === 'myself') {
- return thisObj.newClone(!this.isFirstStep);
- }
- thatObj = this.getOtherObject(name, thisObj);
- if (thatObj) {
- return thatObj.newClone(!this.isFirstStep);
- }
- }
-};
-
-// Process sensing primitives
-
-Process.prototype.reportTouchingObject = function (name) {
- var thisObj = this.blockReceiver();
-
- if (thisObj) {
- return this.objectTouchingObject(thisObj, name);
- }
- return false;
-};
-
-Process.prototype.objectTouchingObject = function (thisObj, name) {
- // helper function for reportTouchingObject()
- // also check for temparary clones, as in Scratch 2.0,
- // and for any parts (subsprites)
- var those,
- stage,
- box,
- mouse;
-
- if (this.inputOption(name) === 'mouse-pointer') {
- mouse = thisObj.world().hand.position();
- if (thisObj.bounds.containsPoint(mouse) &&
- !thisObj.isTransparentAt(mouse)) {
- return true;
- }
- } else {
- stage = thisObj.parentThatIsA(StageMorph);
- if (stage) {
- 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())) {
- return true;
- }
- if (isSnapObject(name)) {
- return name.isVisible && thisObj.isTouching(name);
- }
- if (name instanceof List) { // assume all elements to be sprites
- those = name.itemsArray();
- } else {
- those = this.getObjectsNamed(name, thisObj, stage); // clones
- }
- if (those.some(any => any.isVisible && thisObj.isTouching(any)
- // check collision with any part, performance issue
- // commented out for now
- /*
- return any.allParts().some(function (part) {
- return part.isVisible && thisObj.isTouching(part);
- })
- */
- )) {
- return true;
- }
- }
- }
- return thisObj.parts.some(any =>
- this.objectTouchingObject(any, name)
- );
-};
-
-Process.prototype.reportAspect = function (aspect, location) {
- // sense colors and sprites anywhere,
- // use sprites to read/write data encoded in colors.
- //
- // usage:
- // ------
- // left input selects color/saturation/brightness/transparency or "sprites".
- // right input selects "mouse-pointer", "myself" or name of another sprite.
- // you can also embed a a reporter with a reference to a sprite itself
- // or a list of two items representing x- and y- coordinates.
- //
- // what you'll get:
- // ----------------
- // left input (aspect):
- //
- // 'hue' - hsv HUE on a scale of 0 - 100
- // 'saturation' - hsv SATURATION on a scale of 0 - 100
- // 'brightness' - hsv VALUE on a scale of 0 - 100
- // 'transparency' - rgba ALPHA on a reversed (!) scale of 0 - 100
- // 'r-g-b-a' - list of rgba values on a scale of 0 - 255 each
- // 'sprites' - a list of sprites at the location, empty if none
- //
- // right input (location):
- //
- // 'mouse-pointer' - color/sprites at mouse-pointer anywhere in Snap
- // 'myself' - sprites at or color UNDERNEATH the rotation center
- // sprite-name - sprites at or color UNDERNEATH sprites's rot-ctr.
- // two-item-list - color/sprites at x-/y- coordinates on the Stage
- //
- // what does "underneath" mean?
- // ----------------------------
- // the not-fully-transparent color of the top-layered sprite at the given
- // location excluding the receiver sprite's own layer and all layers above
- // it gets reported.
- //
- // color-aspect "underneath" a sprite means that the sprite's layer is
- // relevant for what gets reported. Sprites can only sense colors in layers
- // below themselves, not their own color and not colors in sprites above
- // their own layer.
-
- var choice = this.inputOption(aspect),
- target = this.inputOption(location),
- options = ['hue', 'saturation', 'brightness', 'transparency'],
- idx = options.indexOf(choice),
- thisObj = this.blockReceiver(),
- thatObj,
- stage = thisObj.parentThatIsA(StageMorph),
- world = thisObj.world(),
- point,
- clr;
-
- if (target === 'myself') {
- if (choice === 'sprites') {
- if (thisObj instanceof StageMorph) {
- point = thisObj.center();
- } else {
- point = thisObj.rotationCenter();
- }
- return this.spritesAtPoint(point, stage);
- } else {
- clr = this.colorAtSprite(thisObj);
- }
- } else if (target === 'mouse-pointer') {
- if (choice === 'sprites') {
- return this.spritesAtPoint(world.hand.position(), stage);
- } else {
- clr = world.getGlobalPixelColor(world.hand.position());
- }
- } else if (target instanceof List) {
- point = new Point(
- target.at(1) * stage.scale + stage.center().x,
- stage.center().y - (target.at(2) * stage.scale)
- );
- if (choice === 'sprites') {
- return this.spritesAtPoint(point, stage);
- } else {
- clr = world.getGlobalPixelColor(point);
- }
- } else {
- if (!target) {return; }
- thatObj = this.getOtherObject(target, thisObj, stage);
- if (thatObj) {
- if (choice === 'sprites') {
- point = thatObj instanceof SpriteMorph ?
- thatObj.rotationCenter() : thatObj.center();
- return this.spritesAtPoint(point, stage);
- } else {
- clr = this.colorAtSprite(thatObj);
- }
- } else {
- return;
- }
-
- }
-
- if (choice === 'r-g-b-a') {
- return new List([clr.r, clr.g, clr.b, Math.round(clr.a * 255)]);
- }
- if (idx < 0 || idx > 3) {
- return;
- }
- if (idx === 3) {
- return (1 - clr.a) * 100;
- }
- return clr.hsv()[idx] * 100;
-};
-
-Process.prototype.colorAtSprite = function (sprite) {
- // private - helper function for aspect of location
- // answer the top-most color at the sprite's rotation center
- // excluding the sprite itself
- var point = sprite instanceof SpriteMorph ? sprite.rotationCenter()
- : sprite.center(),
- stage = sprite.parentThatIsA(StageMorph),
- child,
- i;
-
- if (!stage) {return new Color(); }
- for (i = stage.children.length; i > 0; i -= 1) {
- child = stage.children[i - 1];
- if ((child !== sprite) &&
- child.isVisible &&
- child.bounds.containsPoint(point) &&
- !child.isTransparentAt(point)
- ) {
- return child.getPixelColor(point);
- }
- }
- if (stage.bounds.containsPoint(point)) {
- return stage.getPixelColor(point);
- }
- return new Color();
-};
-
-Process.prototype.colorBelowSprite = function (sprite) {
- // private - helper function for aspect of location
- // answer the color underneath the layer of the sprite's rotation center
- // NOTE: layer-aware color sensing is currently unused
- // in favor of top-layer detection because of user-observations
- var point = sprite instanceof SpriteMorph ? sprite.rotationCenter()
- : sprite.center(),
- stage = sprite.parentThatIsA(StageMorph),
- below = stage,
- found = false,
- child,
- i;
-
- if (!stage) {return new Color(); }
- for (i = 0; i < stage.children.length; i += 1) {
- if (!found) {
- child = stage.children[i];
- if (child === sprite) {
- found = true;
- } else if (child.isVisible &&
- child.bounds.containsPoint(point) &&
- !child.isTransparentAt(point)
- ) {
- below = child;
- }
- }
- }
- if (below.bounds.containsPoint(point)) {
- return below.getPixelColor(point);
- }
- return new Color();
-};
-
-Process.prototype.spritesAtPoint = function (point, stage) {
- // private - helper function for aspect of location
- // point argument is an absolute (Morphic) point
- // answer a list of sprites, if any, at the given point
- // ordered by their layer, i.e. top-layer is last in the list
- return new List(
- stage.children.filter(morph =>
- morph instanceof SpriteMorph &&
- morph.isVisible &&
- morph.bounds.containsPoint(point) &&
- !morph.isTransparentAt(point)
- )
- );
-};
-
-Process.prototype.reportRelationTo = function (relation, name) {
- var rel = this.inputOption(relation);
- if (rel === 'distance') {
- return this.reportDistanceTo(name);
- }
- if (rel === 'direction') {
- return this.reportDirectionTo(name);
- }
- return 0;
-};
-
-Process.prototype.reportDistanceTo = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj,
- stage,
- rc,
- point;
-
- if (thisObj) {
- rc = thisObj.rotationCenter();
- point = rc;
- if (this.inputOption(name) === 'mouse-pointer') {
- point = thisObj.world().hand.position();
- } else if (this.inputOption(name) === 'center') {
- return new Point(thisObj.xPosition(), thisObj.yPosition())
- .distanceTo(ZERO);
- } else if (name instanceof List) {
- return new Point(thisObj.xPosition(), thisObj.yPosition())
- .distanceTo(new Point(name.at(1), name.at(2)));
- }
- stage = thisObj.parentThatIsA(StageMorph);
- thatObj = this.getOtherObject(name, thisObj, stage);
- if (thatObj) {
- point = thatObj.rotationCenter();
- }
- return rc.distanceTo(point) / stage.scale;
- }
- return 0;
-};
-
-Process.prototype.reportDirectionTo = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj;
-
- if (thisObj) {
- if (this.inputOption(name) === 'mouse-pointer') {
- return thisObj.angleToXY(this.reportMouseX(), this.reportMouseY());
- }
- if (this.inputOption(name) === 'center') {
- return thisObj.angleToXY(0, 0);
- }
- if (name instanceof List) {
- return thisObj.angleToXY(
- name.at(1),
- name.at(2)
- );
- }
- thatObj = this.getOtherObject(name, this.homeContext.receiver);
- if (thatObj) {
- return thisObj.angleToXY(
- thatObj.xPosition(),
- thatObj.yPosition()
- );
- }
- return thisObj.direction();
- }
- return 0;
-};
-
-Process.prototype.reportAttributeOf = function (attribute, name) {
- var thisObj = this.blockReceiver(),
- thatObj,
- stage;
-
- if (thisObj) {
- this.assertAlive(thisObj);
- stage = thisObj.parentThatIsA(StageMorph);
- if (stage.name === name) {
- thatObj = stage;
- } else {
- thatObj = this.getOtherObject(name, thisObj, stage);
- }
- if (thatObj) {
- this.assertAlive(thatObj);
- if (attribute instanceof BlockMorph) { // a "wish"
- return this.reportContextFor(
- this.reify(
- thatObj.getMethod(attribute.semanticSpec)
- .blockInstance(),
- new List()
- ),
- thatObj
- );
- }
- if (attribute instanceof Context) {
- return this.reportContextFor(attribute, thatObj);
- }
- if (isString(attribute)) {
- return thatObj.variables.getVar(attribute);
- }
- switch (this.inputOption(attribute)) {
- case 'x position':
- return thatObj.xPosition ? thatObj.xPosition() : '';
- case 'y position':
- return thatObj.yPosition ? thatObj.yPosition() : '';
- case 'direction':
- return thatObj.direction ? thatObj.direction() : '';
- case 'costume #':
- return thatObj.getCostumeIdx();
- case 'costume name':
- return thatObj.costume ? thatObj.costume.name
- : thatObj instanceof SpriteMorph ? localize('Turtle')
- : localize('Empty');
- case 'size':
- return thatObj.getScale ? thatObj.getScale() : '';
- case 'volume':
- return thatObj.getVolume();
- case 'balance':
- return thatObj.getPan();
- case 'width':
- if (thatObj instanceof StageMorph) {
- return thatObj.dimensions.x;
- }
- this.assertType(thatObj, 'sprite');
- return thatObj.width() / stage.scale;
- case 'height':
- if (thatObj instanceof StageMorph) {
- return thatObj.dimensions.y;
- }
- this.assertType(thatObj, 'sprite');
- return thatObj.height() / stage.scale;
- case 'left':
- return thatObj.xLeft();
- case 'right':
- return thatObj.xRight();
- case 'top':
- return thatObj.yTop();
- case 'bottom':
- return thatObj.yBottom();
- }
- }
- }
- return '';
-};
-
-Process.prototype.reportGet = function (query) {
- // answer a reference to a first-class member
- // or a list of first-class members
- var thisObj = this.blockReceiver(),
- neighborhood,
- stage,
- objName;
-
- if (thisObj) {
- switch (this.inputOption(query)) {
- case 'self' :
- return thisObj;
- case 'other sprites':
- stage = thisObj.parentThatIsA(StageMorph);
- return new List(
- stage.children.filter(each =>
- each instanceof SpriteMorph &&
- each !== thisObj
- )
- );
- case 'parts': // shallow copy to disable side-effects
- return new List((thisObj.parts || []).map(each => each));
- case 'anchor':
- return thisObj.anchor || '';
- case 'parent':
- return thisObj.exemplar || '';
- case 'children':
- return new List(thisObj.specimens ? thisObj.specimens() : []);
- case 'temporary?':
- return thisObj.isTemporary || false;
- case 'clones':
- stage = thisObj.parentThatIsA(StageMorph);
- objName = thisObj.name || thisObj.cloneOriginName;
- return new List(
- stage.children.filter(each =>
- each.isTemporary &&
- (each !== thisObj) &&
- (each.cloneOriginName === objName)
- )
- );
- case 'other clones':
- return thisObj.isTemporary ?
- this.reportGet(['clones']) : new List();
- case 'neighbors':
- stage = thisObj.parentThatIsA(StageMorph);
- neighborhood = thisObj.bounds.expandBy(new Point(
- thisObj.width(),
- thisObj.height()
- ));
- return new List(
- stage.children.filter(each =>
- each instanceof SpriteMorph &&
- (each !== thisObj) &&
- each.bounds.intersects(neighborhood)
- )
- );
- case 'dangling?':
- return !thisObj.rotatesWithAnchor;
- case 'draggable?':
- return thisObj.isDraggable;
- case 'rotation style':
- return thisObj.rotationStyle || 0;
- case 'rotation x':
- return thisObj.xPosition();
- case 'rotation y':
- return thisObj.yPosition();
- case 'center x':
- return thisObj.xCenter();
- case 'center y':
- return thisObj.yCenter();
- case 'left':
- return thisObj.xLeft();
- case 'right':
- return thisObj.xRight();
- case 'top':
- return thisObj.yTop();
- case 'bottom':
- return thisObj.yBottom();
- case 'name':
- return thisObj.name;
- case 'stage':
- return thisObj.parentThatIsA(StageMorph);
- case 'costume':
- return thisObj.costume;
- case 'costumes':
- return thisObj.reportCostumes();
- case 'sounds':
- return thisObj.sounds;
- case 'width':
- if (thisObj instanceof StageMorph) {
- return thisObj.dimensions.x;
- }
- stage = thisObj.parentThatIsA(StageMorph);
- return stage ? thisObj.width() / stage.scale : 0;
- case 'height':
- if (thisObj instanceof StageMorph) {
- return thisObj.dimensions.y;
- }
- stage = thisObj.parentThatIsA(StageMorph);
- return stage ? thisObj.height() / stage.scale : 0;
- }
- }
- return '';
-};
-
-Process.prototype.reportObject = function (name) {
- var thisObj = this.blockReceiver(),
- thatObj,
- stage;
-
- if (thisObj) {
- this.assertAlive(thisObj);
- stage = thisObj.parentThatIsA(StageMorph);
- if (stage.name === name) {
- thatObj = stage;
- } else {
- thatObj = this.getOtherObject(name, thisObj, stage);
- }
- if (thatObj) {
- this.assertAlive(thatObj);
- }
- return thatObj;
- }
-};
-
-Process.prototype.doSet = function (attribute, value) {
- // experimental, manipulate sprites' attributes
- var name, rcvr, ide;
- rcvr = this.blockReceiver();
- this.assertAlive(rcvr);
- if (!(attribute instanceof Context || attribute instanceof Array) ||
- (attribute instanceof Context &&
- attribute.expression.selector !== 'reportGet')) {
- throw new Error(localize('unsupported attribute'));
- }
- name = attribute instanceof Context ?
- attribute.expression.inputs()[0].evaluate()
- : attribute;
- if (name instanceof Array) {
- name = name[0];
- }
- switch (name) {
- case 'anchor':
- this.assertType(rcvr, 'sprite');
- if (value instanceof SpriteMorph) {
- // avoid circularity here, because the GUI already checks for
- // conflicts while the user drags parts over prospective targets
- if (!rcvr.enableNesting || contains(rcvr.allParts(), value)) {
- throw new Error(
- localize('unable to nest\n(disabled or circular?)')
- );
- }
- value.attachPart(rcvr);
- } else {
- rcvr.detachFromAnchor();
- }
- break;
- case 'parent':
- this.assertType(rcvr, 'sprite');
- value = value instanceof SpriteMorph ? value : null;
- rcvr.setExemplar(value, true); // throw an error in case of circularity
- break;
- case 'temporary?':
- this.assertType(rcvr, 'sprite');
- this.assertType(value, 'Boolean');
- if (value) {
- rcvr.release();
- } else {
- rcvr.perpetuate();
- }
- break;
- case 'name':
- this.assertType(rcvr, ['sprite', 'stage']);
- this.assertType(value, ['text', 'number']);
- ide = rcvr.parentThatIsA(IDE_Morph);
- if (ide) {
- rcvr.setName(
- ide.newSpriteName(value.toString(), rcvr)
- );
- ide.spriteBar.nameField.setContents(
- ide.currentSprite.name.toString()
- );
- }
- break;
- case 'dangling?':
- this.assertType(rcvr, 'sprite');
- this.assertType(value, 'Boolean');
- rcvr.rotatesWithAnchor = !value;
- rcvr.version = Date.now();
- break;
- case 'draggable?':
- this.assertType(rcvr, 'sprite');
- this.assertType(value, 'Boolean');
- rcvr.isDraggable = value;
- // update padlock symbol in the IDE:
- ide = rcvr.parentThatIsA(IDE_Morph);
- if (ide) {
- ide.spriteBar.children.forEach(each => {
- if (each.refresh) {
- each.refresh();
- }
- });
- }
- rcvr.version = Date.now();
- break;
- case 'rotation style':
- this.assertType(rcvr, 'sprite');
- this.assertType(+value, 'number');
- if (!contains([0, 1, 2], +value)) {
- return; // maybe throw an error msg
- }
- rcvr.changed();
- rcvr.rotationStyle = +value;
- rcvr.fixLayout();
- rcvr.rerender();
- // update padlock symbol in the IDE:
- ide = rcvr.parentThatIsA(IDE_Morph);
- if (ide) {
- ide.spriteBar.children.forEach(each => {
- if (each.refresh) {
- each.refresh();
- }
- });
- }
- rcvr.version = Date.now();
- break;
- case 'rotation x':
- this.assertType(rcvr, 'sprite');
- this.assertType(value, 'number');
- rcvr.setRotationX(value);
- break;
- case 'rotation y':
- this.assertType(rcvr, 'sprite');
- this.assertType(value, 'number');
- rcvr.setRotationY(value);
- break;
- case 'microphone modifier':
- this.setMicrophoneModifier(value);
- break;
- default:
- throw new Error(
- '"' + localize(name) + '" ' + localize('is read-only')
- );
- }
-};
-
-Process.prototype.reportContextFor = function (context, otherObj) {
- // Private - return a copy of the context
- // and bind it to another receiver
- var result = copy(context);
- result.receiver = otherObj;
- if (result.outerContext) {
- result.outerContext = copy(result.outerContext);
- result.outerContext.variables = copy(result.outerContext.variables);
- result.outerContext.receiver = otherObj;
- if (result.outerContext.variables.parentFrame) {
- result.outerContext.variables.parentFrame =
- copy(result.outerContext.variables.parentFrame);
- result.outerContext.variables.parentFrame.parentFrame =
- otherObj.variables;
- } else {
- result.outerContext.variables.parentFrame = otherObj.variables;
- }
- }
- return result;
-};
-
-Process.prototype.reportMouseX = function () {
- var stage, world;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- world = stage.world();
- if (world) {
- return (world.hand.position().x - stage.center().x)
- / stage.scale;
- }
- }
- }
- return 0;
-};
-
-Process.prototype.reportMouseY = function () {
- var stage, world;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- world = stage.world();
- if (world) {
- return (stage.center().y - world.hand.position().y)
- / stage.scale;
- }
- }
- }
- return 0;
-};
-
-Process.prototype.reportMouseDown = function () {
- var world;
- if (this.homeContext.receiver) {
- world = this.homeContext.receiver.world();
- if (world) {
- return world.hand.mouseButton === 'left';
- }
- }
- return false;
-};
-
-Process.prototype.reportKeyPressed = function (keyString) {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- if (this.inputOption(keyString) === 'any key') {
- return Object.keys(stage.keysPressed).length > 0;
- }
- return stage.keysPressed[keyString] !== undefined;
- }
- }
- return false;
-};
-
-Process.prototype.doResetTimer = function () {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.resetTimer();
- }
- }
-};
-
-Process.prototype.reportTimer = function () {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- return stage.getTimer();
- }
- }
- return 0;
-};
-
-// Process Dates and times in Snap
-Process.prototype.reportDate = function (datefn) {
- 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;
-};
-
-// Process video motion detection primitives
-
-Process.prototype.doSetVideoTransparency = function(factor) {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.projectionTransparency = Math.max(0, Math.min(100, factor));
- }
- }
-};
-
-Process.prototype.reportVideo = function(attribute, name) {
- var thisObj = this.blockReceiver(),
- stage = thisObj.parentThatIsA(StageMorph),
- thatObj = this.getOtherObject(name, thisObj, stage);
-
- if (!stage.projectionSource || !stage.projectionSource.stream) {
- // wait until video is turned on
- if (!this.context.accumulator) {
- this.context.accumulator = true; // started video
- stage.startVideo();
- }
- this.pushContext('doYield');
- this.pushContext();
- return;
- }
-
- switch (this.inputOption(attribute)) {
- case 'motion':
- if (thatObj instanceof SpriteMorph) {
- stage.videoMotion.getLocalMotion(thatObj);
- return thatObj.motionAmount;
- }
- stage.videoMotion.getStageMotion();
- return stage.videoMotion.motionAmount;
- case 'direction':
- if (thatObj instanceof SpriteMorph) {
- stage.videoMotion.getLocalMotion(thatObj);
- return thatObj.motionDirection;
- }
- stage.videoMotion.getStageMotion();
- return stage.videoMotion.motionDirection;
- case 'snap':
- if (thatObj instanceof SpriteMorph) {
- return thatObj.projectionSnap();
- }
- return stage.projectionSnap();
- }
- return -1;
-};
-
-Process.prototype.startVideo = function(stage) {
- // interpolated
- if (this.reportGlobalFlag('video capture')) {return; }
- if (!stage.projectionSource || !stage.projectionSource.stream) {
- // wait until video is turned on
- if (!this.context.accumulator) {
- this.context.accumulator = true; // started video
- stage.startVideo();
- }
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-// Process code mapping
-
-/*
- for generating textual source code using
- blocks - not needed to run or debug Snap
-*/
-
-Process.prototype.doMapCodeOrHeader = function (aContext, anOption, aString) {
- if (this.inputOption(anOption) === 'code') {
- return this.doMapCode(aContext, aString);
- }
- if (this.inputOption(anOption) === 'header') {
- return this.doMapHeader(aContext, aString);
- }
- throw new Error(
- ' \'' + anOption + '\'\nis not a valid option'
- );
-};
-
-Process.prototype.doMapHeader = function (aContext, aString) {
- if (aContext instanceof Context) {
- if (aContext.expression instanceof SyntaxElementMorph) {
- return aContext.expression.mapHeader(aString || '');
- }
- }
-};
-
-Process.prototype.doMapCode = function (aContext, aString) {
- if (aContext instanceof Context) {
- if (aContext.expression instanceof SyntaxElementMorph) {
- return aContext.expression.mapCode(aString || '');
- }
- }
-};
-
-Process.prototype.doMapValueCode = function (type, aString) {
- var tp = this.inputOption(type);
- switch (tp) {
- case 'String':
- StageMorph.prototype.codeMappings.string = aString || '<#1>';
- break;
- case 'Number':
- StageMorph.prototype.codeMappings.number = aString || '<#1>';
- break;
- case 'true':
- StageMorph.prototype.codeMappings.boolTrue = aString || 'true';
- break;
- case 'false':
- StageMorph.prototype.codeMappings.boolFalse = aString || 'true';
- break;
- default:
- throw new Error(
- localize('unsupported data type') + ' ' + tp
- );
- }
-
-};
-
-Process.prototype.doMapListCode = function (part, kind, aString) {
- var key1 = '',
- key2 = 'delim';
-
- if (this.inputOption(kind) === 'parameters') {
- key1 = 'parms_';
- } else if (this.inputOption(kind) === 'variables') {
- key1 = 'tempvars_';
- }
-
- if (this.inputOption(part) === 'list') {
- key2 = 'list';
- } else if (this.inputOption(part) === 'item') {
- key2 = 'item';
- }
-
- StageMorph.prototype.codeMappings[key1 + key2] = aString || '';
-};
-
-Process.prototype.reportMappedCode = function (aContext) {
- if (aContext instanceof Context) {
- if (aContext.expression instanceof SyntaxElementMorph) {
- return aContext.expression.mappedCode();
- }
- }
- return '';
-};
-
-// Process music primitives
-
-Process.prototype.doRest = function (beats) {
- var tempo = this.reportTempo();
- this.doWait(60 / tempo * beats);
-};
-
-Process.prototype.reportTempo = function () {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- return stage.getTempo();
- }
- }
- return 0;
-};
-
-Process.prototype.doChangeTempo = function (delta) {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.changeTempo(delta);
- }
- }
-};
-
-Process.prototype.doSetTempo = function (bpm) {
- var stage;
- if (this.homeContext.receiver) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (stage) {
- stage.setTempo(bpm);
- }
- }
-};
-
-Process.prototype.doPlayNote = function (pitch, beats) {
- var tempo = this.reportTempo();
- this.doPlayNoteForSecs(
- parseFloat(pitch || '0'),
- 60 / tempo * parseFloat(beats || '0')
- );
-};
-
-Process.prototype.doPlayNoteForSecs = function (pitch, secs) {
- // interpolated
- var rcvr = this.blockReceiver();
- if (!this.context.startTime) {
- rcvr.setVolume(rcvr.getVolume()); // b/c Chrome needs lazy init
- rcvr.setPan(rcvr.getPan()); // b/c Chrome needs lazy initialization
- this.context.startTime = Date.now();
- this.context.activeNote = new Note(pitch);
- this.context.activeNote.play(
- this.instrument,
- rcvr.getGainNode(),
- rcvr.getPannerNode()
- );
- }
- if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
- if (this.context.activeNote) {
- this.context.activeNote.stop();
- this.context.activeNote = null;
- }
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.doPlayFrequency = function (hz, secs) {
- this.doPlayFrequencyForSecs(
- parseFloat(hz || '0'),
- parseFloat(secs || '0')
- );
-};
-
-Process.prototype.doPlayFrequencyForSecs = function (hz, secs) {
- // interpolated
- if (!this.context.startTime) {
- this.context.startTime = Date.now();
- this.context.activeNote = new Note();
- this.context.activeNote.frequency = hz;
- this.context.activeNote.play(this.instrument);
- }
- if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
- if (this.context.activeNote) {
- this.context.activeNote.stop();
- this.context.activeNote = null;
- }
- return null;
- }
- this.pushContext('doYield');
- this.pushContext();
-};
-
-Process.prototype.doSetInstrument = function (num) {
- this.instrument = +num;
- this.receiver.instrument = +num;
- if (this.receiver.freqPlayer) {
- this.receiver.freqPlayer.setInstrument(+num);
- }
-};
-
-// Process image processing primitives
-
-Process.prototype.reportGetImageAttribute = function (choice, name) {
- var cst = this.costumeNamed(name) || new Costume(),
- option = this.inputOption(choice);
-
- switch (option) {
- case 'name':
- return cst.name;
- case 'width':
- return cst.width();
- case 'height':
- return cst.height();
- case 'pixels':
- return cst.rasterized().pixels();
- default:
- return cst;
- }
-};
-
-Process.prototype.reportNewCostumeStretched = function (name, xP, yP) {
- var cst;
- if (name instanceof List) {
- return this.reportNewCostume(name, xP, yP);
- }
- cst = this.costumeNamed(name);
- if (!cst) {
- return new Costume();
- }
- if (!isFinite(+xP * +yP) || isNaN(+xP * +yP)) {
- throw new Error(
- 'expecting a finite number\nbut getting Infinity or NaN'
- );
- }
- return cst.stretched(
- Math.round(cst.width() * +xP / 100),
- Math.round(cst.height() * +yP / 100)
- );
-};
-
-Process.prototype.costumeNamed = function (name) {
- // private
- if (name instanceof Costume) {
- return name;
- }
- if (typeof name === 'number') {
- return this.blockReceiver().costumes.at(name);
- }
- if (this.inputOption(name) === 'current') {
- return this.blockReceiver().costume;
- }
- return detect(
- this.blockReceiver().costumes.asArray(),
- c => c.name === name.toString()
- );
-};
-
-Process.prototype.reportNewCostume = function (pixels, width, height, name) {
- var rcvr, stage, canvas, ctx, src, dta, i, k, px;
-
- this.assertType(pixels, 'list');
- if (this.inputOption(width) === 'current') {
- rcvr = this.blockReceiver();
- stage = rcvr.parentThatIsA(StageMorph);
- width = rcvr.costume ? rcvr.costume.width() : stage.dimensions.x;
- }
- if (this.inputOption(height) === 'current') {
- rcvr = rcvr || this.blockReceiver();
- stage = stage || rcvr.parentThatIsA(StageMorph);
- height = rcvr.costume ? rcvr.costume.height() : stage.dimensions.y;
- }
- width = Math.abs(Math.floor(+width));
- height = Math.abs(Math.floor(+height));
- if (width <= 0 || height <= 0) {
- return new Costume();
- }
- if (!isFinite(width * height) || isNaN(width * height)) {
- throw new Error(
- 'expecting a finite number\nbut getting Infinity or NaN'
- );
- }
-
- canvas = newCanvas(new Point(width, height), true);
- ctx = canvas.getContext('2d');
- src = pixels.asArray();
- dta = ctx.createImageData(width, height);
- for (i = 0; i < src.length; i += 1) {
- px = src[i].asArray();
- for (k = 0; k < 4; k += 1) {
- dta.data[(i * 4) + k] = px[k];
- }
- }
- ctx.putImageData(dta, 0, 0);
- return new Costume(
- canvas,
- name || (rcvr || this.blockReceiver()).newCostumeName(
- localize('costume')
- )
- );
-};
-
-Process.prototype.reportPentrailsAsSVG = function () {
- // interpolated
- var rcvr, stage, svg, acc, offset;
-
- if (!this.context.accumulator) {
- stage = this.homeContext.receiver.parentThatIsA(StageMorph);
- if (!stage.trailsLog.length) {
- throw new Error (localize(
- 'there are currently no\nvectorizable pen trail segments'
- ));
- }
- svg = stage.trailsLogAsSVG();
- this.context.accumulator = {
- img : new Image(),
- rot : svg.rot,
- ready : false
- };
- acc = this.context.accumulator;
- acc.img.onload = () => acc.ready = true;
- acc.img.src = 'data:image/svg+xml,' + svg.src;
- acc.img.rot = svg.rotationShift;
- } else if (this.context.accumulator.ready) {
- offset = ZERO;
- rcvr = this.blockReceiver();
- if (rcvr instanceof SpriteMorph) {
- offset = new Point(rcvr.xPosition(), -rcvr.yPosition());
- }
- this.returnValueToParentContext(
- new SVG_Costume(
- this.context.accumulator.img,
- this.blockReceiver().newCostumeName(localize('Costume')),
- this.context.accumulator.rot.translateBy(offset)
- )
- );
- return;
- }
- this.pushContext();
-};
-
-// Process constant input options
-
-Process.prototype.inputOption = function (dta) {
- // private - for localization
- return dta instanceof Array ? dta[0] : dta;
-};
-
-// Process stack
-
-Process.prototype.pushContext = function (expression, outerContext) {
- this.context = new Context(
- this.context,
- expression,
- outerContext || (this.context ? this.context.outerContext : null),
- // for tail call elimination
- this.context ? // check needed due to tail call elimination
- this.context.receiver : this.homeContext.receiver
- );
-};
-
-Process.prototype.popContext = function () {
- if (this.context) {
- this.context.stopMusic();
- }
- this.context = this.context ? this.context.parentContext : null;
-};
-
-Process.prototype.returnValueToParentContext = function (value) {
- // if no parent context exists treat value as result
- if (value !== undefined) {
- var target = this.context ? // in case of tail call elimination
- this.context.parentContext || this.homeContext
- : this.homeContext;
- target.addInput(value);
- }
-};
-
-Process.prototype.reportStackSize = function () {
- return this.context ? this.context.stackSize() : 0;
-};
-
-Process.prototype.reportFrameCount = function () {
- return this.frameCount;
-};
-
-// Process single-stepping
-
-Process.prototype.flashContext = function () {
- var expr = this.context.expression;
- if (this.enableSingleStepping &&
- !this.isAtomic &&
- expr instanceof SyntaxElementMorph &&
- !(expr instanceof CommandSlotMorph) &&
- !this.context.isFlashing &&
- expr.world() &&
- !(expr instanceof ColorSlotMorph)) {
- this.unflash();
- expr.flash();
- this.context.isFlashing = true;
- this.flashingContext = this.context;
- if (this.flashTime > 0 && (this.flashTime <= 0.5)) {
- this.pushContext('doIdle');
- this.context.addInput(this.flashTime);
- } else {
- this.pushContext('doInterrupt');
- }
- return true;
- }
- return false;
-};
-
-Process.prototype.flashPausedContext = function () {
- var flashable = this.context ? this.context.lastFlashable() : null;
- if (flashable) {
- this.unflash();
- flashable.expression.flash();
- flashable.isFlashing = true;
- this.flashingContext = flashable;
- }
-};
-
-Process.prototype.doInterrupt = function () {
- this.popContext();
- if (!this.isAtomic) {
- this.isInterrupted = true;
- }
-};
-
-Process.prototype.doIdle = function (secs) {
- if (!this.context.startTime) {
- this.context.startTime = Date.now();
- }
- if ((Date.now() - this.context.startTime) < (secs * 1000)) {
- this.pushContext('doInterrupt');
- return;
- }
- this.popContext();
-};
-
-Process.prototype.unflash = function () {
- if (this.flashingContext) {
- this.flashingContext.expression.unflash();
- this.flashingContext.isFlashing = false;
- this.flashingContext = null;
- }
-};
-
-// Process: Compile (as of yet simple) block scripts to JS
-
-/*
- with either only explicit formal parameters or a specified number of
- implicit formal parameters mapped to empty input slots
- *** highly experimental and heavily under construction ***
-*/
-
-Process.prototype.reportCompiled = function (context, implicitParamCount) {
- // implicitParamCount is optional and indicates the number of
- // expected parameters, if any. This is only used to handle
- // implicit (empty slot) parameters and can otherwise be
- // ignored
- return new JSCompiler(this).compileFunction(context, implicitParamCount);
-};
-
-Process.prototype.capture = function (aContext) {
- // private - answer a new process on a full copy of the given context
- // while retaining the lexical variable scope
- var proc = new Process(this.topBlock, this.receiver);
- var clos = new Context(
- aContext.parentContext,
- aContext.expression,
- aContext.outerContext,
- aContext.receiver
- );
- clos.variables = aContext.variables.fullCopy();
- clos.variables.root().parentFrame = proc.variables;
- proc.context = clos;
- return proc;
-};
-
-Process.prototype.getVarNamed = function (name) {
- // private - special form for compiled expressions
- // DO NOT use except in compiled methods!
- // first check script vars, then global ones
- var frame = this.homeContext.variables.silentFind(name) ||
- this.context.variables.silentFind(name),
- value;
- if (frame) {
- value = frame.vars[name].value;
- return (value === 0 ? 0
- : value === false ? false
- : value === '' ? ''
- : value || 0); // don't return null
- }
- throw new Error(
- localize('a variable of name \'')
- + name
- + localize('\'\ndoes not exist in this context')
- );
-};
-
-Process.prototype.setVarNamed = function (name, value) {
- // private - special form for compiled expressions
- // incomplete, currently only sets named vars
- // DO NOT use except in compiled methods!
- // first check script vars, then global ones
- var frame = this.homeContext.variables.silentFind(name) ||
- this.context.variables.silentFind(name);
- if (isNil(frame)) {
- throw new Error(
- localize('a variable of name \'')
- + name
- + localize('\'\ndoes not exist in this context')
- );
- }
- frame.vars[name].value = value;
-};
-
-Process.prototype.incrementVarNamed = function (name, delta) {
- // private - special form for compiled expressions
- this.setVarNamed(name, this.getVarNamed(name) + (+delta));
-};
-
-// Process: Atomic HOFs using experimental JIT-compilation
-
-Process.prototype.reportAtomicMap = function (reporter, list) {
- // if the reporter uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - element
- // #2 - optional | index
- // #3 - optional | source list
-
- this.assertType(list, 'list');
- var result = [],
- src = list.asArray(),
- len = src.length,
- formalParameterCount = reporter.inputs.length,
- parms,
- func,
- i;
-
- // try compiling the reporter into generic JavaScript
- // fall back to the morphic reporter if unsuccessful
- try {
- func = this.reportCompiled(reporter, 1); // a single expected input
- } catch (err) {
- console.log(err.message);
- func = reporter;
- }
-
- // iterate over the data in a single frame:
- // to do: Insert some kind of user escape mechanism
-
- for (i = 0; i < len; i += 1) {
- parms = [src[i]];
- if (formalParameterCount > 1) {
- parms.push(i + 1);
- }
- if (formalParameterCount > 2) {
- parms.push(list);
- }
- result.push(
- invoke(
- func,
- new List(parms),
- null,
- null,
- null,
- null,
- this.capture(reporter) // process
- )
- );
- }
- return new List(result);
-};
-
-Process.prototype.reportAtomicKeep = function (reporter, list) {
- // if the reporter uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - element
- // #2 - optional | index
- // #3 - optional | source list
-
- this.assertType(list, 'list');
- var result = [],
- src = list.asArray(),
- len = src.length,
- formalParameterCount = reporter.inputs.length,
- parms,
- func,
- i;
-
- // try compiling the reporter into generic JavaScript
- // fall back to the morphic reporter if unsuccessful
- try {
- func = this.reportCompiled(reporter, 1); // a single expected input
- } catch (err) {
- console.log(err.message);
- func = reporter;
- }
-
- // iterate over the data in a single frame:
- // to do: Insert some kind of user escape mechanism
- for (i = 0; i < len; i += 1) {
- parms = [src[i]];
- if (formalParameterCount > 1) {
- parms.push(i + 1);
- }
- if (formalParameterCount > 2) {
- parms.push(list);
- }
- if (
- invoke(
- func,
- new List(parms),
- null,
- null,
- null,
- null,
- this.capture(reporter) // process
- )
- ) {
- result.push(src[i]);
- }
- }
- return new List(result);
-};
-
-Process.prototype.reportAtomicFindFirst = function (reporter, list) {
- // if the reporter uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - element
- // #2 - optional | index
- // #3 - optional | source list
-
- this.assertType(list, 'list');
- var src = list.asArray(),
- len = src.length,
- formalParameterCount = reporter.inputs.length,
- parms,
- func,
- i;
-
- // try compiling the reporter into generic JavaScript
- // fall back to the morphic reporter if unsuccessful
- try {
- func = this.reportCompiled(reporter, 1); // a single expected input
- } catch (err) {
- console.log(err.message);
- func = reporter;
- }
-
- // iterate over the data in a single frame:
- // to do: Insert some kind of user escape mechanism
- for (i = 0; i < len; i += 1) {
- parms = [src[i]];
- if (formalParameterCount > 1) {
- parms.push(i + 1);
- }
- if (formalParameterCount > 2) {
- parms.push(list);
- }
- if (
- invoke(
- func,
- new List(parms),
- null,
- null,
- null,
- null,
- this.capture(reporter) // process
- )
- ) {
- return src[i];
- }
- }
- return false;
-};
-
-Process.prototype.reportAtomicCombine = function (list, reporter) {
- // if the reporter uses formal parameters instead of implicit empty slots
- // there are two additional optional parameters:
- // #1 - accumulator
- // #2 - element
- // #3 - optional | index
- // #4 - optional | source list
-
- this.assertType(list, 'list');
- var result = '',
- src = list.asArray(),
- len = src.length,
- formalParameterCount = reporter.inputs.length,
- parms,
- func,
- i;
-
- if (len === 0) {
- return result;
- }
- result = src[0];
-
- // try compiling the reporter into generic JavaScript
- // fall back to the morphic reporter if unsuccessful
- try {
- func = this.reportCompiled(reporter, 2); // a single expected input
- } catch (err) {
- console.log(err.message);
- func = reporter;
- }
-
- // iterate over the data in a single frame:
- // to do: Insert some kind of user escape mechanism
- for (i = 1; i < len; i += 1) {
- parms = [result, src[i]];
- if (formalParameterCount > 2) {
- parms.push(i + 1);
- }
- if (formalParameterCount > 3) {
- parms.push(list);
- }
- result = invoke(
- func,
- new List(parms),
- null,
- null,
- null,
- null,
- this.capture(reporter) // process
- );
- }
- return result;
-};
-
-Process.prototype.reportAtomicSort = function (list, reporter) {
- this.assertType(list, 'list');
- var func;
-
- // try compiling the reporter into generic JavaScript
- // fall back to the morphic reporter if unsuccessful
- try {
- func = this.reportCompiled(reporter, 2); // two inputs expected
- } catch (err) {
- console.log(err.message);
- func = reporter;
- }
-
- // iterate over the data in a single frame:
- return new List(
- list.asArray().slice().sort((a, b) =>
- invoke(
- func,
- new List([a, b]),
- null,
- null,
- null,
- null,
- this.capture(reporter) // process
- ) ? -1 : 1
- )
- );
-};
-
-Process.prototype.reportAtomicGroup = function (list, reporter) {
- this.assertType(list, 'list');
- var result = [],
- dict = new Map(),
- groupKey,
- src = list.asArray(),
- len = src.length,
- func,
- i;
-
- // try compiling the reporter into generic JavaScript
- // fall back to the morphic reporter if unsuccessful
- try {
- func = this.reportCompiled(reporter, 1); // a single expected input
- } catch (err) {
- console.log(err.message);
- func = reporter;
- }
-
- // iterate over the data in a single frame:
- // to do: Insert some kind of user escape mechanism
-
- for (i = 0; i < len; i += 1) {
- groupKey = invoke(
- func,
- new List([src[i]]),
- null,
- null,
- null,
- null,
- this.capture(reporter) // process
- );
- if (dict.has(groupKey)) {
- dict.get(groupKey).push(src[i]);
- } else {
- dict.set(groupKey, [src[i]]);
- }
- }
-
- dict.forEach((value, key) =>
- result.push(new List([key, value.length, new List(value)]))
- );
- return new List(result);
-};
-
-// Context /////////////////////////////////////////////////////////////
-
-/*
- A Context describes the state of a Process.
-
- Each Process has a pointer to a Context containing its
- state. Whenever the Process yields control, its Context
- tells it exactly where it left off.
-
- structure:
-
- 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,
- null or a String denoting a selector, e.g. 'doYield'
- origin the object of origin, only used for serialization
- 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
- (if expression is a BlockMorph)
- pc the index of the next block to evaluate
- (if expression is an array)
- isContinuation flag for marking a transient continuation context
- 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
- activeSends forked processes waiting to be completed
- isCustomBlock marker for return ops
- isCustomCommand marker for interpolated blocking reporters (reportURL)
- 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)
- isFlashing flag for single-stepping
- accumulator slot for collecting data from reentrant visits
-*/
-
-function Context(
- parentContext,
- expression,
- outerContext,
- receiver
-) {
- this.outerContext = outerContext || null;
- this.parentContext = parentContext || null;
- this.expression = expression || null;
- this.receiver = receiver || null;
- this.origin = receiver || null; // only for serialization
- this.variables = new VariableFrame();
- if (this.outerContext) {
- this.variables.parentFrame = this.outerContext.variables;
- this.receiver = this.outerContext.receiver;
- }
- this.inputs = [];
- this.pc = 0;
- this.isContinuation = false;
- this.startTime = null;
- this.activeSends = null;
- this.activeAudio = null;
- this.activeNote = null;
- this.isCustomBlock = false; // marks the end of a custom block's stack
- this.isCustomCommand = null; // used for ignoring URL reporters' results
- this.emptySlots = 0; // used for block reification
- this.tag = null; // lexical catch-tag for custom blocks
- this.isFlashing = false; // for single-stepping
- this.accumulator = null;
-}
-
-Context.prototype.toString = function () {
- var expr = this.expression;
- if (expr instanceof Array) {
- if (expr.length > 0) {
- expr = '[' + expr[0] + ']';
- }
- }
- return 'Context >> ' + expr + ' ' + this.variables;
-};
-
-Context.prototype.image = function () {
- var ring = new RingMorph(),
- block,
- cont;
-
- if (this.expression instanceof Morph) {
- block = this.expression.fullCopy();
-
- // replace marked call/cc block with empty slot
- if (this.isContinuation) {
- cont = detect(
- block.allInputs(),
- inp => inp.bindingID === 1
- );
- if (cont) {
- block.revertToDefaultInput(cont, true);
- }
- }
- ring.embed(block, this.inputs);
- return ring.fullImage();
- }
- if (this.expression instanceof Array) {
- block = this.expression[this.pc].fullCopy();
- if (block instanceof RingMorph && !block.contents()) { // empty ring
- return block.fullImage();
- }
- ring.embed(block, this.isContinuation ? [] : this.inputs);
- return ring.fullImage();
- }
-
- // otherwise show an empty ring
- ring.color = SpriteMorph.prototype.blockColor.other;
- ring.setSpec('%rc %ringparms');
-
- // also show my inputs, unless I'm a continuation
- if (!this.isContinuation) {
- this.inputs.forEach(inp =>
- ring.parts()[1].addInput(inp)
- );
- }
- return ring.fullImage();
-};
-
-// Context continuations:
-
-Context.prototype.continuation = function () {
- var cont;
- if (this.expression instanceof Array) {
- cont = this;
- } else if (this.parentContext) {
- cont = this.parentContext;
- } else {
- cont = new Context(null, 'expectReport');
- cont.isContinuation = true;
- return cont;
- }
- cont = cont.copyForContinuation();
- cont.tag = null;
- cont.isContinuation = true;
- return cont;
-};
-
-Context.prototype.copyForContinuation = function () {
- var cpy = copy(this),
- cur = cpy,
- isReporter = !(this.expression instanceof Array ||
- isString(this.expression));
- if (isReporter) {
- cur.prepareContinuationForBinding();
- while (cur.parentContext) {
- cur.parentContext = copy(cur.parentContext);
- cur = cur.parentContext;
- cur.inputs = [];
- }
- }
- return cpy;
-};
-
-Context.prototype.copyForContinuationCall = function () {
- var cpy = copy(this),
- cur = cpy,
- isReporter = !(this.expression instanceof Array ||
- isString(this.expression));
- if (isReporter) {
- this.expression = this.expression.fullCopy();
- this.inputs = [];
- while (cur.parentContext) {
- cur.parentContext = copy(cur.parentContext);
- cur = cur.parentContext;
- cur.inputs = [];
- }
- }
- return cpy;
-};
-
-Context.prototype.prepareContinuationForBinding = function () {
- var pos = this.inputs.length,
- slot;
- this.expression = this.expression.fullCopy();
- slot = this.expression.inputs()[pos];
- if (slot) {
- this.inputs = [];
- // mark slot containing the call/cc reporter with an identifier
- slot.bindingID = 1;
- // and remember the number of detected empty slots
- this.emptySlots = 1;
- }
-};
-
-// Context accessing:
-
-Context.prototype.addInput = function (input) {
- this.inputs.push(input);
-};
-
-// Context music
-
-Context.prototype.stopMusic = function () {
- if (this.activeNote) {
- this.activeNote.stop();
- this.activeNote = null;
- }
-};
-
-// Context single-stepping:
-
-Context.prototype.lastFlashable = function () {
- // for experimental single-stepping when pausing
- if (this.expression instanceof SyntaxElementMorph &&
- !(this.expression instanceof CommandSlotMorph)) {
- return this;
- } else if (this.parentContext) {
- return this.parentContext.lastFlashable();
- }
- return null;
-};
-
-// Context debugging
-
-Context.prototype.stackSize = function () {
- if (!this.parentContext) {
- return 1;
- }
- return 1 + this.parentContext.stackSize();
-};
-
-// Variable /////////////////////////////////////////////////////////////////
-
-function Variable(value, isTransient) {
- this.value = value;
- this.isTransient = isTransient || false; // prevent value serialization
-}
-
-Variable.prototype.toString = function () {
- return 'a ' + (this.isTransient ? 'transient ' : '') + 'Variable [' +
- this.value + ']';
-};
-
-Variable.prototype.copy = function () {
- return new Variable(this.value, this.isTransient);
-};
-
-// VariableFrame ///////////////////////////////////////////////////////
-
-function VariableFrame(parentFrame, owner) {
- this.vars = {};
- this.parentFrame = parentFrame || null;
- this.owner = owner || null;
-}
-
-VariableFrame.prototype.toString = function () {
- return 'a VariableFrame {' + this.names() + '}';
-};
-
-VariableFrame.prototype.copy = function () {
- var frame = new VariableFrame(this.parentFrame);
- this.names().forEach(vName =>
- frame.addVar(vName, this.getVar(vName))
- );
- return frame;
-};
-
-VariableFrame.prototype.fullCopy = function () {
- // experimental - for compiling to JS
- var frame;
- if (this.parentFrame) {
- frame = new VariableFrame(this.parentFrame.fullCopy());
- } else {
- frame = new VariableFrame();
- }
- frame.vars = copy(this.vars);
- return frame;
-};
-
-VariableFrame.prototype.root = function () {
- if (this.parentFrame) {
- return this.parentFrame.root();
- }
- return this;
-};
-
-VariableFrame.prototype.find = function (name) {
- // answer the closest variable frame containing
- // the specified variable. otherwise throw an exception.
- var frame = this.silentFind(name);
- if (frame) {return frame; }
- throw new Error(
- localize('a variable of name \'')
- + name
- + localize('\'\ndoes not exist in this context')
- );
-};
-
-VariableFrame.prototype.silentFind = function (name) {
- // answer the closest variable frame containing
- // the specified variable. Otherwise return null.
- if (this.vars[name] instanceof Variable) {
- return this;
- }
- if (this.parentFrame) {
- return this.parentFrame.silentFind(name);
- }
- return null;
-};
-
-VariableFrame.prototype.setVar = function (name, value, sender) {
- // change the specified variable if it exists
- // else throw an error, because variables need to be
- // declared explicitly (e.g. through a "script variables" block),
- // before they can be accessed.
- // if the found frame is inherited by the sender sprite
- // shadow it (create an explicit one for the sender)
- // before setting the value ("create-on-write")
-
- var frame = this.find(name);
- if (frame) {
- if (sender instanceof SpriteMorph &&
- (frame.owner instanceof SpriteMorph) &&
- (sender !== frame.owner)) {
- sender.shadowVar(name, value);
- } else {
- frame.vars[name].value = value;
- }
- }
-};
-
-VariableFrame.prototype.changeVar = function (name, delta, sender) {
- // change the specified variable if it exists
- // else throw an error, because variables need to be
- // declared explicitly (e.g. through a "script variables" block,
- // before they can be accessed.
- // if the found frame is inherited by the sender sprite
- // shadow it (create an explicit one for the sender)
- // before changing the value ("create-on-write")
-
- var frame = this.find(name),
- value,
- newValue;
- if (frame) {
- value = parseFloat(frame.vars[name].value);
- newValue = isNaN(value) ? delta : value + parseFloat(delta);
- if (sender instanceof SpriteMorph &&
- (frame.owner instanceof SpriteMorph) &&
- (sender !== frame.owner)) {
- sender.shadowVar(name, newValue);
- } else {
- frame.vars[name].value = newValue;
- }
-
- }
-};
-
-VariableFrame.prototype.getVar = function (name) {
- var frame = this.silentFind(name),
- value;
- if (frame) {
- value = frame.vars[name].value;
- return (value === 0 ? 0
- : value === false ? false
- : value === '' ? ''
- : value || 0); // don't return null
- }
- if (typeof name === 'number') {
- // empty input with a Binding-ID called without an argument
- return '';
- }
- throw new Error(
- localize('a variable of name \'')
- + name
- + localize('\'\ndoes not exist in this context')
- );
-};
-
-VariableFrame.prototype.addVar = function (name, value) {
- this.vars[name] = new Variable(value === 0 ? 0
- : value === false ? false
- : value === '' ? '' : value || 0);
-};
-
-VariableFrame.prototype.deleteVar = function (name) {
- var frame = this.find(name);
- if (frame) {
- delete frame.vars[name];
- }
-};
-
-// VariableFrame tools
-
-VariableFrame.prototype.names = function () {
- var each, names = [];
- for (each in this.vars) {
- if (Object.prototype.hasOwnProperty.call(this.vars, each)) {
- names.push(each);
- }
- }
- return names;
-};
-
-VariableFrame.prototype.allNamesDict = function (upTo) {
- // "upTo" is an optional parent frame at which to stop, e.g. globals
- var dict = {}, current = this;
-
- function addKeysToDict(srcDict, trgtDict) {
- var eachKey;
- for (eachKey in srcDict) {
- if (Object.prototype.hasOwnProperty.call(srcDict, eachKey)) {
- trgtDict[eachKey] = eachKey;
- }
- }
- }
-
- while (current && (current !== upTo)) {
- addKeysToDict(current.vars, dict);
- current = current.parentFrame;
- }
- return dict;
-};
-
-VariableFrame.prototype.allNames = function (upTo) {
-/*
- only show the names of the lexical scope, hybrid scoping is
- reserved to the daring ;-)
- "upTo" is an optional parent frame at which to stop, e.g. globals
-*/
- var answer = [], each, dict = this.allNamesDict(upTo);
-
- for (each in dict) {
- if (Object.prototype.hasOwnProperty.call(dict, each)) {
- answer.push(each);
- }
- }
- return answer;
-};
-
-// JSCompiler /////////////////////////////////////////////////////////////////
-
-/*
- Compile simple, side-effect free Reporters
- with either only explicit formal parameters or a specified number of
- implicit formal parameters mapped to empty input slots
- *** highly experimental and heavily under construction ***
-*/
-
-function JSCompiler(aProcess) {
- this.process = aProcess;
- this.source = null; // a context
- this.gensyms = null; // temp dictionary for parameter substitutions
- this.implicitParams = null;
- this.paramCount = null;
-}
-
-JSCompiler.prototype.toString = function () {
- return 'a JSCompiler';
-};
-
-JSCompiler.prototype.compileFunction = function (aContext, implicitParamCount) {
- var block = aContext.expression,
- parameters = aContext.inputs,
- parms = [],
- hasEmptySlots = false,
- i;
-
- this.source = aContext;
- this.implicitParams = implicitParamCount || 1;
-
- // scan for empty input slots
- hasEmptySlots = !isNil(detect(
- block.allChildren(),
- morph => morph.isEmptySlot && morph.isEmptySlot()
- ));
-
- // translate formal parameters into gensyms
- this.gensyms = {};
- this.paramCount = 0;
- if (parameters.length) {
- // test for conflicts
- if (hasEmptySlots) {
- throw new Error(
- 'compiling does not yet support\n' +
- 'mixing explicit formal parameters\n' +
- 'with empty input slots'
- );
- }
- // map explicit formal parameters
- parameters.forEach((pName, idx) => {
- var pn = 'p' + idx;
- parms.push(pn);
- this.gensyms[pName] = pn;
- });
- } else if (hasEmptySlots) {
- if (this.implicitParams > 1) {
- for (i = 0; i < this.implicitParams; i += 1) {
- parms.push('p' + i);
- }
- } else {
- // allow for a single implicit formal parameter
- parms = ['p0'];
- }
- }
-
- // compile using gensyms
-
- if (block instanceof CommandBlockMorph) {
- return Function.apply(
- null,
- parms.concat([this.compileSequence(block)])
- );
- }
- return Function.apply(
- null,
- parms.concat(['return ' + this.compileExpression(block)])
- );
-};
-
-JSCompiler.prototype.compileExpression = function (block) {
- var selector = block.selector,
- inputs = block.inputs(),
- target,
- rcvr,
- args;
-
- // first check for special forms and infix operators
- switch (selector) {
- case 'reportOr':
- return this.compileInfix('||', inputs);
- case 'reportAnd':
- return this.compileInfix('&&', inputs);
- case 'reportIfElse':
- return '(' +
- this.compileInput(inputs[0]) +
- ' ? ' +
- this.compileInput(inputs[1]) +
- ' : ' +
- this.compileInput(inputs[2]) +
- ')';
- case 'evaluateCustomBlock':
- throw new Error(
- 'compiling does not yet support\n' +
- 'custom blocks'
- );
-
- // special command forms
- case 'doSetVar': // redirect var to process
- return 'arguments[arguments.length - 1].setVarNamed(' +
- this.compileInput(inputs[0]) +
- ',' +
- this.compileInput(inputs[1]) +
- ')';
- case 'doChangeVar': // redirect var to process
- return 'arguments[arguments.length - 1].incrementVarNamed(' +
- this.compileInput(inputs[0]) +
- ',' +
- this.compileInput(inputs[1]) +
- ')';
- case 'doReport':
- return 'return ' + this.compileInput(inputs[0]);
- case 'doIf':
- return 'if (' +
- this.compileInput(inputs[0]) +
- ') {\n' +
- this.compileSequence(inputs[1].evaluate()) +
- '}';
- case 'doIfElse':
- return 'if (' +
- this.compileInput(inputs[0]) +
- ') {\n' +
- this.compileSequence(inputs[1].evaluate()) +
- '} else {\n' +
- this.compileSequence(inputs[2].evaluate()) +
- '}';
-
- default:
- target = this.process[selector] ? this.process
- : (this.source.receiver || this.process.receiver);
- rcvr = target.constructor.name + '.prototype';
- args = this.compileInputs(inputs);
- if (isSnapObject(target)) {
- return rcvr + '.' + selector + '.apply('+ rcvr + ', [' + args +'])';
- } else {
- return 'arguments[arguments.length - 1].' +
- selector +
- '.apply(arguments[arguments.length - 1], [' + args +'])';
- }
- }
-};
-
-JSCompiler.prototype.compileSequence = function (commandBlock) {
- var body = '';
- commandBlock.blockSequence().forEach(block => {
- body += this.compileExpression(block);
- body += ';\n';
- });
- return body;
-};
-
-JSCompiler.prototype.compileInfix = function (operator, inputs) {
- return '(' + this.compileInput(inputs[0]) + ' ' + operator + ' ' +
- this.compileInput(inputs[1]) +')';
-};
-
-JSCompiler.prototype.compileInputs = function (array) {
- var args = '';
- array.forEach(inp => {
- if (args.length) {
- args += ', ';
- }
- args += this.compileInput(inp);
- });
- return args;
-};
-
-JSCompiler.prototype.compileInput = function (inp) {
- var value, type;
-
- if (inp.isEmptySlot && inp.isEmptySlot()) {
- // implicit formal parameter
- if (this.implicitParams > 1) {
- if (this.paramCount < this.implicitParams) {
- this.paramCount += 1;
- return 'p' + (this.paramCount - 1);
- }
- throw new Error(
- localize('expecting') + ' ' + this.implicitParams + ' '
- + localize('input(s), but getting') + ' '
- + this.paramCount
- );
- }
- return 'p0';
- } else if (inp instanceof MultiArgMorph) {
- return 'new List([' + this.compileInputs(inp.inputs()) + '])';
- } else if (inp instanceof ArgLabelMorph) {
- return this.compileInput(inp.argMorph());
- } else if (inp instanceof ArgMorph) {
- // literal - evaluate inline
- value = inp.evaluate();
- type = this.process.reportTypeOf(value);
- switch (type) {
- case 'number':
- case 'Boolean':
- return '' + value;
- case 'text':
- // enclose in double quotes
- return '"' + value + '"';
- case 'list':
- return 'new List([' + this.compileInputs(value) + '])';
- default:
- if (value instanceof Array) {
- return '"' + value[0] + '"';
- }
- throw new Error(
- 'compiling does not yet support\n' +
- 'inputs of type\n' +
- type
- );
- }
- } else if (inp instanceof BlockMorph) {
- if (inp.selector === 'reportGetVar') {
- if (contains(this.source.inputs, inp.blockSpec)) {
- // un-quoted gensym:
- return this.gensyms[inp.blockSpec];
- }
- // redirect var query to process
- return 'arguments[arguments.length - 1].getVarNamed("' +
- inp.blockSpec +
- '")';
- }
- return this.compileExpression(inp);
- } else {
- throw new Error(
- 'compiling does not yet support\n' +
- 'input slots of type\n' +
- inp.constructor.name
- );
- }
-};