2016-06-17 15:10:12 -04:00
|
|
|
var Thread = require('./thread');
|
|
|
|
|
2016-08-23 18:12:32 -04:00
|
|
|
/**
|
|
|
|
* Utility function to determine if a value is a Promise.
|
|
|
|
* @param {*} value Value to check for a Promise.
|
|
|
|
* @return {Boolean} True if the value appears to be a Promise.
|
|
|
|
*/
|
|
|
|
var isPromise = function (value) {
|
|
|
|
return value && value.then && typeof value.then === 'function';
|
|
|
|
};
|
|
|
|
|
2016-06-17 15:10:12 -04:00
|
|
|
/**
|
|
|
|
* Execute a block.
|
|
|
|
* @param {!Sequencer} sequencer Which sequencer is executing.
|
|
|
|
* @param {!Thread} thread Thread which to read and execute.
|
|
|
|
*/
|
2016-06-17 15:53:58 -04:00
|
|
|
var execute = function (sequencer, thread) {
|
2016-06-09 13:27:30 -04:00
|
|
|
var runtime = sequencer.runtime;
|
2016-09-15 19:37:12 -04:00
|
|
|
var target = thread.target;
|
2016-06-09 13:27:30 -04:00
|
|
|
|
2016-06-09 17:08:30 -04:00
|
|
|
// Current block to execute is the one on the top of the stack.
|
|
|
|
var currentBlockId = thread.peekStack();
|
|
|
|
var currentStackFrame = thread.peekStackFrame();
|
|
|
|
|
2016-10-13 17:15:49 -04:00
|
|
|
// Check where the block lives: target blocks or flyout blocks.
|
|
|
|
var targetHasBlock = (
|
|
|
|
typeof target.blocks.getBlock(currentBlockId) !== 'undefined'
|
|
|
|
);
|
|
|
|
var flyoutHasBlock = (
|
|
|
|
typeof runtime.flyoutBlocks.getBlock(currentBlockId) !== 'undefined'
|
|
|
|
);
|
|
|
|
|
|
|
|
// Stop if block or target no longer exists.
|
|
|
|
if (!target || (!targetHasBlock && !flyoutHasBlock)) {
|
2016-09-15 13:51:40 -04:00
|
|
|
// No block found: stop the thread; script no longer exists.
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
return;
|
|
|
|
}
|
2016-10-13 17:15:49 -04:00
|
|
|
|
2016-09-12 13:14:16 -04:00
|
|
|
// Query info about the block.
|
2016-10-13 17:15:49 -04:00
|
|
|
var blockContainer = null;
|
|
|
|
if (targetHasBlock) {
|
|
|
|
blockContainer = target.blocks;
|
|
|
|
} else {
|
|
|
|
blockContainer = runtime.flyoutBlocks;
|
|
|
|
}
|
|
|
|
var opcode = blockContainer.getOpcode(currentBlockId);
|
|
|
|
var fields = blockContainer.getFields(currentBlockId);
|
|
|
|
var inputs = blockContainer.getInputs(currentBlockId);
|
2016-09-12 13:14:16 -04:00
|
|
|
var blockFunction = runtime.getOpcodeFunction(opcode);
|
|
|
|
var isHat = runtime.getIsHat(opcode);
|
2016-10-13 17:15:49 -04:00
|
|
|
|
2016-06-09 13:27:30 -04:00
|
|
|
|
2016-06-17 17:18:44 -04:00
|
|
|
if (!opcode) {
|
|
|
|
console.warn('Could not get opcode for block: ' + currentBlockId);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-09-12 13:14:16 -04:00
|
|
|
/**
|
|
|
|
* Handle any reported value from the primitive, either directly returned
|
|
|
|
* or after a promise resolves.
|
|
|
|
* @param {*} resolvedValue Value eventually returned from the primitive.
|
|
|
|
*/
|
|
|
|
var handleReport = function (resolvedValue) {
|
|
|
|
thread.pushReportedValue(resolvedValue);
|
|
|
|
if (isHat) {
|
|
|
|
// Hat predicate was evaluated.
|
|
|
|
if (runtime.getIsEdgeActivatedHat(opcode)) {
|
|
|
|
// If this is an edge-activated hat, only proceed if
|
|
|
|
// the value is true and used to be false.
|
|
|
|
var oldEdgeValue = runtime.updateEdgeActivatedValue(
|
|
|
|
currentBlockId,
|
|
|
|
resolvedValue
|
|
|
|
);
|
|
|
|
var edgeWasActivated = !oldEdgeValue && resolvedValue;
|
|
|
|
if (!edgeWasActivated) {
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Not an edge-activated hat: retire the thread
|
|
|
|
// if predicate was false.
|
|
|
|
if (!resolvedValue) {
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// In a non-hat, report the value visually if necessary if
|
|
|
|
// at the top of the thread stack.
|
|
|
|
if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
|
|
|
|
runtime.visualReport(currentBlockId, resolvedValue);
|
|
|
|
}
|
|
|
|
// Finished any yields.
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.status = Thread.STATUS_RUNNING;
|
2016-09-12 13:14:16 -04:00
|
|
|
}
|
|
|
|
};
|
2016-08-23 18:12:32 -04:00
|
|
|
|
2016-09-12 13:14:16 -04:00
|
|
|
// Hats and single-field shadows are implemented slightly differently
|
|
|
|
// from regular blocks.
|
|
|
|
// For hats: if they have an associated block function,
|
|
|
|
// it's treated as a predicate; if not, execution will proceed as a no-op.
|
|
|
|
// For single-field shadows: If the block has a single field, and no inputs,
|
|
|
|
// immediately return the value of the field.
|
2016-08-24 11:04:23 -04:00
|
|
|
if (!blockFunction) {
|
2016-09-12 13:14:16 -04:00
|
|
|
if (isHat) {
|
|
|
|
// Skip through the block (hat with no predicate).
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if (Object.keys(fields).length == 1 &&
|
|
|
|
Object.keys(inputs).length == 0) {
|
|
|
|
// One field and no inputs - treat as arg.
|
|
|
|
for (var fieldKey in fields) { // One iteration.
|
|
|
|
handleReport(fields[fieldKey].value);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.warn('Could not get implementation for opcode: ' +
|
|
|
|
opcode);
|
|
|
|
}
|
|
|
|
thread.requestScriptGlowInFrame = true;
|
|
|
|
return;
|
2016-08-23 18:12:32 -04:00
|
|
|
}
|
2016-06-17 17:18:44 -04:00
|
|
|
}
|
|
|
|
|
2016-06-09 13:28:50 -04:00
|
|
|
// Generate values for arguments (inputs).
|
2016-06-09 13:27:30 -04:00
|
|
|
var argValues = {};
|
|
|
|
|
2016-06-09 13:28:50 -04:00
|
|
|
// Add all fields on this block to the argValues.
|
|
|
|
for (var fieldName in fields) {
|
2016-06-09 14:27:11 -04:00
|
|
|
argValues[fieldName] = fields[fieldName].value;
|
2016-06-09 13:28:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Recursively evaluate input blocks.
|
|
|
|
for (var inputName in inputs) {
|
|
|
|
var input = inputs[inputName];
|
|
|
|
var inputBlockId = input.block;
|
2016-06-20 14:24:00 -04:00
|
|
|
// Is there no value for this input waiting in the stack frame?
|
2016-10-17 23:23:16 -04:00
|
|
|
if (typeof currentStackFrame.reported[inputName] === 'undefined'
|
|
|
|
&& inputBlockId) {
|
2016-06-20 14:24:00 -04:00
|
|
|
// If there's not, we need to evaluate the block.
|
2016-10-17 23:23:16 -04:00
|
|
|
// Push to the stack to evaluate the reporter block.
|
|
|
|
thread.pushStack(inputBlockId);
|
|
|
|
// Save name of input for `Thread.pushReportedValue`.
|
|
|
|
currentStackFrame.waitingReporter = inputName;
|
|
|
|
// Actually execute the block.
|
|
|
|
execute(sequencer, thread);
|
|
|
|
if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
2016-06-17 15:10:12 -04:00
|
|
|
return;
|
2016-10-17 23:23:16 -04:00
|
|
|
} else {
|
|
|
|
// Execution returned immediately,
|
|
|
|
// and presumably a value was reported, so pop the stack.
|
|
|
|
currentStackFrame.waitingReporter = null;
|
|
|
|
thread.popStack();
|
2016-06-17 15:10:12 -04:00
|
|
|
}
|
|
|
|
}
|
2016-06-17 17:18:44 -04:00
|
|
|
argValues[inputName] = currentStackFrame.reported[inputName];
|
2016-06-09 13:27:30 -04:00
|
|
|
}
|
|
|
|
|
2016-10-03 17:43:24 -04:00
|
|
|
// Add any mutation to args (e.g., for procedures).
|
2016-10-13 17:15:49 -04:00
|
|
|
var mutation = blockContainer.getMutation(currentBlockId);
|
2016-10-03 17:43:24 -04:00
|
|
|
if (mutation) {
|
|
|
|
argValues.mutation = mutation;
|
|
|
|
}
|
|
|
|
|
2016-06-20 14:29:47 -04:00
|
|
|
// If we've gotten this far, all of the input blocks are evaluated,
|
|
|
|
// and `argValues` is fully populated. So, execute the block primitive.
|
|
|
|
// First, clear `currentStackFrame.reported`, so any subsequent execution
|
2016-08-10 11:27:21 -04:00
|
|
|
// (e.g., on return from a branch) gets fresh inputs.
|
2016-06-20 14:29:47 -04:00
|
|
|
currentStackFrame.reported = {};
|
|
|
|
|
2016-06-20 14:31:48 -04:00
|
|
|
var primitiveReportedValue = null;
|
|
|
|
primitiveReportedValue = blockFunction(argValues, {
|
2016-08-23 18:12:32 -04:00
|
|
|
stackFrame: currentStackFrame.executionContext,
|
|
|
|
target: target,
|
2016-07-01 10:44:43 -04:00
|
|
|
yield: function() {
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.status = Thread.STATUS_YIELD;
|
2016-07-01 10:44:43 -04:00
|
|
|
},
|
2016-10-17 23:23:16 -04:00
|
|
|
startBranch: function (branchNum, isLoop) {
|
|
|
|
sequencer.stepToBranch(thread, branchNum, isLoop);
|
2016-06-29 20:56:55 -04:00
|
|
|
},
|
2016-10-13 23:00:46 -04:00
|
|
|
stopAll: function () {
|
|
|
|
runtime.stopAll();
|
|
|
|
},
|
|
|
|
stopOtherTargetThreads: function() {
|
|
|
|
runtime.stopForTarget(target, thread);
|
|
|
|
},
|
|
|
|
stopThread: function() {
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
},
|
2016-10-17 23:23:16 -04:00
|
|
|
startProcedure: function (procedureCode) {
|
|
|
|
sequencer.stepToProcedure(thread, procedureCode);
|
2016-10-03 17:43:24 -04:00
|
|
|
},
|
2016-10-17 23:23:16 -04:00
|
|
|
getProcedureParamNames: function (procedureCode) {
|
|
|
|
return blockContainer.getProcedureParamNames(procedureCode);
|
2016-10-13 13:11:26 -04:00
|
|
|
},
|
|
|
|
pushParam: function (paramName, paramValue) {
|
|
|
|
thread.pushParam(paramName, paramValue);
|
|
|
|
},
|
|
|
|
getParam: function (paramName) {
|
|
|
|
return thread.getParam(paramName);
|
|
|
|
},
|
2016-08-29 10:26:26 -04:00
|
|
|
startHats: function(requestedHat, opt_matchFields, opt_target) {
|
2016-08-23 18:12:32 -04:00
|
|
|
return (
|
2016-08-29 10:26:26 -04:00
|
|
|
runtime.startHats(requestedHat, opt_matchFields, opt_target)
|
2016-08-23 18:12:32 -04:00
|
|
|
);
|
|
|
|
},
|
2016-08-15 21:37:36 -04:00
|
|
|
ioQuery: function (device, func, args) {
|
|
|
|
// Find the I/O device and execute the query/function call.
|
|
|
|
if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
|
|
|
|
var devObject = runtime.ioDevices[device];
|
|
|
|
return devObject[func].call(devObject, args);
|
|
|
|
}
|
|
|
|
}
|
2016-06-13 11:23:39 -04:00
|
|
|
});
|
2016-06-24 11:14:22 -04:00
|
|
|
|
2016-09-08 09:40:53 -04:00
|
|
|
if (typeof primitiveReportedValue === 'undefined') {
|
|
|
|
// No value reported - potentially a command block.
|
|
|
|
// Edge-activated hats don't request a glow; all commands do.
|
|
|
|
thread.requestScriptGlowInFrame = true;
|
|
|
|
}
|
|
|
|
|
2016-06-24 11:14:22 -04:00
|
|
|
// If it's a promise, wait until promise resolves.
|
2016-08-23 18:12:32 -04:00
|
|
|
if (isPromise(primitiveReportedValue)) {
|
2016-06-28 13:39:44 -04:00
|
|
|
if (thread.status === Thread.STATUS_RUNNING) {
|
|
|
|
// Primitive returned a promise; automatically yield thread.
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.status = Thread.STATUS_PROMISE_WAIT;
|
2016-06-28 13:39:44 -04:00
|
|
|
}
|
|
|
|
// Promise handlers
|
2016-06-24 11:14:22 -04:00
|
|
|
primitiveReportedValue.then(function(resolvedValue) {
|
2016-08-23 18:12:32 -04:00
|
|
|
handleReport(resolvedValue);
|
2016-10-17 23:23:16 -04:00
|
|
|
if (typeof resolvedValue !== 'undefined') {
|
|
|
|
thread.popStack();
|
|
|
|
} else {
|
|
|
|
var popped = thread.popStack();
|
|
|
|
var nextBlockId = thread.target.blocks.getNextBlock(popped);
|
|
|
|
thread.pushStack(nextBlockId);
|
|
|
|
}
|
2016-06-24 11:19:38 -04:00
|
|
|
}, function(rejectionReason) {
|
|
|
|
// Promise rejected: the primitive had some error.
|
|
|
|
// Log it and proceed.
|
2016-06-30 17:13:43 -04:00
|
|
|
console.warn('Primitive rejected promise: ', rejectionReason);
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.status = Thread.STATUS_RUNNING;
|
|
|
|
thread.popStack();
|
2016-06-28 13:39:44 -04:00
|
|
|
});
|
2016-06-24 11:14:22 -04:00
|
|
|
} else if (thread.status === Thread.STATUS_RUNNING) {
|
2016-08-23 18:12:32 -04:00
|
|
|
handleReport(primitiveReportedValue);
|
2016-06-20 14:11:21 -04:00
|
|
|
}
|
2016-06-09 13:27:30 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = execute;
|