This is progress
- if compilation errors, fallback to interpreter - retire compiled threads once done - add control_repeat - add math_whole_number
This commit is contained in:
16 files changed
+446
-47
No files matched your search
@@ -1,17 +1,18 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
}
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
math_number: number,
|
||||
};
|
||||
return {
|
||||
math_number: number,
|
||||
math_whole_number: number,
|
||||
};
|
||||
};
|
||||
|
||||
const number = (util) => {
|
||||
const NUM = util.getFieldUnsafe('NUM');
|
||||
const number = Number(NUM);
|
||||
return number;
|
||||
};
|
||||
const NUM = util.getFieldUnsafe('NUM');
|
||||
const number = Number(NUM);
|
||||
return number;
|
||||
};
|
||||
@@ -1,19 +1,32 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
control_forever: forever,
|
||||
}
|
||||
return {
|
||||
control_repeat: repeat,
|
||||
control_forever: forever,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
const repeat = (util) => {
|
||||
const TIMES = util.getInput('TIMES');
|
||||
const SUBSTACK = util.compileSubstack('SUBSTACK');
|
||||
const times = util.nextVariable();
|
||||
util.writeLn(`var ${times} = ${TIMES};`);
|
||||
util.writeLn(`while (${times} >= 0.5) {`);
|
||||
util.writeLn(`${times}--;`);
|
||||
util.write(SUBSTACK);
|
||||
util.yieldLoop();
|
||||
util.writeLn(`}`);
|
||||
};
|
||||
|
||||
const forever = (util) => {
|
||||
const SUBSTACK = util.compileSubstack('SUBSTACK');
|
||||
util.writeLn('while (true) {');
|
||||
util.write(SUBSTACK);
|
||||
util.yieldLoop();
|
||||
util.writeLn('}');
|
||||
const SUBSTACK = util.compileSubstack('SUBSTACK');
|
||||
util.writeLn('while (true) {');
|
||||
util.write(SUBSTACK);
|
||||
util.yieldLoop();
|
||||
util.writeLn('}');
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
looks_changeeffectby: changeEffect,
|
||||
}
|
||||
return {
|
||||
looks_changeeffectby: changeEffect,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
const changeEffect = (util) => {
|
||||
const CHANGE = util.getInput('CHANGE');
|
||||
util.writeLn(`target.setEffect("color", target.effects.color + ${CHANGE});`);
|
||||
const CHANGE = util.getInput('CHANGE');
|
||||
util.writeLn(`target.setEffect("color", target.effects.color + ${CHANGE});`);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports.getStatements = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getInputs = () => {
|
||||
return {
|
||||
|
||||
};
|
||||
};
|
||||
@@ -6,8 +6,15 @@ const inputs = {};
|
||||
|
||||
const defaultExtensions = [
|
||||
require('./blocks/compiler_natives'),
|
||||
require('./blocks/compiler_scratch3_control'),
|
||||
require('./blocks/compiler_scratch3_motion'),
|
||||
require('./blocks/compiler_scratch3_looks'),
|
||||
require('./blocks/compiler_scratch3_sounds'),
|
||||
require('./blocks/compiler_scratch3_events'),
|
||||
require('./blocks/compiler_scratch3_control'),
|
||||
require('./blocks/compiler_scratch3_sensing'),
|
||||
require('./blocks/compiler_scratch3_operators'),
|
||||
require('./blocks/compiler_scratch3_data'),
|
||||
require('./blocks/compiler_scratch3_procedures'),
|
||||
];
|
||||
|
||||
defaultExtensions.forEach((ext) => {
|
||||
@@ -26,7 +33,7 @@ defaultExtensions.forEach((ext) => {
|
||||
}
|
||||
});
|
||||
|
||||
class BLockUtil {
|
||||
class BlockUtil {
|
||||
constructor(compiler, block) {
|
||||
this.compiler = compiler;
|
||||
this.block = block;
|
||||
@@ -41,13 +48,13 @@ class BLockUtil {
|
||||
}
|
||||
}
|
||||
|
||||
class InputUtil extends BLockUtil {
|
||||
class InputUtil extends BlockUtil {
|
||||
constructor(compiler, block) {
|
||||
super(compiler, block);
|
||||
}
|
||||
}
|
||||
|
||||
class StatementUtil extends BLockUtil {
|
||||
class StatementUtil extends BlockUtil {
|
||||
constructor(compiler, block) {
|
||||
super(compiler, block);
|
||||
this.source = '';
|
||||
@@ -65,20 +72,26 @@ class StatementUtil extends BLockUtil {
|
||||
this.source += s;
|
||||
}
|
||||
|
||||
nextVariable() {
|
||||
this.compiler.variables++;
|
||||
return 'var' + this.compiler.variables;
|
||||
}
|
||||
|
||||
compileSubstack(inputName) {
|
||||
const inputValue = this.block.inputs[inputName];
|
||||
if (!inputValue) {
|
||||
// empty substack
|
||||
return '';
|
||||
}
|
||||
const substack = inputValue.block;
|
||||
return this.compiler.compileStack(substack);
|
||||
}
|
||||
}
|
||||
|
||||
class Compiler {
|
||||
/**
|
||||
*
|
||||
* @param {Thread} thread
|
||||
*/
|
||||
constructor(thread) {
|
||||
this.thread = thread;
|
||||
this.variables = 0;
|
||||
}
|
||||
|
||||
compileInput(parentBlock, inputName) {
|
||||
@@ -110,6 +123,7 @@ class Compiler {
|
||||
const util = new StatementUtil(this, block);
|
||||
let compiler = statements[block.opcode];
|
||||
if (!compiler) {
|
||||
log.error('unknown opcode', block);
|
||||
throw new Error('unknown opcode: ' + block.opcode);
|
||||
}
|
||||
|
||||
@@ -126,6 +140,7 @@ class Compiler {
|
||||
|
||||
const topBlockId = this.thread.topBlock;
|
||||
const topBlock = this.thread.target.blocks.getBlock(topBlockId);
|
||||
if (!topBlock) throw new Error('not a hat');
|
||||
|
||||
const script = this.compileStack(topBlock.next);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const execute = function (sequencer, thread) {
|
||||
const result = thread.generator.next();
|
||||
|
||||
if (result.done) {
|
||||
sequencer.retireThread(thread);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = execute;
|
||||
+184
-1
@@ -4,6 +4,7 @@ const log = require('../util/log');
|
||||
const Thread = require('./thread');
|
||||
const {Map} = require('immutable');
|
||||
const cast = require('../util/cast');
|
||||
const compilerExecute = require('../compiler/executor');
|
||||
|
||||
/**
|
||||
* Single BlockUtility instance reused by execute for every pritimive ran.
|
||||
@@ -397,7 +398,189 @@ const _prepareBlockProfiling = function (profiler, blockCached) {
|
||||
* @param {!Thread} thread Thread which to read and execute.
|
||||
*/
|
||||
const execute = function (sequencer, thread) {
|
||||
thread.generator.next();
|
||||
if (thread.isCompiled) {
|
||||
compilerExecute(sequencer, thread);
|
||||
return;
|
||||
}
|
||||
|
||||
const runtime = sequencer.runtime;
|
||||
|
||||
// store sequencer and thread so block functions can access them through
|
||||
// convenience methods.
|
||||
blockUtility.sequencer = sequencer;
|
||||
blockUtility.thread = thread;
|
||||
|
||||
// Current block to execute is the one on the top of the stack.
|
||||
const currentBlockId = thread.peekStack();
|
||||
const currentStackFrame = thread.peekStackFrame();
|
||||
|
||||
let blockContainer = thread.blockContainer;
|
||||
let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);
|
||||
if (blockCached === null) {
|
||||
blockContainer = runtime.flyoutBlocks;
|
||||
blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);
|
||||
// Stop if block or target no longer exists.
|
||||
if (blockCached === null) {
|
||||
// No block found: stop the thread; script no longer exists.
|
||||
sequencer.retireThread(thread);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const ops = blockCached._ops;
|
||||
const length = ops.length;
|
||||
let i = 0;
|
||||
|
||||
if (currentStackFrame.reported !== null) {
|
||||
const reported = currentStackFrame.reported;
|
||||
// Reinstate all the previous values.
|
||||
for (; i < reported.length; i++) {
|
||||
const {opCached: oldOpCached, inputValue} = reported[i];
|
||||
|
||||
const opCached = ops.find(op => op.id === oldOpCached);
|
||||
|
||||
if (opCached) {
|
||||
const inputName = opCached._parentKey;
|
||||
const argValues = opCached._parentValues;
|
||||
|
||||
if (inputName === 'BROADCAST_INPUT') {
|
||||
// Something is plugged into the broadcast input.
|
||||
// Cast it to a string. We don't need an id here.
|
||||
argValues.BROADCAST_OPTION.id = null;
|
||||
argValues.BROADCAST_OPTION.name = cast.toString(inputValue);
|
||||
} else {
|
||||
argValues[inputName] = inputValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the last reported block that is still in the set of operations.
|
||||
// This way if the last operation was removed, we'll find the next
|
||||
// candidate. If an earlier block that was performed was removed then
|
||||
// we'll find the index where the last operation is now.
|
||||
if (reported.length > 0) {
|
||||
const lastExisting = reported.reverse().find(report => ops.find(op => op.id === report.opCached));
|
||||
if (lastExisting) {
|
||||
i = ops.findIndex(opCached => opCached.id === lastExisting.opCached) + 1;
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// The reporting block must exist and must be the next one in the sequence of operations.
|
||||
if (thread.justReported !== null && ops[i] && ops[i].id === currentStackFrame.reporting) {
|
||||
const opCached = ops[i];
|
||||
const inputValue = thread.justReported;
|
||||
|
||||
thread.justReported = null;
|
||||
|
||||
const inputName = opCached._parentKey;
|
||||
const argValues = opCached._parentValues;
|
||||
|
||||
if (inputName === 'BROADCAST_INPUT') {
|
||||
// Something is plugged into the broadcast input.
|
||||
// Cast it to a string. We don't need an id here.
|
||||
argValues.BROADCAST_OPTION.id = null;
|
||||
argValues.BROADCAST_OPTION.name = cast.toString(inputValue);
|
||||
} else {
|
||||
argValues[inputName] = inputValue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
currentStackFrame.reporting = null;
|
||||
currentStackFrame.reported = null;
|
||||
}
|
||||
|
||||
const start = i;
|
||||
|
||||
for (; i < length; i++) {
|
||||
const lastOperation = i === length - 1;
|
||||
const opCached = ops[i];
|
||||
|
||||
const blockFunction = opCached._blockFunction;
|
||||
|
||||
// Update values for arguments (inputs).
|
||||
const argValues = opCached._argValues;
|
||||
|
||||
// Fields are set during opCached initialization.
|
||||
|
||||
// Blocks should glow when a script is starting,
|
||||
// not after it has finished (see #1404).
|
||||
// Only blocks in blockContainers that don't forceNoGlow
|
||||
// should request a glow.
|
||||
if (!blockContainer.forceNoGlow) {
|
||||
thread.requestScriptGlowInFrame = true;
|
||||
}
|
||||
|
||||
// Inputs are set during previous steps in the loop.
|
||||
|
||||
const primitiveReportedValue = blockFunction(argValues, blockUtility);
|
||||
|
||||
// If it's a promise, wait until promise resolves.
|
||||
if (isPromise(primitiveReportedValue)) {
|
||||
handlePromise(primitiveReportedValue, sequencer, thread, opCached, lastOperation);
|
||||
|
||||
// Store the already reported values. They will be thawed into the
|
||||
// future versions of the same operations by block id. The reporting
|
||||
// operation if it is promise waiting will set its parent value at
|
||||
// that time.
|
||||
thread.justReported = null;
|
||||
currentStackFrame.reporting = ops[i].id;
|
||||
currentStackFrame.reported = ops.slice(0, i).map(reportedCached => {
|
||||
const inputName = reportedCached._parentKey;
|
||||
const reportedValues = reportedCached._parentValues;
|
||||
|
||||
if (inputName === 'BROADCAST_INPUT') {
|
||||
return {
|
||||
opCached: reportedCached.id,
|
||||
inputValue: reportedValues[inputName].BROADCAST_OPTION.name
|
||||
};
|
||||
}
|
||||
return {
|
||||
opCached: reportedCached.id,
|
||||
inputValue: reportedValues[inputName]
|
||||
};
|
||||
});
|
||||
|
||||
// We are waiting for a promise. Stop running this set of operations
|
||||
// and continue them later after thawing the reported values.
|
||||
break;
|
||||
} else if (thread.status === Thread.STATUS_RUNNING) {
|
||||
if (lastOperation) {
|
||||
handleReport(primitiveReportedValue, sequencer, thread, opCached, lastOperation);
|
||||
} else {
|
||||
// By definition a block that is not last in the list has a
|
||||
// parent.
|
||||
const inputName = opCached._parentKey;
|
||||
const parentValues = opCached._parentValues;
|
||||
|
||||
if (inputName === 'BROADCAST_INPUT') {
|
||||
// Something is plugged into the broadcast input.
|
||||
// Cast it to a string. We don't need an id here.
|
||||
parentValues.BROADCAST_OPTION.id = null;
|
||||
parentValues.BROADCAST_OPTION.name = cast.toString(primitiveReportedValue);
|
||||
} else {
|
||||
parentValues[inputName] = primitiveReportedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime.profiler !== null) {
|
||||
if (blockCached._profiler !== runtime.profiler) {
|
||||
_prepareBlockProfiling(runtime.profiler, blockCached);
|
||||
}
|
||||
// Determine the index that is after the last executed block. `i` is
|
||||
// currently the block that was just executed. `i + 1` will be the block
|
||||
// after that. `length` with the min call makes sure we don't try to
|
||||
// reference an operation outside of the set of operations.
|
||||
const end = Math.min(i + 1, length);
|
||||
for (let p = start; p < end; p++) {
|
||||
ops[p]._profilerFrame.count += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = execute;
|
||||
@@ -1591,7 +1591,15 @@ class Runtime extends EventEmitter {
|
||||
thread.pushStack(id);
|
||||
this.threads.push(thread);
|
||||
|
||||
thread.compile();
|
||||
// Attempt to compile the script
|
||||
// If compilation fails, fallback to the standard scratch-vm interpreter
|
||||
try {
|
||||
thread.compile();
|
||||
thread.isCompiled = true;
|
||||
} catch (e) {
|
||||
log.error('Cannot compile thread', e);
|
||||
thread.isCompiled = false;
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
+100
-9
@@ -1,6 +1,7 @@
|
||||
const Timer = require('../util/timer');
|
||||
const Thread = require('./thread');
|
||||
const execute = require('./execute.js');
|
||||
const compilerExecute = require('../compiler/executor');
|
||||
|
||||
/**
|
||||
* Profiler frame name for stepping a single thread.
|
||||
@@ -177,17 +178,107 @@ class Sequencer {
|
||||
* @param {!Thread} thread Thread object to step.
|
||||
*/
|
||||
stepThread (thread) {
|
||||
if (this.runtime.profiler !== null) {
|
||||
if (executeProfilerId === -1) {
|
||||
executeProfilerId = this.runtime.profiler.idByName(executeProfilerFrame);
|
||||
}
|
||||
|
||||
// Increment the number of times execute is called.
|
||||
this.runtime.profiler.increment(executeProfilerId);
|
||||
if (thread.isCompiled) {
|
||||
compilerExecute(this, thread);
|
||||
return;
|
||||
}
|
||||
execute(this, thread);
|
||||
|
||||
// TODO: all of the control flow for turbo, warp, ...
|
||||
let currentBlockId = thread.peekStack();
|
||||
if (!currentBlockId) {
|
||||
// A "null block" - empty branch.
|
||||
thread.popStack();
|
||||
|
||||
// Did the null follow a hat block?
|
||||
if (thread.stack.length === 0) {
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Save the current block ID to notice if we did control flow.
|
||||
while ((currentBlockId = thread.peekStack())) {
|
||||
let isWarpMode = thread.peekStackFrame().warpMode;
|
||||
if (isWarpMode && !thread.warpTimer) {
|
||||
// Initialize warp-mode timer if it hasn't been already.
|
||||
// This will start counting the thread toward `Sequencer.WARP_TIME`.
|
||||
thread.warpTimer = new Timer();
|
||||
thread.warpTimer.start();
|
||||
}
|
||||
// Execute the current block.
|
||||
if (this.runtime.profiler !== null) {
|
||||
if (executeProfilerId === -1) {
|
||||
executeProfilerId = this.runtime.profiler.idByName(executeProfilerFrame);
|
||||
}
|
||||
|
||||
// Increment the number of times execute is called.
|
||||
this.runtime.profiler.increment(executeProfilerId);
|
||||
}
|
||||
if (thread.target === null) {
|
||||
this.retireThread(thread);
|
||||
} else {
|
||||
execute(this, thread);
|
||||
}
|
||||
thread.blockGlowInFrame = currentBlockId;
|
||||
// If the thread has yielded or is waiting, yield to other threads.
|
||||
if (thread.status === Thread.STATUS_YIELD) {
|
||||
// Mark as running for next iteration.
|
||||
thread.status = Thread.STATUS_RUNNING;
|
||||
// In warp mode, yielded blocks are re-executed immediately.
|
||||
if (isWarpMode &&
|
||||
thread.warpTimer.timeElapsed() <= Sequencer.WARP_TIME) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
} else if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
||||
// A promise was returned by the primitive. Yield the thread
|
||||
// until the promise resolves. Promise resolution should reset
|
||||
// thread.status to Thread.STATUS_RUNNING.
|
||||
return;
|
||||
} else if (thread.status === Thread.STATUS_YIELD_TICK) {
|
||||
// stepThreads will reset the thread to Thread.STATUS_RUNNING
|
||||
return;
|
||||
}
|
||||
// If no control flow has happened, switch to next block.
|
||||
if (thread.peekStack() === currentBlockId) {
|
||||
thread.goToNextBlock();
|
||||
}
|
||||
// If no next block has been found at this point, look on the stack.
|
||||
while (!thread.peekStack()) {
|
||||
thread.popStack();
|
||||
|
||||
if (thread.stack.length === 0) {
|
||||
// No more stack to run!
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
const stackFrame = thread.peekStackFrame();
|
||||
isWarpMode = stackFrame.warpMode;
|
||||
|
||||
if (stackFrame.isLoop) {
|
||||
// The current level of the stack is marked as a loop.
|
||||
// Return to yield for the frame/tick in general.
|
||||
// Unless we're in warp mode - then only return if the
|
||||
// warp timer is up.
|
||||
if (!isWarpMode ||
|
||||
thread.warpTimer.timeElapsed() > Sequencer.WARP_TIME) {
|
||||
// Don't do anything to the stack, since loops need
|
||||
// to be re-executed.
|
||||
return;
|
||||
}
|
||||
// Don't go to the next block for this level of the stack,
|
||||
// since loops need to be re-executed.
|
||||
continue;
|
||||
|
||||
} else if (stackFrame.waitingReporter) {
|
||||
// This level of the stack was waiting for a value.
|
||||
// This means a reporter has just returned - so don't go
|
||||
// to the next block for this level of the stack.
|
||||
return;
|
||||
}
|
||||
// Get next block of existing block on the stack.
|
||||
thread.goToNextBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -189,6 +189,8 @@ class Thread {
|
||||
this.warpTimer = null;
|
||||
|
||||
this.justReported = null;
|
||||
|
||||
this.isCompiled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user