2013-03-16 08:02:16 +00:00
|
|
|
/*
|
|
|
|
|
|
|
|
threads.js
|
|
|
|
|
|
|
|
a tail call optimized blocks-based programming language interpreter
|
|
|
|
based on morphic.js and blocks.js
|
|
|
|
inspired by Scratch, Scheme and Squeak
|
|
|
|
|
2013-04-09 02:15:30 +00:00
|
|
|
written by Jens Mönig
|
2013-03-16 08:02:16 +00:00
|
|
|
jens@moenig.org
|
|
|
|
|
2022-01-03 11:05:24 +00:00
|
|
|
Copyright (C) 2022 by Jens Mönig
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2013-04-09 01:49:31 +00:00
|
|
|
This file is part of Snap!.
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2014-09-18 12:26:28 +00:00
|
|
|
Variable
|
2013-03-16 08:02:16 +00:00
|
|
|
VariableFrame
|
2018-03-20 08:38:36 +00:00
|
|
|
JSCompiler
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
credits
|
|
|
|
-------
|
|
|
|
John Maloney and Dave Feinberg designed the original Scratch evaluator
|
|
|
|
Ivan Motyashov contributed initial porting from Squeak
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Global stuff ////////////////////////////////////////////////////////
|
|
|
|
|
2020-04-17 22:09:36 +00:00
|
|
|
/*global ArgMorph, BlockMorph, CommandBlockMorph, CommandSlotMorph, Morph, ZERO,
|
2017-07-26 14:03:23 +00:00
|
|
|
MultiArgMorph, Point, ReporterBlockMorph, SyntaxElementMorph, contains, Costume,
|
|
|
|
degrees, detect, nop, radians, ReporterSlotMorph, CSlotMorph, RingMorph, Sound,
|
2016-02-24 10:35:18 +00:00
|
|
|
IDE_Morph, ArgLabelMorph, localize, XML_Element, hex_sha512, TableDialogMorph,
|
2020-04-17 22:09:36 +00:00
|
|
|
StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, Map,
|
2020-07-01 16:59:32 +00:00
|
|
|
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, BLACK,
|
2021-06-14 11:04:25 +00:00
|
|
|
TableFrameMorph, ColorSlotMorph, isSnapObject, newCanvas, Symbol, SVG_Costume,
|
2021-11-08 17:19:02 +00:00
|
|
|
SnapExtensions, AlignmentMorph, TextMorph, Cloud, HatBlockMorph*/
|
2016-02-24 10:35:18 +00:00
|
|
|
|
2021-12-02 09:46:56 +00:00
|
|
|
/*jshint esversion: 6*/
|
2021-07-05 14:46:31 +00:00
|
|
|
|
2022-01-03 11:05:24 +00:00
|
|
|
modules.threads = '2022-January-03';
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
var ThreadManager;
|
|
|
|
var Process;
|
|
|
|
var Context;
|
2018-03-20 08:38:36 +00:00
|
|
|
var Variable;
|
2013-03-16 08:02:16 +00:00
|
|
|
var VariableFrame;
|
2018-03-20 08:38:36 +00:00
|
|
|
var JSCompiler;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2020-06-27 14:34:12 +00:00
|
|
|
const NONNUMBERS = [true, false, ''];
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
// "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 (var i = 9; i <= 13; i += 1) {
|
|
|
|
NONNUMBERS.push(String.fromCharCode(i));
|
|
|
|
}
|
|
|
|
NONNUMBERS.push(String.fromCharCode(160));
|
|
|
|
})();
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
function snapEquals(a, b) {
|
2021-12-02 09:46:56 +00:00
|
|
|
// nil
|
|
|
|
if (isNil(a) || isNil(b)) {
|
|
|
|
return a === b;
|
|
|
|
}
|
|
|
|
|
2021-12-01 18:51:01 +00:00
|
|
|
// lists, functions and blocks
|
2021-12-02 09:46:56 +00:00
|
|
|
if (a.equalTo || b.equalTo) {
|
2021-12-01 18:51:01 +00:00
|
|
|
if (a.constructor.name === b.constructor.name) {
|
2013-03-16 08:02:16 +00:00
|
|
|
return a.equalTo(b);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2013-12-04 10:11:07 +00:00
|
|
|
|
2013-11-26 09:40:24 +00:00
|
|
|
var x = +a,
|
2020-06-27 14:34:12 +00:00
|
|
|
y = +b;
|
2014-11-24 08:28:45 +00:00
|
|
|
|
2013-12-04 10:11:07 +00:00
|
|
|
// check for special values before coercing to numbers
|
|
|
|
if (isNaN(x) || isNaN(y) ||
|
2020-06-27 14:34:12 +00:00
|
|
|
[a, b].some(any => contains(NONNUMBERS, any) ||
|
2020-04-28 17:27:42 +00:00
|
|
|
(isString(any) && (any.indexOf(' ') > -1)))
|
|
|
|
) {
|
2013-03-16 08:02:16 +00:00
|
|
|
x = a;
|
|
|
|
y = b;
|
|
|
|
}
|
2013-12-04 08:47:42 +00:00
|
|
|
|
2014-11-24 08:28:45 +00:00
|
|
|
// handle text comparison case-insensitive.
|
2013-03-16 08:02:16 +00:00
|
|
|
if (isString(x) && isString(y)) {
|
|
|
|
return x.toLowerCase() === y.toLowerCase();
|
|
|
|
}
|
2013-12-04 08:47:42 +00:00
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
return x === y;
|
|
|
|
}
|
|
|
|
|
2015-12-15 09:14:56 +00:00
|
|
|
function invoke(
|
|
|
|
action, // a BlockMorph or a Context, a reified ("ringified") block
|
|
|
|
contextArgs, // optional List of arguments for the context, or null
|
2017-05-30 15:07:09 +00:00
|
|
|
receiver, // sprite or environment, optional for contexts
|
2015-12-15 09:14:56 +00:00
|
|
|
timeout, // msecs
|
|
|
|
timeoutErrorMsg, // string
|
2018-03-20 08:38:36 +00:00
|
|
|
suppressErrors, // bool
|
2019-06-03 10:56:06 +00:00
|
|
|
callerProcess, // optional for JS-functions
|
|
|
|
returnContext // bool
|
2015-12-15 09:14:56 +00:00
|
|
|
) {
|
|
|
|
// 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
|
2015-11-16 11:18:40 +00:00
|
|
|
// after the timeout has been reached and throw an error.
|
2015-12-15 09:14:56 +00:00
|
|
|
// SuppressErrors (bool) if non-timeout errors occurring in the
|
|
|
|
// block are handled elsewhere.
|
|
|
|
// This is highly experimental.
|
2015-11-16 11:18:40 +00:00
|
|
|
// Caution: Kids, do not try this at home!
|
2015-12-15 09:14:56 +00:00
|
|
|
// Use ThreadManager::startProcess with a callback instead
|
|
|
|
|
|
|
|
var proc = new Process(),
|
2017-05-30 15:07:09 +00:00
|
|
|
deadline = (timeout ? Date.now() + timeout : null);
|
2015-12-15 09:14:56 +00:00
|
|
|
|
|
|
|
if (action instanceof Context) {
|
2017-05-30 15:07:09 +00:00
|
|
|
if (receiver) { // optional
|
2015-12-15 09:14:56 +00:00
|
|
|
action = proc.reportContextFor(receiver);
|
|
|
|
}
|
|
|
|
proc.initializeFor(action, contextArgs || new List());
|
|
|
|
} else if (action instanceof BlockMorph) {
|
|
|
|
proc.topBlock = action;
|
2017-05-30 15:07:09 +00:00
|
|
|
if (receiver) {
|
2015-12-15 09:14:56 +00:00
|
|
|
proc.homeContext = new Context();
|
2017-05-30 15:07:09 +00:00
|
|
|
proc.homeContext.receiver = receiver;
|
|
|
|
if (receiver.variables) {
|
|
|
|
proc.homeContext.variables.parentFrame = receiver.variables;
|
2015-12-15 09:14:56 +00:00
|
|
|
}
|
2017-05-30 15:07:09 +00:00
|
|
|
} else {
|
|
|
|
throw new Error('expecting a receiver but getting ' + receiver);
|
2015-11-16 11:18:40 +00:00
|
|
|
}
|
2015-12-15 09:14:56 +00:00
|
|
|
proc.context = new Context(
|
|
|
|
null,
|
|
|
|
action.blockSequence(),
|
|
|
|
proc.homeContext
|
|
|
|
);
|
2016-06-01 11:35:54 +00:00
|
|
|
} else if (action.evaluate) {
|
|
|
|
return action.evaluate();
|
2018-02-12 10:08:58 +00:00
|
|
|
} else if (action instanceof Function) {
|
2018-03-20 08:38:36 +00:00
|
|
|
return action.apply(
|
|
|
|
receiver,
|
2020-11-30 08:46:41 +00:00
|
|
|
contextArgs.itemsArray().concat(callerProcess)
|
2018-03-20 08:38:36 +00:00
|
|
|
);
|
2015-12-15 09:14:56 +00:00
|
|
|
} else {
|
|
|
|
throw new Error('expecting a block or ring but getting ' + action);
|
|
|
|
}
|
|
|
|
if (suppressErrors) {
|
|
|
|
proc.isCatchingErrors = false;
|
2015-11-16 11:18:40 +00:00
|
|
|
}
|
2015-12-15 09:14:56 +00:00
|
|
|
while (proc.isRunning()) {
|
|
|
|
if (deadline && (Date.now() > deadline)) {
|
|
|
|
throw (new Error(
|
|
|
|
localize(
|
|
|
|
timeoutErrorMsg ||
|
|
|
|
"a synchronous Snap! script has timed out")
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
proc.runStep(deadline);
|
2015-11-16 11:18:40 +00:00
|
|
|
}
|
2019-06-03 10:56:06 +00:00
|
|
|
return returnContext ? proc.homeContext : proc.homeContext.inputs[0];
|
2015-11-16 11:18:40 +00:00
|
|
|
}
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// ThreadManager ///////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function ThreadManager() {
|
|
|
|
this.processes = [];
|
2016-10-15 09:15:01 +00:00
|
|
|
this.wantsToPause = false; // single stepping support
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
|
2015-12-15 09:14:56 +00:00
|
|
|
ThreadManager.prototype.pauseCustomHatBlocks = false;
|
2021-11-11 14:49:35 +00:00
|
|
|
ThreadManager.prototype.disableClickToRun = false;
|
2015-12-15 09:14:56 +00:00
|
|
|
|
2017-05-30 15:07:09 +00:00
|
|
|
ThreadManager.prototype.toggleProcess = function (block, receiver) {
|
2021-11-11 14:49:35 +00:00
|
|
|
if (this.disableClickToRun) {
|
|
|
|
return;
|
|
|
|
}
|
2017-05-30 15:07:09 +00:00
|
|
|
var active = this.findProcess(block, receiver);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (active) {
|
|
|
|
active.stop();
|
|
|
|
} else {
|
2021-11-08 17:19:02 +00:00
|
|
|
return this.startProcess(
|
|
|
|
block,
|
|
|
|
receiver,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
true, // isClicked
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
this.clickFrameFor(block) // for upvars declared inside hat blocks
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-30 14:28:01 +00:00
|
|
|
ThreadManager.prototype.startProcess = function (
|
|
|
|
block,
|
2017-05-30 15:07:09 +00:00
|
|
|
receiver,
|
2014-07-30 14:28:01 +00:00
|
|
|
isThreadSafe,
|
2017-05-30 15:07:09 +00:00
|
|
|
exportResult, // bool
|
2015-12-15 09:14:56 +00:00
|
|
|
callback,
|
2016-05-02 10:52:58 +00:00
|
|
|
isClicked,
|
2018-06-21 09:17:25 +00:00
|
|
|
rightAway,
|
2019-06-03 10:56:06 +00:00
|
|
|
atomic, // special option used (only) for "onStop" scripts
|
|
|
|
variables // optional variable frame, used for WHEN hats
|
2014-07-30 14:28:01 +00:00
|
|
|
) {
|
2017-05-30 15:07:09 +00:00
|
|
|
var top = block.topBlock(),
|
|
|
|
active = this.findProcess(top, receiver),
|
2017-06-19 14:59:44 +00:00
|
|
|
glow,
|
2013-03-16 08:02:16 +00:00
|
|
|
newProc;
|
|
|
|
if (active) {
|
|
|
|
if (isThreadSafe) {
|
|
|
|
return active;
|
|
|
|
}
|
|
|
|
active.stop();
|
2018-07-19 18:06:15 +00:00
|
|
|
active.canBroadcast = true; // broadcasts to fire despite reentrancy
|
2013-03-16 08:02:16 +00:00
|
|
|
this.removeTerminatedProcesses();
|
|
|
|
}
|
2017-06-19 08:39:16 +00:00
|
|
|
newProc = new Process(top, receiver, callback, isClicked);
|
2014-07-30 14:28:01 +00:00
|
|
|
newProc.exportResult = exportResult;
|
2015-12-15 09:14:56 +00:00
|
|
|
newProc.isClicked = isClicked || false;
|
2018-06-21 09:17:25 +00:00
|
|
|
newProc.isAtomic = atomic || false;
|
2017-06-02 15:44:26 +00:00
|
|
|
|
2019-06-03 10:56:06 +00:00
|
|
|
// 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) {
|
2020-04-28 17:27:42 +00:00
|
|
|
Object.keys(variables.vars).forEach(vName =>
|
2019-06-03 10:56:06 +00:00
|
|
|
newProc.context.outerContext.variables.vars[vName] =
|
2020-04-28 17:27:42 +00:00
|
|
|
variables.vars[vName]
|
|
|
|
);
|
2019-06-03 10:56:06 +00:00
|
|
|
}
|
|
|
|
|
2017-06-02 15:44:26 +00:00
|
|
|
// 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
|
2017-06-19 14:59:44 +00:00
|
|
|
glow = top.getHighlight();
|
|
|
|
if (glow) {
|
|
|
|
glow.threadCount = this.processesForBlock(top).length + 1;
|
|
|
|
glow.updateReadout();
|
|
|
|
} else {
|
2015-01-12 09:17:13 +00:00
|
|
|
top.addHighlight();
|
|
|
|
}
|
2017-06-02 15:44:26 +00:00
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
this.processes.push(newProc);
|
2016-05-02 10:52:58 +00:00
|
|
|
if (rightAway) {
|
|
|
|
newProc.runStep();
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
return newProc;
|
|
|
|
};
|
|
|
|
|
2014-01-08 16:51:34 +00:00
|
|
|
ThreadManager.prototype.stopAll = function (excpt) {
|
|
|
|
// excpt is optional
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => {
|
2014-01-08 16:51:34 +00:00
|
|
|
if (proc !== excpt) {
|
2013-03-16 08:02:16 +00:00
|
|
|
proc.stop();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-01-08 16:51:34 +00:00
|
|
|
ThreadManager.prototype.stopAllForReceiver = function (rcvr, excpt) {
|
|
|
|
// excpt is optional
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => {
|
2014-01-08 16:51:34 +00:00
|
|
|
if (proc.homeContext.receiver === rcvr && proc !== excpt) {
|
2013-03-16 08:02:16 +00:00
|
|
|
proc.stop();
|
2017-07-04 11:51:22 +00:00
|
|
|
if (rcvr.isTemporary) {
|
2013-03-16 08:02:16 +00:00
|
|
|
proc.isDead = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-06-20 06:51:55 +00:00
|
|
|
ThreadManager.prototype.stopAllForBlock = function (aTopBlock) {
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processesForBlock(aTopBlock, true).forEach(proc =>
|
|
|
|
proc.stop()
|
|
|
|
);
|
2017-06-20 06:51:55 +00:00
|
|
|
};
|
|
|
|
|
2017-05-30 15:07:09 +00:00
|
|
|
ThreadManager.prototype.stopProcess = function (block, receiver) {
|
|
|
|
var active = this.findProcess(block, receiver);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (active) {
|
|
|
|
active.stop();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ThreadManager.prototype.pauseAll = function (stage) {
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => proc.pause());
|
2013-03-16 08:02:16 +00:00
|
|
|
if (stage) {
|
|
|
|
stage.pauseAllActiveSounds();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ThreadManager.prototype.isPaused = function () {
|
2020-04-28 17:27:42 +00:00
|
|
|
return detect(
|
|
|
|
this.processes,
|
|
|
|
proc => proc.isPaused
|
|
|
|
) !== null;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ThreadManager.prototype.resumeAll = function (stage) {
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => proc.resume());
|
2013-03-16 08:02:16 +00:00
|
|
|
if (stage) {
|
|
|
|
stage.resumeAllActiveSounds();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ThreadManager.prototype.step = function () {
|
2014-11-25 11:24:20 +00:00
|
|
|
// 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
|
|
|
|
|
2016-10-15 09:15:01 +00:00
|
|
|
var isInterrupted;
|
|
|
|
if (Process.prototype.enableSingleStepping) {
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => {
|
2016-10-15 09:15:01 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => {
|
2013-03-16 08:02:16 +00:00
|
|
|
if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) {
|
|
|
|
proc.runStep();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.removeTerminatedProcesses();
|
|
|
|
};
|
|
|
|
|
|
|
|
ThreadManager.prototype.removeTerminatedProcesses = function () {
|
|
|
|
// and un-highlight their scripts
|
2017-06-02 15:44:26 +00:00
|
|
|
var remaining = [],
|
2020-04-28 17:27:42 +00:00
|
|
|
count;
|
|
|
|
this.processes.forEach(proc => {
|
2017-06-19 14:59:44 +00:00
|
|
|
var result,
|
|
|
|
glow;
|
2014-12-17 08:41:21 +00:00
|
|
|
if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) {
|
2017-06-02 15:44:26 +00:00
|
|
|
if (proc.topBlock instanceof BlockMorph) {
|
|
|
|
proc.unflash();
|
2017-06-19 14:59:44 +00:00
|
|
|
// adjust the thread count indicator, if any
|
2020-04-28 17:27:42 +00:00
|
|
|
count = this.processesForBlock(proc.topBlock).length;
|
2017-06-19 14:59:44 +00:00
|
|
|
if (count) {
|
|
|
|
glow = proc.topBlock.getHighlight() ||
|
|
|
|
proc.topBlock.addHighlight();
|
|
|
|
glow.threadCount = count;
|
|
|
|
glow.updateReadout();
|
|
|
|
} else {
|
2017-06-02 15:44:26 +00:00
|
|
|
proc.topBlock.removeHighlight();
|
|
|
|
}
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
if (proc.prompter) {
|
|
|
|
proc.prompter.destroy();
|
|
|
|
if (proc.homeContext.receiver.stopTalking) {
|
|
|
|
proc.homeContext.receiver.stopTalking();
|
|
|
|
}
|
|
|
|
}
|
2016-02-24 10:35:18 +00:00
|
|
|
if (proc.topBlock instanceof ReporterBlockMorph ||
|
2020-10-07 22:22:42 +00:00
|
|
|
proc.isShowingResult || proc.exportResult) {
|
2016-02-24 10:35:18 +00:00
|
|
|
result = proc.homeContext.inputs[0];
|
2014-11-20 13:40:13 +00:00
|
|
|
if (proc.onComplete instanceof Function) {
|
2016-02-24 10:35:18 +00:00
|
|
|
proc.onComplete(result);
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2016-02-24 10:35:18 +00:00
|
|
|
if (result instanceof List) {
|
2014-11-19 08:34:59 +00:00
|
|
|
proc.topBlock.showBubble(
|
2016-02-24 10:35:18 +00:00
|
|
|
result.isTable() ?
|
|
|
|
new TableFrameMorph(
|
|
|
|
new TableMorph(result, 10)
|
|
|
|
)
|
|
|
|
: new ListWatcherMorph(result),
|
2017-06-02 15:44:26 +00:00
|
|
|
proc.exportResult,
|
|
|
|
proc.receiver
|
2014-11-19 08:34:59 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
proc.topBlock.showBubble(
|
2016-02-24 10:35:18 +00:00
|
|
|
result,
|
2017-06-02 15:44:26 +00:00
|
|
|
proc.exportResult,
|
|
|
|
proc.receiver
|
2014-11-19 08:34:59 +00:00
|
|
|
);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2019-12-15 12:32:10 +00:00
|
|
|
} else if (proc.onComplete instanceof Function) {
|
|
|
|
proc.onComplete();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
remaining.push(proc);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.processes = remaining;
|
|
|
|
};
|
|
|
|
|
2017-05-30 15:07:09 +00:00
|
|
|
ThreadManager.prototype.findProcess = function (block, receiver) {
|
2013-03-16 08:02:16 +00:00
|
|
|
var top = block.topBlock();
|
|
|
|
return detect(
|
|
|
|
this.processes,
|
2020-04-28 17:27:42 +00:00
|
|
|
each => each.topBlock === top && (each.receiver === receiver)
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2017-06-20 06:51:55 +00:00
|
|
|
ThreadManager.prototype.processesForBlock = function (block, only) {
|
|
|
|
var top = only ? block : block.topBlock();
|
2020-04-28 17:27:42 +00:00
|
|
|
return this.processes.filter(each =>
|
|
|
|
each.topBlock === top &&
|
|
|
|
each.isRunning() &&
|
|
|
|
!each.isDead
|
|
|
|
);
|
2017-06-02 15:44:26 +00:00
|
|
|
};
|
|
|
|
|
2017-05-30 15:07:09 +00:00
|
|
|
ThreadManager.prototype.doWhen = function (block, receiver, stopIt) {
|
2015-12-15 09:14:56 +00:00
|
|
|
if (this.pauseCustomHatBlocks) {return; }
|
2017-05-30 15:07:09 +00:00
|
|
|
if ((!block) || this.findProcess(block, receiver)) {
|
2017-01-11 12:39:48 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-06-03 10:56:06 +00:00
|
|
|
var pred = block.inputs()[0], world, test;
|
2015-12-15 09:14:56 +00:00
|
|
|
if (block.removeHighlight()) {
|
2016-02-24 22:15:32 +00:00
|
|
|
world = block.world();
|
|
|
|
if (world) {
|
|
|
|
world.hand.destroyTemporaries();
|
|
|
|
}
|
2015-12-15 09:14:56 +00:00
|
|
|
}
|
|
|
|
if (stopIt) {return; }
|
|
|
|
try {
|
2019-06-03 10:56:06 +00:00
|
|
|
test = invoke(
|
2015-12-15 09:14:56 +00:00
|
|
|
pred,
|
|
|
|
null,
|
2017-05-30 15:07:09 +00:00
|
|
|
receiver,
|
2019-06-03 10:56:06 +00:00
|
|
|
50, // timeout in msecs
|
2015-12-15 09:14:56 +00:00
|
|
|
'the predicate takes\ntoo long for a\ncustom hat block',
|
2019-06-03 10:56:06 +00:00
|
|
|
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
|
|
|
|
);
|
2015-12-15 09:14:56 +00:00
|
|
|
} catch (error) {
|
|
|
|
block.addErrorHighlight();
|
|
|
|
block.showBubble(
|
|
|
|
error.name
|
|
|
|
+ '\n'
|
|
|
|
+ error.message
|
|
|
|
);
|
|
|
|
}
|
2019-06-03 10:56:06 +00:00
|
|
|
// 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
|
2019-07-15 05:38:05 +00:00
|
|
|
if (test === true || (test && test.inputs && test.inputs[0] === true)) {
|
2019-06-03 10:56:06 +00:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
2015-12-15 09:14:56 +00:00
|
|
|
};
|
|
|
|
|
2016-10-15 09:15:01 +00:00
|
|
|
ThreadManager.prototype.toggleSingleStepping = function () {
|
|
|
|
Process.prototype.enableSingleStepping =
|
|
|
|
!Process.prototype.enableSingleStepping;
|
|
|
|
if (!Process.prototype.enableSingleStepping) {
|
2020-04-28 17:27:42 +00:00
|
|
|
this.processes.forEach(proc => {
|
2016-10-15 09:15:01 +00:00
|
|
|
if (!proc.isPaused) {
|
|
|
|
proc.unflash();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-08 17:19:02 +00:00
|
|
|
ThreadManager.prototype.clickFrameFor = function (block) {
|
|
|
|
// private - answer a variable frame or null containing upvar declarations
|
|
|
|
// in certain hat blocks if the user manually clicks on them
|
|
|
|
var name, frame;
|
|
|
|
if (block instanceof HatBlockMorph) {
|
|
|
|
if (block.selector === 'receiveKey' ||
|
|
|
|
block.selector === 'receiveMessage') {
|
|
|
|
name = block.inputs()[1].evaluate()[0];
|
|
|
|
if (name) {
|
|
|
|
frame = new VariableFrame();
|
|
|
|
frame.addVar(name, '');
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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
|
2017-08-02 12:33:12 +00:00
|
|
|
instrument musical instrument type, cached from the receiver,
|
|
|
|
so a single sprite can play several instruments
|
|
|
|
at once
|
2014-11-20 13:40:13 +00:00
|
|
|
context the Context describing the current state
|
2013-03-16 08:02:16 +00:00
|
|
|
of this process
|
2014-11-20 13:40:13 +00:00
|
|
|
homeContext stores information relevant to the whole process,
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2014-11-20 13:40:13 +00:00
|
|
|
timeout msecs after which to force yield
|
|
|
|
lastYield msecs when the process last yielded
|
2016-06-12 12:10:48 +00:00
|
|
|
isFirstStep boolean indicating whether on first step - for clones
|
2014-11-20 13:40:13 +00:00
|
|
|
errorFlag boolean indicating whether an error was encountered
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2015-12-15 09:14:56 +00:00
|
|
|
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
|
2014-07-30 14:28:01 +00:00
|
|
|
exportResult boolean flag indicating whether a picture of the top
|
|
|
|
block along with the result bubble shoud be exported
|
2014-11-20 13:40:13 +00:00
|
|
|
onComplete an optional callback function to be executed when
|
|
|
|
the process is done
|
2014-11-21 15:55:25 +00:00
|
|
|
procedureCount number counting procedure call entries,
|
|
|
|
used to tag custom block calls, so "stop block"
|
|
|
|
invocations can catch them
|
2016-10-15 09:15:01 +00:00
|
|
|
flashingContext for single stepping
|
|
|
|
isInterrupted boolean, indicates intra-step flashing of blocks
|
2018-07-19 18:06:15 +00:00
|
|
|
canBroadcast boolean, used to control reentrancy & "when stopped"
|
2013-03-16 08:02:16 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
Process.prototype = {};
|
2015-05-19 20:48:17 +00:00
|
|
|
Process.prototype.constructor = Process;
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.timeout = 500; // msecs after which to force yield
|
|
|
|
Process.prototype.isCatchingErrors = true;
|
2021-12-09 12:27:01 +00:00
|
|
|
Process.prototype.enableHyperOps = true;
|
2016-05-02 10:52:58 +00:00
|
|
|
Process.prototype.enableLiveCoding = false; // experimental
|
2021-12-09 12:27:01 +00:00
|
|
|
Process.prototype.enableSingleStepping = false;
|
2018-02-15 14:03:40 +00:00
|
|
|
Process.prototype.enableCompiling = false; // experimental
|
2021-12-09 12:27:01 +00:00
|
|
|
Process.prototype.flashTime = 0;
|
2021-06-09 16:30:09 +00:00
|
|
|
Process.prototype.enableJS = false;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2017-06-19 08:39:16 +00:00
|
|
|
function Process(topBlock, receiver, onComplete, yieldFirst) {
|
2013-03-16 08:02:16 +00:00
|
|
|
this.topBlock = topBlock || null;
|
2017-05-30 15:07:09 +00:00
|
|
|
this.receiver = receiver;
|
2017-08-02 12:33:12 +00:00
|
|
|
this.instrument = receiver ? receiver.instrument : null;
|
2013-03-16 08:02:16 +00:00
|
|
|
this.readyToYield = false;
|
|
|
|
this.readyToTerminate = false;
|
|
|
|
this.isDead = false;
|
2015-12-15 09:14:56 +00:00
|
|
|
this.isClicked = false;
|
|
|
|
this.isShowingResult = false;
|
2013-03-16 08:02:16 +00:00
|
|
|
this.errorFlag = false;
|
|
|
|
this.context = null;
|
2017-05-30 15:07:09 +00:00
|
|
|
this.homeContext = new Context(null, null, null, receiver);
|
2016-06-12 12:10:48 +00:00
|
|
|
this.lastYield = Date.now();
|
|
|
|
this.isFirstStep = true;
|
2013-03-16 08:02:16 +00:00
|
|
|
this.isAtomic = false;
|
|
|
|
this.prompter = null;
|
|
|
|
this.httpRequest = null;
|
|
|
|
this.isPaused = false;
|
|
|
|
this.pauseOffset = null;
|
2020-12-19 12:53:04 +00:00
|
|
|
this.currentTime = Date.now(); // keeping track of time between yields
|
|
|
|
this.frameCount = 0; // only used for profiling and debugging
|
|
|
|
this.stepFrameCount = 0; // keeping track of when to keep time
|
|
|
|
this.yieldCount = 0; // only used for profiling and debugging
|
2014-07-30 14:28:01 +00:00
|
|
|
this.exportResult = false;
|
2014-11-20 13:40:13 +00:00
|
|
|
this.onComplete = onComplete || null;
|
2014-11-21 15:55:25 +00:00
|
|
|
this.procedureCount = 0;
|
2021-12-09 12:27:01 +00:00
|
|
|
this.flashingContext = null; // for single-stepping
|
|
|
|
this.isInterrupted = false; // for single-stepping
|
2018-07-19 18:06:15 +00:00
|
|
|
this.canBroadcast = true; // used to control "when I am stopped"
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
if (topBlock) {
|
|
|
|
this.homeContext.variables.parentFrame =
|
|
|
|
this.homeContext.receiver.variables;
|
|
|
|
this.context = new Context(
|
|
|
|
null,
|
|
|
|
topBlock.blockSequence(),
|
|
|
|
this.homeContext
|
|
|
|
);
|
2017-06-19 08:39:16 +00:00
|
|
|
if (yieldFirst) {
|
2016-05-02 10:52:58 +00:00
|
|
|
this.pushContext('doYield'); // highlight top block
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process accessing
|
|
|
|
|
|
|
|
Process.prototype.isRunning = function () {
|
2020-07-08 11:47:41 +00:00
|
|
|
return !this.readyToTerminate && (this.context || this.isPaused);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Process entry points
|
|
|
|
|
2015-12-15 09:14:56 +00:00
|
|
|
Process.prototype.runStep = function (deadline) {
|
2014-11-25 11:24:20 +00:00
|
|
|
// a step is an an uninterruptable 'atom', it can consist
|
|
|
|
// of several contexts, even of several blocks
|
|
|
|
|
2013-07-30 11:48:12 +00:00
|
|
|
if (this.isPaused) { // allow pausing in between atomic steps:
|
2013-03-16 08:02:16 +00:00
|
|
|
return this.pauseStep();
|
|
|
|
}
|
|
|
|
this.readyToYield = false;
|
2016-10-15 09:15:01 +00:00
|
|
|
this.isInterrupted = false;
|
|
|
|
|
2020-12-19 12:53:04 +00:00
|
|
|
// repeatedly evaluate the next context (stack frame) until
|
|
|
|
// it's time to yield. In case of WARP or infinite recursive
|
|
|
|
// reporters (or long HOFs) emergency-yield every 500 ms.
|
|
|
|
// Since looking up the current time at every stack frame puts
|
|
|
|
// an amazing strain on performance, only check the system time
|
|
|
|
// every n (=100) contexts.
|
|
|
|
// This is happens over at evaluateContext().
|
2016-10-15 09:15:01 +00:00
|
|
|
while (!this.readyToYield && !this.isInterrupted
|
2013-03-16 08:02:16 +00:00
|
|
|
&& this.context
|
2020-12-18 17:28:15 +00:00
|
|
|
&& (this.currentTime - this.lastYield < this.timeout)
|
2016-06-12 12:10:48 +00:00
|
|
|
) {
|
2013-07-30 11:48:12 +00:00
|
|
|
// also allow pausing inside atomic steps - for PAUSE block primitive:
|
|
|
|
if (this.isPaused) {
|
|
|
|
return this.pauseStep();
|
|
|
|
}
|
2020-12-18 17:28:15 +00:00
|
|
|
if (deadline && (this.currentTime > deadline)) {
|
2015-12-15 09:14:56 +00:00
|
|
|
if (this.isAtomic &&
|
|
|
|
this.homeContext.receiver &&
|
|
|
|
this.homeContext.receiver.endWarp) {
|
|
|
|
this.homeContext.receiver.endWarp();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
this.evaluateContext();
|
|
|
|
}
|
2016-10-15 09:15:01 +00:00
|
|
|
|
2020-12-18 17:28:15 +00:00
|
|
|
this.stepFrameCount = 0;
|
2020-12-16 12:13:16 +00:00
|
|
|
this.yieldCount += 1;
|
2013-03-16 08:02:16 +00:00
|
|
|
this.lastYield = Date.now();
|
2016-06-12 12:10:48 +00:00
|
|
|
this.isFirstStep = false;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
2018-07-19 18:06:15 +00:00
|
|
|
this.canBroadcast = false;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.pause = function () {
|
2016-10-15 09:15:01 +00:00
|
|
|
if (this.readyToTerminate) {
|
|
|
|
return;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
this.isPaused = true;
|
2016-10-15 09:15:01 +00:00
|
|
|
this.flashPausedContext();
|
2013-03-16 08:02:16 +00:00
|
|
|
if (this.context && this.context.startTime) {
|
|
|
|
this.pauseOffset = Date.now() - this.context.startTime;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.resume = function () {
|
2016-10-15 09:15:01 +00:00
|
|
|
if (!this.enableSingleStepping) {
|
|
|
|
this.unflash();
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
2020-12-18 17:28:15 +00:00
|
|
|
|
2020-12-19 12:53:04 +00:00
|
|
|
// keep track of overall frames for profiling purposes.
|
|
|
|
// also keep track of frames inside the current atomic step.
|
|
|
|
// In order to let Snap! behave similarly on a wide range of
|
|
|
|
// differently performant hardware decide when to yield inside
|
|
|
|
// a WARPed script or an infinitely recursive reporter
|
|
|
|
// by how much time has elapsed since the last yield, but since
|
|
|
|
// looking up the system time is surprisingly costly only look it
|
|
|
|
// up every 100 frames.
|
2013-03-16 08:02:16 +00:00
|
|
|
this.frameCount += 1;
|
2020-12-18 17:28:15 +00:00
|
|
|
this.stepFrameCount += 1;
|
|
|
|
if (this.stepFrameCount > 100) {
|
|
|
|
this.currentTime = Date.now();
|
|
|
|
this.stepFrameCount = 0;
|
|
|
|
}
|
|
|
|
|
2014-11-25 11:24:20 +00:00
|
|
|
if (this.context.tag === 'exit') {
|
|
|
|
this.expectReport();
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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)) {
|
2016-10-15 09:15:01 +00:00
|
|
|
return this[exp].apply(this, this.context.inputs);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2019-11-02 16:08:03 +00:00
|
|
|
if (exp instanceof Variable) { // special case for empty reporter rings
|
|
|
|
this.returnValueToParentContext(exp.value);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
this.popContext(); // default: just ignore it
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.evaluateBlock = function (block, argCount) {
|
2018-02-12 16:23:35 +00:00
|
|
|
var rcvr, inputs,
|
|
|
|
selector = block.selector;
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// check for special forms
|
2015-12-15 09:14:56 +00:00
|
|
|
if (selector === 'reportOr' ||
|
|
|
|
selector === 'reportAnd' ||
|
2019-04-24 11:26:19 +00:00
|
|
|
selector === 'reportIfElse' ||
|
2015-12-15 09:14:56 +00:00
|
|
|
selector === 'doReport') {
|
2020-05-20 14:09:59 +00:00
|
|
|
if (this.isCatchingErrors) {
|
|
|
|
try {
|
|
|
|
return this[selector](block);
|
|
|
|
} catch (error) {
|
|
|
|
this.handleError(error, block);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return this[selector](block);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// first evaluate all inputs, then apply the primitive
|
2018-02-12 16:23:35 +00:00
|
|
|
rcvr = this.context.receiver || this.receiver;
|
|
|
|
inputs = this.context.inputs;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
if (argCount > inputs.length) {
|
2021-01-04 17:44:19 +00:00
|
|
|
// this.evaluateNextInput(block);
|
|
|
|
this.evaluateNextInputSet(block); // frame-optimized version
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2016-10-15 09:15:01 +00:00
|
|
|
if (this.flashContext()) {return; } // yield to flash the block
|
2015-12-15 09:14:56 +00:00
|
|
|
if (this[selector]) {
|
2013-03-16 08:02:16 +00:00
|
|
|
rcvr = this;
|
|
|
|
}
|
|
|
|
if (this.isCatchingErrors) {
|
|
|
|
try {
|
2014-11-20 13:40:13 +00:00
|
|
|
this.returnValueToParentContext(
|
2015-12-15 09:14:56 +00:00
|
|
|
rcvr[selector].apply(rcvr, inputs)
|
2014-11-20 13:40:13 +00:00
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
this.popContext();
|
|
|
|
} catch (error) {
|
|
|
|
this.handleError(error, block);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.returnValueToParentContext(
|
2015-12-15 09:14:56 +00:00
|
|
|
rcvr[selector].apply(rcvr, inputs)
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-14 11:04:25 +00:00
|
|
|
// Process: Primitive Extensions (for libraries etc.)
|
|
|
|
|
|
|
|
Process.prototype.doApplyExtension = function (prim, args) {
|
|
|
|
this.reportApplyExtension(prim, args);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportApplyExtension = function (prim, args) {
|
2021-06-18 16:41:02 +00:00
|
|
|
var ext = SnapExtensions.primitives.get(prim);
|
2021-06-14 11:04:25 +00:00
|
|
|
if (isNil(ext)) {
|
|
|
|
throw new Error('missing / unspecified extension: ' + prim);
|
|
|
|
}
|
|
|
|
return ext.apply(
|
|
|
|
this.blockReceiver(),
|
|
|
|
args.itemsArray().concat([this])
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process: Special Forms Blocks Primitives
|
|
|
|
|
|
|
|
Process.prototype.reportOr = function (block) {
|
|
|
|
var inputs = this.context.inputs;
|
|
|
|
|
|
|
|
if (inputs.length < 1) {
|
|
|
|
this.evaluateNextInput(block);
|
2020-05-24 12:04:30 +00:00
|
|
|
} else if (inputs.length === 1) {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(inputs[0], 'Boolean');
|
2020-05-24 12:04:30 +00:00
|
|
|
if (inputs[0]) {
|
2020-05-24 11:58:54 +00:00
|
|
|
if (this.flashContext()) {return; }
|
2020-05-24 12:04:30 +00:00
|
|
|
this.returnValueToParentContext(true);
|
2020-05-24 11:58:54 +00:00
|
|
|
this.popContext();
|
2020-05-24 12:04:30 +00:00
|
|
|
} else {
|
|
|
|
this.evaluateNextInput(block);
|
2020-05-24 11:58:54 +00:00
|
|
|
}
|
2020-05-24 12:04:30 +00:00
|
|
|
} else {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(inputs[1], 'Boolean');
|
2020-05-24 12:04:30 +00:00
|
|
|
if (this.flashContext()) {return; }
|
|
|
|
this.returnValueToParentContext(inputs[1] === true);
|
|
|
|
this.popContext();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportAnd = function (block) {
|
|
|
|
var inputs = this.context.inputs;
|
|
|
|
|
|
|
|
if (inputs.length < 1) {
|
|
|
|
this.evaluateNextInput(block);
|
2020-05-24 12:16:21 +00:00
|
|
|
} else if (inputs.length === 1) {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(inputs[0], 'Boolean');
|
2020-05-24 12:16:21 +00:00
|
|
|
if (!inputs[0]) {
|
|
|
|
if (this.flashContext()) {return; }
|
|
|
|
this.returnValueToParentContext(false);
|
|
|
|
this.popContext();
|
|
|
|
} else {
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(inputs[1], 'Boolean');
|
2016-10-15 09:15:01 +00:00
|
|
|
if (this.flashContext()) {return; }
|
2013-03-16 08:02:16 +00:00
|
|
|
this.returnValueToParentContext(inputs[1] === true);
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-11-24 15:02:21 +00:00
|
|
|
Process.prototype.doReport = function (block) {
|
2014-11-26 15:26:53 +00:00
|
|
|
var outer = this.context.outerContext;
|
2016-10-15 09:15:01 +00:00
|
|
|
if (this.flashContext()) {return; } // flash the block here, special form
|
2015-12-15 09:14:56 +00:00
|
|
|
if (this.isClicked && (block.topBlock() === this.topBlock)) {
|
|
|
|
this.isShowingResult = true;
|
|
|
|
}
|
2018-07-03 07:32:37 +00:00
|
|
|
if (block.partOfCustomCommand) {
|
2014-11-25 11:24:20 +00:00
|
|
|
this.doStopCustomBlock();
|
|
|
|
this.popContext();
|
2014-11-26 15:26:53 +00:00
|
|
|
} else {
|
|
|
|
while (this.context && this.context.tag !== 'exit') {
|
|
|
|
if (this.context.expression === 'doStopWarping') {
|
|
|
|
this.doStopWarping();
|
|
|
|
} else {
|
|
|
|
this.popContext();
|
|
|
|
}
|
2014-11-24 15:02:21 +00:00
|
|
|
}
|
2014-11-26 15:26:53 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-11-25 16:51:04 +00:00
|
|
|
}
|
2014-11-25 11:24:20 +00:00
|
|
|
}
|
2014-11-26 15:26:53 +00:00
|
|
|
// in any case evaluate (and ignore)
|
|
|
|
// the input, because it could be
|
2018-07-03 07:32:37 +00:00
|
|
|
// an HTTP Request for a hardware extension
|
2014-11-24 15:02:21 +00:00
|
|
|
this.pushContext(block.inputs()[0], outer);
|
2018-07-03 07:32:37 +00:00
|
|
|
this.context.isCustomCommand = block.partOfCustomCommand;
|
2014-11-24 15:02:21 +00:00
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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) {
|
2021-01-04 17:44:19 +00:00
|
|
|
// this.evaluateNextInput(multiSlot);
|
|
|
|
this.evaluateNextInputSet(multiSlot); // frame-optimized version
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
|
|
|
this.returnValueToParentContext(new List(inputs));
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-12-17 07:13:48 +00:00
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
2016-10-15 09:15:01 +00:00
|
|
|
if (this.flashContext()) {return; } // yield to flash the current argMorph
|
2013-03-16 08:02:16 +00:00
|
|
|
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) {
|
2015-12-15 09:14:56 +00:00
|
|
|
if (input.constructor === CommandSlotMorph ||
|
|
|
|
input.constructor === ReporterSlotMorph ||
|
|
|
|
(input instanceof CSlotMorph &&
|
2015-11-16 11:18:40 +00:00
|
|
|
(!input.isStatic || input.isLambda))) {
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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,
|
2014-09-18 12:26:28 +00:00
|
|
|
isCustomBlock = this.context.isCustomBlock;
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2014-11-14 11:49:01 +00:00
|
|
|
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
|
2013-03-16 08:02:16 +00:00
|
|
|
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],
|
2015-12-15 09:14:56 +00:00
|
|
|
sel = this.context.expression.selector,
|
2013-03-16 08:02:16 +00:00
|
|
|
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.
|
|
|
|
*/
|
2015-12-15 09:14:56 +00:00
|
|
|
if (sel === 'reify' || sel === 'reportScript') {
|
2013-03-16 08:02:16 +00:00
|
|
|
this.context.addInput(exp);
|
|
|
|
} else {
|
|
|
|
this.context.addInput(this.reify(exp, new List()));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.pushContext(exp, outer);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.pushContext(exp, outer);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-01-04 17:44:19 +00:00
|
|
|
Process.prototype.evaluateNextInputSet = function (element) {
|
|
|
|
// Optimization to use instead of evaluateNextInput(), bums out a few
|
|
|
|
// frames and function calls to save a some milliseconds.
|
|
|
|
// the idea behind this optimization is to keep evaluating the inputs
|
|
|
|
// while we know for sure that we aren't boing to yield anyway
|
|
|
|
var args = element.inputs(),
|
|
|
|
sel = this.context.expression.selector,
|
|
|
|
outer = this.context.outerContext, // for tail call elimination
|
|
|
|
exp, ans;
|
|
|
|
|
|
|
|
while (args.length > this.context.inputs.length) {
|
|
|
|
exp = args[this.context.inputs.length];
|
|
|
|
if (exp.isUnevaluated) {
|
|
|
|
if (exp.isUnevaluated === true || exp.isUnevaluated()) {
|
|
|
|
if (sel === 'reify' || sel === 'reportScript') {
|
|
|
|
this.context.addInput(exp);
|
|
|
|
} else {
|
|
|
|
this.context.addInput(this.reify(exp, new List()));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.pushContext(exp, outer);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (exp instanceof MultiArgMorph || exp instanceof ArgLabelMorph ||
|
|
|
|
exp instanceof BlockMorph) {
|
|
|
|
this.pushContext(exp, outer);
|
|
|
|
break;
|
|
|
|
} else { // asuming an ArgMorph
|
|
|
|
if (this.flashContext()) {return; } // yield to flash
|
|
|
|
if (exp.bindingID) {
|
|
|
|
if (this.isCatchingErrors) {
|
|
|
|
try {
|
|
|
|
ans = this.context.variables.getVar(exp.bindingID);
|
|
|
|
} catch (error) {
|
|
|
|
this.handleError(error, exp);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ans = this.context.variables.getVar(exp.bindingID);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ans = exp.evaluate();
|
|
|
|
if (ans) {
|
|
|
|
if (exp.constructor === CommandSlotMorph ||
|
|
|
|
exp.constructor === ReporterSlotMorph ||
|
|
|
|
(exp instanceof CSlotMorph &&
|
|
|
|
(!exp.isStatic || exp.isLambda))) {
|
|
|
|
ans = this.reify(ans, new List());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.context.addInput(ans);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.doYield = function () {
|
|
|
|
this.popContext();
|
|
|
|
if (!this.isAtomic) {
|
|
|
|
this.readyToYield = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-11-25 11:24:20 +00:00
|
|
|
Process.prototype.expectReport = function () {
|
|
|
|
this.handleError(new Error("reporter didn't report"));
|
2014-11-14 11:49:01 +00:00
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process Exception Handling
|
|
|
|
|
2021-06-16 19:16:02 +00:00
|
|
|
Process.prototype.throwError = function (error, element) {
|
2021-06-10 14:18:42 +00:00
|
|
|
var m = element,
|
|
|
|
ide = this.homeContext.receiver.parentThatIsA(IDE_Morph);
|
2013-03-16 08:02:16 +00:00
|
|
|
this.stop();
|
|
|
|
this.errorFlag = true;
|
|
|
|
this.topBlock.addErrorHighlight();
|
2021-06-10 14:18:42 +00:00
|
|
|
if (ide.isAppMode) {
|
|
|
|
ide.showMessage(error.name + '\n' + error.message);
|
|
|
|
} else {
|
|
|
|
if (isNil(m) || isNil(m.world())) {m = this.topBlock; }
|
|
|
|
m.showBubble(
|
2021-07-06 01:20:00 +00:00
|
|
|
this.errorBubble(error, element),
|
2021-06-10 14:18:42 +00:00
|
|
|
this.exportResult,
|
|
|
|
this.receiver
|
|
|
|
);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2021-06-16 19:16:02 +00:00
|
|
|
Process.prototype.tryCatch = function (action, exception, errVarName) {
|
|
|
|
var next = this.context.continuation();
|
|
|
|
|
|
|
|
this.handleError = function(error) {
|
|
|
|
this.resetErrorHandling();
|
|
|
|
if (exception.expression instanceof CommandBlockMorph) {
|
|
|
|
exception.expression = exception.expression.blockSequence();
|
|
|
|
}
|
|
|
|
exception.pc = 0;
|
|
|
|
exception.outerContext.variables.addVar(errVarName);
|
|
|
|
exception.outerContext.variables.setVar(errVarName, error.message);
|
|
|
|
this.context = exception;
|
|
|
|
this.evaluate(next, new List(), true);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.evaluate(action, new List(), true);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.resetErrorHandling = function () {
|
|
|
|
this.handleError = this.throwError;
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.resetErrorHandling();
|
|
|
|
|
2016-01-18 23:03:17 +00:00
|
|
|
Process.prototype.errorObsolete = function () {
|
|
|
|
throw new Error('a custom block definition is missing');
|
|
|
|
};
|
|
|
|
|
2021-07-06 01:20:00 +00:00
|
|
|
Process.prototype.errorBubble = function (error, element) {
|
|
|
|
// Return a morph containing an image of the elment causing the error
|
|
|
|
// above the text of error.
|
|
|
|
var errorMorph = new AlignmentMorph('column', 5),
|
|
|
|
errorIsNested = isNil(element.world()),
|
2021-07-07 11:53:01 +00:00
|
|
|
errorPrefix = errorIsNested ? `${localize('Inside a custom block')}:\n`
|
|
|
|
: '',
|
2021-07-06 01:20:00 +00:00
|
|
|
errorMessage = new TextMorph(
|
2021-07-06 18:53:28 +00:00
|
|
|
`${errorPrefix}${localize(error.name)}:\n${localize(error.message)}`,
|
2021-07-06 01:20:00 +00:00
|
|
|
SyntaxElementMorph.prototype.fontSize
|
|
|
|
),
|
2021-07-07 02:03:57 +00:00
|
|
|
blockToShow = element;
|
2021-07-06 01:20:00 +00:00
|
|
|
|
|
|
|
errorMorph.add(errorMessage);
|
|
|
|
if (errorIsNested) {
|
2021-07-07 02:03:57 +00:00
|
|
|
if (blockToShow.selector === 'reportGetVar') {
|
|
|
|
// if I am a single variable, show my caller in the output.
|
|
|
|
blockToShow = blockToShow.parent;
|
|
|
|
}
|
2021-07-06 18:53:28 +00:00
|
|
|
errorMorph.text += `\n${localize('The error occured at:')}\n`;
|
2021-07-09 11:50:51 +00:00
|
|
|
errorMorph.add(blockToShow.fullCopy());
|
2021-07-06 01:20:00 +00:00
|
|
|
errorMorph.fixLayout();
|
|
|
|
}
|
|
|
|
|
|
|
|
return errorMorph;
|
2021-07-07 11:53:01 +00:00
|
|
|
};
|
2021-07-06 01:20:00 +00:00
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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) {
|
2016-10-15 09:15:01 +00:00
|
|
|
context.expression = this.enableLiveCoding ||
|
|
|
|
this.enableSingleStepping ?
|
2016-05-02 10:52:58 +00:00
|
|
|
topBlock : topBlock.fullCopy();
|
2013-03-16 08:02:16 +00:00
|
|
|
context.expression.show(); // be sure to make visible if in app mode
|
|
|
|
|
2017-04-10 09:48:55 +00:00
|
|
|
if (!isCustomBlock && !parameterNames.length()) {
|
2013-03-16 08:02:16 +00:00
|
|
|
// mark all empty slots with an identifier
|
2020-04-28 17:27:42 +00:00
|
|
|
context.expression.allEmptySlots().forEach(slot => {
|
2013-03-16 08:02:16 +00:00
|
|
|
i += 1;
|
|
|
|
if (slot instanceof MultiArgMorph) {
|
2019-04-22 15:04:12 +00:00
|
|
|
slot.bindingID = Symbol.for('arguments');
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
|
|
|
slot.bindingID = i;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// and remember the number of detected empty slots
|
|
|
|
context.emptySlots = i;
|
|
|
|
}
|
|
|
|
} else {
|
2016-10-15 09:15:01 +00:00
|
|
|
context.expression = this.enableLiveCoding ||
|
|
|
|
this.enableSingleStepping ? [this.context.expression]
|
2016-05-02 10:52:58 +00:00
|
|
|
: [this.context.expression.fullCopy()];
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 08:46:41 +00:00
|
|
|
context.inputs = parameterNames.itemsArray();
|
2013-03-16 08:02:16 +00:00
|
|
|
context.receiver
|
2017-05-30 15:07:09 +00:00
|
|
|
= this.context ? this.context.receiver : this.receiver;
|
2017-04-10 09:48:55 +00:00
|
|
|
context.origin = context.receiver; // for serialization
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2014-07-25 12:35:36 +00:00
|
|
|
Process.prototype.reportJSFunction = function (parmNames, body) {
|
2021-06-14 20:53:12 +00:00
|
|
|
if (!this.enableJS) {
|
|
|
|
throw new Error('JavaScript extensions for Snap!\nare turned off');
|
|
|
|
}
|
2014-07-25 12:35:36 +00:00
|
|
|
return Function.apply(
|
2014-07-28 12:41:15 +00:00
|
|
|
null,
|
2020-11-30 08:46:41 +00:00
|
|
|
parmNames.itemsArray().concat([body])
|
2014-07-25 12:35:36 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2014-11-14 11:49:01 +00:00
|
|
|
Process.prototype.doRun = function (context, args) {
|
|
|
|
return this.evaluate(context, args, true);
|
|
|
|
};
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
Process.prototype.evaluate = function (
|
|
|
|
context,
|
|
|
|
args,
|
|
|
|
isCommand
|
|
|
|
) {
|
2020-06-27 14:11:09 +00:00
|
|
|
if (!context) {
|
|
|
|
return this.returnValueToParentContext(null);
|
|
|
|
}
|
2014-07-25 12:35:36 +00:00
|
|
|
if (context instanceof Function) {
|
2014-07-28 12:41:15 +00:00
|
|
|
return context.apply(
|
2014-08-13 15:43:49 +00:00
|
|
|
this.blockReceiver(),
|
2020-11-30 08:46:41 +00:00
|
|
|
args.itemsArray().concat([this])
|
2014-07-28 12:41:15 +00:00
|
|
|
);
|
2014-07-25 12:35:36 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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),
|
2014-11-25 11:24:20 +00:00
|
|
|
caller = this.context.parentContext,
|
|
|
|
exit,
|
2013-03-16 08:02:16 +00:00
|
|
|
runnable,
|
2019-11-02 16:08:03 +00:00
|
|
|
expr,
|
2020-11-30 08:46:41 +00:00
|
|
|
parms = args.itemsArray(),
|
2013-03-16 08:02:16 +00:00
|
|
|
i,
|
2013-05-14 14:16:21 +00:00
|
|
|
value;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
if (!outer.receiver) {
|
|
|
|
outer.receiver = context.receiver; // for custom blocks
|
|
|
|
}
|
|
|
|
runnable = new Context(
|
|
|
|
this.context.parentContext,
|
|
|
|
context.expression,
|
|
|
|
outer,
|
|
|
|
context.receiver
|
|
|
|
);
|
2018-07-05 20:31:50 +00:00
|
|
|
runnable.isCustomCommand = isCommand; // for short-circuiting HTTP requests
|
2014-11-25 16:51:04 +00:00
|
|
|
this.context.parentContext = runnable;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2014-11-14 11:49:01 +00:00
|
|
|
if (context.expression instanceof ReporterBlockMorph) {
|
2014-11-25 16:51:04 +00:00
|
|
|
// auto-"warp" nested reporters
|
2020-12-18 17:28:15 +00:00
|
|
|
this.readyToYield = (this.currentTime - this.lastYield > this.timeout);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
|
2018-09-09 13:50:54 +00:00
|
|
|
// assign arguments to parameters
|
|
|
|
|
|
|
|
// assign the actual arguments list to the special
|
2019-04-22 15:04:12 +00:00
|
|
|
// parameter ID Symbol.for('arguments'), to be used for variadic inputs
|
|
|
|
outer.variables.addVar(Symbol.for('arguments'), args);
|
2018-09-09 13:50:54 +00:00
|
|
|
|
|
|
|
// assign arguments that are actually passed
|
2013-03-16 08:02:16 +00:00
|
|
|
if (parms.length > 0) {
|
|
|
|
|
|
|
|
// assign formal parameters
|
|
|
|
for (i = 0; i < context.inputs.length; i += 1) {
|
|
|
|
value = 0;
|
2013-11-26 12:23:35 +00:00
|
|
|
if (!isNil(parms[i])) {
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2019-11-02 16:08:03 +00:00
|
|
|
// assign it to all empty slots...
|
2013-03-16 08:02:16 +00:00
|
|
|
if (parms.length === 1) {
|
2019-11-02 16:08:03 +00:00
|
|
|
// ... 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]);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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(
|
2014-11-20 14:53:14 +00:00
|
|
|
localize('expecting') + ' ' + context.emptySlots + ' '
|
|
|
|
+ localize('input(s), but getting') + ' '
|
|
|
|
+ parms.length
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (runnable.expression instanceof CommandBlockMorph) {
|
|
|
|
runnable.expression = runnable.expression.blockSequence();
|
2014-11-14 11:49:01 +00:00
|
|
|
if (!isCommand) {
|
2014-11-25 11:24:20 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-11-14 11:49:01 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.fork = function (context, args) {
|
2018-06-14 06:24:02 +00:00
|
|
|
if (this.readyToTerminate) {return; }
|
2015-12-15 09:14:56 +00:00
|
|
|
var proc = new Process(),
|
|
|
|
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
2017-10-12 10:34:50 +00:00
|
|
|
proc.instrument = this.instrument;
|
2017-10-12 17:17:33 +00:00
|
|
|
proc.receiver = this.receiver;
|
2018-07-10 16:28:57 +00:00
|
|
|
proc.initializeFor(context, args);
|
2016-01-08 13:31:22 +00:00
|
|
|
// proc.pushContext('doYield');
|
2015-12-15 09:14:56 +00:00
|
|
|
stage.threads.processes.push(proc);
|
|
|
|
};
|
|
|
|
|
2018-07-10 16:28:57 +00:00
|
|
|
Process.prototype.initializeFor = function (context, args) {
|
2015-12-15 09:14:56 +00:00
|
|
|
// used by Process.fork() and global invoke()
|
2013-03-16 08:02:16 +00:00
|
|
|
if (context.isContinuation) {
|
|
|
|
throw new Error(
|
|
|
|
'continuations cannot be forked'
|
|
|
|
);
|
|
|
|
}
|
2014-11-20 13:21:56 +00:00
|
|
|
if (!(context instanceof Context)) {
|
|
|
|
throw new Error('expecting a ring but getting ' + context);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
var outer = new Context(null, null, context.outerContext),
|
|
|
|
runnable = new Context(null,
|
|
|
|
context.expression,
|
|
|
|
outer
|
|
|
|
),
|
2020-11-30 08:46:41 +00:00
|
|
|
parms = args.itemsArray(),
|
2013-03-16 08:02:16 +00:00
|
|
|
i,
|
2018-07-10 16:28:57 +00:00
|
|
|
value;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2017-05-30 15:07:09 +00:00
|
|
|
// remember the receiver
|
|
|
|
this.context = context.receiver;
|
|
|
|
|
2018-09-09 13:50:54 +00:00
|
|
|
// assign arguments to parameters
|
|
|
|
|
|
|
|
// assign the actual arguments list to the special
|
2019-04-22 15:04:12 +00:00
|
|
|
// parameter ID Symbol.for('arguments'), to be used for variadic inputs
|
|
|
|
outer.variables.addVar(Symbol.for('arguments'), args);
|
2018-09-09 13:50:54 +00:00
|
|
|
|
|
|
|
// assign arguments that are actually passed
|
2013-03-16 08:02:16 +00:00
|
|
|
if (parms.length > 0) {
|
|
|
|
|
|
|
|
// assign formal parameters
|
|
|
|
for (i = 0; i < context.inputs.length; i += 1) {
|
|
|
|
value = 0;
|
2013-11-26 12:23:35 +00:00
|
|
|
if (!isNil(parms[i])) {
|
2013-03-16 08:02:16 +00:00
|
|
|
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(
|
2014-11-20 14:53:14 +00:00
|
|
|
localize('expecting') + ' ' + context.emptySlots + ' '
|
|
|
|
+ localize('input(s), but getting') + ' '
|
|
|
|
+ parms.length
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (runnable.expression instanceof CommandBlockMorph) {
|
|
|
|
runnable.expression = runnable.expression.blockSequence();
|
|
|
|
}
|
|
|
|
|
2015-12-15 09:14:56 +00:00
|
|
|
this.homeContext = new Context(); // context.outerContext;
|
|
|
|
this.homeContext.receiver = context.outerContext.receiver;
|
|
|
|
this.topBlock = context.expression;
|
|
|
|
this.context = runnable;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2014-11-25 11:24:20 +00:00
|
|
|
// Process stopping blocks primitives
|
2014-11-14 11:49:01 +00:00
|
|
|
|
|
|
|
Process.prototype.doStopBlock = function () {
|
2014-11-21 15:55:25 +00:00
|
|
|
var target = this.context.expression.exitTag;
|
|
|
|
if (isNil(target)) {
|
|
|
|
return this.doStopCustomBlock();
|
|
|
|
}
|
2014-11-23 12:53:34 +00:00
|
|
|
while (this.context &&
|
|
|
|
(isNil(this.context.tag) || (this.context.tag > target))) {
|
2014-11-21 15:55:25 +00:00
|
|
|
if (this.context.expression === 'doStopWarping') {
|
|
|
|
this.doStopWarping();
|
|
|
|
} else {
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
}
|
2014-11-23 12:53:34 +00:00
|
|
|
this.pushContext();
|
2014-11-21 15:55:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doStopCustomBlock = function () {
|
|
|
|
// fallback solution for "report" blocks inside
|
|
|
|
// custom command definitions and untagged "stop" blocks
|
2014-11-14 11:49:01 +00:00
|
|
|
while (this.context && !this.context.isCustomBlock) {
|
2013-03-16 08:02:16 +00:00
|
|
|
if (this.context.expression === 'doStopWarping') {
|
|
|
|
this.doStopWarping();
|
|
|
|
} else {
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Process continuations primitives
|
|
|
|
|
2014-11-14 11:49:01 +00:00
|
|
|
Process.prototype.doCallCC = function (aContext, isReporter) {
|
|
|
|
this.evaluate(
|
|
|
|
aContext,
|
2020-07-01 23:25:41 +00:00
|
|
|
new List([this.context.continuation(isReporter)]),
|
2014-11-14 11:49:01 +00:00
|
|
|
!isReporter
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportCallCC = function (aContext) {
|
2014-11-14 11:49:01 +00:00
|
|
|
this.doCallCC(aContext, true);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.runContinuation = function (aContext, args) {
|
2020-11-30 08:46:41 +00:00
|
|
|
var parms = args.itemsArray();
|
2016-10-21 14:29:04 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
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 () {
|
2014-11-23 12:53:34 +00:00
|
|
|
var caller = this.context.parentContext,
|
2017-04-10 09:48:55 +00:00
|
|
|
block = this.context.expression,
|
|
|
|
method = block.isGlobal ? block.definition
|
2017-12-01 11:55:15 +00:00
|
|
|
: this.blockReceiver().getMethod(block.semanticSpec),
|
2017-04-10 09:48:55 +00:00
|
|
|
context = method.body,
|
|
|
|
declarations = method.declarations,
|
2013-03-16 08:02:16 +00:00
|
|
|
args = new List(this.context.inputs),
|
2020-11-30 08:46:41 +00:00
|
|
|
parms = args.itemsArray(),
|
2013-03-16 08:02:16 +00:00
|
|
|
runnable,
|
2014-11-14 11:49:01 +00:00
|
|
|
exit,
|
2013-03-16 08:02:16 +00:00
|
|
|
i,
|
|
|
|
value,
|
|
|
|
outer;
|
|
|
|
|
|
|
|
if (!context) {return null; }
|
2014-11-21 15:55:25 +00:00
|
|
|
this.procedureCount += 1;
|
2013-03-16 08:02:16 +00:00
|
|
|
outer = new Context();
|
2014-11-14 11:49:01 +00:00
|
|
|
outer.receiver = this.context.receiver;
|
2015-12-15 09:14:56 +00:00
|
|
|
|
2017-04-10 09:48:55 +00:00
|
|
|
outer.variables.parentFrame = block.variables;
|
2015-12-15 09:14:56 +00:00
|
|
|
|
2021-12-09 12:27:01 +00:00
|
|
|
// block (instance) var support:
|
2015-12-15 09:14:56 +00:00
|
|
|
// 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.
|
2017-04-10 09:48:55 +00:00
|
|
|
if (method.variableNames.length) {
|
|
|
|
block.variables.parentFrame = outer.receiver ?
|
2015-12-15 09:14:56 +00:00
|
|
|
outer.receiver.variables : null;
|
|
|
|
} else {
|
|
|
|
// original code without block variables:
|
|
|
|
outer.variables.parentFrame = outer.receiver ?
|
|
|
|
outer.receiver.variables : null;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
runnable = new Context(
|
|
|
|
this.context.parentContext,
|
|
|
|
context.expression,
|
|
|
|
outer,
|
2014-11-14 11:49:01 +00:00
|
|
|
outer.receiver
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
2014-11-14 11:49:01 +00:00
|
|
|
runnable.isCustomBlock = true;
|
2014-11-25 16:51:04 +00:00
|
|
|
this.context.parentContext = runnable;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
// passing parameters if any were passed
|
|
|
|
if (parms.length > 0) {
|
|
|
|
|
|
|
|
// assign formal parameters
|
|
|
|
for (i = 0; i < context.inputs.length; i += 1) {
|
|
|
|
value = 0;
|
2013-11-26 12:23:35 +00:00
|
|
|
if (!isNil(parms[i])) {
|
2013-03-16 08:02:16 +00:00
|
|
|
value = parms[i];
|
|
|
|
}
|
2014-07-08 15:04:15 +00:00
|
|
|
outer.variables.addVar(context.inputs[i], value);
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
// if the parameter is an upvar,
|
2014-09-18 12:26:28 +00:00
|
|
|
// create a reference to the variable it points to
|
2018-06-06 16:13:36 +00:00
|
|
|
if (declarations.get(context.inputs[i])[0] === '%upvar') {
|
2014-09-18 12:26:28 +00:00
|
|
|
this.context.outerContext.variables.vars[value] =
|
|
|
|
outer.variables.vars[context.inputs[i]];
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-25 16:51:04 +00:00
|
|
|
// tag return target
|
2017-04-10 09:48:55 +00:00
|
|
|
if (method.type !== 'command') {
|
2014-11-25 16:51:04 +00:00
|
|
|
if (caller) {
|
|
|
|
// tag caller, so "report" can catch it later
|
|
|
|
caller.tag = 'exit';
|
2014-11-21 15:55:25 +00:00
|
|
|
} else {
|
2014-11-25 16:51:04 +00:00
|
|
|
// 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
|
2020-12-18 17:28:15 +00:00
|
|
|
this.readyToYield = (this.currentTime - this.lastYield > this.timeout);
|
2014-11-25 16:51:04 +00:00
|
|
|
} 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;
|
|
|
|
}
|
2016-10-15 09:15:01 +00:00
|
|
|
// yield commands unless explicitly "warped" or directly recursive
|
2017-04-10 09:48:55 +00:00
|
|
|
if (!this.isAtomic && method.isDirectlyRecursive()) {
|
2014-11-25 16:51:04 +00:00
|
|
|
this.readyToYield = true;
|
2014-11-14 11:49:01 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2014-11-25 16:51:04 +00:00
|
|
|
runnable.expression = runnable.expression.blockSequence();
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Process variables primitives
|
|
|
|
|
|
|
|
Process.prototype.doDeclareVariables = function (varNames) {
|
|
|
|
var varFrame = this.context.outerContext.variables;
|
2020-11-30 08:46:41 +00:00
|
|
|
varNames.itemsArray().forEach(name =>
|
2020-04-28 17:27:42 +00:00
|
|
|
varFrame.addVar(name)
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doSetVar = function (varName, value) {
|
|
|
|
var varFrame = this.context.variables,
|
2018-05-02 11:34:49 +00:00
|
|
|
name = varName;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (name instanceof Context) {
|
|
|
|
if (name.expression.selector === 'reportGetVar') {
|
2015-03-25 13:03:06 +00:00
|
|
|
name.variables.setVar(
|
|
|
|
name.expression.blockSpec,
|
2015-07-26 22:35:36 +00:00
|
|
|
value,
|
2018-05-02 11:34:49 +00:00
|
|
|
this.blockReceiver()
|
2015-03-25 13:03:06 +00:00
|
|
|
);
|
|
|
|
return;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2016-05-02 10:52:58 +00:00
|
|
|
this.doSet(name, value);
|
|
|
|
return;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2018-05-02 11:34:49 +00:00
|
|
|
if (name instanceof Array) {
|
|
|
|
this.doSet(name, value);
|
|
|
|
return;
|
|
|
|
}
|
2015-03-25 13:03:06 +00:00
|
|
|
varFrame.setVar(name, value, this.blockReceiver());
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doChangeVar = function (varName, value) {
|
|
|
|
var varFrame = this.context.variables,
|
|
|
|
name = varName;
|
2020-11-15 18:48:25 +00:00
|
|
|
this.assertType(value, 'number');
|
2013-03-16 08:02:16 +00:00
|
|
|
if (name instanceof Context) {
|
|
|
|
if (name.expression.selector === 'reportGetVar') {
|
2015-03-25 13:03:06 +00:00
|
|
|
name.variables.changeVar(
|
|
|
|
name.expression.blockSpec,
|
2015-07-26 22:35:36 +00:00
|
|
|
value,
|
|
|
|
this.blockReceiver()
|
2015-03-25 13:03:06 +00:00
|
|
|
);
|
|
|
|
return;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
2015-03-25 13:03:06 +00:00
|
|
|
varFrame.changeVar(name, value, this.blockReceiver());
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportGetVar = function () {
|
|
|
|
// assumes a getter block whose blockSpec is a variable name
|
|
|
|
return this.context.variables.getVar(
|
2014-09-18 12:26:28 +00:00
|
|
|
this.context.expression.blockSpec
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2021-06-17 18:27:06 +00:00
|
|
|
Process.prototype.doShowVar = function (varName, context) {
|
|
|
|
// context is an optional start-context to be used by extensions
|
|
|
|
var varFrame = (context || (this.context || this.homeContext)).variables,
|
2013-03-16 08:02:16 +00:00
|
|
|
stage,
|
|
|
|
watcher,
|
|
|
|
target,
|
|
|
|
label,
|
|
|
|
others,
|
2014-07-22 10:33:26 +00:00
|
|
|
isGlobal,
|
2013-03-16 08:02:16 +00:00
|
|
|
name = varName;
|
|
|
|
|
|
|
|
if (name instanceof Context) {
|
|
|
|
if (name.expression.selector === 'reportGetVar') {
|
|
|
|
name = name.expression.blockSpec;
|
2017-07-26 08:58:17 +00:00
|
|
|
} else {
|
2021-10-06 16:34:00 +00:00
|
|
|
this.blockReceiver().changeBlockVisibility(name.expression, false);
|
2017-07-26 08:58:17 +00:00
|
|
|
return;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.homeContext.receiver) {
|
|
|
|
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
2015-07-27 07:42:41 +00:00
|
|
|
target = varFrame.silentFind(name);
|
|
|
|
if (!target) {return; }
|
2013-03-16 08:02:16 +00:00
|
|
|
// first try to find an existing (hidden) watcher
|
|
|
|
watcher = detect(
|
|
|
|
stage.children,
|
2020-04-28 17:27:42 +00:00
|
|
|
morph => morph instanceof WatcherMorph &&
|
|
|
|
morph.target === target &&
|
|
|
|
morph.getter === name
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
if (watcher !== null) {
|
|
|
|
watcher.show();
|
|
|
|
watcher.fixLayout(); // re-hide hidden parts
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// if no watcher exists, create a new one
|
2014-07-22 10:33:26 +00:00
|
|
|
isGlobal = contains(
|
2015-07-26 22:35:36 +00:00
|
|
|
this.homeContext.receiver.globalVariables().names(),
|
2014-07-22 10:33:26 +00:00
|
|
|
varName
|
|
|
|
);
|
|
|
|
if (isGlobal || target.owner) {
|
2013-03-16 08:02:16 +00:00
|
|
|
label = name;
|
|
|
|
} else {
|
2014-11-18 18:11:14 +00:00
|
|
|
label = name + ' ' + localize('(temporary)');
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
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();
|
2020-07-08 11:51:15 +00:00
|
|
|
watcher.rerender();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-17 18:27:06 +00:00
|
|
|
Process.prototype.doHideVar = function (varName, context) {
|
2013-03-16 08:02:16 +00:00
|
|
|
// if no varName is specified delete all watchers on temporaries
|
2021-06-17 18:27:06 +00:00
|
|
|
// context is an optional start-context to be used by extensions
|
|
|
|
var varFrame = (context || this.context).variables,
|
2013-03-16 08:02:16 +00:00
|
|
|
stage,
|
|
|
|
watcher,
|
|
|
|
target,
|
|
|
|
name = varName;
|
|
|
|
|
|
|
|
if (name instanceof Context) {
|
|
|
|
if (name.expression.selector === 'reportGetVar') {
|
|
|
|
name = name.expression.blockSpec;
|
2017-07-26 08:58:17 +00:00
|
|
|
} else {
|
2021-10-06 16:34:00 +00:00
|
|
|
this.blockReceiver().changeBlockVisibility(name.expression, true);
|
2017-07-26 08:58:17 +00:00
|
|
|
return;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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,
|
2020-04-28 17:27:42 +00:00
|
|
|
morph => morph instanceof WatcherMorph &&
|
|
|
|
morph.target === target &&
|
|
|
|
morph.getter === name
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
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) {
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.watchers().forEach(watcher => {
|
2013-03-16 08:02:16 +00:00
|
|
|
if (watcher.isTemporary()) {
|
|
|
|
watcher.destroy();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-07-26 22:35:36 +00:00
|
|
|
// 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;
|
2017-04-23 16:30:49 +00:00
|
|
|
} else { // attribute
|
|
|
|
name = {
|
2017-05-09 16:08:56 +00:00
|
|
|
xPosition: 'x position',
|
|
|
|
yPosition: 'y position',
|
|
|
|
direction: 'direction',
|
2017-07-12 08:29:27 +00:00
|
|
|
getCostumeIdx: 'costume #',
|
2017-05-09 16:08:56 +00:00
|
|
|
size: 'size'
|
2017-04-23 16:30:49 +00:00
|
|
|
}[name.expression.selector];
|
|
|
|
if (!isNil(name)) {
|
|
|
|
rcvr.inheritAttribute(name);
|
|
|
|
}
|
2017-05-30 15:07:09 +00:00
|
|
|
return; // error: cannot delete attribute...
|
2015-07-26 22:35:36 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-09 16:08:56 +00:00
|
|
|
if (name instanceof Array) {
|
|
|
|
return rcvr.inheritAttribute(this.inputOption(name));
|
|
|
|
}
|
2015-07-26 22:35:36 +00:00
|
|
|
if (contains(rcvr.inheritedVariableNames(true), name)) {
|
|
|
|
rcvr.deleteVariable(name);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-12-09 12:27:01 +00:00
|
|
|
// message passing primitives
|
2017-07-09 15:44:28 +00:00
|
|
|
|
2017-10-17 07:19:53 +00:00
|
|
|
Process.prototype.doTellTo = function (sprite, context, args) {
|
2017-07-09 15:44:28 +00:00
|
|
|
this.doRun(
|
|
|
|
this.reportAttributeOf(context, sprite),
|
2017-10-17 07:19:53 +00:00
|
|
|
args
|
2017-07-09 15:44:28 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2017-10-17 07:19:53 +00:00
|
|
|
Process.prototype.reportAskFor = function (sprite, context, args) {
|
2017-07-09 15:44:28 +00:00
|
|
|
this.evaluate(
|
|
|
|
this.reportAttributeOf(context, sprite),
|
2017-10-17 07:19:53 +00:00
|
|
|
args
|
2017-07-09 15:44:28 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process lists primitives
|
|
|
|
|
|
|
|
Process.prototype.reportNewList = function (elements) {
|
|
|
|
return elements;
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportCONS = function (car, cdr) {
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(cdr, 'list');
|
2013-03-16 08:02:16 +00:00
|
|
|
return new List().cons(car, cdr);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportCDR = function (list) {
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2013-03-16 08:02:16 +00:00
|
|
|
return list.cdr();
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doAddToList = function (element, list) {
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2017-07-26 15:07:18 +00:00
|
|
|
if (list.type) {
|
|
|
|
this.assertType(element, list.type);
|
2020-06-10 11:19:25 +00:00
|
|
|
list = this.shadowListAttribute(list);
|
2017-07-26 15:07:18 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
list.add(element);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doDeleteFromList = function (index, list) {
|
|
|
|
var idx = index;
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2020-06-10 11:19:25 +00:00
|
|
|
if (list.type) {
|
|
|
|
list = this.shadowListAttribute(list);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
if (this.inputOption(index) === 'all') {
|
|
|
|
return list.clear();
|
|
|
|
}
|
|
|
|
if (index === '') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'last') {
|
|
|
|
idx = list.length();
|
2015-03-25 13:03:06 +00:00
|
|
|
} else if (isNaN(+this.inputOption(index))) {
|
|
|
|
return null;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
list.remove(idx);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doInsertInList = function (element, index, list) {
|
|
|
|
var idx = index;
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2017-07-26 15:07:18 +00:00
|
|
|
if (list.type) {
|
|
|
|
this.assertType(element, list.type);
|
2020-06-10 11:19:25 +00:00
|
|
|
list = this.shadowListAttribute(list);
|
2017-07-26 15:07:18 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
if (index === '') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'any') {
|
2020-05-04 15:18:24 +00:00
|
|
|
idx = this.reportBasicRandom(1, list.length() + 1);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'last') {
|
|
|
|
idx = list.length() + 1;
|
|
|
|
}
|
|
|
|
list.add(element, idx);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doReplaceInList = function (index, list, element) {
|
|
|
|
var idx = index;
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2017-07-26 15:07:18 +00:00
|
|
|
if (list.type) {
|
|
|
|
this.assertType(element, list.type);
|
2020-06-10 11:19:25 +00:00
|
|
|
list = this.shadowListAttribute(list);
|
2017-07-26 15:07:18 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
if (index === '') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'any') {
|
2020-05-04 15:18:24 +00:00
|
|
|
idx = this.reportBasicRandom(1, list.length());
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'last') {
|
|
|
|
idx = list.length();
|
|
|
|
}
|
|
|
|
list.put(element, idx);
|
|
|
|
};
|
|
|
|
|
2020-06-10 11:19:25 +00:00
|
|
|
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.
|
2020-06-11 05:58:03 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-06-10 11:19:25 +00:00
|
|
|
}
|
|
|
|
return list;
|
|
|
|
};
|
|
|
|
|
2020-05-11 04:24:09 +00:00
|
|
|
// Process accessing list elements - hyper dyadic
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportListItem = function (index, list) {
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2013-03-16 08:02:16 +00:00
|
|
|
if (index === '') {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'any') {
|
2020-05-11 15:35:15 +00:00
|
|
|
return list.at(this.reportBasicRandom(1, list.length()));
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
if (this.inputOption(index) === 'last') {
|
2020-05-11 15:35:15 +00:00
|
|
|
return list.at(list.length());
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2021-02-02 16:33:38 +00:00
|
|
|
if (index instanceof List && this.enableHyperOps) {
|
2021-02-04 17:59:27 +00:00
|
|
|
return list.query(index);
|
2020-05-14 17:03:38 +00:00
|
|
|
}
|
|
|
|
return list.at(index);
|
|
|
|
};
|
|
|
|
|
2021-12-09 12:27:01 +00:00
|
|
|
// Process - tabular list ops
|
2021-01-26 15:40:22 +00:00
|
|
|
|
2021-01-30 09:49:14 +00:00
|
|
|
Process.prototype.reportTranspose = function (list) {
|
2021-01-29 09:07:57 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-01-30 09:49:14 +00:00
|
|
|
return list.transpose();
|
2021-01-29 09:07:57 +00:00
|
|
|
};
|
|
|
|
|
2021-02-08 15:37:03 +00:00
|
|
|
Process.prototype.reportCrossproduct = function (lists) {
|
|
|
|
this.assertType(lists, 'list');
|
|
|
|
if (lists.isEmpty()) {
|
|
|
|
return lists;
|
|
|
|
}
|
|
|
|
this.assertType(lists.at(1), 'list');
|
|
|
|
return lists.crossproduct();
|
|
|
|
};
|
|
|
|
|
2021-02-08 07:57:26 +00:00
|
|
|
Process.prototype.reportReshape = function (list, shape) {
|
|
|
|
this.assertType(shape, 'list');
|
2021-02-09 09:32:45 +00:00
|
|
|
list = list instanceof List ? list : new List([list]);
|
2021-02-08 07:57:26 +00:00
|
|
|
return list.reshape(shape);
|
|
|
|
};
|
|
|
|
|
2021-02-20 18:33:41 +00:00
|
|
|
Process.prototype.reportSlice = function (list, indices) {
|
2021-02-23 07:58:13 +00:00
|
|
|
// currently not in use
|
2021-02-20 18:33:41 +00:00
|
|
|
this.assertType(list, 'list');
|
|
|
|
this.assertType(indices, 'list');
|
|
|
|
return list.slice(indices);
|
|
|
|
};
|
|
|
|
|
2021-01-26 15:40:22 +00:00
|
|
|
// Process - other basic list accessors
|
|
|
|
|
2021-02-05 14:32:31 +00:00
|
|
|
Process.prototype.reportListAttribute = function (choice, list) {
|
|
|
|
var option = this.inputOption(choice);
|
|
|
|
switch (option) {
|
|
|
|
case 'length':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-05 14:32:31 +00:00
|
|
|
return list.length();
|
|
|
|
case 'size':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-05 14:32:31 +00:00
|
|
|
return list.size();
|
|
|
|
case 'rank':
|
2021-02-09 09:11:26 +00:00
|
|
|
return list instanceof List ? list.rank() : 0;
|
2021-02-07 10:23:53 +00:00
|
|
|
case 'dimensions':
|
2021-02-09 09:11:26 +00:00
|
|
|
return list instanceof List ? list.shape() : new List();
|
2021-02-07 10:23:53 +00:00
|
|
|
case 'flatten':
|
2021-02-09 09:11:26 +00:00
|
|
|
return list instanceof List ? list.ravel() : new List([list]);
|
2021-02-13 12:32:14 +00:00
|
|
|
case 'columns':
|
2021-02-12 16:44:14 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-13 12:35:53 +00:00
|
|
|
return list.columns();
|
2021-02-13 12:32:14 +00:00
|
|
|
case 'transpose':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-05 14:32:31 +00:00
|
|
|
return list.transpose();
|
2021-02-08 16:19:50 +00:00
|
|
|
case 'reverse':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-08 16:19:50 +00:00
|
|
|
return list.reversed();
|
2021-02-07 10:23:53 +00:00
|
|
|
case 'lines':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-05 22:40:40 +00:00
|
|
|
if (list.canBeTXT()) {
|
|
|
|
return list.asTXT();
|
|
|
|
}
|
2021-02-10 09:37:42 +00:00
|
|
|
throw new Error('unable to convert to lines');
|
2021-02-05 14:32:31 +00:00
|
|
|
case 'csv':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-05 14:32:31 +00:00
|
|
|
if (list.canBeCSV()) {
|
|
|
|
return list.asCSV();
|
|
|
|
}
|
|
|
|
throw new Error('unable to convert to CSV');
|
|
|
|
case 'json':
|
2021-02-09 09:11:26 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-02-05 14:32:31 +00:00
|
|
|
if (list.canBeJSON()) {
|
|
|
|
return list.asJSON();
|
|
|
|
}
|
|
|
|
throw new Error('unable to convert to JSON');
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportListLength = function (list) {
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2015-12-17 07:13:48 +00:00
|
|
|
return list.length();
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2020-04-25 16:53:18 +00:00
|
|
|
Process.prototype.reportListIndex = function(element, list) {
|
|
|
|
this.assertType(list, 'list');
|
|
|
|
return list.indexOf(element);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportListContainsItem = function (list, element) {
|
2017-07-08 08:18:44 +00:00
|
|
|
this.assertType(list, 'list');
|
2013-03-16 08:02:16 +00:00
|
|
|
return list.contains(element);
|
|
|
|
};
|
|
|
|
|
2019-04-27 05:47:33 +00:00
|
|
|
Process.prototype.reportListIsEmpty = function (list) {
|
|
|
|
this.assertType(list, 'list');
|
|
|
|
return list.isEmpty();
|
|
|
|
};
|
|
|
|
|
2016-02-24 10:35:18 +00:00
|
|
|
Process.prototype.doShowTable = function (list) {
|
|
|
|
// experimental
|
|
|
|
this.assertType(list, 'list');
|
|
|
|
new TableDialogMorph(list).popUp(this.blockReceiver().world());
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process non-HOF list primitives
|
2019-04-27 06:59:12 +00:00
|
|
|
|
|
|
|
Process.prototype.reportNumbers = function (start, end) {
|
2020-04-23 09:07:06 +00:00
|
|
|
// hyper-dyadic
|
2020-04-22 13:58:33 +00:00
|
|
|
if (this.enableHyperOps) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(
|
|
|
|
(strt, stp) => this.reportBasicNumbers(strt, stp),
|
|
|
|
start,
|
|
|
|
end
|
|
|
|
);
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.reportLinkedNumbers(start, end);
|
|
|
|
};
|
2020-04-21 16:28:12 +00:00
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
Process.prototype.reportBasicNumbers = function (start, end) {
|
|
|
|
// answer a new arrayed list containing an linearly ascending progression
|
|
|
|
// of integers beginning at start to end.
|
2020-04-22 07:20:00 +00:00
|
|
|
var result, len, i,
|
2020-05-18 08:25:29 +00:00
|
|
|
s = +start,
|
|
|
|
e = +end,
|
|
|
|
n = s;
|
2020-04-22 07:20:00 +00:00
|
|
|
|
2020-05-18 08:25:29 +00:00
|
|
|
this.assertType(s, 'number');
|
|
|
|
this.assertType(e, 'number');
|
2020-04-22 07:20:00 +00:00
|
|
|
|
2020-05-18 08:25:29 +00:00
|
|
|
if (e > s) {
|
|
|
|
len = Math.floor(e - s);
|
2020-04-22 07:20:00 +00:00
|
|
|
result = new Array(len);
|
|
|
|
for(i = 0; i <= len; i += 1) {
|
|
|
|
result[i] = n;
|
|
|
|
n += 1;
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-05-18 08:25:29 +00:00
|
|
|
len = Math.floor(s - e);
|
2020-04-22 07:20:00 +00:00
|
|
|
result = new Array(len);
|
|
|
|
for(i = 0; i <= len; i += 1) {
|
|
|
|
result[i] = n;
|
|
|
|
n -= 1;
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return new List(result);
|
|
|
|
};
|
|
|
|
|
2021-02-08 16:19:50 +00:00
|
|
|
Process.prototype.reportListCombination = function (choice, lists) {
|
2021-02-09 21:42:33 +00:00
|
|
|
// experimental, currently not in use
|
2021-02-08 16:19:50 +00:00
|
|
|
var option = this.inputOption(choice);
|
|
|
|
switch (option) {
|
|
|
|
case 'append':
|
|
|
|
return this.reportConcatenatedLists(lists);
|
|
|
|
case 'cross product':
|
|
|
|
return this.reportCrossproduct(lists);
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-05-04 15:12:32 +00:00
|
|
|
Process.prototype.reportConcatenatedLists = function (lists) {
|
|
|
|
var first, result, rows, row, rowIdx, cols, col;
|
|
|
|
this.assertType(lists, 'list');
|
2020-06-07 10:35:04 +00:00
|
|
|
if (lists.isEmpty()) {
|
|
|
|
return lists;
|
|
|
|
}
|
2020-05-04 15:12:32 +00:00
|
|
|
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()) {
|
2020-06-07 10:35:04 +00:00
|
|
|
return lists;
|
2020-05-04 15:12:32 +00:00
|
|
|
}
|
|
|
|
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()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process interpolated non-HOF list primitives
|
|
|
|
|
2020-04-21 16:28:12 +00:00
|
|
|
Process.prototype.reportLinkedNumbers = function (start, end) {
|
2019-04-27 06:59:12 +00:00
|
|
|
// 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;
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2019-10-29 17:06:25 +00:00
|
|
|
this.assertType(start, 'number');
|
|
|
|
this.assertType(end, 'number');
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = {
|
2019-04-27 06:59:12 +00:00
|
|
|
target : new List(),
|
|
|
|
end : null,
|
2019-10-29 06:48:22 +00:00
|
|
|
idx : +start,
|
|
|
|
step: +end > +start ? +1 : -1
|
2019-04-27 06:59:12 +00:00
|
|
|
};
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target.isLinked = true;
|
|
|
|
this.context.accumulator.end = this.context.accumulator.target;
|
2019-04-27 06:59:12 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
dta = this.context.accumulator;
|
2019-10-29 06:48:22 +00:00
|
|
|
if (dta.step === 1 ? dta.idx > +end : dta.idx < +end) {
|
2019-04-27 07:07:11 +00:00
|
|
|
dta.end.rest = new List();
|
2019-04-27 06:59:12 +00:00
|
|
|
this.returnValueToParentContext(dta.target.cdr());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
dta.end.rest = dta.target.cons(dta.idx);
|
|
|
|
dta.end = dta.end.rest;
|
2019-10-29 06:48:22 +00:00
|
|
|
dta.idx += dta.step;
|
2019-04-27 06:59:12 +00:00
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process conditionals primitives
|
|
|
|
|
|
|
|
Process.prototype.doIf = function () {
|
|
|
|
var args = this.context.inputs,
|
|
|
|
outer = this.context.outerContext, // for tail call elimination
|
2014-09-18 12:26:28 +00:00
|
|
|
isCustomBlock = this.context.isCustomBlock;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(args[0], ['Boolean']);
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2014-09-18 12:26:28 +00:00
|
|
|
isCustomBlock = this.context.isCustomBlock;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(args[0], ['Boolean']);
|
2013-03-16 08:02:16 +00:00
|
|
|
this.popContext();
|
|
|
|
if (args[0]) {
|
|
|
|
if (args[1]) {
|
|
|
|
this.pushContext(args[1].blockSequence(), outer);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (args[2]) {
|
|
|
|
this.pushContext(args[2].blockSequence(), outer);
|
2013-10-17 16:56:35 +00:00
|
|
|
} else {
|
|
|
|
this.pushContext('doYield');
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.context) {
|
|
|
|
this.context.isCustomBlock = isCustomBlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2019-04-24 11:26:19 +00:00
|
|
|
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 {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(inputs[0], ['Boolean']);
|
2020-05-20 14:09:59 +00:00
|
|
|
if (inputs[0]) {
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
} else {
|
|
|
|
inputs.push(null);
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
}
|
2019-04-24 11:26:19 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-05 07:44:32 +00:00
|
|
|
/*
|
|
|
|
// Process - hyperized reporter-if, experimental, commented out for now
|
|
|
|
|
|
|
|
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; }
|
|
|
|
if (inputs[0] instanceof List && this.enableHyperOps) {
|
|
|
|
if (inputs.length < 3) {
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
} else {
|
|
|
|
this.returnValueToParentContext(
|
|
|
|
this.hyperIf.apply(this, inputs)
|
|
|
|
);
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.returnValueToParentContext(inputs.pop());
|
|
|
|
this.popContext();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (inputs[0] instanceof List && this.enableHyperOps) {
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
} else {
|
|
|
|
// this.assertType(inputs[0], ['Boolean']);
|
|
|
|
if (inputs[0]) {
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
} else {
|
|
|
|
inputs.push(null);
|
|
|
|
this.evaluateNextInput(block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.hyperIf = function (test, trueValue, falseValue) {
|
|
|
|
if (test instanceof List) {
|
|
|
|
return test.map(each => this.hyperIf(each, trueValue, falseValue));
|
|
|
|
}
|
|
|
|
return test ? trueValue : falseValue;
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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) {
|
2019-10-16 14:42:25 +00:00
|
|
|
if (stage.enableCustomHatBlocks) {
|
|
|
|
stage.threads.pauseCustomHatBlocks =
|
|
|
|
!stage.threads.pauseCustomHatBlocks;
|
|
|
|
} else {
|
|
|
|
stage.threads.pauseCustomHatBlocks = false;
|
|
|
|
}
|
2019-04-29 17:09:56 +00:00
|
|
|
stage.stopAllActiveSounds();
|
2013-03-16 08:02:16 +00:00
|
|
|
stage.threads.resumeAll(stage);
|
|
|
|
stage.keysPressed = {};
|
2018-06-08 22:26:11 +00:00
|
|
|
stage.runStopScripts();
|
2013-03-16 08:02:16 +00:00
|
|
|
stage.threads.stopAll();
|
2019-05-15 11:22:05 +00:00
|
|
|
if (stage.projectionSource) {
|
2019-05-15 12:35:40 +00:00
|
|
|
stage.stopProjection();
|
2019-05-08 13:25:29 +00:00
|
|
|
}
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.children.forEach(morph => {
|
2013-03-16 08:02:16 +00:00
|
|
|
if (morph.stopTalking) {
|
|
|
|
morph.stopTalking();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
stage.removeAllClones();
|
|
|
|
}
|
|
|
|
ide = stage.parentThatIsA(IDE_Morph);
|
2019-04-29 17:09:56 +00:00
|
|
|
if (ide) {
|
|
|
|
ide.controlBar.pauseButton.refresh();
|
2019-10-16 14:42:25 +00:00
|
|
|
ide.controlBar.stopButton.refresh();
|
2019-04-29 17:09:56 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-01-09 14:34:12 +00:00
|
|
|
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:
|
2017-07-27 08:06:51 +00:00
|
|
|
this.doStopOthers(choice);
|
2014-01-09 14:34:12 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-01-08 16:51:34 +00:00
|
|
|
Process.prototype.doStopOthers = function (choice) {
|
|
|
|
var stage;
|
2013-12-23 00:28:11 +00:00
|
|
|
if (this.homeContext.receiver) {
|
|
|
|
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
2014-01-08 16:51:34 +00:00
|
|
|
switch (this.inputOption(choice)) {
|
|
|
|
case 'all but this script':
|
|
|
|
stage.threads.stopAll(this);
|
|
|
|
break;
|
|
|
|
case 'other scripts in sprite':
|
|
|
|
stage.threads.stopAllForReceiver(
|
2021-11-24 07:36:02 +00:00
|
|
|
this.context.outerContext.receiver,
|
|
|
|
// this.homeContext.receiver,
|
2014-01-08 16:51:34 +00:00
|
|
|
this
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
nop();
|
|
|
|
}
|
2013-12-23 00:28:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-05-12 10:14:48 +00:00
|
|
|
|
2020-04-22 07:51:58 +00:00
|
|
|
// this.pushContext('doYield'); // no longer needed in Morphic2
|
2020-05-12 10:14:48 +00:00
|
|
|
this.pushContext('popContext'); // instead we do this...
|
|
|
|
|
2020-04-22 07:51:58 +00:00
|
|
|
if (this.context) {
|
|
|
|
this.context.isCustomBlock = isCustomBlock;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2019-01-28 15:54:48 +00:00
|
|
|
Process.prototype.doSetGlobalFlag = function (name, bool) {
|
2019-05-07 22:45:22 +00:00
|
|
|
var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
2019-01-28 15:54:48 +00:00
|
|
|
name = this.inputOption(name);
|
|
|
|
this.assertType(bool, 'Boolean');
|
2019-05-07 22:45:22 +00:00
|
|
|
switch (name) {
|
|
|
|
case 'turbo mode':
|
2019-01-28 15:54:48 +00:00
|
|
|
this.doSetFastTracking(bool);
|
2019-05-07 22:45:22 +00:00
|
|
|
break;
|
|
|
|
case 'flat line ends':
|
2019-01-28 15:54:48 +00:00
|
|
|
SpriteMorph.prototype.useFlatLineEnds = bool;
|
2019-05-07 22:45:22 +00:00
|
|
|
break;
|
2019-12-03 08:08:06 +00:00
|
|
|
case 'log pen vectors':
|
|
|
|
StageMorph.prototype.enablePenLogging = bool;
|
|
|
|
break;
|
2019-05-07 22:45:22 +00:00
|
|
|
case 'video capture':
|
|
|
|
if (bool) {
|
2019-11-19 06:56:45 +00:00
|
|
|
this.startVideo(stage);
|
2019-05-07 22:45:22 +00:00
|
|
|
} else {
|
2019-05-15 12:35:40 +00:00
|
|
|
stage.stopProjection();
|
2019-05-07 22:45:22 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'mirror video':
|
|
|
|
stage.mirrorVideo = bool;
|
|
|
|
break;
|
2019-01-28 15:54:48 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportGlobalFlag = function (name) {
|
2019-05-07 22:45:22 +00:00
|
|
|
var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
2019-01-28 15:54:48 +00:00
|
|
|
name = this.inputOption(name);
|
2019-05-07 22:45:22 +00:00
|
|
|
switch (name) {
|
|
|
|
case 'turbo mode':
|
2019-01-28 15:54:48 +00:00
|
|
|
return this.reportIsFastTracking();
|
2019-05-07 22:45:22 +00:00
|
|
|
case 'flat line ends':
|
2019-01-28 15:54:48 +00:00
|
|
|
return SpriteMorph.prototype.useFlatLineEnds;
|
2019-12-03 08:08:06 +00:00
|
|
|
case 'log pen vectors':
|
|
|
|
return StageMorph.prototype.enablePenLogging;
|
2019-05-07 22:45:22 +00:00
|
|
|
case 'video capture':
|
2019-11-18 16:37:06 +00:00
|
|
|
return !isNil(stage.projectionSource) &&
|
|
|
|
stage.projectionLayer()
|
|
|
|
.getContext('2d')
|
|
|
|
.getImageData(0, 0, 1, 1)
|
|
|
|
.data[3] > 0;
|
2019-05-07 22:45:22 +00:00
|
|
|
case 'mirror video':
|
|
|
|
return stage.mirrorVideo;
|
|
|
|
default:
|
|
|
|
return '';
|
2019-01-28 15:54:48 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
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) {
|
2014-04-27 19:46:57 +00:00
|
|
|
if (bool) {
|
2013-03-16 08:02:16 +00:00
|
|
|
ide.startFastTracking();
|
2014-04-23 00:05:14 +00:00
|
|
|
} else {
|
|
|
|
ide.stopFastTracking();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-07-30 11:48:12 +00:00
|
|
|
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(); }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process loop primitives
|
|
|
|
|
|
|
|
Process.prototype.doForever = function (body) {
|
2016-05-31 07:50:27 +00:00
|
|
|
this.context.inputs = []; // force re-evaluation of C-slot
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2014-09-18 12:26:28 +00:00
|
|
|
isCustomBlock = this.context.isCustomBlock;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2021-07-06 01:20:00 +00:00
|
|
|
if (isNaN(counter) || counter < 1) {
|
2021-03-17 22:07:59 +00:00
|
|
|
// was '=== 0', which caused infinite loops on non-ints
|
2013-03-16 08:02:16 +00:00
|
|
|
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) {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(goalCondition, ['Boolean']);
|
2013-03-16 08:02:16 +00:00
|
|
|
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) {
|
2020-05-30 12:26:01 +00:00
|
|
|
// this.assertType(goalCondition, ['Boolean']);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (goalCondition) {
|
|
|
|
this.popContext();
|
|
|
|
this.pushContext('doYield');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this.context.inputs = [];
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2019-04-24 08:26:17 +00:00
|
|
|
// 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
|
2019-05-31 08:31:21 +00:00
|
|
|
// within the script.
|
|
|
|
// Distinguish between linked and arrayed lists.
|
2019-04-24 08:26:17 +00:00
|
|
|
|
2019-05-31 08:31:21 +00:00
|
|
|
var next;
|
2019-07-01 07:06:12 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
|
|
|
this.context.accumulator = {
|
|
|
|
source : list,
|
|
|
|
remaining : list.length(),
|
2019-07-01 07:06:12 +00:00
|
|
|
idx : 0
|
2019-10-30 09:58:18 +00:00
|
|
|
};
|
2019-07-01 07:06:12 +00:00
|
|
|
}
|
|
|
|
if (this.context.accumulator.remaining === 0) {
|
2019-10-30 09:58:18 +00:00
|
|
|
return;
|
2019-07-01 07:06:12 +00:00
|
|
|
}
|
|
|
|
this.context.accumulator.remaining -= 1;
|
|
|
|
if (this.context.accumulator.source.isLinked) {
|
2019-05-31 08:31:21 +00:00
|
|
|
next = this.context.accumulator.source.at(1);
|
|
|
|
this.context.accumulator.source =
|
|
|
|
this.context.accumulator.source.cdr();
|
|
|
|
} else { // arrayed
|
|
|
|
this.context.accumulator.idx += 1;
|
2020-07-09 05:11:54 +00:00
|
|
|
next = this.context.accumulator.source.at(this.context.accumulator.idx);
|
2019-05-31 08:31:21 +00:00
|
|
|
}
|
2019-04-24 08:26:17 +00:00
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
2019-05-31 08:31:21 +00:00
|
|
|
this.context.outerContext.variables.addVar(upvar);
|
|
|
|
this.context.outerContext.variables.setVar(upvar, next);
|
2021-03-15 11:13:13 +00:00
|
|
|
this.evaluate(script, new List(/*[next]*/), true);
|
2019-04-24 08:26:17 +00:00
|
|
|
};
|
|
|
|
|
2019-10-16 14:43:31 +00:00
|
|
|
Process.prototype.doFor = function (upvar, start, end, script) {
|
2019-04-24 09:59:15 +00:00
|
|
|
// 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.
|
|
|
|
|
2019-10-14 14:57:54 +00:00
|
|
|
var vars = this.context.outerContext.variables,
|
|
|
|
dta = this.context.accumulator;
|
|
|
|
if (dta === null) {
|
2019-10-29 17:06:25 +00:00
|
|
|
this.assertType(start, 'number');
|
|
|
|
this.assertType(end, 'number');
|
2019-10-14 14:57:54 +00:00
|
|
|
dta = this.context.accumulator = {
|
2019-10-29 17:01:03 +00:00
|
|
|
test : +start < +end ?
|
2020-04-28 17:27:42 +00:00
|
|
|
(() => vars.getVar(upvar) > +end)
|
|
|
|
: (() => vars.getVar(upvar) < +end),
|
2019-10-29 17:01:03 +00:00
|
|
|
step : +start < +end ? 1 : -1,
|
2019-04-24 09:59:15 +00:00
|
|
|
parms : new List() // empty parameters, reusable to avoid GC
|
|
|
|
};
|
2019-10-14 14:57:54 +00:00
|
|
|
vars.addVar(upvar);
|
2019-10-29 17:01:03 +00:00
|
|
|
vars.setVar(upvar, Math.floor(+start));
|
2019-10-14 14:57:54 +00:00
|
|
|
} else {
|
|
|
|
vars.changeVar(upvar, dta.step);
|
2019-04-24 09:59:15 +00:00
|
|
|
}
|
|
|
|
if (dta.test()) {return; }
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
this.evaluate(script, dta.parms, true);
|
|
|
|
};
|
|
|
|
|
2019-04-23 16:19:59 +00:00
|
|
|
// Process interpolated HOF primitives
|
|
|
|
|
2019-04-24 08:26:17 +00:00
|
|
|
/*
|
|
|
|
this.context.inputs:
|
|
|
|
[0] - reporter
|
|
|
|
[1] - list (original source)
|
|
|
|
-----------------------------
|
|
|
|
[2] - last reporter evaluation result
|
|
|
|
|
2019-04-27 07:30:09 +00:00
|
|
|
these primitives used to store the accumulated data in the unused parts
|
2019-04-24 08:26:17 +00:00
|
|
|
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
|
2019-04-27 07:30:09 +00:00
|
|
|
aggregations are now accumulated in the "accumulator" property slot
|
2019-04-24 08:26:17 +00:00
|
|
|
of Context. Why this speeds up execution by orders of magnitude while
|
|
|
|
"fixing" the stack-overflow issue eludes me. -Jens
|
|
|
|
*/
|
|
|
|
|
2013-10-08 14:59:55 +00:00
|
|
|
Process.prototype.reportMap = function (reporter, list) {
|
2013-10-09 10:37:50 +00:00
|
|
|
// answer a new list containing the results of the reporter applied
|
|
|
|
// to each value of the given list. Distinguish between linked and
|
|
|
|
// arrayed lists.
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
2019-04-23 14:43:23 +00:00
|
|
|
|
2019-06-25 14:05:28 +00:00
|
|
|
var next, index, parms;
|
2013-10-09 10:37:50 +00:00
|
|
|
if (list.isLinked) {
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = {
|
2019-04-23 14:43:23 +00:00
|
|
|
source : list,
|
2019-06-25 14:05:28 +00:00
|
|
|
idx : 1,
|
2019-04-23 14:43:23 +00:00
|
|
|
target : new List(),
|
|
|
|
end : null,
|
|
|
|
remaining : list.length()
|
|
|
|
};
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target.isLinked = true;
|
|
|
|
this.context.accumulator.end = this.context.accumulator.target;
|
2019-04-23 14:43:23 +00:00
|
|
|
} else if (this.context.inputs.length > 2) {
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.end.rest = list.cons(
|
2019-04-23 14:43:23 +00:00
|
|
|
this.context.inputs.pop()
|
|
|
|
);
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.end = this.context.accumulator.end.rest;
|
2019-06-25 14:05:28 +00:00
|
|
|
this.context.accumulator.idx += 1;
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.remaining -= 1;
|
2013-10-09 10:37:50 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator.remaining === 0) {
|
|
|
|
this.context.accumulator.end.rest = list.cons(
|
2019-04-23 14:43:23 +00:00
|
|
|
this.context.inputs[2]
|
|
|
|
).cdr();
|
|
|
|
this.returnValueToParentContext(
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target.cdr()
|
2019-04-23 14:43:23 +00:00
|
|
|
);
|
|
|
|
return;
|
2013-10-09 10:37:50 +00:00
|
|
|
}
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.idx;
|
2019-04-27 07:30:09 +00:00
|
|
|
next = this.context.accumulator.source.at(1);
|
|
|
|
this.context.accumulator.source = this.context.accumulator.source.cdr();
|
2019-04-23 14:43:23 +00:00
|
|
|
} else { // arrayed
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = [];
|
2019-04-23 14:43:23 +00:00
|
|
|
} else if (this.context.inputs.length > 2) {
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.push(this.context.inputs.pop());
|
2019-04-23 14:43:23 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator.length === list.length()) {
|
2013-10-09 10:37:50 +00:00
|
|
|
this.returnValueToParentContext(
|
2019-04-27 07:30:09 +00:00
|
|
|
new List(this.context.accumulator)
|
2013-10-09 10:37:50 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.length + 1;
|
|
|
|
next = list.at(index);
|
2013-10-08 14:59:55 +00:00
|
|
|
}
|
2019-04-23 14:43:23 +00:00
|
|
|
this.pushContext();
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [next];
|
|
|
|
if (reporter.inputs.length > 1) {
|
|
|
|
parms.push(index);
|
|
|
|
}
|
|
|
|
if (reporter.inputs.length > 2) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
|
|
|
this.evaluate(reporter, new List(parms));
|
2013-10-08 14:59:55 +00:00
|
|
|
};
|
|
|
|
|
2019-04-23 16:19:59 +00:00
|
|
|
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.
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
2019-04-23 16:19:59 +00:00
|
|
|
|
2019-06-25 14:05:28 +00:00
|
|
|
var next, index, parms;
|
2019-04-23 16:19:59 +00:00
|
|
|
if (list.isLinked) {
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = {
|
2019-04-23 16:19:59 +00:00
|
|
|
source : list,
|
2019-06-25 14:05:28 +00:00
|
|
|
idx: 1,
|
2019-04-23 16:19:59 +00:00
|
|
|
target : new List(),
|
|
|
|
end : null,
|
|
|
|
remaining : list.length()
|
|
|
|
};
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target.isLinked = true;
|
|
|
|
this.context.accumulator.end = this.context.accumulator.target;
|
2019-04-23 16:19:59 +00:00
|
|
|
} else if (this.context.inputs.length > 2) {
|
|
|
|
if (this.context.inputs.pop() === true) {
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.end.rest = list.cons(
|
|
|
|
this.context.accumulator.source.at(1)
|
2019-04-23 16:19:59 +00:00
|
|
|
);
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.end =
|
|
|
|
this.context.accumulator.end.rest;
|
2019-04-23 16:19:59 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.remaining -= 1;
|
2019-06-25 14:05:28 +00:00
|
|
|
this.context.accumulator.idx += 1;
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.source =
|
|
|
|
this.context.accumulator.source.cdr();
|
2019-04-23 16:19:59 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator.remaining === 0) {
|
2019-04-27 08:04:41 +00:00
|
|
|
this.context.accumulator.end.rest = new List();
|
2019-04-23 16:19:59 +00:00
|
|
|
this.returnValueToParentContext(
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target.cdr()
|
2019-04-23 16:19:59 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.idx;
|
2019-04-27 07:30:09 +00:00
|
|
|
next = this.context.accumulator.source.at(1);
|
2019-04-23 16:19:59 +00:00
|
|
|
} else { // arrayed
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = {
|
2019-04-23 16:19:59 +00:00
|
|
|
idx : 0,
|
|
|
|
target : []
|
|
|
|
};
|
|
|
|
} else if (this.context.inputs.length > 2) {
|
|
|
|
if (this.context.inputs.pop() === true) {
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target.push(
|
|
|
|
list.at(this.context.accumulator.idx)
|
2019-04-23 16:19:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator.idx === list.length()) {
|
2019-04-23 16:19:59 +00:00
|
|
|
this.returnValueToParentContext(
|
2019-04-27 07:30:09 +00:00
|
|
|
new List(this.context.accumulator.target)
|
2019-04-23 16:19:59 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.idx += 1;
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.idx;
|
|
|
|
next = list.at(index);
|
2019-04-23 16:19:59 +00:00
|
|
|
}
|
|
|
|
this.pushContext();
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [next];
|
|
|
|
if (predicate.inputs.length > 1) {
|
|
|
|
parms.push(index);
|
|
|
|
}
|
|
|
|
if (predicate.inputs.length > 2) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
|
|
|
this.evaluate(predicate, new List(parms));
|
2019-04-23 16:19:59 +00:00
|
|
|
};
|
|
|
|
|
2019-05-29 09:34:30 +00:00
|
|
|
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.
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
2019-05-29 09:34:30 +00:00
|
|
|
|
2019-06-25 14:05:28 +00:00
|
|
|
var next, index, parms;
|
2019-05-29 09:34:30 +00:00
|
|
|
if (list.isLinked) {
|
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-05-29 09:34:30 +00:00
|
|
|
this.context.accumulator = {
|
|
|
|
source : list,
|
2019-06-25 14:05:28 +00:00
|
|
|
idx : 1,
|
2019-05-29 09:34:30 +00:00
|
|
|
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;
|
2019-06-25 14:05:28 +00:00
|
|
|
this.context.accumulator.idx += 1;
|
2019-05-29 09:34:30 +00:00
|
|
|
this.context.accumulator.source =
|
|
|
|
this.context.accumulator.source.cdr();
|
|
|
|
}
|
|
|
|
if (this.context.accumulator.remaining === 0) {
|
2020-05-27 06:23:04 +00:00
|
|
|
this.returnValueToParentContext('');
|
2019-05-29 09:34:30 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.idx;
|
2019-05-29 09:34:30 +00:00
|
|
|
next = this.context.accumulator.source.at(1);
|
|
|
|
} else { // arrayed
|
|
|
|
if (this.context.accumulator === null) {
|
2019-10-30 09:58:18 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-05-29 09:34:30 +00:00
|
|
|
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()) {
|
2020-05-27 06:23:04 +00:00
|
|
|
this.returnValueToParentContext('');
|
2019-05-29 09:34:30 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.context.accumulator.idx += 1;
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.idx;
|
|
|
|
next = list.at(index);
|
2019-05-29 09:34:30 +00:00
|
|
|
this.context.accumulator.current = next;
|
|
|
|
}
|
|
|
|
this.pushContext();
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [next];
|
|
|
|
if (predicate.inputs.length > 1) {
|
|
|
|
parms.push(index);
|
|
|
|
}
|
|
|
|
if (predicate.inputs.length > 2) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
|
|
|
this.evaluate(predicate, new List(parms));
|
2019-05-29 09:34:30 +00:00
|
|
|
};
|
|
|
|
|
2019-06-25 14:21:42 +00:00
|
|
|
Process.prototype.reportCombine = function (list, reporter) {
|
2019-04-23 22:08:05 +00:00
|
|
|
// Fold - answer an aggregation of all list items from "left to right"
|
|
|
|
// Distinguish between linked and arrayed lists.
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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;
|
2019-04-24 08:26:17 +00:00
|
|
|
this.assertType(list, 'list');
|
2019-04-23 22:08:05 +00:00
|
|
|
if (list.isLinked) {
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2021-03-02 09:55:45 +00:00
|
|
|
// check for special cases to speed up
|
|
|
|
if (this.canRunOptimizedForCombine(reporter)) {
|
|
|
|
return this.reportListAggregation(
|
|
|
|
list,
|
|
|
|
reporter.expression.selector
|
|
|
|
);
|
|
|
|
}
|
2021-03-02 16:43:17 +00:00
|
|
|
|
|
|
|
// test for base cases
|
|
|
|
if (list.length() < 2) {
|
|
|
|
this.returnValueToParentContext(list.length() ? list.at(1) : 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-02 09:55:45 +00:00
|
|
|
// initialize the accumulator
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = {
|
2019-04-23 22:08:05 +00:00
|
|
|
source : list.cdr(),
|
2019-06-25 14:05:28 +00:00
|
|
|
idx : 1,
|
2019-04-23 22:08:05 +00:00
|
|
|
target : list.at(1),
|
|
|
|
remaining : list.length() - 1
|
|
|
|
};
|
|
|
|
} else if (this.context.inputs.length > 2) {
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target = this.context.inputs.pop();
|
|
|
|
this.context.accumulator.remaining -= 1;
|
2019-06-25 14:05:28 +00:00
|
|
|
this.context.accumulator.idx += 1;
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.source =
|
|
|
|
this.context.accumulator.source.cdr();
|
2019-04-23 22:08:05 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator.remaining === 0) {
|
|
|
|
this.returnValueToParentContext(this.context.accumulator.target);
|
2019-04-23 22:08:05 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
next = this.context.accumulator.source.at(1);
|
2019-04-23 22:08:05 +00:00
|
|
|
} else { // arrayed
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator === null) {
|
2021-03-02 09:55:45 +00:00
|
|
|
// check for special cases to speed up
|
|
|
|
if (this.canRunOptimizedForCombine(reporter)) {
|
|
|
|
return this.reportListAggregation(
|
|
|
|
list,
|
|
|
|
reporter.expression.selector
|
|
|
|
);
|
|
|
|
}
|
2021-03-02 16:43:17 +00:00
|
|
|
|
|
|
|
// test for base cases
|
|
|
|
if (list.length() < 2) {
|
|
|
|
this.returnValueToParentContext(list.length() ? list.at(1) : 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-02 09:55:45 +00:00
|
|
|
// initialize the accumulator
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator = {
|
2019-04-23 22:08:05 +00:00
|
|
|
idx : 1,
|
|
|
|
target : list.at(1)
|
|
|
|
};
|
|
|
|
} else if (this.context.inputs.length > 2) {
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.target = this.context.inputs.pop();
|
2019-04-23 22:08:05 +00:00
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
if (this.context.accumulator.idx === list.length()) {
|
|
|
|
this.returnValueToParentContext(this.context.accumulator.target);
|
2019-04-23 22:08:05 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-04-27 07:30:09 +00:00
|
|
|
this.context.accumulator.idx += 1;
|
|
|
|
next = list.at(this.context.accumulator.idx);
|
2019-04-23 22:08:05 +00:00
|
|
|
}
|
2019-06-25 14:05:28 +00:00
|
|
|
index = this.context.accumulator.idx;
|
2019-04-27 07:30:09 +00:00
|
|
|
current = this.context.accumulator.target;
|
2019-04-23 22:08:05 +00:00
|
|
|
this.pushContext();
|
2019-06-25 14:05:28 +00:00
|
|
|
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));
|
2019-04-23 22:08:05 +00:00
|
|
|
};
|
|
|
|
|
2021-03-02 09:55:45 +00:00
|
|
|
Process.prototype.reportListAggregation = function (list, selector) {
|
|
|
|
// private - used by reportCombine to optimize certain commutative
|
|
|
|
// operations such as sum, product, min, max hyperized all at once
|
|
|
|
var len = list.length(),
|
|
|
|
result, i;
|
|
|
|
if (len === 0) {
|
2021-03-02 16:43:17 +00:00
|
|
|
switch (selector) {
|
|
|
|
case 'reportProduct':
|
|
|
|
return 1;
|
|
|
|
case 'reportMin':
|
|
|
|
return Infinity;
|
|
|
|
case 'reportMax':
|
|
|
|
return -Infinity;
|
|
|
|
default: // reportSum
|
|
|
|
return 0;
|
|
|
|
}
|
2021-03-02 09:55:45 +00:00
|
|
|
}
|
|
|
|
result = list.at(1);
|
|
|
|
if (len > 1) {
|
|
|
|
for (i = 2; i <= len; i += 1) {
|
|
|
|
result = this[selector](result, list.at(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.canRunOptimizedForCombine = function (aContext) {
|
|
|
|
// private - used by reportCombine to check for optimizable
|
|
|
|
// special cases
|
|
|
|
var op = aContext.expression.selector,
|
|
|
|
eligible;
|
|
|
|
if (!op) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
eligible = ['reportSum', 'reportProduct', 'reportMin', 'reportMax'];
|
|
|
|
if (!contains(eligible, op)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// scan the expression's inputs, we can assume there are exactly two,
|
|
|
|
// because we're only looking at eligible selectors. Make sure none is
|
|
|
|
// a non-empty input slot or a variable getter whose name doesn't
|
|
|
|
// correspond to an input of the context.
|
|
|
|
// make sure the context has either no or exactly two inputs.
|
|
|
|
if (aContext.inputs.length === 0) {
|
|
|
|
return aContext.expression.inputs().every(each => each.bindingID);
|
|
|
|
}
|
|
|
|
if (aContext.inputs.length !== 2) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return aContext.expression.inputs().every(each =>
|
|
|
|
each.selector === 'reportGetVar' &&
|
|
|
|
contains(aContext.inputs, each.blockSpec)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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)) {
|
2016-10-15 09:15:01 +00:00
|
|
|
if (!this.isAtomic && (secs === 0)) {
|
|
|
|
// "wait 0 secs" is a plain "yield"
|
|
|
|
// that can be overridden by "warp"
|
|
|
|
this.readyToYield = true;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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(
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().xPosition(),
|
|
|
|
this.blockReceiver().yPosition()
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().gotoXY(endX, endY);
|
2013-03-16 08:02:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().glide(
|
2013-03-16 08:02:16 +00:00
|
|
|
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();
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().bubble(data);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().stopTalking();
|
2013-03-16 08:02:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doThinkFor = function (data, secs) {
|
|
|
|
if (!this.context.startTime) {
|
|
|
|
this.context.startTime = Date.now();
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().doThink(data);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
|
2014-02-03 16:11:46 +00:00
|
|
|
this.blockReceiver().stopTalking();
|
2013-03-16 08:02:16 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2014-02-03 16:11:46 +00:00
|
|
|
Process.prototype.blockReceiver = function () {
|
|
|
|
return this.context ? this.context.receiver || this.homeContext.receiver
|
2017-05-30 15:07:09 +00:00
|
|
|
: this.homeContext.receiver || this.receiver;
|
2014-02-03 16:11:46 +00:00
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process sound primitives (interpolated)
|
|
|
|
|
2019-04-08 14:43:42 +00:00
|
|
|
Process.prototype.playSound = function (name) {
|
|
|
|
if (name instanceof List) {
|
|
|
|
return this.doPlaySoundAtRate(name, 44100);
|
|
|
|
}
|
|
|
|
return this.blockReceiver().doPlaySound(name);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.doPlaySoundUntilDone = function (name) {
|
|
|
|
if (this.context.activeAudio === null) {
|
2019-04-08 14:43:42 +00:00
|
|
|
this.context.activeAudio = this.playSound(name);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2019-02-20 08:22:11 +00:00
|
|
|
if (name === null || this.context.activeAudio.ended
|
2013-03-16 08:02:16 +00:00
|
|
|
|| this.context.activeAudio.terminated) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doStopAllSounds = function () {
|
|
|
|
var stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.threads.processes.forEach(thread => {
|
2013-03-16 08:02:16 +00:00
|
|
|
if (thread.context) {
|
|
|
|
thread.context.stopMusic();
|
|
|
|
if (thread.context.activeAudio) {
|
|
|
|
thread.popContext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
stage.stopAllActiveSounds();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-08 13:13:55 +00:00
|
|
|
Process.prototype.doPlaySoundAtRate = function (name, rate) {
|
2019-04-08 15:22:27 +00:00
|
|
|
var sound, samples, ctx, gain, pan, source, rcvr;
|
2019-04-08 13:13:55 +00:00
|
|
|
|
|
|
|
if (!(name instanceof List)) {
|
2019-04-08 14:04:49 +00:00
|
|
|
sound = name instanceof Sound ? name
|
|
|
|
: (typeof name === 'number' ? this.blockReceiver().sounds.at(name)
|
|
|
|
: detect(
|
|
|
|
this.blockReceiver().sounds.asArray(),
|
2020-04-28 17:27:42 +00:00
|
|
|
s => s.name === name.toString()
|
2019-04-08 14:04:49 +00:00
|
|
|
)
|
2019-04-08 13:13:55 +00:00
|
|
|
);
|
|
|
|
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();
|
2019-04-08 15:22:27 +00:00
|
|
|
source = this.encodeSound(samples, rate);
|
2019-04-08 13:13:55 +00:00
|
|
|
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;
|
2019-04-08 14:43:42 +00:00
|
|
|
source.ended = false;
|
2020-04-28 17:27:42 +00:00
|
|
|
source.onended = () => source.ended = true;
|
2019-04-08 14:43:42 +00:00
|
|
|
source.start();
|
2019-04-08 13:13:55 +00:00
|
|
|
rcvr.parentThatIsA(StageMorph).activeSounds.push(source);
|
2019-04-08 14:43:42 +00:00
|
|
|
return source;
|
2019-04-08 13:13:55 +00:00
|
|
|
};
|
|
|
|
|
2019-04-08 12:21:02 +00:00
|
|
|
Process.prototype.reportGetSoundAttribute = function (choice, soundName) {
|
2019-04-08 14:04:49 +00:00
|
|
|
var sound = soundName instanceof Sound ? soundName
|
|
|
|
: (typeof soundName === 'number' ?
|
2019-04-08 15:22:27 +00:00
|
|
|
this.blockReceiver().sounds.at(soundName)
|
|
|
|
: (soundName instanceof List ? this.encodeSound(soundName)
|
|
|
|
: detect(
|
|
|
|
this.blockReceiver().sounds.asArray(),
|
2020-04-28 17:27:42 +00:00
|
|
|
s => s.name === soundName.toString()
|
2019-04-08 15:22:27 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
2019-04-08 12:21:02 +00:00
|
|
|
option = this.inputOption(choice);
|
|
|
|
|
|
|
|
if (option === 'name') {
|
|
|
|
return sound.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sound.audioBuffer) {
|
|
|
|
this.decodeSound(sound);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (option) {
|
|
|
|
case 'samples':
|
|
|
|
if (!sound.cachedSamples) {
|
2020-05-01 14:40:38 +00:00
|
|
|
sound.cachedSamples = function (sound, untype) {
|
2019-04-08 12:21:02 +00:00
|
|
|
var buf = sound.audioBuffer,
|
|
|
|
result, i;
|
|
|
|
if (buf.numberOfChannels > 1) {
|
|
|
|
result = new List();
|
|
|
|
for (i = 0; i < buf.numberOfChannels; i += 1) {
|
2020-04-24 11:44:26 +00:00
|
|
|
result.add(new List(untype(buf.getChannelData(i))));
|
2019-04-08 12:21:02 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-04-24 11:44:26 +00:00
|
|
|
return new List(untype(buf.getChannelData(0)));
|
2020-05-01 14:40:38 +00:00
|
|
|
} (sound, this.untype);
|
2019-04-08 12:21:02 +00:00
|
|
|
}
|
|
|
|
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
|
2020-04-28 17:27:42 +00:00
|
|
|
var base64, binaryString, len, bytes, i, arrayBuffer, audioCtx;
|
|
|
|
|
2019-04-08 12:21:02 +00:00
|
|
|
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,
|
2020-04-28 17:27:42 +00:00
|
|
|
buffer => {
|
2019-04-08 12:21:02 +00:00
|
|
|
sound.audioBuffer = buffer;
|
|
|
|
sound.isDecoding = false;
|
|
|
|
},
|
2020-04-28 17:27:42 +00:00
|
|
|
err => {
|
2019-04-08 12:21:02 +00:00
|
|
|
sound.isDecoding = false;
|
2020-04-28 17:27:42 +00:00
|
|
|
this.handleError(err);
|
2019-04-08 12:21:02 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2019-04-08 15:22:27 +00:00
|
|
|
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(
|
2020-11-30 08:46:41 +00:00
|
|
|
Float32Array.from(samples.itemsArray()),
|
2019-04-08 15:22:27 +00:00
|
|
|
0,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
for (i = 0; i < channels; i += 1) {
|
|
|
|
arrayBuffer.copyToChannel(
|
2020-11-30 08:46:41 +00:00
|
|
|
Float32Array.from(samples.at(i + 1).itemsArray()),
|
2019-04-08 15:22:27 +00:00
|
|
|
i,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
source = ctx.createBufferSource();
|
|
|
|
source.buffer = arrayBuffer;
|
2019-04-09 08:04:14 +00:00
|
|
|
source.audioBuffer = source.buffer;
|
2019-04-08 15:22:27 +00:00
|
|
|
return source;
|
|
|
|
};
|
|
|
|
|
2019-10-20 11:15:54 +00:00
|
|
|
// Process first-class sound creation from samples, interpolated
|
|
|
|
|
2019-10-20 12:07:55 +00:00
|
|
|
Process.prototype.reportNewSoundFromSamples = function (samples, rate) {
|
2019-10-20 17:59:53 +00:00
|
|
|
// this method inspired by: https://github.com/Jam3/audiobuffer-to-wav
|
|
|
|
// https://www.russellgood.com/how-to-convert-audiobuffer-to-audio-file
|
|
|
|
|
2020-04-28 17:27:42 +00:00
|
|
|
var audio, blob, reader;
|
2019-10-20 11:15:54 +00:00
|
|
|
|
|
|
|
if (isNil(this.context.accumulator)) {
|
2019-10-20 18:14:26 +00:00
|
|
|
this.assertType(samples, 'list'); // check only the first time
|
2019-10-20 11:15:54 +00:00
|
|
|
this.context.accumulator = {
|
|
|
|
audio: null
|
|
|
|
};
|
|
|
|
audio = new Audio();
|
2019-10-20 17:59:53 +00:00
|
|
|
blob = new Blob(
|
|
|
|
[
|
|
|
|
this.audioBufferToWav(
|
|
|
|
this.encodeSound(samples, rate || 44100).audioBuffer
|
|
|
|
)
|
|
|
|
],
|
|
|
|
{type: "audio/wav"}
|
2019-10-20 11:15:54 +00:00
|
|
|
);
|
|
|
|
reader = new FileReader();
|
2020-04-28 17:27:42 +00:00
|
|
|
reader.onload = () => {
|
2019-10-20 11:15:54 +00:00
|
|
|
audio.src = reader.result;
|
2020-04-28 17:27:42 +00:00
|
|
|
this.context.accumulator.audio = audio;
|
2019-10-20 11:15:54 +00:00
|
|
|
};
|
|
|
|
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();
|
|
|
|
};
|
|
|
|
|
2019-10-20 17:59:53 +00:00
|
|
|
Process.prototype.audioBufferToWav = function (buffer, opt) {
|
|
|
|
var numChannels = buffer.numberOfChannels,
|
|
|
|
sampleRate = buffer.sampleRate,
|
|
|
|
format = (opt || {}).float32 ? 3 : 1,
|
|
|
|
bitDepth = format === 3 ? 32 : 16,
|
|
|
|
result;
|
2019-10-20 11:15:54 +00:00
|
|
|
|
2019-10-20 17:59:53 +00:00
|
|
|
function interleave(inputL, inputR) {
|
|
|
|
var length = inputL.length + inputR.length,
|
|
|
|
result = new Float32Array(length),
|
|
|
|
index = 0,
|
|
|
|
inputIndex = 0;
|
2019-10-20 11:15:54 +00:00
|
|
|
|
2019-10-20 17:59:53 +00:00
|
|
|
while (index < length) {
|
|
|
|
result[index++] = inputL[inputIndex];
|
|
|
|
result[index++] = inputR[inputIndex];
|
|
|
|
inputIndex += 1;
|
|
|
|
}
|
|
|
|
return result;
|
2019-10-20 11:15:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 17:59:53 +00:00
|
|
|
if (numChannels === 2) {
|
|
|
|
result = interleave(
|
|
|
|
buffer.getChannelData(0),
|
|
|
|
buffer.getChannelData(1)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
result = buffer.getChannelData(0);
|
2019-10-20 11:15:54 +00:00
|
|
|
}
|
2019-10-20 17:59:53 +00:00
|
|
|
return this.encodeWAV(result, format, sampleRate, numChannels, bitDepth);
|
|
|
|
};
|
2019-10-20 11:15:54 +00:00
|
|
|
|
2019-10-20 17:59:53 +00:00
|
|
|
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);
|
2019-10-20 11:15:54 +00:00
|
|
|
}
|
2019-10-20 17:59:53 +00:00
|
|
|
return buffer;
|
2019-10-20 11:15:54 +00:00
|
|
|
};
|
|
|
|
|
2019-03-06 15:44:20 +00:00
|
|
|
// Process audio input (interpolated)
|
|
|
|
|
2019-03-10 10:33:23 +00:00
|
|
|
Process.prototype.reportAudio = function (choice) {
|
2019-03-11 14:25:23 +00:00
|
|
|
var stage = this.blockReceiver().parentThatIsA(StageMorph),
|
|
|
|
selection = this.inputOption(choice);
|
2019-03-18 14:36:51 +00:00
|
|
|
if (selection === 'resolution') {
|
2019-03-11 14:25:23 +00:00
|
|
|
return stage.microphone.binSize();
|
|
|
|
}
|
2019-03-28 16:20:28 +00:00
|
|
|
if (selection === 'modifier') {
|
|
|
|
return stage.microphone.modifier;
|
|
|
|
}
|
2019-03-06 15:44:20 +00:00
|
|
|
if (stage.microphone.isOn()) {
|
2019-03-11 14:25:23 +00:00
|
|
|
switch (selection) {
|
2019-03-06 15:44:20 +00:00
|
|
|
case 'volume':
|
2019-03-12 06:40:02 +00:00
|
|
|
return stage.microphone.volume * 100;
|
2019-03-18 14:36:51 +00:00
|
|
|
case 'frequency':
|
2019-03-10 10:33:23 +00:00
|
|
|
return stage.microphone.pitch;
|
|
|
|
case 'note':
|
|
|
|
return stage.microphone.note;
|
2019-03-18 14:36:51 +00:00
|
|
|
case 'samples':
|
2020-05-01 14:40:38 +00:00
|
|
|
return new List(this.untype(stage.microphone.signals));
|
2019-04-05 10:00:25 +00:00
|
|
|
case 'sample rate':
|
|
|
|
return stage.microphone.audioContext.sampleRate;
|
2019-03-28 16:20:28 +00:00
|
|
|
case 'output':
|
2020-05-01 14:40:38 +00:00
|
|
|
return new List(this.untype(stage.microphone.output));
|
2019-03-18 14:36:51 +00:00
|
|
|
case 'spectrum':
|
2020-05-01 14:40:38 +00:00
|
|
|
return new List(this.untype(stage.microphone.frequencies));
|
2019-03-06 15:44:20 +00:00
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2020-05-01 14:40:38 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2019-03-28 16:20:28 +00:00
|
|
|
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;
|
2019-03-30 10:55:29 +00:00
|
|
|
stage.microphone.compiledModifier = this.reportCompiled(modifier, 1);
|
|
|
|
stage.microphone.compilerProcess = this;
|
2019-03-28 16:20:28 +00:00
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process user prompting primitives (interpolated)
|
|
|
|
|
|
|
|
Process.prototype.doAsk = function (data) {
|
|
|
|
var stage = this.homeContext.receiver.parentThatIsA(StageMorph),
|
2016-08-12 09:48:56 +00:00
|
|
|
rcvr = this.blockReceiver(),
|
|
|
|
isStage = rcvr instanceof StageMorph,
|
|
|
|
isHiddenSprite = rcvr instanceof SpriteMorph && !rcvr.isVisible,
|
2013-03-16 08:02:16 +00:00
|
|
|
activePrompter;
|
|
|
|
|
2015-04-15 15:03:36 +00:00
|
|
|
stage.keysPressed = {};
|
2013-03-16 08:02:16 +00:00
|
|
|
if (!this.prompter) {
|
|
|
|
activePrompter = detect(
|
|
|
|
stage.children,
|
2020-04-28 17:27:42 +00:00
|
|
|
morph => morph instanceof StagePrompterMorph
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
if (!activePrompter) {
|
2016-08-12 09:48:56 +00:00
|
|
|
if (!isStage && !isHiddenSprite) {
|
|
|
|
rcvr.bubble(data, false, true);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2016-08-12 09:48:56 +00:00
|
|
|
this.prompter = new StagePrompterMorph(
|
|
|
|
isStage || isHiddenSprite ? data : null
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
2016-08-12 09:48:56 +00:00
|
|
|
if (!isStage) {rcvr.stopTalking(); }
|
2013-03-16 08:02:16 +00:00
|
|
|
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) {
|
2017-09-06 05:55:30 +00:00
|
|
|
var response;
|
2021-07-20 07:37:09 +00:00
|
|
|
this.checkURLAllowed(url);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (!this.httpRequest) {
|
2017-09-05 09:24:57 +00:00
|
|
|
// use the location protocol unless the user specifies otherwise
|
2017-10-20 07:19:17 +00:00
|
|
|
if (url.indexOf('//') < 0 || url.indexOf('//') > 8) {
|
2017-09-06 05:55:30 +00:00
|
|
|
if (location.protocol === 'file:') {
|
|
|
|
// allow requests from locally loaded sources
|
|
|
|
url = 'https://' + url;
|
|
|
|
} else {
|
|
|
|
url = location.protocol + '//' + url;
|
|
|
|
}
|
2017-09-05 09:24:57 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
this.httpRequest = new XMLHttpRequest();
|
2017-09-06 05:55:30 +00:00
|
|
|
this.httpRequest.open("GET", url, true);
|
2018-07-10 06:29:35 +00:00
|
|
|
// cache-control, commented out for now
|
|
|
|
// added for Snap4Arduino but has issues with local robot servers
|
|
|
|
// this.httpRequest.setRequestHeader('Cache-Control', 'max-age=0');
|
2013-03-16 08:02:16 +00:00
|
|
|
this.httpRequest.send(null);
|
2018-07-03 07:32:37 +00:00
|
|
|
if (this.context.isCustomCommand) {
|
|
|
|
// special case when ignoring the result, e.g. when
|
|
|
|
// communicating with a robot to control its motors
|
2018-07-03 13:15:25 +00:00
|
|
|
this.httpRequest = null;
|
2018-07-03 07:32:37 +00:00
|
|
|
return null;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
} else if (this.httpRequest.readyState === 4) {
|
|
|
|
response = this.httpRequest.responseText;
|
|
|
|
this.httpRequest = null;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2021-07-20 07:37:09 +00:00
|
|
|
Process.prototype.checkURLAllowed = function (url) {
|
|
|
|
if ([ 'users', 'logout', 'projects', 'collections' ].some(
|
2021-07-23 05:34:23 +00:00
|
|
|
pathPart => {
|
|
|
|
// Check out whether we're targeting one of the remote domains
|
|
|
|
return Object.values(Cloud.prototype.knownDomains).filter(
|
|
|
|
each => each.includes('snap')
|
|
|
|
).some(
|
|
|
|
domain => url.match(
|
|
|
|
// Check only against the host -not the protocol, path or
|
|
|
|
// port- of the domain
|
|
|
|
new RegExp(`${(new URL(domain)).host}.*${pathPart}`, 'i'))
|
2021-09-07 08:14:24 +00:00
|
|
|
);
|
2021-07-23 05:34:23 +00:00
|
|
|
}
|
|
|
|
)) {
|
2021-07-20 07:37:09 +00:00
|
|
|
throw new Error('Request blocked');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process event messages primitives
|
|
|
|
|
2021-10-22 13:39:25 +00:00
|
|
|
Process.prototype.doBroadcast = function (message, receivers) {
|
2021-10-21 11:01:59 +00:00
|
|
|
var stage = this.homeContext.receiver.parentThatIsA(StageMorph),
|
2021-10-22 13:39:25 +00:00
|
|
|
target = this.inputOption(receivers.at(1) || ['all']),
|
2021-10-21 11:01:59 +00:00
|
|
|
thisObj,
|
|
|
|
msg = this.inputOption(message),
|
|
|
|
rcvrs,
|
|
|
|
procs = [];
|
|
|
|
if (!this.canBroadcast) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2021-12-10 20:36:05 +00:00
|
|
|
// remove all clones when the green flag event is broadcast to all
|
|
|
|
if (msg === '__shout__go__' && target === 'all') {
|
|
|
|
stage.removeAllClones();
|
|
|
|
}
|
|
|
|
|
2021-10-21 11:01:59 +00:00
|
|
|
// determine the receivers
|
|
|
|
thisObj = this.blockReceiver();
|
2021-10-22 13:39:25 +00:00
|
|
|
if (target === 'all') {
|
2021-10-21 12:10:59 +00:00
|
|
|
rcvrs = stage.children.concat(stage);
|
|
|
|
} else if (isSnapObject(target)) {
|
2021-10-21 11:01:59 +00:00
|
|
|
rcvrs = [target];
|
|
|
|
} else if (isString(target)) {
|
|
|
|
// assume the string to be the name of a sprite or the stage
|
|
|
|
if (target === stage.name) {
|
|
|
|
rcvrs = [stage];
|
|
|
|
} else {
|
|
|
|
rcvrs = [this.getOtherObject(target, thisObj, stage)];
|
|
|
|
}
|
|
|
|
} else if (target instanceof List) {
|
|
|
|
// assume all elements to be sprites or sprite names
|
|
|
|
rcvrs = target.itemsArray().map(each =>
|
|
|
|
this.getOtherObject(each, thisObj, stage)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return; // abort
|
|
|
|
}
|
|
|
|
|
|
|
|
// transmit the message
|
|
|
|
if (msg !== '') {
|
|
|
|
stage.lastMessage = message; // retained for backwards compatibility
|
|
|
|
rcvrs.forEach(morph => {
|
|
|
|
if (isSnapObject(morph)) {
|
|
|
|
morph.allHatBlocksFor(msg).forEach(block => {
|
|
|
|
var varName, varFrame;
|
|
|
|
if (block.selector === 'receiveMessage') {
|
|
|
|
varName = block.inputs()[1].evaluate()[0];
|
|
|
|
if (varName) {
|
|
|
|
varFrame = new VariableFrame();
|
|
|
|
varFrame.addVar(varName, message);
|
|
|
|
}
|
|
|
|
procs.push(stage.threads.startProcess(
|
|
|
|
block,
|
|
|
|
morph,
|
2022-01-03 11:05:24 +00:00
|
|
|
stage.isThreadSafe,
|
|
|
|
// commented out for now to enable tail recursion:
|
|
|
|
// || // make "any msg" threadsafe
|
|
|
|
// block.inputs()[0].evaluate() instanceof Array,
|
2021-10-21 11:01:59 +00:00
|
|
|
null, // exportResult (bool)
|
|
|
|
null, // callback
|
|
|
|
null, // isClicked
|
|
|
|
null, // rightAway
|
|
|
|
null, // atomic
|
|
|
|
varFrame
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
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;
|
2020-04-26 11:11:58 +00:00
|
|
|
};
|
|
|
|
|
2021-10-22 13:39:25 +00:00
|
|
|
Process.prototype.doBroadcastAndWait = function (message, target) {
|
2021-10-21 13:12:42 +00:00
|
|
|
if (!this.context.activeSends) {
|
2021-10-22 13:39:25 +00:00
|
|
|
this.context.activeSends = this.doBroadcast(message, target);
|
2021-10-21 13:12:42 +00:00
|
|
|
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 '';
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process type inference
|
|
|
|
|
|
|
|
Process.prototype.reportIsA = function (thing, typeString) {
|
|
|
|
return this.reportTypeOf(thing) === this.inputOption(typeString);
|
|
|
|
};
|
|
|
|
|
2015-12-17 07:13:48 +00:00
|
|
|
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
|
2017-07-26 15:07:18 +00:00
|
|
|
// use responsibly wrt performance implications
|
2015-12-17 07:13:48 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2016-05-02 10:52:58 +00:00
|
|
|
Process.prototype.assertAlive = function (thing) {
|
|
|
|
if (thing && thing.isCorpse) {
|
|
|
|
throw new Error('cannot operate on a deleted sprite');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
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';
|
|
|
|
}
|
2018-03-14 07:47:59 +00:00
|
|
|
if (thing instanceof List) {
|
|
|
|
return 'list';
|
|
|
|
}
|
2019-02-19 21:34:57 +00:00
|
|
|
if (parseFloat(thing) === +thing) { // I hate this! -Jens
|
2013-03-16 08:02:16 +00:00
|
|
|
return 'number';
|
|
|
|
}
|
|
|
|
if (isString(thing)) {
|
|
|
|
return 'text';
|
|
|
|
}
|
2016-05-02 10:52:58 +00:00
|
|
|
if (thing instanceof SpriteMorph) {
|
|
|
|
return 'sprite';
|
|
|
|
}
|
|
|
|
if (thing instanceof StageMorph) {
|
|
|
|
return 'stage';
|
|
|
|
}
|
2017-07-26 14:03:23 +00:00
|
|
|
if (thing instanceof Costume) {
|
|
|
|
return 'costume';
|
|
|
|
}
|
|
|
|
if (thing instanceof Sound) {
|
|
|
|
return 'sound';
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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';
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process math primtives - hyper-dyadic
|
2013-03-16 08:02:16 +00:00
|
|
|
|
2020-06-24 21:43:41 +00:00
|
|
|
Process.prototype.hyperDyadic = function (baseOp, a, b) {
|
2020-06-22 11:01:20 +00:00
|
|
|
// enable dyadic operations to be performed on lists and tables
|
|
|
|
var len, i, result;
|
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (this.isMatrix(a)) {
|
|
|
|
if (this.isMatrix(b)) {
|
|
|
|
// zip both arguments ignoring out-of-bounds indices
|
2020-12-18 19:26:39 +00:00
|
|
|
a = a.itemsArray();
|
2020-11-30 08:46:41 +00:00
|
|
|
b = b.itemsArray();
|
2020-06-22 11:01:20 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
return a.map(each => this.hyperDyadic(baseOp, each, b));
|
|
|
|
}
|
|
|
|
if (this.isMatrix(b)) {
|
|
|
|
return b.map(each => this.hyperDyadic(baseOp, a, each));
|
|
|
|
}
|
|
|
|
return this.hyperZip(baseOp, a, b);
|
|
|
|
}
|
|
|
|
return baseOp(a, b);
|
|
|
|
};
|
|
|
|
|
2020-06-24 21:43:41 +00:00
|
|
|
Process.prototype.hyperZip = function (baseOp, a, b) {
|
2020-06-22 11:01:20 +00:00
|
|
|
// enable dyadic operations to be performed on lists and tables
|
|
|
|
var len, i, result;
|
|
|
|
if (a instanceof List) {
|
|
|
|
if (b instanceof List) {
|
|
|
|
// zip both arguments ignoring out-of-bounds indices
|
2020-11-30 08:46:41 +00:00
|
|
|
a = a.itemsArray();
|
|
|
|
b = b.itemsArray();
|
2020-06-22 11:01:20 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2020-06-22 07:56:49 +00:00
|
|
|
Process.prototype.isMatrix = function (data) {
|
2020-06-22 11:01:20 +00:00
|
|
|
return data instanceof List && data.at(1) instanceof List;
|
2020-06-22 07:56:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Process math primtives - arithmetic
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
Process.prototype.reportSum = function (a, b) {
|
|
|
|
return this.hyperDyadic(this.reportBasicSum, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicSum = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
return +a + (+b);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportDifference = function (a, b) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicDifference, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicDifference = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
return +a - +b;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportProduct = function (a, b) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicProduct, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicProduct = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
return +a * +b;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportQuotient = function (a, b) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicQuotient, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicQuotient = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
return +a / +b;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2019-03-12 10:18:05 +00:00
|
|
|
Process.prototype.reportPower = function (a, b) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicPower, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicPower = function (a, b) {
|
2019-03-12 10:18:05 +00:00
|
|
|
return Math.pow(+a, +b);
|
|
|
|
};
|
|
|
|
|
2020-12-01 08:01:01 +00:00
|
|
|
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 math primtives - arithmetic hyperdyadic
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportModulus = function (a, b) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicModulus, a, b);
|
|
|
|
};
|
2020-04-21 16:28:12 +00:00
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
Process.prototype.reportBasicModulus = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
var x = +a,
|
|
|
|
y = +b;
|
2013-03-16 08:02:16 +00:00
|
|
|
return ((x % y) + y) % y;
|
|
|
|
};
|
|
|
|
|
2020-12-09 10:24:22 +00:00
|
|
|
Process.prototype.reportAtan2 = function (a, b) {
|
|
|
|
return this.hyperDyadic(this.reportBasicAtan2, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicAtan2 = function (a, b) {
|
|
|
|
return degrees(Math.atan2(+a, +b));
|
|
|
|
};
|
|
|
|
|
2020-12-01 08:01:01 +00:00
|
|
|
Process.prototype.reportMin = function (a, b) {
|
|
|
|
return this.hyperDyadic(this.reportBasicMin, a, b);
|
2020-04-23 09:07:06 +00:00
|
|
|
};
|
2020-04-21 16:28:12 +00:00
|
|
|
|
2020-12-01 08:01:01 +00:00
|
|
|
Process.prototype.reportBasicMin = function (a, b) {
|
2021-02-09 08:53:06 +00:00
|
|
|
// return Math.min(+a, +b); // enhanced to also work with text
|
|
|
|
var x = +a,
|
|
|
|
y = +b;
|
|
|
|
if (isNaN(x) || isNaN(y)) {
|
|
|
|
x = a;
|
|
|
|
y = b;
|
|
|
|
}
|
|
|
|
return x < y ? x : y;
|
2020-12-01 08:01:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportMax = function (a, b) {
|
|
|
|
return this.hyperDyadic(this.reportBasicMax, a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicMax = function (a, b) {
|
2021-02-09 08:53:06 +00:00
|
|
|
// return Math.max(+a, +b); // enhanced to also work with text
|
|
|
|
var x = +a,
|
|
|
|
y = +b;
|
|
|
|
if (isNaN(x) || isNaN(y)) {
|
|
|
|
x = a;
|
|
|
|
y = b;
|
|
|
|
}
|
|
|
|
return x > y ? x : y;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2020-04-24 11:44:26 +00:00
|
|
|
// Process logic primitives - hyper-diadic / monadic where applicable
|
2020-04-23 09:07:06 +00:00
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportLessThan = function (a, b) {
|
2020-04-24 11:44:26 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicLessThan, a, b);
|
|
|
|
};
|
|
|
|
|
2020-12-01 08:33:33 +00:00
|
|
|
Process.prototype.reportLessThanOrEquals = function (a, b) {
|
|
|
|
return this.hyperDyadic(
|
|
|
|
(a, b) => !this.reportBasicGreaterThan(a, b),
|
|
|
|
a,
|
|
|
|
b
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-04-24 11:44:26 +00:00
|
|
|
Process.prototype.reportBasicLessThan = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
var x = +a,
|
|
|
|
y = +b;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (isNaN(x) || isNaN(y)) {
|
|
|
|
x = a;
|
|
|
|
y = b;
|
|
|
|
}
|
|
|
|
return x < y;
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportGreaterThan = function (a, b) {
|
2020-04-24 11:44:26 +00:00
|
|
|
return this.hyperDyadic(this.reportBasicGreaterThan, a, b);
|
|
|
|
};
|
|
|
|
|
2020-12-01 08:33:33 +00:00
|
|
|
Process.prototype.reportGreaterThanOrEquals = function (a, b) {
|
|
|
|
return this.hyperDyadic(
|
|
|
|
(a, b) => !this.reportBasicLessThan(a, b),
|
|
|
|
a,
|
|
|
|
b
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-04-24 11:44:26 +00:00
|
|
|
Process.prototype.reportBasicGreaterThan = function (a, b) {
|
2013-11-26 09:40:24 +00:00
|
|
|
var x = +a,
|
|
|
|
y = +b;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (isNaN(x) || isNaN(y)) {
|
|
|
|
x = a;
|
|
|
|
y = b;
|
|
|
|
}
|
|
|
|
return x > y;
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportEquals = function (a, b) {
|
|
|
|
return snapEquals(a, b);
|
|
|
|
};
|
|
|
|
|
2021-01-05 15:47:09 +00:00
|
|
|
Process.prototype.reportNotEquals = function (a, b) {
|
|
|
|
return !snapEquals(a, b);
|
|
|
|
};
|
|
|
|
|
2020-12-01 08:33:33 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportIsIdentical = function (a, b) {
|
|
|
|
var tag = 'idTag';
|
2021-02-01 09:30:59 +00:00
|
|
|
if (isString(a) && isString(b)) {
|
|
|
|
// compare texts case-sentitive
|
|
|
|
return a === b;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
if (this.isImmutable(a) || this.isImmutable(b)) {
|
|
|
|
return snapEquals(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
function clear() {
|
2013-04-16 00:29:03 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(a, tag)) {
|
2013-03-16 08:02:16 +00:00
|
|
|
delete a[tag];
|
|
|
|
}
|
2013-04-16 00:29:03 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(b, tag)) {
|
2013-03-16 08:02:16 +00:00
|
|
|
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
|
2015-12-15 09:14:56 +00:00
|
|
|
var type = this.reportTypeOf(obj);
|
|
|
|
return type === 'nothing' ||
|
|
|
|
type === 'Boolean' ||
|
|
|
|
type === 'text' ||
|
|
|
|
type === 'number' ||
|
|
|
|
type === 'undefined';
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2016-06-01 11:35:54 +00:00
|
|
|
Process.prototype.reportBoolean = function (bool) {
|
|
|
|
return bool;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process hyper-monadic primitives
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportRound = function (n) {
|
2020-04-22 13:58:33 +00:00
|
|
|
if (this.enableHyperOps) {
|
2020-04-21 16:28:12 +00:00
|
|
|
if (n instanceof List) {
|
2020-05-16 13:44:58 +00:00
|
|
|
return n.map(each => this.reportRound(each));
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
|
|
|
}
|
2013-11-26 09:40:24 +00:00
|
|
|
return Math.round(+n);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportMonadic = function (fname, n) {
|
2020-04-22 13:58:33 +00:00
|
|
|
if (this.enableHyperOps) {
|
2020-04-21 16:28:12 +00:00
|
|
|
if (n instanceof List) {
|
2020-05-16 13:44:58 +00:00
|
|
|
return n.map(each => this.reportMonadic(fname, each));
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-26 09:40:24 +00:00
|
|
|
var x = +n,
|
2013-03-16 08:02:16 +00:00
|
|
|
result = 0;
|
|
|
|
|
|
|
|
switch (this.inputOption(fname)) {
|
|
|
|
case 'abs':
|
|
|
|
result = Math.abs(x);
|
|
|
|
break;
|
2019-04-11 13:58:03 +00:00
|
|
|
// case '\u2212': // minus-sign
|
|
|
|
case 'neg':
|
2018-12-28 18:29:15 +00:00
|
|
|
result = n * -1;
|
|
|
|
break;
|
2020-12-02 07:16:29 +00:00
|
|
|
case 'sign':
|
|
|
|
result = Math.sign(x);
|
|
|
|
break;
|
2015-08-15 10:28:57 +00:00
|
|
|
case 'ceiling':
|
|
|
|
result = Math.ceil(x);
|
|
|
|
break;
|
2013-07-11 15:45:09 +00:00
|
|
|
case 'floor':
|
|
|
|
result = Math.floor(x);
|
|
|
|
break;
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
2015-06-16 00:19:25 +00:00
|
|
|
case 'log': // base 10
|
2019-04-02 14:13:50 +00:00
|
|
|
result = Math.log10(x);
|
|
|
|
break;
|
2019-04-11 08:27:35 +00:00
|
|
|
case 'lg': // base 2
|
2019-04-02 14:13:50 +00:00
|
|
|
result = Math.log2(x);
|
2013-03-16 08:02:16 +00:00
|
|
|
break;
|
|
|
|
case 'e^':
|
|
|
|
result = Math.exp(x);
|
|
|
|
break;
|
|
|
|
case '10^':
|
2015-06-16 00:19:25 +00:00
|
|
|
result = Math.pow(10, x);
|
2013-03-16 08:02:16 +00:00
|
|
|
break;
|
2019-04-11 08:27:35 +00:00
|
|
|
case '2^':
|
|
|
|
result = Math.pow(2, x);
|
|
|
|
break;
|
2020-06-24 09:58:28 +00:00
|
|
|
case 'id':
|
|
|
|
return n;
|
2013-03-16 08:02:16 +00:00
|
|
|
default:
|
2013-11-26 09:40:24 +00:00
|
|
|
nop();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process - non hyper-monadic text primitives
|
|
|
|
|
2013-07-15 13:45:11 +00:00
|
|
|
Process.prototype.reportTextFunction = function (fname, string) {
|
2020-04-23 09:07:06 +00:00
|
|
|
// currently in dev mode only, not hyper-monadic
|
2013-07-15 13:45:11 +00:00
|
|
|
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:
|
2013-11-26 09:40:24 +00:00
|
|
|
nop();
|
2013-07-15 13:45:11 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
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) {
|
2021-12-02 22:05:21 +00:00
|
|
|
if (this.isAST(aList)) {
|
|
|
|
return this.assemble(aList);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
return aList.asText();
|
|
|
|
}
|
|
|
|
return (aList || '').toString();
|
|
|
|
};
|
|
|
|
|
2021-12-02 22:05:21 +00:00
|
|
|
Process.prototype.isAST = function (aList) {
|
|
|
|
var first = aList.at(1);
|
|
|
|
if (first instanceof Context) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (first instanceof List) {
|
|
|
|
return first.at(1) instanceof Context;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process string ops - hyper-monadic/dyadic
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
Process.prototype.reportLetter = function (idx, string) {
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(
|
|
|
|
(ix, str) => this.reportBasicLetter(ix, str),
|
|
|
|
idx,
|
|
|
|
string
|
|
|
|
);
|
|
|
|
};
|
2020-04-21 16:28:12 +00:00
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
Process.prototype.reportBasicLetter = function (idx, string) {
|
2018-06-14 06:24:02 +00:00
|
|
|
var str, i;
|
2020-04-21 16:28:12 +00:00
|
|
|
|
2018-10-03 06:19:36 +00:00
|
|
|
str = isNil(string) ? '' : string.toString();
|
2018-06-11 12:44:53 +00:00
|
|
|
if (this.inputOption(idx) === 'any') {
|
2020-05-04 15:18:24 +00:00
|
|
|
idx = this.reportBasicRandom(1, str.length);
|
2018-06-11 12:44:53 +00:00
|
|
|
}
|
|
|
|
if (this.inputOption(idx) === 'last') {
|
2018-10-03 06:19:36 +00:00
|
|
|
idx = str.length;
|
2018-06-11 12:44:53 +00:00
|
|
|
}
|
2018-06-18 11:47:15 +00:00
|
|
|
i = +(idx || 0);
|
2013-03-16 08:02:16 +00:00
|
|
|
return str[i - 1] || '';
|
|
|
|
};
|
|
|
|
|
2016-07-19 09:34:08 +00:00
|
|
|
Process.prototype.reportStringSize = function (data) {
|
2020-04-23 09:07:06 +00:00
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (data instanceof List) {
|
2020-05-16 13:44:58 +00:00
|
|
|
return data.map(each => this.reportStringSize(each));
|
2020-04-23 09:07:06 +00:00
|
|
|
}
|
|
|
|
}
|
2016-07-19 09:34:08 +00:00
|
|
|
if (data instanceof List) { // catch a common user error
|
|
|
|
return data.length();
|
2013-12-11 09:20:40 +00:00
|
|
|
}
|
2016-07-19 09:54:52 +00:00
|
|
|
return isNil(data) ? 0 : data.toString().length;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportUnicode = function (string) {
|
2021-12-11 22:37:33 +00:00
|
|
|
var str, unicodeList;
|
2020-04-22 06:49:50 +00:00
|
|
|
|
2020-04-22 13:58:33 +00:00
|
|
|
if (this.enableHyperOps) {
|
2020-04-21 16:28:12 +00:00
|
|
|
if (string instanceof List) {
|
2020-05-16 13:44:58 +00:00
|
|
|
return string.map(each => this.reportUnicode(each));
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
2020-04-22 06:49:50 +00:00
|
|
|
str = isNil(string) ? '\u0000' : string.toString();
|
2021-12-11 22:37:33 +00:00
|
|
|
unicodeList = Array.from(str);
|
|
|
|
if (unicodeList.length > 1) {
|
|
|
|
return this.reportUnicode(new List(unicodeList));
|
2020-04-22 06:49:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
str = isNil(string) ? '\u0000' : string.toString();
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
2017-11-02 09:46:55 +00:00
|
|
|
if (str.codePointAt) { // support for Unicode in newer browsers.
|
2019-04-07 05:24:42 +00:00
|
|
|
return str.codePointAt(0) || 0;
|
2017-11-02 09:46:55 +00:00
|
|
|
}
|
2019-04-07 05:24:42 +00:00
|
|
|
return str.charCodeAt(0) || 0;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportUnicodeAsLetter = function (num) {
|
2020-04-22 13:58:33 +00:00
|
|
|
if (this.enableHyperOps) {
|
2020-04-21 16:28:12 +00:00
|
|
|
if (num instanceof List) {
|
2020-05-16 13:44:58 +00:00
|
|
|
return num.map(each => this.reportUnicodeAsLetter(each));
|
2020-04-21 16:28:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-26 09:40:24 +00:00
|
|
|
var code = +(num || 0);
|
2017-11-02 09:46:55 +00:00
|
|
|
|
|
|
|
if (String.fromCodePoint) { // support for Unicode in newer browsers.
|
2017-11-08 05:25:23 +00:00
|
|
|
return String.fromCodePoint(code);
|
2017-11-02 09:46:55 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
return String.fromCharCode(code);
|
|
|
|
};
|
|
|
|
|
2013-07-31 13:15:27 +00:00
|
|
|
Process.prototype.reportTextSplit = function (string, delimiter) {
|
2021-12-02 22:05:21 +00:00
|
|
|
if (this.inputOption(delimiter) === 'blocks') {
|
|
|
|
this.assertType(string, ['command', 'reporter', 'predicate']);
|
|
|
|
return string.components();
|
|
|
|
}
|
2020-04-23 09:07:06 +00:00
|
|
|
return this.hyperDyadic(
|
|
|
|
(str, delim) => this.reportBasicTextSplit(str, delim),
|
|
|
|
string,
|
|
|
|
delimiter
|
|
|
|
);
|
|
|
|
};
|
2020-04-22 11:45:45 +00:00
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
Process.prototype.reportBasicTextSplit = function (string, delimiter) {
|
2013-10-04 08:34:59 +00:00
|
|
|
var types = ['text', 'number'],
|
|
|
|
strType = this.reportTypeOf(string),
|
|
|
|
delType = this.reportTypeOf(this.inputOption(delimiter)),
|
|
|
|
str,
|
2013-07-31 13:15:27 +00:00
|
|
|
del;
|
2013-10-04 08:34:59 +00:00
|
|
|
if (!contains(types, strType)) {
|
2014-10-19 06:00:11 +00:00
|
|
|
throw new Error('expecting text instead of a ' + strType);
|
2013-10-04 08:34:59 +00:00
|
|
|
}
|
|
|
|
if (!contains(types, delType)) {
|
2014-10-19 06:00:11 +00:00
|
|
|
throw new Error('expecting a text delimiter instead of a ' + delType);
|
2013-10-04 08:34:59 +00:00
|
|
|
}
|
2016-07-19 12:44:39 +00:00
|
|
|
str = isNil(string) ? '' : string.toString();
|
2013-08-01 09:49:35 +00:00
|
|
|
switch (this.inputOption(delimiter)) {
|
|
|
|
case 'line':
|
2016-07-19 10:09:29 +00:00
|
|
|
// Unicode compliant line splitting (platform independent)
|
2014-10-19 06:00:11 +00:00
|
|
|
// http://www.unicode.org/reports/tr18/#Line_Boundaries
|
|
|
|
del = /\r\n|[\n\v\f\r\x85\u2028\u2029]/;
|
2013-08-01 09:49:35 +00:00
|
|
|
break;
|
|
|
|
case 'tab':
|
|
|
|
del = '\t';
|
|
|
|
break;
|
|
|
|
case 'cr':
|
|
|
|
del = '\r';
|
|
|
|
break;
|
2019-05-29 10:14:26 +00:00
|
|
|
case 'word':
|
2013-08-01 09:49:35 +00:00
|
|
|
case 'whitespace':
|
2014-10-19 06:00:11 +00:00
|
|
|
str = str.trim();
|
|
|
|
del = /\s+/;
|
|
|
|
break;
|
2021-12-11 22:27:56 +00:00
|
|
|
case '':
|
2014-07-18 13:36:56 +00:00
|
|
|
case 'letter':
|
2021-12-11 22:19:52 +00:00
|
|
|
return new List(Array.from(str));
|
2018-10-26 06:05:09 +00:00
|
|
|
case 'csv':
|
|
|
|
return this.parseCSV(string);
|
2019-01-09 10:03:43 +00:00
|
|
|
case 'json':
|
|
|
|
return this.parseJSON(string);
|
2018-10-26 06:05:09 +00:00
|
|
|
/*
|
2018-10-24 14:14:34 +00:00
|
|
|
case 'csv records':
|
|
|
|
return this.parseCSVrecords(string);
|
|
|
|
case 'csv fields':
|
2018-10-26 06:05:09 +00:00
|
|
|
return this.parseCSVfields(string);
|
|
|
|
*/
|
2013-08-01 09:49:35 +00:00
|
|
|
default:
|
2021-12-11 22:27:56 +00:00
|
|
|
del = delimiter.toString();
|
2013-07-31 13:15:27 +00:00
|
|
|
}
|
|
|
|
return new List(str.split(del));
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:07:06 +00:00
|
|
|
// Process - parsing primitives
|
|
|
|
|
2018-10-26 06:05:09 +00:00
|
|
|
Process.prototype.parseCSV = function (text) {
|
2019-07-10 08:31:07 +00:00
|
|
|
// 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) {
|
2018-10-26 06:05:09 +00:00
|
|
|
// 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 = [''],
|
2018-10-26 09:23:47 +00:00
|
|
|
records = [fields],
|
2018-10-26 06:05:09 +00:00
|
|
|
col = 0,
|
|
|
|
r = 0,
|
|
|
|
esc = true,
|
|
|
|
len = text.length,
|
|
|
|
idx,
|
|
|
|
char;
|
2019-07-10 08:31:07 +00:00
|
|
|
delim = delim || ',';
|
2018-10-26 06:05:09 +00:00
|
|
|
for (idx = 0; idx < len; idx += 1) {
|
|
|
|
char = text[idx];
|
2019-01-12 08:33:56 +00:00
|
|
|
if (char === '\"') {
|
2018-10-26 06:05:09 +00:00
|
|
|
if (esc && char === prev) {
|
|
|
|
fields[col] += char;
|
|
|
|
}
|
|
|
|
esc = !esc;
|
2019-07-10 08:31:07 +00:00
|
|
|
} else if (char === delim && esc) {
|
2018-10-26 06:05:09 +00:00
|
|
|
char = '';
|
|
|
|
col += 1;
|
|
|
|
fields[col] = char;
|
2019-01-25 08:31:11 +00:00
|
|
|
} else if (char === '\r' && esc) {
|
2018-10-26 06:05:09 +00:00
|
|
|
r += 1;
|
2019-01-25 08:31:11 +00:00
|
|
|
records[r] = [''];
|
2018-10-26 09:23:47 +00:00
|
|
|
fields = records[r];
|
2018-10-26 06:05:09 +00:00
|
|
|
col = 0;
|
2019-01-25 08:31:11 +00:00
|
|
|
} else if (char === '\n' && esc) {
|
|
|
|
if (prev !== '\r') {
|
|
|
|
r += 1;
|
|
|
|
records[r] = [''];
|
|
|
|
fields = records[r];
|
|
|
|
col = 0;
|
|
|
|
}
|
2018-10-26 06:05:09 +00:00
|
|
|
} else {
|
|
|
|
fields[col] += char;
|
|
|
|
}
|
|
|
|
prev = char;
|
|
|
|
}
|
2018-10-26 09:23:47 +00:00
|
|
|
|
|
|
|
// 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
|
2020-04-28 17:27:42 +00:00
|
|
|
records = new List(
|
|
|
|
records.map(row => new List(row))
|
2018-10-26 06:05:09 +00:00
|
|
|
);
|
2018-10-26 09:23:47 +00:00
|
|
|
|
|
|
|
// for backwards compatibility return the first row if it is the only one
|
|
|
|
if (records.length() === 1) {
|
|
|
|
return records.at(1);
|
2018-10-26 06:05:09 +00:00
|
|
|
}
|
2018-10-26 09:23:47 +00:00
|
|
|
return records;
|
2018-10-26 06:05:09 +00:00
|
|
|
};
|
|
|
|
|
2019-01-09 10:03:43 +00:00
|
|
|
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));
|
|
|
|
};
|
|
|
|
|
2021-12-09 12:27:01 +00:00
|
|
|
// Process syntax analysis
|
2021-12-02 22:05:21 +00:00
|
|
|
|
|
|
|
Process.prototype.assemble = function (blocks) {
|
|
|
|
var first;
|
|
|
|
if (!(blocks instanceof List)) {
|
|
|
|
return blocks;
|
|
|
|
}
|
|
|
|
first = blocks.at(1);
|
|
|
|
if (first instanceof Context) {
|
|
|
|
return first.copyWithInputs(
|
|
|
|
blocks.cdr().map(each => this.assemble(each))
|
|
|
|
);
|
|
|
|
}
|
2021-12-06 13:09:25 +00:00
|
|
|
if (blocks.isEmpty()) {
|
|
|
|
return blocks;
|
|
|
|
}
|
2021-12-02 22:05:21 +00:00
|
|
|
return blocks.map(each => this.assemble(each)).itemsArray().reduce(
|
|
|
|
(a, b) => a.copyWithNext(b)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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) {
|
2020-11-30 08:46:41 +00:00
|
|
|
alert('Snap! ' + data.itemsArray());
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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) {
|
2020-11-30 08:46:41 +00:00
|
|
|
console.log('Snap! ' + data.itemsArray());
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2019-05-08 07:41:31 +00:00
|
|
|
// deal with first-class sprites
|
2016-05-02 10:52:58 +00:00
|
|
|
if (isSnapObject(name)) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2019-05-08 07:41:31 +00:00
|
|
|
if (this.inputOption(name) === 'myself') {
|
|
|
|
return thisObj;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
var stage = isNil(stageObj) ?
|
|
|
|
thisObj.parentThatIsA(StageMorph) : stageObj,
|
|
|
|
thatObj = null;
|
|
|
|
if (stage) {
|
|
|
|
// find the corresponding sprite on the stage
|
|
|
|
thatObj = detect(
|
|
|
|
stage.children,
|
2020-04-28 17:27:42 +00:00
|
|
|
morph => morph.name === name
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
if (!thatObj) {
|
|
|
|
// check if the sprite in question is currently being
|
|
|
|
// dragged around
|
|
|
|
thatObj = detect(
|
|
|
|
stage.world().hand.children,
|
2020-04-28 17:27:42 +00:00
|
|
|
morph => morph instanceof SpriteMorph &&
|
|
|
|
morph.name === name
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
2017-07-04 11:51:22 +00:00
|
|
|
return obj instanceof SpriteMorph && obj.isTemporary ?
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2018-03-09 10:47:11 +00:00
|
|
|
Process.prototype.setHeading = function (direction) {
|
|
|
|
var thisObj = this.blockReceiver();
|
|
|
|
|
|
|
|
if (thisObj) {
|
|
|
|
if (this.inputOption(direction) === 'random') {
|
2020-05-04 15:18:24 +00:00
|
|
|
direction = this.reportBasicRandom(1, 36000) / 100;
|
2018-03-09 10:47:11 +00:00
|
|
|
}
|
|
|
|
thisObj.setHeading(direction);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.doFaceTowards = function (name) {
|
2014-07-11 10:54:02 +00:00
|
|
|
var thisObj = this.blockReceiver(),
|
2013-03-16 08:02:16 +00:00
|
|
|
thatObj;
|
|
|
|
|
|
|
|
if (thisObj) {
|
2018-05-02 08:08:41 +00:00
|
|
|
if (this.inputOption(name) === 'center') {
|
|
|
|
thisObj.faceToXY(0, 0);
|
|
|
|
} else if (this.inputOption(name) === 'mouse-pointer') {
|
2013-03-16 08:02:16 +00:00
|
|
|
thisObj.faceToXY(this.reportMouseX(), this.reportMouseY());
|
2018-05-02 08:08:41 +00:00
|
|
|
} else if (this.inputOption(name) === 'random position') {
|
2020-05-04 15:18:24 +00:00
|
|
|
thisObj.setHeading(this.reportBasicRandom(1, 36000) / 100);
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2017-07-27 14:20:37 +00:00
|
|
|
if (name instanceof List) {
|
|
|
|
thisObj.faceToXY(
|
|
|
|
name.at(1),
|
|
|
|
name.at(2)
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2014-07-11 10:54:02 +00:00
|
|
|
thatObj = this.getOtherObject(name, this.homeContext.receiver);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (thatObj) {
|
|
|
|
thisObj.faceToXY(
|
|
|
|
thatObj.xPosition(),
|
|
|
|
thatObj.yPosition()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doGotoObject = function (name) {
|
2014-07-11 10:54:02 +00:00
|
|
|
var thisObj = this.blockReceiver(),
|
2018-03-09 10:47:11 +00:00
|
|
|
thatObj,
|
|
|
|
stage;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
if (thisObj) {
|
2018-05-02 08:08:41 +00:00
|
|
|
if (this.inputOption(name) === 'center') {
|
|
|
|
thisObj.gotoXY(0, 0);
|
|
|
|
} else if (this.inputOption(name) === 'mouse-pointer') {
|
2013-03-16 08:02:16 +00:00
|
|
|
thisObj.gotoXY(this.reportMouseX(), this.reportMouseY());
|
2018-03-09 10:47:11 +00:00
|
|
|
} else if (this.inputOption(name) === 'random position') {
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
|
|
|
thisObj.setCenter(new Point(
|
2020-05-04 15:18:24 +00:00
|
|
|
this.reportBasicRandom(stage.left(), stage.right()),
|
|
|
|
this.reportBasicRandom(stage.top(), stage.bottom())
|
2018-03-09 10:47:11 +00:00
|
|
|
));
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2017-07-27 14:20:37 +00:00
|
|
|
if (name instanceof List) {
|
|
|
|
thisObj.gotoXY(
|
|
|
|
name.at(1),
|
|
|
|
name.at(2)
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2014-07-11 10:54:02 +00:00
|
|
|
thatObj = this.getOtherObject(name, this.homeContext.receiver);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (thatObj) {
|
|
|
|
thisObj.gotoXY(
|
|
|
|
thatObj.xPosition(),
|
|
|
|
thatObj.yPosition()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-12-28 19:00:11 +00:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-04-12 08:46:45 +00:00
|
|
|
// Process scene primitives
|
|
|
|
|
2021-12-11 06:48:55 +00:00
|
|
|
Process.prototype.doSwitchToScene = function (id, transmission) { // +++
|
2021-04-12 08:46:45 +00:00
|
|
|
var rcvr = this.blockReceiver(),
|
|
|
|
idx = 0,
|
2021-10-20 14:50:14 +00:00
|
|
|
message = this.inputOption(transmission.at(1)),
|
2021-04-12 08:46:45 +00:00
|
|
|
ide, scenes, num, scene;
|
|
|
|
this.assertAlive(rcvr);
|
2021-10-21 13:54:39 +00:00
|
|
|
this.assertType(message, ['text', 'number', 'Boolean', 'list']);
|
2021-10-22 13:48:24 +00:00
|
|
|
if (message instanceof List) {
|
2021-10-21 10:04:58 +00:00
|
|
|
// make sure only atomic leafs are inside the list
|
|
|
|
// don't actually encode the list as json, though
|
2021-10-22 13:48:24 +00:00
|
|
|
if (message.canBeJSON()) {
|
|
|
|
message = message.deepMap(leaf => leaf); // deep copy the list
|
|
|
|
} else {
|
|
|
|
throw new Error(localize(
|
|
|
|
'cannot send media,\nsprites or procedures\nto another scene'
|
|
|
|
));
|
|
|
|
}
|
2021-10-21 10:04:58 +00:00
|
|
|
}
|
2021-10-20 14:50:14 +00:00
|
|
|
if (this.readyToTerminate) {
|
2021-09-08 16:05:29 +00:00
|
|
|
// let the user press "stop" or "esc",
|
|
|
|
// prevent "when this scene starts" hat blocks from directly
|
|
|
|
// switching to another
|
|
|
|
return;
|
|
|
|
}
|
2021-04-12 08:46:45 +00:00
|
|
|
ide = rcvr.parentThatIsA(IDE_Morph);
|
|
|
|
scenes = ide.scenes;
|
|
|
|
|
|
|
|
if (id instanceof Array) { // special named indices
|
|
|
|
switch (this.inputOption(id)) {
|
|
|
|
case 'next':
|
|
|
|
idx = scenes.indexOf(ide.scene) + 1;
|
|
|
|
if (idx > scenes.length()) {
|
|
|
|
idx = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'previous':
|
|
|
|
idx = scenes.indexOf(ide.scene) - 1;
|
|
|
|
if (idx < 1) {
|
|
|
|
idx = scenes.length();
|
|
|
|
}
|
|
|
|
break;
|
2021-10-20 15:33:15 +00:00
|
|
|
/*
|
2021-04-12 08:46:45 +00:00
|
|
|
case 'last':
|
|
|
|
idx = scenes.length();
|
|
|
|
break;
|
2021-10-20 15:33:15 +00:00
|
|
|
*/
|
2021-04-12 08:46:45 +00:00
|
|
|
case 'random':
|
|
|
|
idx = this.reportBasicRandom(1, scenes.length());
|
|
|
|
break;
|
|
|
|
}
|
2021-09-08 15:58:00 +00:00
|
|
|
this.stop();
|
2021-09-08 17:52:33 +00:00
|
|
|
// ide.onNextStep = () => // slow down scene switching, disabled for now
|
2021-09-30 11:52:51 +00:00
|
|
|
ide.switchToScene(scenes.at(idx), null, message);
|
2021-04-12 08:46:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
scene = detect(scenes.itemsArray(), scn => scn.name === id);
|
|
|
|
if (scene === null) {
|
|
|
|
num = parseFloat(id);
|
|
|
|
if (isNaN(num)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
scene = scenes.at(num);
|
|
|
|
}
|
|
|
|
|
2021-09-08 15:58:00 +00:00
|
|
|
this.stop();
|
2021-09-30 11:52:51 +00:00
|
|
|
ide.switchToScene(scene, null, message);
|
2021-04-12 08:46:45 +00:00
|
|
|
};
|
|
|
|
|
2019-01-23 16:09:54 +00:00
|
|
|
// Process color primitives
|
2019-01-02 13:27:04 +00:00
|
|
|
|
2021-11-08 11:17:18 +00:00
|
|
|
Process.prototype.setColorDimension = function (name, num) {
|
2021-11-26 15:25:36 +00:00
|
|
|
var options = ['hue', 'saturation', 'brightness', 'transparency'],
|
|
|
|
choice = this.inputOption(name);
|
2021-11-27 11:56:33 +00:00
|
|
|
if (choice === 'r-g-b(-a)') {
|
2021-11-26 15:25:36 +00:00
|
|
|
this.blockReceiver().setColorRGBA(num);
|
|
|
|
return;
|
|
|
|
}
|
2021-11-08 11:17:18 +00:00
|
|
|
this.blockReceiver().setColorDimension(
|
2021-11-26 15:25:36 +00:00
|
|
|
options.indexOf(choice),
|
2019-01-23 16:09:54 +00:00
|
|
|
+num
|
|
|
|
);
|
2019-01-02 13:27:04 +00:00
|
|
|
};
|
|
|
|
|
2021-11-08 11:17:18 +00:00
|
|
|
Process.prototype.changeColorDimension = function (name, num) {
|
2021-11-26 15:25:36 +00:00
|
|
|
var options = ['hue', 'saturation', 'brightness', 'transparency'],
|
|
|
|
choice = this.inputOption(name);
|
2021-11-27 11:56:33 +00:00
|
|
|
if (choice === 'r-g-b(-a)') {
|
2021-11-26 15:25:36 +00:00
|
|
|
this.blockReceiver().changeColorRGBA(num);
|
|
|
|
return;
|
|
|
|
}
|
2021-11-08 11:17:18 +00:00
|
|
|
this.blockReceiver().changeColorDimension(
|
2021-11-26 15:25:36 +00:00
|
|
|
options.indexOf(choice),
|
2019-01-23 16:09:54 +00:00
|
|
|
+num
|
|
|
|
);
|
2019-01-02 13:27:04 +00:00
|
|
|
};
|
|
|
|
|
2021-11-08 11:17:18 +00:00
|
|
|
Process.prototype.setPenColorDimension =
|
|
|
|
Process.prototype.setColorDimension;
|
|
|
|
|
|
|
|
Process.prototype.changePenColorDimension =
|
|
|
|
Process.prototype.changeColorDimension;
|
|
|
|
|
|
|
|
Process.prototype.setBackgroundColorDimension =
|
|
|
|
Process.prototype.setColorDimension;
|
|
|
|
|
|
|
|
Process.prototype.changeBackgroundColorDimension =
|
|
|
|
Process.prototype.changeColorDimension;
|
2019-01-23 16:09:54 +00:00
|
|
|
|
2020-11-02 13:31:10 +00:00
|
|
|
// Process cutting & pasting primitives
|
2019-08-06 05:53:56 +00:00
|
|
|
|
2020-11-02 13:31:10 +00:00
|
|
|
Process.prototype.doPasteOn = function (name) {
|
|
|
|
this.blitOn(name, 'source-atop');
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.doCutFrom = function (name) {
|
|
|
|
this.blitOn(name, 'destination-out');
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.blitOn = function (name, mask, thisObj, stage) {
|
2019-08-06 05:53:56 +00:00
|
|
|
// allow for lists of sprites and also check for temparary clones,
|
|
|
|
// as in Scratch 2.0,
|
2020-04-28 17:27:42 +00:00
|
|
|
var those;
|
2019-08-06 05:53:56 +00:00
|
|
|
thisObj = thisObj || this.blockReceiver();
|
|
|
|
stage = stage || thisObj.parentThatIsA(StageMorph);
|
|
|
|
if (stage.name === name) {
|
|
|
|
name = stage;
|
|
|
|
}
|
|
|
|
if (isSnapObject(name)) {
|
2020-11-02 13:31:10 +00:00
|
|
|
return thisObj.blitOn(name, mask);
|
2019-08-06 05:53:56 +00:00
|
|
|
}
|
|
|
|
if (name instanceof List) { // assume all elements to be sprites
|
|
|
|
those = name.itemsArray();
|
|
|
|
} else {
|
|
|
|
those = this.getObjectsNamed(name, thisObj, stage); // clones
|
|
|
|
}
|
2020-11-11 11:11:56 +00:00
|
|
|
those.forEach(each => {
|
|
|
|
// only draw on same-named clones that don't dynamically the costume
|
|
|
|
if (!each.inheritsAttribute('costume #')) {
|
|
|
|
this.blitOn(each, mask, thisObj, stage);
|
|
|
|
}
|
|
|
|
});
|
2019-08-06 05:53:56 +00:00
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process temporary cloning (Scratch-style)
|
|
|
|
|
|
|
|
Process.prototype.createClone = function (name) {
|
2016-05-02 10:52:58 +00:00
|
|
|
var thisObj = this.blockReceiver(),
|
2013-03-16 08:02:16 +00:00
|
|
|
thatObj;
|
|
|
|
|
2018-06-08 17:21:11 +00:00
|
|
|
if (!name || this.readyToTerminate) {return; }
|
2013-03-16 08:02:16 +00:00
|
|
|
if (thisObj) {
|
|
|
|
if (this.inputOption(name) === 'myself') {
|
2016-06-12 12:10:48 +00:00
|
|
|
thisObj.createClone(!this.isFirstStep);
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
|
|
|
thatObj = this.getOtherObject(name, thisObj);
|
|
|
|
if (thatObj) {
|
2016-06-12 12:10:48 +00:00
|
|
|
thatObj.createClone(!this.isFirstStep);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-07-12 07:43:35 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Process sensing primitives
|
|
|
|
|
|
|
|
Process.prototype.reportTouchingObject = function (name) {
|
2014-02-03 16:11:46 +00:00
|
|
|
var thisObj = this.blockReceiver();
|
2013-08-12 11:05:42 +00:00
|
|
|
|
|
|
|
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)
|
2020-04-28 17:27:42 +00:00
|
|
|
var those,
|
2013-03-16 08:02:16 +00:00
|
|
|
stage,
|
2014-12-03 11:48:31 +00:00
|
|
|
box,
|
2013-03-16 08:02:16 +00:00
|
|
|
mouse;
|
|
|
|
|
2013-08-12 11:05:42 +00:00
|
|
|
if (this.inputOption(name) === 'mouse-pointer') {
|
|
|
|
mouse = thisObj.world().hand.position();
|
|
|
|
if (thisObj.bounds.containsPoint(mouse) &&
|
|
|
|
!thisObj.isTransparentAt(mouse)) {
|
|
|
|
return true;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2013-08-12 11:05:42 +00:00
|
|
|
} else {
|
2013-03-16 08:02:16 +00:00
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
2014-12-03 11:48:31 +00:00
|
|
|
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;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2013-08-12 11:05:42 +00:00
|
|
|
if (this.inputOption(name) === 'pen trails' &&
|
|
|
|
thisObj.isTouching(stage.penTrailsMorph())) {
|
|
|
|
return true;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2016-05-02 10:52:58 +00:00
|
|
|
if (isSnapObject(name)) {
|
2018-04-12 10:58:40 +00:00
|
|
|
return name.isVisible && thisObj.isTouching(name);
|
2016-05-02 10:52:58 +00:00
|
|
|
}
|
|
|
|
if (name instanceof List) { // assume all elements to be sprites
|
|
|
|
those = name.itemsArray();
|
|
|
|
} else {
|
|
|
|
those = this.getObjectsNamed(name, thisObj, stage); // clones
|
|
|
|
}
|
2020-04-28 17:27:42 +00:00
|
|
|
if (those.some(any => any.isVisible && thisObj.isTouching(any)
|
2018-12-28 18:29:15 +00:00
|
|
|
// check collision with any part, performance issue
|
|
|
|
// commented out for now
|
|
|
|
/*
|
|
|
|
return any.allParts().some(function (part) {
|
|
|
|
return part.isVisible && thisObj.isTouching(part);
|
|
|
|
})
|
|
|
|
*/
|
2020-04-28 17:27:42 +00:00
|
|
|
)) {
|
2013-08-12 11:05:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-28 17:27:42 +00:00
|
|
|
return thisObj.parts.some(any =>
|
|
|
|
this.objectTouchingObject(any, name)
|
2013-08-12 11:05:42 +00:00
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
2019-01-04 10:20:50 +00:00
|
|
|
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):
|
|
|
|
//
|
2021-11-08 11:35:57 +00:00
|
|
|
// 'hue' - hsl HUE on a scale of 0 - 100
|
|
|
|
// 'saturation' - hsl SATURATION on a scale of 0 - 100
|
|
|
|
// 'brightness' - hsl BRIGHTNESS on a scale of 0 - 100
|
2019-01-04 10:20:50 +00:00
|
|
|
// 'transparency' - rgba ALPHA on a reversed (!) scale of 0 - 100
|
2019-08-07 10:54:45 +00:00
|
|
|
// 'r-g-b-a' - list of rgba values on a scale of 0 - 255 each
|
2019-01-04 10:20:50 +00:00
|
|
|
// '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.
|
|
|
|
|
2020-11-21 11:53:35 +00:00
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (location instanceof List && !this.isCoordinate(location)) {
|
|
|
|
return location.map(each => this.reportAspect(aspect, each));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 10:20:50 +00:00
|
|
|
var choice = this.inputOption(aspect),
|
|
|
|
target = this.inputOption(location),
|
2019-01-08 16:28:52 +00:00
|
|
|
options = ['hue', 'saturation', 'brightness', 'transparency'],
|
2019-01-04 10:20:50 +00:00
|
|
|
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 {
|
2019-03-18 08:19:35 +00:00
|
|
|
clr = this.colorAtSprite(thisObj);
|
2019-01-04 10:20:50 +00:00
|
|
|
}
|
|
|
|
} 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 {
|
2019-03-18 08:19:35 +00:00
|
|
|
clr = this.colorAtSprite(thatObj);
|
2019-01-04 10:20:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-07 10:54:45 +00:00
|
|
|
if (choice === 'r-g-b-a') {
|
2019-08-07 09:08:32 +00:00
|
|
|
return new List([clr.r, clr.g, clr.b, Math.round(clr.a * 255)]);
|
|
|
|
}
|
2019-01-04 10:20:50 +00:00
|
|
|
if (idx < 0 || idx > 3) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (idx === 3) {
|
|
|
|
return (1 - clr.a) * 100;
|
|
|
|
}
|
2021-11-10 13:40:09 +00:00
|
|
|
return clr[SpriteMorph.prototype.penColorModel]()[idx] * 100;
|
2019-01-04 10:20:50 +00:00
|
|
|
};
|
|
|
|
|
2019-03-18 08:19:35 +00:00
|
|
|
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;
|
|
|
|
|
2020-07-01 16:59:32 +00:00
|
|
|
if (!stage) {return BLACK; }
|
2019-03-18 08:19:35 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-07-01 16:59:32 +00:00
|
|
|
return BLACK;
|
2019-03-18 08:19:35 +00:00
|
|
|
};
|
|
|
|
|
2019-03-17 18:28:43 +00:00
|
|
|
Process.prototype.colorBelowSprite = function (sprite) {
|
2019-01-04 10:20:50 +00:00
|
|
|
// private - helper function for aspect of location
|
|
|
|
// answer the color underneath the layer of the sprite's rotation center
|
2019-03-18 08:19:35 +00:00
|
|
|
// NOTE: layer-aware color sensing is currently unused
|
|
|
|
// in favor of top-layer detection because of user-observations
|
2019-01-04 10:20:50 +00:00
|
|
|
var point = sprite instanceof SpriteMorph ? sprite.rotationCenter()
|
|
|
|
: sprite.center(),
|
|
|
|
stage = sprite.parentThatIsA(StageMorph),
|
|
|
|
below = stage,
|
|
|
|
found = false,
|
|
|
|
child,
|
|
|
|
i;
|
|
|
|
|
2020-07-01 16:59:32 +00:00
|
|
|
if (!stage) {return BLACK; }
|
2019-01-04 10:20:50 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-07-01 16:59:32 +00:00
|
|
|
return BLACK;
|
2019-01-04 10:20:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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(
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.children.filter(morph =>
|
|
|
|
morph instanceof SpriteMorph &&
|
2019-01-04 10:20:50 +00:00
|
|
|
morph.isVisible &&
|
2020-04-28 17:27:42 +00:00
|
|
|
morph.bounds.containsPoint(point) &&
|
|
|
|
!morph.isTransparentAt(point)
|
|
|
|
)
|
2019-01-04 10:20:50 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2018-01-02 10:23:01 +00:00
|
|
|
Process.prototype.reportRelationTo = function (relation, name) {
|
2020-09-02 16:17:23 +00:00
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (name instanceof List && !this.isCoordinate(name)) {
|
|
|
|
return name.map(each => this.reportRelationTo(relation, each));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-02 10:23:01 +00:00
|
|
|
var rel = this.inputOption(relation);
|
|
|
|
if (rel === 'distance') {
|
|
|
|
return this.reportDistanceTo(name);
|
|
|
|
}
|
2020-12-04 10:04:43 +00:00
|
|
|
if (rel === 'ray length') {
|
|
|
|
return this.reportRayLengthTo(name);
|
|
|
|
}
|
2018-01-02 10:23:01 +00:00
|
|
|
if (rel === 'direction') {
|
|
|
|
return this.reportDirectionTo(name);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
2020-09-02 16:17:23 +00:00
|
|
|
Process.prototype.isCoordinate = function (data) {
|
|
|
|
return data instanceof List &&
|
|
|
|
(data.length() === 2) &&
|
|
|
|
this.reportTypeOf(data.at(1)) === 'number' &&
|
|
|
|
this.reportTypeOf(data.at(2)) === 'number';
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportDistanceTo = function (name) {
|
2014-02-03 16:11:46 +00:00
|
|
|
var thisObj = this.blockReceiver(),
|
2013-03-16 08:02:16 +00:00
|
|
|
thatObj,
|
|
|
|
stage,
|
|
|
|
rc,
|
|
|
|
point;
|
|
|
|
|
|
|
|
if (thisObj) {
|
|
|
|
rc = thisObj.rotationCenter();
|
|
|
|
point = rc;
|
|
|
|
if (this.inputOption(name) === 'mouse-pointer') {
|
|
|
|
point = thisObj.world().hand.position();
|
2018-05-02 08:08:41 +00:00
|
|
|
} else if (this.inputOption(name) === 'center') {
|
|
|
|
return new Point(thisObj.xPosition(), thisObj.yPosition())
|
2020-04-17 22:09:36 +00:00
|
|
|
.distanceTo(ZERO);
|
2017-08-29 08:06:45 +00:00
|
|
|
} else if (name instanceof List) {
|
|
|
|
return new Point(thisObj.xPosition(), thisObj.yPosition())
|
|
|
|
.distanceTo(new Point(name.at(1), name.at(2)));
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
thatObj = this.getOtherObject(name, thisObj, stage);
|
|
|
|
if (thatObj) {
|
|
|
|
point = thatObj.rotationCenter();
|
|
|
|
}
|
|
|
|
return rc.distanceTo(point) / stage.scale;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
2020-12-04 10:04:43 +00:00
|
|
|
Process.prototype.reportRayLengthTo = function (name) {
|
|
|
|
// raycasting edge detection - answer the distance between the asking
|
|
|
|
// sprite's rotation center to the target sprite's outer edge (the first
|
|
|
|
// opaque pixel) in the asking sprite's current direction
|
2020-12-02 17:54:27 +00:00
|
|
|
var thisObj = this.blockReceiver(),
|
|
|
|
thatObj,
|
|
|
|
stage,
|
|
|
|
rc,
|
|
|
|
targetBounds,
|
|
|
|
intersections = [],
|
|
|
|
dir,
|
|
|
|
a, b, x, y,
|
|
|
|
top, bottom, left, right,
|
2021-04-17 11:51:17 +00:00
|
|
|
circa, hSect, vSect,
|
2020-12-03 13:36:55 +00:00
|
|
|
point, hit,
|
|
|
|
temp,
|
2020-12-04 07:45:01 +00:00
|
|
|
width, imageData;
|
2020-12-03 08:02:06 +00:00
|
|
|
|
2021-04-17 11:51:17 +00:00
|
|
|
circa = (num) => Math.round(num * 10000000) / 10000000; // good enough
|
|
|
|
|
2020-12-03 08:02:06 +00:00
|
|
|
hSect = (yLevel) => {
|
|
|
|
var theta = radians(dir);
|
|
|
|
b = rc.y - yLevel;
|
|
|
|
a = b * Math.tan(theta);
|
|
|
|
x = rc.x + a;
|
|
|
|
if (
|
2021-04-17 11:51:17 +00:00
|
|
|
(circa(x) === circa(rc.x) &&
|
2020-12-03 08:02:06 +00:00
|
|
|
((dir === 180 && rc.y < yLevel) ||
|
|
|
|
dir === 0 && rc.y > yLevel)
|
|
|
|
) ||
|
|
|
|
(x > rc.x && dir >= 0 && dir < 180) ||
|
2021-04-17 11:51:17 +00:00
|
|
|
(circa(x) < circa(rc.x) &&
|
|
|
|
dir >= 180 && dir < 360)
|
2020-12-03 08:02:06 +00:00
|
|
|
) {
|
|
|
|
if (x >= left && x <= right) {
|
|
|
|
intersections.push(new Point(x, yLevel));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
vSect = (xLevel) => {
|
|
|
|
var theta = radians(360 - dir - 90);
|
|
|
|
b = rc.x - xLevel;
|
|
|
|
a = b * Math.tan(theta);
|
|
|
|
y = rc.y + a;
|
|
|
|
if (
|
2021-04-17 11:51:17 +00:00
|
|
|
(circa(y) === circa(rc.y) &&
|
2020-12-03 08:02:06 +00:00
|
|
|
((dir === 90 && rc.x < xLevel) ||
|
|
|
|
dir === 270 && rc.x > xLevel)
|
|
|
|
) ||
|
|
|
|
(y > rc.y && dir >= 90 && dir < 270) ||
|
|
|
|
(y < rc.y && (dir >= 270 || dir < 90))
|
|
|
|
) {
|
|
|
|
if (y >= top && y <= bottom) {
|
|
|
|
intersections.push(new Point(xLevel, y));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-12-02 17:54:27 +00:00
|
|
|
|
2020-12-03 13:36:55 +00:00
|
|
|
if (!thisObj) {return -1; }
|
|
|
|
rc = thisObj.rotationCenter();
|
|
|
|
point = rc;
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
thatObj = this.getOtherObject(name, thisObj, stage);
|
2020-12-04 10:04:43 +00:00
|
|
|
if (!(thatObj instanceof SpriteMorph)) {return -1; }
|
2020-12-03 13:36:55 +00:00
|
|
|
|
|
|
|
// determine intersections with the target's bounding box
|
|
|
|
dir = thisObj.heading;
|
|
|
|
targetBounds = thatObj.bounds;
|
|
|
|
top = targetBounds.top();
|
|
|
|
bottom = targetBounds.bottom();
|
|
|
|
left = targetBounds.left();
|
|
|
|
right = targetBounds.right();
|
|
|
|
|
|
|
|
// test if already inside the target
|
|
|
|
if (targetBounds.containsPoint(rc)) {
|
|
|
|
intersections.push(rc);
|
|
|
|
hSect(top);
|
|
|
|
hSect(bottom);
|
|
|
|
vSect(left);
|
|
|
|
vSect(right);
|
|
|
|
if (intersections.length < 2) {
|
|
|
|
return -1;
|
2020-12-02 17:54:27 +00:00
|
|
|
}
|
2020-12-03 13:36:55 +00:00
|
|
|
} else {
|
|
|
|
hSect(top);
|
|
|
|
hSect(bottom);
|
|
|
|
vSect(left);
|
|
|
|
vSect(right);
|
|
|
|
if (intersections.length < 2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
// sort
|
|
|
|
if (dir !== 90) {
|
|
|
|
if (Math.sign(rc.x - intersections[0].x) !==
|
|
|
|
Math.sign(intersections[0].x - intersections[1].x) ||
|
|
|
|
Math.sign(rc.y - intersections[0].y) !==
|
|
|
|
Math.sign(intersections[0].y - intersections[1].y)
|
|
|
|
) {
|
|
|
|
temp = intersections[0];
|
|
|
|
intersections[0] = intersections[1];
|
|
|
|
intersections[1] = temp;
|
2020-12-02 17:54:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-03 13:36:55 +00:00
|
|
|
|
|
|
|
// for debugging:
|
|
|
|
/*
|
2020-12-02 17:54:27 +00:00
|
|
|
return new List(intersections)
|
|
|
|
.map(point => thisObj.snapPoint(point))
|
|
|
|
.map(point => new List([point.x, point.y]));
|
2020-12-03 13:36:55 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
// convert intersections to local bitmap coordinates of the target
|
2020-12-03 16:01:12 +00:00
|
|
|
intersections = intersections.map(point =>
|
2020-12-04 07:45:01 +00:00
|
|
|
point.subtract(targetBounds.origin).floorDivideBy(stage.scale)
|
2020-12-03 16:01:12 +00:00
|
|
|
);
|
2020-12-03 13:36:55 +00:00
|
|
|
|
|
|
|
// get image data
|
2020-12-04 07:45:01 +00:00
|
|
|
width = Math.floor(targetBounds.width() / stage.scale);
|
|
|
|
imageData = thatObj.getImageData();
|
2020-12-03 13:36:55 +00:00
|
|
|
|
|
|
|
// scan the ray along the coordinates of a Bresenham line
|
|
|
|
// for the first opaque pixel
|
|
|
|
function alphaAt(imageData, width, x, y) {
|
2020-12-03 16:29:16 +00:00
|
|
|
var idx = y * width + x;
|
|
|
|
return imageData[idx] && 0x000000FF; // alpha
|
2020-12-03 13:36:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function isOpaque(x, y) {
|
|
|
|
return alphaAt(imageData, width, x, y) > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function scan(testFunc, x0, y0, x1, y1) {
|
|
|
|
// Bresenham's algorithm
|
|
|
|
var dx = Math.abs(x1 - x0),
|
|
|
|
sx = x0 < x1 ? 1 : -1,
|
|
|
|
dy = -Math.abs(y1 - y0),
|
|
|
|
sy = y0 < y1 ? 1 : -1,
|
|
|
|
err = dx + dy,
|
|
|
|
e2;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
if (testFunc(x0, y0)) {
|
2020-12-04 07:45:01 +00:00
|
|
|
return new Point(x0 * stage.scale, y0 * stage.scale);
|
2020-12-03 13:36:55 +00:00
|
|
|
}
|
|
|
|
if (x0 === x1 && y0 === y1) {
|
|
|
|
return -1; // not found
|
|
|
|
}
|
|
|
|
e2 = 2 * err;
|
|
|
|
if (e2 > dy) {
|
|
|
|
err += dy;
|
|
|
|
x0 += sx;
|
|
|
|
}
|
|
|
|
if (e2 < dx) {
|
|
|
|
err += dx;
|
|
|
|
y0 += sy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hit = scan(
|
|
|
|
isOpaque,
|
|
|
|
intersections[0].x,
|
|
|
|
intersections[0].y,
|
|
|
|
intersections[1].x,
|
|
|
|
intersections[1].y
|
|
|
|
);
|
|
|
|
if (hit === -1) {return hit; }
|
|
|
|
return rc.distanceTo(hit.add(targetBounds.origin)) / stage.scale;
|
2020-12-02 17:54:27 +00:00
|
|
|
};
|
|
|
|
|
2018-01-02 10:23:01 +00:00
|
|
|
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());
|
|
|
|
}
|
2018-05-02 08:08:41 +00:00
|
|
|
if (this.inputOption(name) === 'center') {
|
|
|
|
return thisObj.angleToXY(0, 0);
|
|
|
|
}
|
2018-01-02 10:23:01 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2021-12-05 12:30:46 +00:00
|
|
|
Process.prototype.reportBlockAttribute = function (attribute, block) {
|
|
|
|
// hyper-dyadic
|
2021-12-09 12:27:01 +00:00
|
|
|
// note: attributes in the left slot
|
2021-12-05 12:30:46 +00:00
|
|
|
// can only be queried via the dropdown menu and are, therefore, not
|
|
|
|
// reachable as dyadic inputs
|
|
|
|
return this.hyperDyadic(
|
|
|
|
(att, obj) => this.reportBasicBlockAttribute(att, obj),
|
|
|
|
attribute,
|
|
|
|
block
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicBlockAttribute = function (attribute, block) {
|
|
|
|
var choice = this.inputOption(attribute),
|
|
|
|
expr;
|
|
|
|
this.assertType(block, ['command', 'reporter', 'predicate']);
|
|
|
|
expr = block.expression;
|
|
|
|
switch (choice) {
|
|
|
|
case 'definition':
|
|
|
|
if (expr.isCustomBlock) {
|
|
|
|
if (expr.isGlobal) {
|
2021-12-09 11:43:19 +00:00
|
|
|
return expr.definition.body || new Context();
|
2021-12-05 12:30:46 +00:00
|
|
|
}
|
|
|
|
return this.blockReceiver().getMethod(expr.semanticSpec).body ||
|
|
|
|
new Context();
|
|
|
|
}
|
|
|
|
return new Context();
|
2021-12-06 10:53:46 +00:00
|
|
|
case 'custom?':
|
2021-12-06 11:11:03 +00:00
|
|
|
return expr ? !!expr.isCustomBlock : false;
|
2021-12-06 10:53:46 +00:00
|
|
|
case 'global?':
|
2021-12-06 11:11:03 +00:00
|
|
|
return (expr && expr.isCustomBlock) ? !!expr.isGlobal : true;
|
2021-12-05 12:30:46 +00:00
|
|
|
}
|
|
|
|
return '';
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportAttributeOf = function (attribute, name) {
|
2020-11-21 09:50:45 +00:00
|
|
|
// hyper-dyadic
|
|
|
|
// note: specifying strings in the left input only accesses
|
|
|
|
// sprite-local variables. Attributes such as "width", "direction" etc.
|
|
|
|
// can only be queried via the dropdown menu and are, therefore, not
|
|
|
|
// reachable as dyadic inputs
|
|
|
|
return this.hyperDyadic(
|
|
|
|
(att, obj) => this.reportBasicAttributeOf(att, obj),
|
|
|
|
attribute,
|
|
|
|
name
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportBasicAttributeOf = function (attribute, name) {
|
2014-02-03 16:11:46 +00:00
|
|
|
var thisObj = this.blockReceiver(),
|
2013-03-16 08:02:16 +00:00
|
|
|
thatObj,
|
2018-01-21 17:13:33 +00:00
|
|
|
stage;
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
if (thisObj) {
|
2016-05-02 10:52:58 +00:00
|
|
|
this.assertAlive(thisObj);
|
2013-03-16 08:02:16 +00:00
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
if (stage.name === name) {
|
|
|
|
thatObj = stage;
|
|
|
|
} else {
|
|
|
|
thatObj = this.getOtherObject(name, thisObj, stage);
|
|
|
|
}
|
|
|
|
if (thatObj) {
|
2016-05-02 10:52:58 +00:00
|
|
|
this.assertAlive(thatObj);
|
2018-01-18 13:23:47 +00:00
|
|
|
if (attribute instanceof BlockMorph) { // a "wish"
|
2018-01-21 17:13:33 +00:00
|
|
|
return this.reportContextFor(
|
|
|
|
this.reify(
|
|
|
|
thatObj.getMethod(attribute.semanticSpec)
|
|
|
|
.blockInstance(),
|
|
|
|
new List()
|
|
|
|
),
|
|
|
|
thatObj
|
2018-01-18 13:23:47 +00:00
|
|
|
);
|
|
|
|
}
|
2014-01-08 11:18:04 +00:00
|
|
|
if (attribute instanceof Context) {
|
|
|
|
return this.reportContextFor(attribute, thatObj);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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() : '';
|
2019-04-04 13:32:36 +00:00
|
|
|
case 'volume':
|
|
|
|
return thatObj.getVolume();
|
|
|
|
case 'balance':
|
|
|
|
return thatObj.getPan();
|
2019-04-04 13:16:13 +00:00
|
|
|
case 'width':
|
2019-05-02 10:51:22 +00:00
|
|
|
if (thatObj instanceof StageMorph) {
|
|
|
|
return thatObj.dimensions.x;
|
|
|
|
}
|
|
|
|
this.assertType(thatObj, 'sprite');
|
|
|
|
return thatObj.width() / stage.scale;
|
2019-04-04 13:16:13 +00:00
|
|
|
case 'height':
|
2019-05-02 10:51:22 +00:00
|
|
|
if (thatObj instanceof StageMorph) {
|
|
|
|
return thatObj.dimensions.y;
|
|
|
|
}
|
|
|
|
this.assertType(thatObj, 'sprite');
|
|
|
|
return thatObj.height() / stage.scale;
|
2019-10-25 13:01:28 +00:00
|
|
|
case 'left':
|
|
|
|
return thatObj.xLeft();
|
|
|
|
case 'right':
|
|
|
|
return thatObj.xRight();
|
|
|
|
case 'top':
|
|
|
|
return thatObj.yTop();
|
|
|
|
case 'bottom':
|
|
|
|
return thatObj.yBottom();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
};
|
|
|
|
|
2016-05-02 10:52:58 +00:00
|
|
|
Process.prototype.reportGet = function (query) {
|
2019-03-31 11:20:39 +00:00
|
|
|
// answer a reference to a first-class member
|
2016-05-02 10:52:58 +00:00
|
|
|
// or a list of first-class members
|
|
|
|
var thisObj = this.blockReceiver(),
|
|
|
|
stage,
|
|
|
|
objName;
|
|
|
|
|
|
|
|
if (thisObj) {
|
|
|
|
switch (this.inputOption(query)) {
|
|
|
|
case 'self' :
|
|
|
|
return thisObj;
|
|
|
|
case 'other sprites':
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
return new List(
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.children.filter(each =>
|
|
|
|
each instanceof SpriteMorph &&
|
|
|
|
each !== thisObj
|
|
|
|
)
|
2016-05-02 10:52:58 +00:00
|
|
|
);
|
2019-10-14 11:21:44 +00:00
|
|
|
case 'parts': // shallow copy to disable side-effects
|
2020-04-28 17:27:42 +00:00
|
|
|
return new List((thisObj.parts || []).map(each => each));
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'anchor':
|
|
|
|
return thisObj.anchor || '';
|
|
|
|
case 'parent':
|
|
|
|
return thisObj.exemplar || '';
|
|
|
|
case 'children':
|
|
|
|
return new List(thisObj.specimens ? thisObj.specimens() : []);
|
2017-08-29 10:21:37 +00:00
|
|
|
case 'temporary?':
|
|
|
|
return thisObj.isTemporary || false;
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'clones':
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
objName = thisObj.name || thisObj.cloneOriginName;
|
|
|
|
return new List(
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.children.filter(each =>
|
|
|
|
each.isTemporary &&
|
2016-05-02 10:52:58 +00:00
|
|
|
(each !== thisObj) &&
|
2020-04-28 17:27:42 +00:00
|
|
|
(each.cloneOriginName === objName)
|
|
|
|
)
|
2016-05-02 10:52:58 +00:00
|
|
|
);
|
|
|
|
case 'other clones':
|
2017-07-04 11:51:22 +00:00
|
|
|
return thisObj.isTemporary ?
|
|
|
|
this.reportGet(['clones']) : new List();
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'neighbors':
|
2020-11-20 13:58:32 +00:00
|
|
|
// old rectangular, bounding-box-based algorithm
|
|
|
|
// deprecated in favor of a circular perimeter based newer one
|
|
|
|
/*
|
2016-05-02 10:52:58 +00:00
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
|
|
|
neighborhood = thisObj.bounds.expandBy(new Point(
|
|
|
|
thisObj.width(),
|
|
|
|
thisObj.height()
|
|
|
|
));
|
|
|
|
return new List(
|
2020-04-28 17:27:42 +00:00
|
|
|
stage.children.filter(each =>
|
|
|
|
each instanceof SpriteMorph &&
|
2020-11-20 11:19:58 +00:00
|
|
|
each.isVisible &&
|
2016-05-02 10:52:58 +00:00
|
|
|
(each !== thisObj) &&
|
2020-11-20 11:19:58 +00:00
|
|
|
each.bounds.intersects(neighborhood)
|
2020-04-28 17:27:42 +00:00
|
|
|
)
|
2016-05-02 10:52:58 +00:00
|
|
|
);
|
2020-11-20 13:58:32 +00:00
|
|
|
*/
|
|
|
|
return thisObj.neighbors();
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'dangling?':
|
|
|
|
return !thisObj.rotatesWithAnchor;
|
2019-01-28 17:01:32 +00:00
|
|
|
case 'draggable?':
|
2019-01-28 17:25:25 +00:00
|
|
|
return thisObj.isDraggable;
|
|
|
|
case 'rotation style':
|
|
|
|
return thisObj.rotationStyle || 0;
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'rotation x':
|
|
|
|
return thisObj.xPosition();
|
|
|
|
case 'rotation y':
|
|
|
|
return thisObj.yPosition();
|
|
|
|
case 'center x':
|
|
|
|
return thisObj.xCenter();
|
|
|
|
case 'center y':
|
|
|
|
return thisObj.yCenter();
|
2019-10-17 16:07:59 +00:00
|
|
|
case 'left':
|
|
|
|
return thisObj.xLeft();
|
|
|
|
case 'right':
|
|
|
|
return thisObj.xRight();
|
|
|
|
case 'top':
|
|
|
|
return thisObj.yTop();
|
|
|
|
case 'bottom':
|
|
|
|
return thisObj.yBottom();
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'name':
|
|
|
|
return thisObj.name;
|
|
|
|
case 'stage':
|
|
|
|
return thisObj.parentThatIsA(StageMorph);
|
2021-12-03 12:08:16 +00:00
|
|
|
case 'scripts':
|
|
|
|
return new List(
|
|
|
|
thisObj.scripts.children.filter(
|
|
|
|
each => each instanceof BlockMorph
|
|
|
|
).map(
|
|
|
|
each => each.fullCopy().reify()
|
|
|
|
)
|
|
|
|
);
|
2021-12-06 10:25:42 +00:00
|
|
|
case 'blocks': // palette unoordered without inherited methods
|
2021-12-05 12:30:46 +00:00
|
|
|
return new List(
|
|
|
|
thisObj.parentThatIsA(StageMorph).globalBlocks.concat(
|
|
|
|
thisObj.allBlocks(true)
|
2021-12-06 10:25:42 +00:00
|
|
|
).filter(
|
|
|
|
def => !def.isHelper
|
2021-12-05 12:30:46 +00:00
|
|
|
).map(
|
|
|
|
def => def.blockInstance().reify()
|
2021-12-06 10:25:42 +00:00
|
|
|
).concat(
|
2021-12-07 13:02:24 +00:00
|
|
|
SpriteMorph.prototype.categories.reduce(
|
2021-12-06 10:25:42 +00:00
|
|
|
(blocks, category) => blocks.concat(
|
|
|
|
thisObj.getPrimitiveTemplates(
|
|
|
|
category
|
|
|
|
).filter(
|
|
|
|
each => each instanceof BlockMorph &&
|
|
|
|
!(each instanceof HatBlockMorph)
|
|
|
|
).map(block => {
|
|
|
|
let instance = block.fullCopy();
|
|
|
|
instance.isTemplate = false;
|
|
|
|
return instance.reify();
|
|
|
|
})
|
|
|
|
),
|
|
|
|
[]
|
|
|
|
)
|
2021-12-05 12:30:46 +00:00
|
|
|
)
|
|
|
|
);
|
2019-04-30 06:37:28 +00:00
|
|
|
case 'costume':
|
|
|
|
return thisObj.costume;
|
2017-05-12 13:21:12 +00:00
|
|
|
case 'costumes':
|
|
|
|
return thisObj.reportCostumes();
|
2017-06-24 16:31:58 +00:00
|
|
|
case 'sounds':
|
|
|
|
return thisObj.sounds;
|
2019-05-02 10:32:04 +00:00
|
|
|
case 'width':
|
|
|
|
if (thisObj instanceof StageMorph) {
|
|
|
|
return thisObj.dimensions.x;
|
|
|
|
}
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
2019-05-02 10:58:35 +00:00
|
|
|
return stage ? thisObj.width() / stage.scale : 0;
|
2019-05-02 10:32:04 +00:00
|
|
|
case 'height':
|
|
|
|
if (thisObj instanceof StageMorph) {
|
|
|
|
return thisObj.dimensions.y;
|
|
|
|
}
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph);
|
2019-05-02 10:58:35 +00:00
|
|
|
return stage ? thisObj.height() / stage.scale : 0;
|
2016-05-02 10:52:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
};
|
|
|
|
|
2019-01-28 09:10:10 +00:00
|
|
|
Process.prototype.reportObject = function (name) {
|
2020-12-09 10:56:43 +00:00
|
|
|
// hyper-monadic
|
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (name instanceof List) {
|
|
|
|
return name.map(each => this.reportObject(each));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-28 09:10:10 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-02 10:52:58 +00:00
|
|
|
Process.prototype.doSet = function (attribute, value) {
|
2021-12-09 12:27:01 +00:00
|
|
|
// manipulate sprites' attributes
|
2019-01-28 17:01:32 +00:00
|
|
|
var name, rcvr, ide;
|
2016-05-02 10:52:58 +00:00
|
|
|
rcvr = this.blockReceiver();
|
|
|
|
this.assertAlive(rcvr);
|
2018-05-02 11:34:49 +00:00
|
|
|
if (!(attribute instanceof Context || attribute instanceof Array) ||
|
|
|
|
(attribute instanceof Context &&
|
|
|
|
attribute.expression.selector !== 'reportGet')) {
|
2016-05-02 10:52:58 +00:00
|
|
|
throw new Error(localize('unsupported attribute'));
|
|
|
|
}
|
2018-05-02 11:34:49 +00:00
|
|
|
name = attribute instanceof Context ?
|
|
|
|
attribute.expression.inputs()[0].evaluate()
|
|
|
|
: attribute;
|
2016-05-02 10:52:58 +00:00
|
|
|
if (name instanceof Array) {
|
|
|
|
name = name[0];
|
|
|
|
}
|
|
|
|
switch (name) {
|
|
|
|
case 'anchor':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my anchor':
|
2016-05-02 10:52:58 +00:00
|
|
|
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':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my parent':
|
2016-05-02 10:52:58 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
value = value instanceof SpriteMorph ? value : null;
|
2019-12-19 08:00:54 +00:00
|
|
|
rcvr.setExemplar(value, true); // throw an error in case of circularity
|
2016-05-02 10:52:58 +00:00
|
|
|
break;
|
2017-08-29 10:21:37 +00:00
|
|
|
case 'temporary?':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my temporary?':
|
2017-08-29 10:21:37 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
this.assertType(value, 'Boolean');
|
2019-08-07 10:27:00 +00:00
|
|
|
if (value) {
|
|
|
|
rcvr.release();
|
|
|
|
} else {
|
|
|
|
rcvr.perpetuate();
|
2017-08-29 10:21:37 +00:00
|
|
|
}
|
|
|
|
break;
|
2019-05-29 11:22:07 +00:00
|
|
|
case 'name':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my name':
|
2019-05-29 11:22:07 +00:00
|
|
|
this.assertType(rcvr, ['sprite', 'stage']);
|
2019-05-31 09:27:55 +00:00
|
|
|
this.assertType(value, ['text', 'number']);
|
2019-05-29 11:22:07 +00:00
|
|
|
ide = rcvr.parentThatIsA(IDE_Morph);
|
|
|
|
if (ide) {
|
|
|
|
rcvr.setName(
|
2019-05-31 09:27:55 +00:00
|
|
|
ide.newSpriteName(value.toString(), rcvr)
|
|
|
|
);
|
|
|
|
ide.spriteBar.nameField.setContents(
|
|
|
|
ide.currentSprite.name.toString()
|
2019-05-29 11:22:07 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'dangling?':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my dangling?':
|
2016-05-02 10:52:58 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
this.assertType(value, 'Boolean');
|
|
|
|
rcvr.rotatesWithAnchor = !value;
|
|
|
|
rcvr.version = Date.now();
|
|
|
|
break;
|
2019-01-28 17:01:32 +00:00
|
|
|
case 'draggable?':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my draggable?':
|
2019-01-28 17:01:32 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
this.assertType(value, 'Boolean');
|
|
|
|
rcvr.isDraggable = value;
|
|
|
|
// update padlock symbol in the IDE:
|
|
|
|
ide = rcvr.parentThatIsA(IDE_Morph);
|
|
|
|
if (ide) {
|
2020-04-28 17:27:42 +00:00
|
|
|
ide.spriteBar.children.forEach(each => {
|
2019-01-28 17:01:32 +00:00
|
|
|
if (each.refresh) {
|
|
|
|
each.refresh();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
rcvr.version = Date.now();
|
|
|
|
break;
|
2019-01-28 17:25:25 +00:00
|
|
|
case 'rotation style':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my rotation style':
|
2019-01-28 17:25:25 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
this.assertType(+value, 'number');
|
|
|
|
if (!contains([0, 1, 2], +value)) {
|
|
|
|
return; // maybe throw an error msg
|
|
|
|
}
|
|
|
|
rcvr.changed();
|
2020-04-17 13:42:43 +00:00
|
|
|
rcvr.rotationStyle = +value;
|
|
|
|
rcvr.fixLayout();
|
|
|
|
rcvr.rerender();
|
2019-01-28 17:25:25 +00:00
|
|
|
// update padlock symbol in the IDE:
|
|
|
|
ide = rcvr.parentThatIsA(IDE_Morph);
|
|
|
|
if (ide) {
|
2020-04-28 17:27:42 +00:00
|
|
|
ide.spriteBar.children.forEach(each => {
|
2019-01-28 17:25:25 +00:00
|
|
|
if (each.refresh) {
|
|
|
|
each.refresh();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
rcvr.version = Date.now();
|
|
|
|
break;
|
2016-05-02 10:52:58 +00:00
|
|
|
case 'rotation x':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my rotation x':
|
2016-05-02 10:52:58 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
this.assertType(value, 'number');
|
|
|
|
rcvr.setRotationX(value);
|
|
|
|
break;
|
|
|
|
case 'rotation y':
|
2020-10-05 07:24:53 +00:00
|
|
|
case 'my rotation y':
|
2016-05-02 10:52:58 +00:00
|
|
|
this.assertType(rcvr, 'sprite');
|
|
|
|
this.assertType(value, 'number');
|
|
|
|
rcvr.setRotationY(value);
|
|
|
|
break;
|
2019-03-28 16:20:28 +00:00
|
|
|
case 'microphone modifier':
|
|
|
|
this.setMicrophoneModifier(value);
|
|
|
|
break;
|
2016-05-02 10:52:58 +00:00
|
|
|
default:
|
|
|
|
throw new Error(
|
|
|
|
'"' + localize(name) + '" ' + localize('is read-only')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-01-08 11:18:04 +00:00
|
|
|
Process.prototype.reportContextFor = function (context, otherObj) {
|
|
|
|
// Private - return a copy of the context
|
|
|
|
// and bind it to another receiver
|
2020-08-01 07:35:49 +00:00
|
|
|
var result = copy(context),
|
|
|
|
receiverVars,
|
|
|
|
rootVars;
|
|
|
|
|
2014-01-08 11:18:04 +00:00
|
|
|
result.receiver = otherObj;
|
|
|
|
if (result.outerContext) {
|
|
|
|
result.outerContext = copy(result.outerContext);
|
2015-09-15 16:18:40 +00:00
|
|
|
result.outerContext.variables = copy(result.outerContext.variables);
|
2014-01-08 11:18:04 +00:00
|
|
|
result.outerContext.receiver = otherObj;
|
2020-07-31 16:32:30 +00:00
|
|
|
if (result.outerContext.variables.parentFrame) {
|
2020-08-01 07:35:49 +00:00
|
|
|
rootVars = result.outerContext.variables.parentFrame;
|
|
|
|
receiverVars = copy(otherObj.variables);
|
|
|
|
receiverVars.parentFrame = rootVars;
|
|
|
|
result.outerContext.variables.parentFrame = receiverVars;
|
2020-07-31 16:32:30 +00:00
|
|
|
} else {
|
|
|
|
result.outerContext.variables.parentFrame = otherObj.variables;
|
|
|
|
}
|
2014-01-08 11:18:04 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
Process.prototype.reportMouseX = function () {
|
2020-12-02 13:55:24 +00:00
|
|
|
var world;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (this.homeContext.receiver) {
|
2020-12-02 13:55:24 +00:00
|
|
|
world = this.homeContext.receiver.world();
|
|
|
|
if (world) {
|
|
|
|
return this.homeContext.receiver.snapPoint(world.hand.position()).x;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportMouseY = function () {
|
2020-12-02 13:55:24 +00:00
|
|
|
var world;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (this.homeContext.receiver) {
|
2020-12-02 13:55:24 +00:00
|
|
|
world = this.homeContext.receiver.world();
|
|
|
|
if (world) {
|
|
|
|
return this.homeContext.receiver.snapPoint(world.hand.position()).y;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
2021-03-19 10:46:00 +00:00
|
|
|
// hyper-monadic
|
2013-03-16 08:02:16 +00:00
|
|
|
var stage;
|
|
|
|
if (this.homeContext.receiver) {
|
|
|
|
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
2015-12-22 07:00:52 +00:00
|
|
|
if (this.inputOption(keyString) === 'any key') {
|
2015-10-02 10:39:41 +00:00
|
|
|
return Object.keys(stage.keysPressed).length > 0;
|
|
|
|
}
|
2021-03-19 10:46:00 +00:00
|
|
|
if (keyString instanceof List && this.enableHyperOps) {
|
|
|
|
return keyString.map(
|
|
|
|
each => stage.keysPressed[each] !== undefined
|
|
|
|
);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2013-12-28 02:49:56 +00:00
|
|
|
// Process Dates and times in Snap
|
|
|
|
Process.prototype.reportDate = function (datefn) {
|
2015-06-25 13:30:58 +00:00
|
|
|
var currDate, func, result,
|
|
|
|
inputFn = this.inputOption(datefn),
|
2015-06-16 01:49:24 +00:00
|
|
|
// 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'
|
|
|
|
};
|
2014-02-07 23:16:14 +00:00
|
|
|
|
|
|
|
if (!dateMap[inputFn]) { return ''; }
|
2015-06-25 13:30:58 +00:00
|
|
|
currDate = new Date();
|
|
|
|
func = dateMap[inputFn];
|
|
|
|
result = currDate[func]();
|
2015-06-16 01:49:24 +00:00
|
|
|
|
2014-01-26 12:25:36 +00:00
|
|
|
// Show months as 1-12 and days as 1-7
|
2014-02-07 23:16:14 +00:00
|
|
|
if (inputFn === 'month' || inputFn === 'day of week') {
|
2014-01-26 12:25:36 +00:00
|
|
|
result += 1;
|
|
|
|
}
|
|
|
|
return result;
|
2014-02-10 18:51:22 +00:00
|
|
|
};
|
2013-12-28 02:49:56 +00:00
|
|
|
|
2019-05-06 22:17:03 +00:00
|
|
|
// Process video motion detection primitives
|
|
|
|
|
2019-05-02 21:58:38 +00:00
|
|
|
Process.prototype.doSetVideoTransparency = function(factor) {
|
|
|
|
var stage;
|
|
|
|
if (this.homeContext.receiver) {
|
|
|
|
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
|
|
|
if (stage) {
|
2019-05-15 10:41:09 +00:00
|
|
|
stage.projectionTransparency = Math.max(0, Math.min(100, factor));
|
2019-05-02 21:58:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-05-08 13:01:47 +00:00
|
|
|
Process.prototype.reportVideo = function(attribute, name) {
|
2020-12-01 12:14:51 +00:00
|
|
|
// hyper-monadic
|
|
|
|
var thisObj = this.blockReceiver(),
|
|
|
|
stage = thisObj.parentThatIsA(StageMorph),
|
|
|
|
thatObj;
|
|
|
|
|
2019-05-15 11:22:05 +00:00
|
|
|
if (!stage.projectionSource || !stage.projectionSource.stream) {
|
2019-05-08 13:25:29 +00:00
|
|
|
// wait until video is turned on
|
|
|
|
if (!this.context.accumulator) {
|
|
|
|
this.context.accumulator = true; // started video
|
|
|
|
stage.startVideo();
|
|
|
|
}
|
|
|
|
this.pushContext('doYield');
|
|
|
|
this.pushContext();
|
|
|
|
return;
|
2019-05-02 21:58:38 +00:00
|
|
|
}
|
2019-05-08 08:40:46 +00:00
|
|
|
|
2020-12-01 12:14:51 +00:00
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (name instanceof List) {
|
|
|
|
return name.map(each => this.reportVideo(attribute, each));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
thatObj = this.getOtherObject(name, thisObj, stage);
|
2019-05-08 08:40:46 +00:00
|
|
|
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;
|
2019-05-08 17:56:24 +00:00
|
|
|
case 'snap':
|
|
|
|
if (thatObj instanceof SpriteMorph) {
|
2019-05-15 11:25:52 +00:00
|
|
|
return thatObj.projectionSnap();
|
2019-05-08 17:56:24 +00:00
|
|
|
}
|
2019-05-15 11:25:52 +00:00
|
|
|
return stage.projectionSnap();
|
2019-05-08 08:40:46 +00:00
|
|
|
}
|
|
|
|
return -1;
|
2019-05-02 21:58:38 +00:00
|
|
|
};
|
|
|
|
|
2019-11-19 06:56:45 +00:00
|
|
|
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();
|
|
|
|
};
|
|
|
|
|
2013-06-18 16:43:15 +00:00
|
|
|
// Process code mapping
|
|
|
|
|
|
|
|
/*
|
|
|
|
for generating textual source code using
|
|
|
|
blocks - not needed to run or debug Snap
|
|
|
|
*/
|
|
|
|
|
2013-07-09 19:10:16 +00:00
|
|
|
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'
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2013-07-04 13:31:05 +00:00
|
|
|
Process.prototype.doMapHeader = function (aContext, aString) {
|
|
|
|
if (aContext instanceof Context) {
|
|
|
|
if (aContext.expression instanceof SyntaxElementMorph) {
|
|
|
|
return aContext.expression.mapHeader(aString || '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-06-18 16:43:15 +00:00
|
|
|
Process.prototype.doMapCode = function (aContext, aString) {
|
|
|
|
if (aContext instanceof Context) {
|
|
|
|
if (aContext.expression instanceof SyntaxElementMorph) {
|
|
|
|
return aContext.expression.mapCode(aString || '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-04-10 09:48:55 +00:00
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-06-18 16:43:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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 '';
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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
|
2019-04-02 05:19:44 +00:00
|
|
|
var rcvr = this.blockReceiver();
|
2013-03-16 08:02:16 +00:00
|
|
|
if (!this.context.startTime) {
|
2019-04-04 16:40:13 +00:00
|
|
|
rcvr.setVolume(rcvr.getVolume()); // b/c Chrome needs lazy init
|
|
|
|
rcvr.setPan(rcvr.getPan()); // b/c Chrome needs lazy initialization
|
2013-03-16 08:02:16 +00:00
|
|
|
this.context.startTime = Date.now();
|
|
|
|
this.context.activeNote = new Note(pitch);
|
2019-04-01 15:43:45 +00:00
|
|
|
this.context.activeNote.play(
|
|
|
|
this.instrument,
|
2019-04-02 10:50:43 +00:00
|
|
|
rcvr.getGainNode(),
|
2019-04-03 10:37:35 +00:00
|
|
|
rcvr.getPannerNode()
|
2019-04-01 15:43:45 +00:00
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
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();
|
|
|
|
};
|
|
|
|
|
2019-03-12 07:30:45 +00:00
|
|
|
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();
|
|
|
|
};
|
|
|
|
|
2017-07-31 18:09:46 +00:00
|
|
|
Process.prototype.doSetInstrument = function (num) {
|
2017-08-02 12:33:12 +00:00
|
|
|
this.instrument = +num;
|
2017-08-02 12:16:36 +00:00
|
|
|
this.receiver.instrument = +num;
|
2019-04-04 05:08:40 +00:00
|
|
|
if (this.receiver.freqPlayer) {
|
|
|
|
this.receiver.freqPlayer.setInstrument(+num);
|
|
|
|
}
|
2017-07-31 18:09:46 +00:00
|
|
|
};
|
|
|
|
|
2019-04-09 08:04:14 +00:00
|
|
|
// Process image processing primitives
|
|
|
|
|
|
|
|
Process.prototype.reportGetImageAttribute = function (choice, name) {
|
2021-01-25 13:34:27 +00:00
|
|
|
if (this.enableHyperOps) {
|
|
|
|
if (name instanceof List) {
|
|
|
|
return name.map(each => this.reportGetImageAttribute(choice, each));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-09 13:42:34 +00:00
|
|
|
var cst = this.costumeNamed(name) || new Costume(),
|
2019-04-09 08:04:14 +00:00
|
|
|
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:
|
2019-04-30 05:24:49 +00:00
|
|
|
return cst;
|
2019-04-09 08:04:14 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-09 12:19:18 +00:00
|
|
|
Process.prototype.reportNewCostumeStretched = function (name, xP, yP) {
|
2019-04-09 13:42:34 +00:00
|
|
|
var cst;
|
2019-04-09 13:08:32 +00:00
|
|
|
if (name instanceof List) {
|
|
|
|
return this.reportNewCostume(name, xP, yP);
|
|
|
|
}
|
2019-04-09 13:42:34 +00:00
|
|
|
cst = this.costumeNamed(name);
|
2019-04-09 12:19:18 +00:00
|
|
|
if (!cst) {
|
|
|
|
return new Costume();
|
|
|
|
}
|
2019-10-18 09:48:11 +00:00
|
|
|
if (!isFinite(+xP * +yP) || isNaN(+xP * +yP)) {
|
|
|
|
throw new Error(
|
|
|
|
'expecting a finite number\nbut getting Infinity or NaN'
|
|
|
|
);
|
|
|
|
}
|
2019-04-09 12:19:18 +00:00
|
|
|
return cst.stretched(
|
|
|
|
Math.round(cst.width() * +xP / 100),
|
|
|
|
Math.round(cst.height() * +yP / 100)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-04-09 13:42:34 +00:00
|
|
|
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(),
|
2020-04-28 17:27:42 +00:00
|
|
|
c => c.name === name.toString()
|
2019-04-09 13:42:34 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-10-11 06:45:45 +00:00
|
|
|
Process.prototype.reportNewCostume = function (pixels, width, height, name) {
|
2019-10-18 11:26:44 +00:00
|
|
|
var rcvr, stage, canvas, ctx, src, dta, i, k, px;
|
2019-10-18 09:48:11 +00:00
|
|
|
|
|
|
|
this.assertType(pixels, 'list');
|
|
|
|
if (this.inputOption(width) === 'current') {
|
2019-10-18 11:26:44 +00:00
|
|
|
rcvr = this.blockReceiver();
|
|
|
|
stage = rcvr.parentThatIsA(StageMorph);
|
2019-10-18 09:48:11 +00:00
|
|
|
width = rcvr.costume ? rcvr.costume.width() : stage.dimensions.x;
|
|
|
|
}
|
|
|
|
if (this.inputOption(height) === 'current') {
|
2019-10-18 11:26:44 +00:00
|
|
|
rcvr = rcvr || this.blockReceiver();
|
|
|
|
stage = stage || rcvr.parentThatIsA(StageMorph);
|
2019-10-18 09:48:11 +00:00
|
|
|
height = rcvr.costume ? rcvr.costume.height() : stage.dimensions.y;
|
|
|
|
}
|
2019-04-09 13:08:32 +00:00
|
|
|
width = Math.abs(Math.floor(+width));
|
|
|
|
height = Math.abs(Math.floor(+height));
|
2019-11-04 10:41:11 +00:00
|
|
|
if (width <= 0 || height <= 0) {
|
|
|
|
return new Costume();
|
|
|
|
}
|
2019-10-18 09:48:11 +00:00
|
|
|
if (!isFinite(width * height) || isNaN(width * height)) {
|
|
|
|
throw new Error(
|
|
|
|
'expecting a finite number\nbut getting Infinity or NaN'
|
|
|
|
);
|
|
|
|
}
|
2019-04-09 13:08:32 +00:00
|
|
|
|
2019-10-18 09:48:11 +00:00
|
|
|
canvas = newCanvas(new Point(width, height), true);
|
|
|
|
ctx = canvas.getContext('2d');
|
2020-11-30 08:46:41 +00:00
|
|
|
src = pixels.itemsArray();
|
2019-10-18 09:48:11 +00:00
|
|
|
dta = ctx.createImageData(width, height);
|
2019-04-09 13:08:32 +00:00
|
|
|
for (i = 0; i < src.length; i += 1) {
|
2021-01-26 07:36:00 +00:00
|
|
|
px = src[i] instanceof List ? src[i].itemsArray() : [src[i]];
|
2020-10-04 10:58:18 +00:00
|
|
|
for (k = 0; k < 3; k += 1) {
|
2021-01-26 07:36:00 +00:00
|
|
|
dta.data[(i * 4) + k] = px[k] === undefined ? +px[0] : +px[k];
|
2019-04-09 13:08:32 +00:00
|
|
|
}
|
2020-12-21 23:09:58 +00:00
|
|
|
dta.data[i * 4 + 3] = (px[3] === undefined ? 255 : +px[3]);
|
2019-04-09 13:08:32 +00:00
|
|
|
}
|
|
|
|
ctx.putImageData(dta, 0, 0);
|
2019-05-12 23:31:23 +00:00
|
|
|
return new Costume(
|
|
|
|
canvas,
|
2019-10-20 15:09:30 +00:00
|
|
|
name || (rcvr || this.blockReceiver()).newCostumeName(
|
|
|
|
localize('costume')
|
|
|
|
)
|
2019-05-12 23:31:23 +00:00
|
|
|
);
|
2019-04-09 13:08:32 +00:00
|
|
|
};
|
|
|
|
|
2019-12-02 21:13:59 +00:00
|
|
|
Process.prototype.reportPentrailsAsSVG = function () {
|
2019-12-02 07:55:54 +00:00
|
|
|
// interpolated
|
2019-12-05 22:28:57 +00:00
|
|
|
var rcvr, stage, svg, acc, offset;
|
2019-12-02 07:55:54 +00:00
|
|
|
|
|
|
|
if (!this.context.accumulator) {
|
|
|
|
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
2019-12-02 16:00:34 +00:00
|
|
|
if (!stage.trailsLog.length) {
|
|
|
|
throw new Error (localize(
|
|
|
|
'there are currently no\nvectorizable pen trail segments'
|
|
|
|
));
|
|
|
|
}
|
2019-12-02 07:55:54 +00:00
|
|
|
svg = stage.trailsLogAsSVG();
|
|
|
|
this.context.accumulator = {
|
|
|
|
img : new Image(),
|
2019-12-02 14:50:50 +00:00
|
|
|
rot : svg.rot,
|
2019-12-02 07:55:54 +00:00
|
|
|
ready : false
|
|
|
|
};
|
|
|
|
acc = this.context.accumulator;
|
2020-04-28 17:27:42 +00:00
|
|
|
acc.img.onload = () => acc.ready = true;
|
2019-12-02 14:50:50 +00:00
|
|
|
acc.img.src = 'data:image/svg+xml,' + svg.src;
|
|
|
|
acc.img.rot = svg.rotationShift;
|
2019-12-02 07:55:54 +00:00
|
|
|
} else if (this.context.accumulator.ready) {
|
2020-04-17 22:09:36 +00:00
|
|
|
offset = ZERO;
|
2019-12-05 22:28:57 +00:00
|
|
|
rcvr = this.blockReceiver();
|
|
|
|
if (rcvr instanceof SpriteMorph) {
|
|
|
|
offset = new Point(rcvr.xPosition(), -rcvr.yPosition());
|
|
|
|
}
|
2019-12-02 07:55:54 +00:00
|
|
|
this.returnValueToParentContext(
|
|
|
|
new SVG_Costume(
|
|
|
|
this.context.accumulator.img,
|
2019-12-02 14:50:50 +00:00
|
|
|
this.blockReceiver().newCostumeName(localize('Costume')),
|
2019-12-05 22:28:57 +00:00
|
|
|
this.context.accumulator.rot.translateBy(offset)
|
2019-12-02 07:55:54 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.pushContext();
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
|
2020-12-16 12:13:16 +00:00
|
|
|
Process.prototype.reportYieldCount = function () {
|
|
|
|
return this.yieldCount;
|
|
|
|
};
|
|
|
|
|
2016-10-15 09:15:01 +00:00
|
|
|
// 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 &&
|
2016-12-21 23:19:38 +00:00
|
|
|
expr.world() &&
|
2016-12-22 08:45:04 +00:00
|
|
|
!(expr instanceof ColorSlotMorph)) {
|
2016-10-15 09:15:01 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-06-18 11:47:15 +00:00
|
|
|
// Process: Compile (as of yet simple) block scripts to JS
|
2018-03-20 08:38:36 +00:00
|
|
|
|
|
|
|
/*
|
2018-03-22 16:45:56 +00:00
|
|
|
with either only explicit formal parameters or a specified number of
|
|
|
|
implicit formal parameters mapped to empty input slots
|
2018-03-20 08:38:36 +00:00
|
|
|
*** highly experimental and heavily under construction ***
|
|
|
|
*/
|
|
|
|
|
2018-03-22 13:55:28 +00:00
|
|
|
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);
|
2018-03-20 08:38:36 +00:00
|
|
|
};
|
|
|
|
|
2018-10-19 10:22:10 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2018-03-20 08:38:36 +00:00
|
|
|
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')
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2018-06-18 11:47:15 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2018-07-05 11:44:03 +00:00
|
|
|
Process.prototype.incrementVarNamed = function (name, delta) {
|
|
|
|
// private - special form for compiled expressions
|
|
|
|
this.setVarNamed(name, this.getVarNamed(name) + (+delta));
|
|
|
|
};
|
|
|
|
|
2018-03-22 16:45:56 +00:00
|
|
|
// Process: Atomic HOFs using experimental JIT-compilation
|
|
|
|
|
|
|
|
Process.prototype.reportAtomicMap = function (reporter, list) {
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
|
|
|
|
2018-03-22 16:45:56 +00:00
|
|
|
this.assertType(list, 'list');
|
|
|
|
var result = [],
|
2020-11-30 08:46:41 +00:00
|
|
|
src = list.itemsArray(),
|
2018-03-22 16:45:56 +00:00
|
|
|
len = src.length,
|
2019-06-25 14:05:28 +00:00
|
|
|
formalParameterCount = reporter.inputs.length,
|
|
|
|
parms,
|
2018-03-22 16:45:56 +00:00
|
|
|
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
|
2018-10-19 10:22:10 +00:00
|
|
|
|
2018-03-22 16:45:56 +00:00
|
|
|
for (i = 0; i < len; i += 1) {
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [src[i]];
|
|
|
|
if (formalParameterCount > 1) {
|
|
|
|
parms.push(i + 1);
|
|
|
|
}
|
|
|
|
if (formalParameterCount > 2) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
2018-03-22 16:45:56 +00:00
|
|
|
result.push(
|
|
|
|
invoke(
|
|
|
|
func,
|
2019-06-25 14:05:28 +00:00
|
|
|
new List(parms),
|
2018-03-22 16:45:56 +00:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
2018-10-19 10:22:10 +00:00
|
|
|
this.capture(reporter) // process
|
2018-03-22 16:45:56 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return new List(result);
|
|
|
|
};
|
|
|
|
|
|
|
|
Process.prototype.reportAtomicKeep = function (reporter, list) {
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
|
|
|
|
2018-03-22 16:45:56 +00:00
|
|
|
this.assertType(list, 'list');
|
|
|
|
var result = [],
|
2020-11-30 08:46:41 +00:00
|
|
|
src = list.itemsArray(),
|
2018-03-22 16:45:56 +00:00
|
|
|
len = src.length,
|
2019-06-25 14:05:28 +00:00
|
|
|
formalParameterCount = reporter.inputs.length,
|
|
|
|
parms,
|
2018-03-22 16:45:56 +00:00
|
|
|
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) {
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [src[i]];
|
|
|
|
if (formalParameterCount > 1) {
|
|
|
|
parms.push(i + 1);
|
|
|
|
}
|
|
|
|
if (formalParameterCount > 2) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
2018-03-22 16:45:56 +00:00
|
|
|
if (
|
|
|
|
invoke(
|
|
|
|
func,
|
2019-06-25 14:05:28 +00:00
|
|
|
new List(parms),
|
2018-03-22 16:45:56 +00:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
2018-10-19 10:22:10 +00:00
|
|
|
this.capture(reporter) // process
|
2018-03-22 16:45:56 +00:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
result.push(src[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new List(result);
|
|
|
|
};
|
|
|
|
|
2019-05-29 09:53:21 +00:00
|
|
|
Process.prototype.reportAtomicFindFirst = function (reporter, list) {
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
|
|
|
|
2019-05-29 09:53:21 +00:00
|
|
|
this.assertType(list, 'list');
|
2020-11-30 08:46:41 +00:00
|
|
|
var src = list.itemsArray(),
|
2019-05-29 09:53:21 +00:00
|
|
|
len = src.length,
|
2019-06-25 14:05:28 +00:00
|
|
|
formalParameterCount = reporter.inputs.length,
|
|
|
|
parms,
|
2019-05-29 09:53:21 +00:00
|
|
|
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) {
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [src[i]];
|
|
|
|
if (formalParameterCount > 1) {
|
|
|
|
parms.push(i + 1);
|
|
|
|
}
|
|
|
|
if (formalParameterCount > 2) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
2019-05-29 09:53:21 +00:00
|
|
|
if (
|
|
|
|
invoke(
|
|
|
|
func,
|
2019-06-25 14:05:28 +00:00
|
|
|
new List(parms),
|
2019-05-29 09:53:21 +00:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
this.capture(reporter) // process
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return src[i];
|
|
|
|
}
|
|
|
|
}
|
2021-02-15 08:41:00 +00:00
|
|
|
return '';
|
2019-05-29 09:53:21 +00:00
|
|
|
};
|
|
|
|
|
2019-06-25 14:21:42 +00:00
|
|
|
Process.prototype.reportAtomicCombine = function (list, reporter) {
|
2019-06-25 14:05:28 +00:00
|
|
|
// 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
|
|
|
|
|
2021-03-02 10:11:58 +00:00
|
|
|
var result, src, len, formalParameterCount, parms, func, i;
|
2018-03-23 07:40:11 +00:00
|
|
|
this.assertType(list, 'list');
|
2021-03-02 10:11:58 +00:00
|
|
|
|
|
|
|
// check for special cases to speed up
|
|
|
|
if (this.canRunOptimizedForCombine(reporter)) {
|
|
|
|
return this.reportListAggregation(
|
|
|
|
list,
|
|
|
|
reporter.expression.selector
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
result = '';
|
|
|
|
src = list.itemsArray();
|
|
|
|
len = src.length;
|
|
|
|
formalParameterCount = reporter.inputs.length;
|
2018-03-23 07:40:11 +00:00
|
|
|
|
|
|
|
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) {
|
2019-06-25 14:05:28 +00:00
|
|
|
parms = [result, src[i]];
|
|
|
|
if (formalParameterCount > 2) {
|
|
|
|
parms.push(i + 1);
|
|
|
|
}
|
|
|
|
if (formalParameterCount > 3) {
|
|
|
|
parms.push(list);
|
|
|
|
}
|
2018-03-23 07:40:11 +00:00
|
|
|
result = invoke(
|
|
|
|
func,
|
2019-06-25 14:05:28 +00:00
|
|
|
new List(parms),
|
2018-03-23 07:40:11 +00:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
2018-10-19 10:22:10 +00:00
|
|
|
this.capture(reporter) // process
|
2018-03-23 07:40:11 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2018-03-22 16:45:56 +00:00
|
|
|
Process.prototype.reportAtomicSort = function (list, reporter) {
|
|
|
|
this.assertType(list, 'list');
|
2020-04-28 17:27:42 +00:00
|
|
|
var func;
|
2018-03-22 16:45:56 +00:00
|
|
|
|
|
|
|
// 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(
|
2020-11-30 08:46:41 +00:00
|
|
|
list.itemsArray().slice().sort((a, b) =>
|
2020-04-28 17:27:42 +00:00
|
|
|
invoke(
|
|
|
|
func,
|
|
|
|
new List([a, b]),
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
this.capture(reporter) // process
|
|
|
|
) ? -1 : 1
|
2018-03-22 16:45:56 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2018-10-23 12:07:30 +00:00
|
|
|
Process.prototype.reportAtomicGroup = function (list, reporter) {
|
|
|
|
this.assertType(list, 'list');
|
|
|
|
var result = [],
|
|
|
|
dict = new Map(),
|
|
|
|
groupKey,
|
2020-11-30 08:46:41 +00:00
|
|
|
src = list.itemsArray(),
|
2018-10-23 12:07:30 +00:00
|
|
|
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]]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 17:27:42 +00:00
|
|
|
dict.forEach((value, key) =>
|
|
|
|
result.push(new List([key, value.length, new List(value)]))
|
|
|
|
);
|
2018-10-23 12:07:30 +00:00
|
|
|
return new List(result);
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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:
|
|
|
|
|
2014-11-21 15:55:25 +00:00
|
|
|
parentContext the Context to return to when this one has
|
2013-03-16 08:02:16 +00:00
|
|
|
been evaluated.
|
|
|
|
outerContext the Context holding my lexical scope
|
2014-11-21 15:55:25 +00:00
|
|
|
expression SyntaxElementMorph, an array of blocks to evaluate,
|
2013-03-16 08:02:16 +00:00
|
|
|
null or a String denoting a selector, e.g. 'doYield'
|
2017-04-10 09:48:55 +00:00
|
|
|
origin the object of origin, only used for serialization
|
2013-03-16 08:02:16 +00:00
|
|
|
receiver the object to which the expression applies, if any
|
2014-11-21 15:55:25 +00:00
|
|
|
variables the current VariableFrame, if any
|
|
|
|
inputs an array of input values computed so far
|
2013-03-16 08:02:16 +00:00
|
|
|
(if expression is a BlockMorph)
|
2014-11-21 15:55:25 +00:00
|
|
|
pc the index of the next block to evaluate
|
2013-03-16 08:02:16 +00:00
|
|
|
(if expression is an array)
|
2016-10-21 14:29:04 +00:00
|
|
|
isContinuation flag for marking a transient continuation context
|
2014-11-21 15:55:25 +00:00
|
|
|
startTime time when the context was first evaluated
|
|
|
|
startValue initial value for interpolated operations
|
2013-03-16 08:02:16 +00:00
|
|
|
activeAudio audio buffer for interpolated operations, don't persist
|
|
|
|
activeNote audio oscillator for interpolated ops, don't persist
|
2018-02-19 16:32:52 +00:00
|
|
|
activeSends forked processes waiting to be completed
|
2013-03-16 08:02:16 +00:00
|
|
|
isCustomBlock marker for return ops
|
2018-07-03 07:32:37 +00:00
|
|
|
isCustomCommand marker for interpolated blocking reporters (reportURL)
|
2014-11-21 15:55:25 +00:00
|
|
|
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)
|
2016-10-15 09:15:01 +00:00
|
|
|
isFlashing flag for single-stepping
|
2019-04-27 07:30:09 +00:00
|
|
|
accumulator slot for collecting data from reentrant visits
|
2013-03-16 08:02:16 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
function Context(
|
|
|
|
parentContext,
|
|
|
|
expression,
|
|
|
|
outerContext,
|
|
|
|
receiver
|
|
|
|
) {
|
|
|
|
this.outerContext = outerContext || null;
|
|
|
|
this.parentContext = parentContext || null;
|
|
|
|
this.expression = expression || null;
|
|
|
|
this.receiver = receiver || null;
|
2017-04-10 09:48:55 +00:00
|
|
|
this.origin = receiver || null; // only for serialization
|
2013-03-16 08:02:16 +00:00
|
|
|
this.variables = new VariableFrame();
|
|
|
|
if (this.outerContext) {
|
|
|
|
this.variables.parentFrame = this.outerContext.variables;
|
|
|
|
this.receiver = this.outerContext.receiver;
|
|
|
|
}
|
|
|
|
this.inputs = [];
|
|
|
|
this.pc = 0;
|
2016-10-21 14:29:04 +00:00
|
|
|
this.isContinuation = false;
|
2013-03-16 08:02:16 +00:00
|
|
|
this.startTime = null;
|
2018-02-19 16:32:52 +00:00
|
|
|
this.activeSends = null;
|
2013-03-16 08:02:16 +00:00
|
|
|
this.activeAudio = null;
|
|
|
|
this.activeNote = null;
|
|
|
|
this.isCustomBlock = false; // marks the end of a custom block's stack
|
2018-07-03 07:32:37 +00:00
|
|
|
this.isCustomCommand = null; // used for ignoring URL reporters' results
|
2013-03-16 08:02:16 +00:00
|
|
|
this.emptySlots = 0; // used for block reification
|
2014-11-21 15:55:25 +00:00
|
|
|
this.tag = null; // lexical catch-tag for custom blocks
|
2016-10-15 09:15:01 +00:00
|
|
|
this.isFlashing = false; // for single-stepping
|
2019-04-27 07:30:09 +00:00
|
|
|
this.accumulator = null;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Context.prototype.toString = function () {
|
2014-11-14 11:49:01 +00:00
|
|
|
var expr = this.expression;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (expr instanceof Array) {
|
|
|
|
if (expr.length > 0) {
|
|
|
|
expr = '[' + expr[0] + ']';
|
|
|
|
}
|
|
|
|
}
|
2014-11-14 11:49:01 +00:00
|
|
|
return 'Context >> ' + expr + ' ' + this.variables;
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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) {
|
2020-04-28 17:27:42 +00:00
|
|
|
cont = detect(
|
|
|
|
block.allInputs(),
|
|
|
|
inp => inp.bindingID === 1
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
if (cont) {
|
|
|
|
block.revertToDefaultInput(cont, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ring.embed(block, this.inputs);
|
2020-07-24 14:34:59 +00:00
|
|
|
return ring.doWithAlpha(
|
|
|
|
1,
|
|
|
|
() => {
|
|
|
|
ring.clearAlpha();
|
|
|
|
return ring.fullImage();
|
|
|
|
}
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
if (this.expression instanceof Array) {
|
|
|
|
block = this.expression[this.pc].fullCopy();
|
|
|
|
if (block instanceof RingMorph && !block.contents()) { // empty ring
|
2020-07-22 10:25:33 +00:00
|
|
|
return block.doWithAlpha(1, () => block.fullImage());
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
ring.embed(block, this.isContinuation ? [] : this.inputs);
|
2020-07-22 10:25:33 +00:00
|
|
|
return ring.doWithAlpha(1, () => ring.fullImage());
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2014-07-24 10:24:19 +00:00
|
|
|
|
|
|
|
// otherwise show an empty ring
|
|
|
|
ring.color = SpriteMorph.prototype.blockColor.other;
|
2021-12-03 12:08:16 +00:00
|
|
|
ring.setSpec('%rr %ringparms');
|
2014-07-24 10:24:19 +00:00
|
|
|
|
|
|
|
// also show my inputs, unless I'm a continuation
|
|
|
|
if (!this.isContinuation) {
|
2020-04-28 17:27:42 +00:00
|
|
|
this.inputs.forEach(inp =>
|
|
|
|
ring.parts()[1].addInput(inp)
|
|
|
|
);
|
2014-07-24 10:24:19 +00:00
|
|
|
}
|
2020-07-22 10:25:33 +00:00
|
|
|
return ring.doWithAlpha(1, () => ring.fullImage());
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Context continuations:
|
|
|
|
|
2020-07-01 23:25:41 +00:00
|
|
|
Context.prototype.continuation = function (isReporter) {
|
2013-03-16 08:02:16 +00:00
|
|
|
var cont;
|
|
|
|
if (this.expression instanceof Array) {
|
|
|
|
cont = this;
|
|
|
|
} else if (this.parentContext) {
|
|
|
|
cont = this.parentContext;
|
|
|
|
} else {
|
2020-07-01 23:25:41 +00:00
|
|
|
cont = new Context(
|
|
|
|
null,
|
|
|
|
isReporter ? 'expectReport' : 'popContext'
|
|
|
|
);
|
2016-10-24 18:37:39 +00:00
|
|
|
cont.isContinuation = true;
|
|
|
|
return cont;
|
2014-11-14 11:49:01 +00:00
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
cont = cont.copyForContinuation();
|
2014-11-25 16:51:04 +00:00
|
|
|
cont.tag = null;
|
2013-03-16 08:02:16 +00:00
|
|
|
cont.isContinuation = true;
|
|
|
|
return cont;
|
|
|
|
};
|
|
|
|
|
|
|
|
Context.prototype.copyForContinuation = function () {
|
|
|
|
var cpy = copy(this),
|
|
|
|
cur = cpy,
|
2014-07-21 06:23:14 +00:00
|
|
|
isReporter = !(this.expression instanceof Array ||
|
|
|
|
isString(this.expression));
|
2013-03-16 08:02:16 +00:00
|
|
|
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,
|
2014-07-21 06:23:14 +00:00
|
|
|
isReporter = !(this.expression instanceof Array ||
|
|
|
|
isString(this.expression));
|
2013-03-16 08:02:16 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-10-15 09:15:01 +00:00
|
|
|
// Context single-stepping:
|
|
|
|
|
|
|
|
Context.prototype.lastFlashable = function () {
|
2021-12-09 12:27:01 +00:00
|
|
|
// for single-stepping when pausing
|
2016-10-15 09:15:01 +00:00
|
|
|
if (this.expression instanceof SyntaxElementMorph &&
|
|
|
|
!(this.expression instanceof CommandSlotMorph)) {
|
|
|
|
return this;
|
|
|
|
} else if (this.parentContext) {
|
|
|
|
return this.parentContext.lastFlashable();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// Context debugging
|
|
|
|
|
|
|
|
Context.prototype.stackSize = function () {
|
|
|
|
if (!this.parentContext) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 1 + this.parentContext.stackSize();
|
|
|
|
};
|
|
|
|
|
2021-06-17 18:27:06 +00:00
|
|
|
Context.prototype.isInCustomBlock = function () {
|
|
|
|
if (this.isCustomBlock) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (this.parentContext) {
|
|
|
|
return this.parentContext.isInCustomBlock();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2021-12-09 12:27:01 +00:00
|
|
|
// Context syntax analysis
|
2021-11-30 13:15:31 +00:00
|
|
|
|
|
|
|
Context.prototype.components = function () {
|
2021-12-05 12:30:46 +00:00
|
|
|
var expr = this.expression;
|
|
|
|
if (expr && expr.components) {
|
|
|
|
expr = expr.components(this.inputs.slice());
|
2021-12-03 18:24:39 +00:00
|
|
|
} else {
|
|
|
|
expr = new Context();
|
|
|
|
expr.inputs = this.inputs.slice();
|
2021-11-30 13:15:31 +00:00
|
|
|
}
|
2021-12-03 18:24:39 +00:00
|
|
|
return expr instanceof Context ? new List([expr]) : expr;
|
2021-11-30 13:15:31 +00:00
|
|
|
};
|
|
|
|
|
2021-12-01 18:51:01 +00:00
|
|
|
Context.prototype.equalTo = function (other) {
|
|
|
|
var c1 = this.components(),
|
|
|
|
c2 = other.components();
|
2021-12-22 15:34:09 +00:00
|
|
|
if (this.emptyOrEqual(c1.cdr(), c2.cdr())) {
|
2021-12-12 21:44:27 +00:00
|
|
|
if (this.expression && this.expression.length === 1 &&
|
|
|
|
other.expression && other.expression.length === 1) {
|
|
|
|
return snapEquals(this.expression[0], other.expression[0]);
|
|
|
|
}
|
2021-12-02 09:28:59 +00:00
|
|
|
return snapEquals(this.expression, other.expression);
|
2021-12-01 18:51:01 +00:00
|
|
|
}
|
2021-12-06 08:29:57 +00:00
|
|
|
return false;
|
2021-12-01 18:51:01 +00:00
|
|
|
};
|
|
|
|
|
2021-12-22 15:34:09 +00:00
|
|
|
Context.prototype.emptyOrEqual = function (list1, list2) {
|
|
|
|
// private - return TRUE if both lists are either equal
|
|
|
|
// or only contain empty items
|
|
|
|
return list1.equalTo(list2) || (
|
|
|
|
list1.itemsArray().every(item => !item) &&
|
|
|
|
list2.itemsArray().every(item => !item)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2021-12-01 16:42:43 +00:00
|
|
|
Context.prototype.copyWithInputs = function (inputs) {
|
2021-12-03 18:24:39 +00:00
|
|
|
return this.expression ?
|
2021-12-20 11:00:52 +00:00
|
|
|
this.expression.copyWithInputs(inputs)
|
2021-12-03 18:24:39 +00:00
|
|
|
: this;
|
2021-12-01 16:42:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Context.prototype.copyWithNext = function (next) {
|
2021-12-03 18:24:39 +00:00
|
|
|
return this.expression.copyWithNext(next.expression, this.inputs.slice());
|
2021-12-01 16:42:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Context.prototype.updateEmptySlots = function () {
|
|
|
|
this.emptySlots = this.expression.markEmptySlots();
|
|
|
|
};
|
|
|
|
|
2014-09-17 12:40:39 +00:00
|
|
|
// Variable /////////////////////////////////////////////////////////////////
|
|
|
|
|
2021-10-05 16:15:42 +00:00
|
|
|
function Variable(value, isTransient, isHidden) {
|
2014-09-17 12:40:39 +00:00
|
|
|
this.value = value;
|
2016-03-16 12:00:31 +00:00
|
|
|
this.isTransient = isTransient || false; // prevent value serialization
|
2021-10-05 16:15:42 +00:00
|
|
|
this.isHidden = isHidden || false; // not shown in the blocks palette
|
2014-09-17 12:40:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Variable.prototype.toString = function () {
|
2021-10-05 16:15:42 +00:00
|
|
|
return 'a ' + (this.isTransient ? 'transient ' : '') +
|
|
|
|
(this.isHidden ? 'hidden ' : '') +
|
|
|
|
'Variable [' + this.value + ']';
|
2014-09-17 12:40:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Variable.prototype.copy = function () {
|
2021-10-05 16:15:42 +00:00
|
|
|
return new Variable(this.value, this.isTransient, this.isHidden);
|
2014-09-17 12:40:39 +00:00
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
// 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 () {
|
2020-04-28 17:27:42 +00:00
|
|
|
var frame = new VariableFrame(this.parentFrame);
|
|
|
|
this.names().forEach(vName =>
|
|
|
|
frame.addVar(vName, this.getVar(vName))
|
|
|
|
);
|
2013-03-16 08:02:16 +00:00
|
|
|
return frame;
|
|
|
|
};
|
|
|
|
|
2018-10-19 10:22:10 +00:00
|
|
|
VariableFrame.prototype.fullCopy = function () {
|
|
|
|
// experimental - for compiling to JS
|
2013-03-16 08:02:16 +00:00
|
|
|
var frame;
|
|
|
|
if (this.parentFrame) {
|
2018-10-19 10:22:10 +00:00
|
|
|
frame = new VariableFrame(this.parentFrame.fullCopy());
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2018-10-19 10:22:10 +00:00
|
|
|
frame = new VariableFrame();
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
frame.vars = copy(this.vars);
|
|
|
|
return frame;
|
|
|
|
};
|
|
|
|
|
2018-10-19 10:22:10 +00:00
|
|
|
VariableFrame.prototype.root = function () {
|
|
|
|
if (this.parentFrame) {
|
|
|
|
return this.parentFrame.root();
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
VariableFrame.prototype.find = function (name) {
|
2019-11-13 08:28:19 +00:00
|
|
|
// answer the closest variable frame containing
|
|
|
|
// the specified variable. otherwise throw an exception.
|
2013-03-16 08:02:16 +00:00
|
|
|
var frame = this.silentFind(name);
|
|
|
|
if (frame) {return frame; }
|
|
|
|
throw new Error(
|
2014-10-14 16:58:57 +00:00
|
|
|
localize('a variable of name \'')
|
2013-03-16 08:02:16 +00:00
|
|
|
+ name
|
2014-11-20 14:53:14 +00:00
|
|
|
+ localize('\'\ndoes not exist in this context')
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
VariableFrame.prototype.silentFind = function (name) {
|
2019-11-13 08:28:19 +00:00
|
|
|
// answer the closest variable frame containing
|
|
|
|
// the specified variable. Otherwise return null.
|
|
|
|
if (this.vars[name] instanceof Variable) {
|
2013-03-16 08:02:16 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
if (this.parentFrame) {
|
|
|
|
return this.parentFrame.silentFind(name);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2015-07-26 22:35:36 +00:00
|
|
|
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")
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
var frame = this.find(name);
|
|
|
|
if (frame) {
|
2015-07-26 22:35:36 +00:00
|
|
|
if (sender instanceof SpriteMorph &&
|
|
|
|
(frame.owner instanceof SpriteMorph) &&
|
|
|
|
(sender !== frame.owner)) {
|
|
|
|
sender.shadowVar(name, value);
|
|
|
|
} else {
|
|
|
|
frame.vars[name].value = value;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-07-26 22:35:36 +00:00
|
|
|
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")
|
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
var frame = this.find(name),
|
2015-07-26 22:35:36 +00:00
|
|
|
value,
|
|
|
|
newValue;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (frame) {
|
2014-09-17 12:40:39 +00:00
|
|
|
value = parseFloat(frame.vars[name].value);
|
2015-07-26 22:35:36 +00:00
|
|
|
newValue = isNaN(value) ? delta : value + parseFloat(delta);
|
|
|
|
if (sender instanceof SpriteMorph &&
|
|
|
|
(frame.owner instanceof SpriteMorph) &&
|
|
|
|
(sender !== frame.owner)) {
|
|
|
|
sender.shadowVar(name, newValue);
|
2013-03-16 08:02:16 +00:00
|
|
|
} else {
|
2015-07-26 22:35:36 +00:00
|
|
|
frame.vars[name].value = newValue;
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
2015-07-26 22:35:36 +00:00
|
|
|
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-09-18 12:26:28 +00:00
|
|
|
VariableFrame.prototype.getVar = function (name) {
|
2013-03-16 08:02:16 +00:00
|
|
|
var frame = this.silentFind(name),
|
2014-09-18 12:26:28 +00:00
|
|
|
value;
|
2013-03-16 08:02:16 +00:00
|
|
|
if (frame) {
|
2014-09-17 12:40:39 +00:00
|
|
|
value = frame.vars[name].value;
|
2013-03-16 08:02:16 +00:00
|
|
|
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(
|
2014-10-14 16:58:57 +00:00
|
|
|
localize('a variable of name \'')
|
2013-03-16 08:02:16 +00:00
|
|
|
+ name
|
2014-10-14 16:58:57 +00:00
|
|
|
+ localize('\'\ndoes not exist in this context')
|
2013-03-16 08:02:16 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
VariableFrame.prototype.addVar = function (name, value) {
|
2014-09-17 12:40:39 +00:00
|
|
|
this.vars[name] = new Variable(value === 0 ? 0
|
2013-11-26 12:09:26 +00:00
|
|
|
: value === false ? false
|
2014-06-05 15:16:27 +00:00
|
|
|
: value === '' ? '' : value || 0);
|
2013-03-16 08:02:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
VariableFrame.prototype.deleteVar = function (name) {
|
|
|
|
var frame = this.find(name);
|
|
|
|
if (frame) {
|
|
|
|
delete frame.vars[name];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// VariableFrame tools
|
|
|
|
|
2021-10-06 11:03:50 +00:00
|
|
|
VariableFrame.prototype.names = function (includeHidden) {
|
2013-03-16 08:02:16 +00:00
|
|
|
var each, names = [];
|
|
|
|
for (each in this.vars) {
|
2013-04-16 00:29:03 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(this.vars, each)) {
|
2021-10-06 11:03:50 +00:00
|
|
|
if (!this.vars[each].isHidden || includeHidden) {
|
|
|
|
names.push(each);
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return names;
|
|
|
|
};
|
|
|
|
|
2021-10-06 11:03:50 +00:00
|
|
|
VariableFrame.prototype.allNamesDict = function (upTo, includeHidden) {
|
2018-01-22 13:33:47 +00:00
|
|
|
// "upTo" is an optional parent frame at which to stop, e.g. globals
|
2013-03-16 08:02:16 +00:00
|
|
|
var dict = {}, current = this;
|
|
|
|
|
|
|
|
function addKeysToDict(srcDict, trgtDict) {
|
|
|
|
var eachKey;
|
|
|
|
for (eachKey in srcDict) {
|
2013-04-16 00:29:03 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(srcDict, eachKey)) {
|
2021-10-06 11:03:50 +00:00
|
|
|
if (!srcDict[eachKey].isHidden || includeHidden) {
|
|
|
|
trgtDict[eachKey] = eachKey;
|
|
|
|
}
|
2013-03-16 08:02:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-22 13:33:47 +00:00
|
|
|
while (current && (current !== upTo)) {
|
2013-03-16 08:02:16 +00:00
|
|
|
addKeysToDict(current.vars, dict);
|
|
|
|
current = current.parentFrame;
|
|
|
|
}
|
|
|
|
return dict;
|
|
|
|
};
|
|
|
|
|
2021-10-06 11:03:50 +00:00
|
|
|
VariableFrame.prototype.allNames = function (upTo, includeHidden) {
|
2013-03-16 08:02:16 +00:00
|
|
|
/*
|
|
|
|
only show the names of the lexical scope, hybrid scoping is
|
|
|
|
reserved to the daring ;-)
|
2018-01-22 13:33:47 +00:00
|
|
|
"upTo" is an optional parent frame at which to stop, e.g. globals
|
2013-03-16 08:02:16 +00:00
|
|
|
*/
|
2021-10-06 11:03:50 +00:00
|
|
|
var answer = [], each, dict = this.allNamesDict(upTo, includeHidden);
|
2013-03-16 08:02:16 +00:00
|
|
|
|
|
|
|
for (each in dict) {
|
2013-04-16 00:29:03 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(dict, each)) {
|
2013-03-16 08:02:16 +00:00
|
|
|
answer.push(each);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return answer;
|
|
|
|
};
|
2018-03-20 08:38:36 +00:00
|
|
|
|
|
|
|
// JSCompiler /////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/*
|
|
|
|
Compile simple, side-effect free Reporters
|
2018-03-22 16:45:56 +00:00
|
|
|
with either only explicit formal parameters or a specified number of
|
|
|
|
implicit formal parameters mapped to empty input slots
|
2018-03-20 08:38:36 +00:00
|
|
|
*** 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
|
2018-03-22 13:55:28 +00:00
|
|
|
this.implicitParams = null;
|
|
|
|
this.paramCount = null;
|
2018-03-20 08:38:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JSCompiler.prototype.toString = function () {
|
|
|
|
return 'a JSCompiler';
|
|
|
|
};
|
|
|
|
|
2018-03-22 13:55:28 +00:00
|
|
|
JSCompiler.prototype.compileFunction = function (aContext, implicitParamCount) {
|
2018-03-20 13:48:42 +00:00
|
|
|
var block = aContext.expression,
|
2018-03-20 08:38:36 +00:00
|
|
|
parameters = aContext.inputs,
|
|
|
|
parms = [],
|
|
|
|
hasEmptySlots = false,
|
2018-03-22 13:55:28 +00:00
|
|
|
i;
|
2018-03-20 08:38:36 +00:00
|
|
|
|
|
|
|
this.source = aContext;
|
2018-03-22 13:55:28 +00:00
|
|
|
this.implicitParams = implicitParamCount || 1;
|
2018-03-20 08:38:36 +00:00
|
|
|
|
2018-03-20 13:48:42 +00:00
|
|
|
// scan for empty input slots
|
|
|
|
hasEmptySlots = !isNil(detect(
|
|
|
|
block.allChildren(),
|
2020-04-28 17:27:42 +00:00
|
|
|
morph => morph.isEmptySlot && morph.isEmptySlot()
|
2018-03-20 13:48:42 +00:00
|
|
|
));
|
2018-03-20 08:38:36 +00:00
|
|
|
|
|
|
|
// translate formal parameters into gensyms
|
|
|
|
this.gensyms = {};
|
2018-03-22 13:55:28 +00:00
|
|
|
this.paramCount = 0;
|
2018-03-20 08:38:36 +00:00
|
|
|
if (parameters.length) {
|
|
|
|
// test for conflicts
|
|
|
|
if (hasEmptySlots) {
|
2018-03-20 13:48:42 +00:00
|
|
|
throw new Error(
|
2018-03-20 08:38:36 +00:00
|
|
|
'compiling does not yet support\n' +
|
|
|
|
'mixing explicit formal parameters\n' +
|
|
|
|
'with empty input slots'
|
|
|
|
);
|
2018-03-20 13:48:42 +00:00
|
|
|
}
|
2018-03-20 08:38:36 +00:00
|
|
|
// map explicit formal parameters
|
2020-04-28 17:27:42 +00:00
|
|
|
parameters.forEach((pName, idx) => {
|
2018-03-20 13:48:42 +00:00
|
|
|
var pn = 'p' + idx;
|
|
|
|
parms.push(pn);
|
2020-04-28 17:27:42 +00:00
|
|
|
this.gensyms[pName] = pn;
|
2018-03-20 08:38:36 +00:00
|
|
|
});
|
|
|
|
} else if (hasEmptySlots) {
|
2018-03-22 13:55:28 +00:00
|
|
|
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'];
|
|
|
|
}
|
2018-03-20 08:38:36 +00:00
|
|
|
}
|
2018-10-03 06:19:36 +00:00
|
|
|
|
2018-03-20 08:38:36 +00:00
|
|
|
// compile using gensyms
|
2018-06-18 11:47:15 +00:00
|
|
|
|
|
|
|
if (block instanceof CommandBlockMorph) {
|
|
|
|
return Function.apply(
|
|
|
|
null,
|
|
|
|
parms.concat([this.compileSequence(block)])
|
|
|
|
);
|
|
|
|
}
|
2018-03-20 13:48:42 +00:00
|
|
|
return Function.apply(
|
2018-03-20 08:38:36 +00:00
|
|
|
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);
|
2019-05-02 16:27:38 +00:00
|
|
|
case 'reportIfElse':
|
|
|
|
return '(' +
|
|
|
|
this.compileInput(inputs[0]) +
|
|
|
|
' ? ' +
|
|
|
|
this.compileInput(inputs[1]) +
|
|
|
|
' : ' +
|
|
|
|
this.compileInput(inputs[2]) +
|
|
|
|
')';
|
2018-03-20 08:38:36 +00:00
|
|
|
case 'evaluateCustomBlock':
|
|
|
|
throw new Error(
|
|
|
|
'compiling does not yet support\n' +
|
|
|
|
'custom blocks'
|
|
|
|
);
|
2018-06-18 11:47:15 +00:00
|
|
|
|
2021-02-15 10:04:41 +00:00
|
|
|
// special evaluation primitives
|
|
|
|
case 'doRun':
|
|
|
|
case 'evaluate':
|
|
|
|
return 'invoke(' +
|
|
|
|
this.compileInput(inputs[0]) +
|
|
|
|
',' +
|
|
|
|
this.compileInput(inputs[1]) +
|
|
|
|
')';
|
|
|
|
|
2018-06-18 11:47:15 +00:00
|
|
|
// special command forms
|
|
|
|
case 'doSetVar': // redirect var to process
|
|
|
|
return 'arguments[arguments.length - 1].setVarNamed(' +
|
2018-07-05 11:44:03 +00:00
|
|
|
this.compileInput(inputs[0]) +
|
|
|
|
',' +
|
|
|
|
this.compileInput(inputs[1]) +
|
|
|
|
')';
|
|
|
|
case 'doChangeVar': // redirect var to process
|
|
|
|
return 'arguments[arguments.length - 1].incrementVarNamed(' +
|
2018-06-18 11:47:15 +00:00
|
|
|
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()) +
|
|
|
|
'}';
|
|
|
|
|
2018-03-20 08:38:36 +00:00
|
|
|
default:
|
|
|
|
target = this.process[selector] ? this.process
|
|
|
|
: (this.source.receiver || this.process.receiver);
|
|
|
|
rcvr = target.constructor.name + '.prototype';
|
|
|
|
args = this.compileInputs(inputs);
|
2018-05-08 05:08:54 +00:00
|
|
|
if (isSnapObject(target)) {
|
|
|
|
return rcvr + '.' + selector + '.apply('+ rcvr + ', [' + args +'])';
|
|
|
|
} else {
|
|
|
|
return 'arguments[arguments.length - 1].' +
|
|
|
|
selector +
|
|
|
|
'.apply(arguments[arguments.length - 1], [' + args +'])';
|
|
|
|
}
|
2018-03-20 08:38:36 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-06-18 11:47:15 +00:00
|
|
|
JSCompiler.prototype.compileSequence = function (commandBlock) {
|
2020-04-28 17:27:42 +00:00
|
|
|
var body = '';
|
|
|
|
commandBlock.blockSequence().forEach(block => {
|
|
|
|
body += this.compileExpression(block);
|
2018-06-18 11:47:15 +00:00
|
|
|
body += ';\n';
|
|
|
|
});
|
|
|
|
return body;
|
|
|
|
};
|
|
|
|
|
2018-03-20 08:38:36 +00:00
|
|
|
JSCompiler.prototype.compileInfix = function (operator, inputs) {
|
|
|
|
return '(' + this.compileInput(inputs[0]) + ' ' + operator + ' ' +
|
|
|
|
this.compileInput(inputs[1]) +')';
|
|
|
|
};
|
|
|
|
|
|
|
|
JSCompiler.prototype.compileInputs = function (array) {
|
2020-04-28 17:27:42 +00:00
|
|
|
var args = '';
|
|
|
|
array.forEach(inp => {
|
2018-03-20 08:38:36 +00:00
|
|
|
if (args.length) {
|
|
|
|
args += ', ';
|
|
|
|
}
|
2020-04-28 17:27:42 +00:00
|
|
|
args += this.compileInput(inp);
|
2018-03-20 08:38:36 +00:00
|
|
|
});
|
|
|
|
return args;
|
|
|
|
};
|
|
|
|
|
|
|
|
JSCompiler.prototype.compileInput = function (inp) {
|
|
|
|
var value, type;
|
|
|
|
|
|
|
|
if (inp.isEmptySlot && inp.isEmptySlot()) {
|
|
|
|
// implicit formal parameter
|
2018-03-22 13:55:28 +00:00
|
|
|
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';
|
2018-03-20 08:38:36 +00:00
|
|
|
} else if (inp instanceof MultiArgMorph) {
|
2018-03-23 07:40:11 +00:00
|
|
|
return 'new List([' + this.compileInputs(inp.inputs()) + '])';
|
|
|
|
} else if (inp instanceof ArgLabelMorph) {
|
|
|
|
return this.compileInput(inp.argMorph());
|
2018-03-20 08:38:36 +00:00
|
|
|
} 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] + '"';
|
2018-03-23 07:40:11 +00:00
|
|
|
}
|
2018-03-20 08:38:36 +00:00
|
|
|
throw new Error(
|
|
|
|
'compiling does not yet support\n' +
|
|
|
|
'inputs of type\n' +
|
|
|
|
type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else if (inp instanceof BlockMorph) {
|
|
|
|
if (inp.selector === 'reportGetVar') {
|
2018-03-20 13:48:42 +00:00
|
|
|
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);
|
2018-03-20 08:38:36 +00:00
|
|
|
} else {
|
|
|
|
throw new Error(
|
|
|
|
'compiling does not yet support\n' +
|
|
|
|
'input slots of type\n' +
|
|
|
|
inp.constructor.name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|