var Thread = require('./thread'); /** * If set, block calls, args, and return values will be logged to the console. * @const {boolean} */ var DEBUG_BLOCK_CALLS = true; /** * Execute a block. * @param {!Sequencer} sequencer Which sequencer is executing. * @param {!Thread} thread Thread which to read and execute. */ var execute = function (sequencer, thread) { var runtime = sequencer.runtime; // Current block to execute is the one on the top of the stack. var currentBlockId = thread.peekStack(); var currentStackFrame = thread.peekStackFrame(); var opcode = runtime.blocks.getOpcode(currentBlockId); if (!opcode) { console.warn('Could not get opcode for block: ' + currentBlockId); return; } var blockFunction = runtime.getOpcodeFunction(opcode); if (!blockFunction) { console.warn('Could not get implementation for opcode: ' + opcode); return; } // Generate values for arguments (inputs). var argValues = {}; // Add all fields on this block to the argValues. var fields = runtime.blocks.getFields(currentBlockId); for (var fieldName in fields) { argValues[fieldName] = fields[fieldName].value; } // Recursively evaluate input blocks. var inputs = runtime.blocks.getInputs(currentBlockId); for (var inputName in inputs) { var input = inputs[inputName]; var inputBlockId = input.block; // Is there no value for this input waiting in the stack frame? if (typeof currentStackFrame.reported[inputName] === 'undefined') { // If there's not, we need to evaluate the block. var reporterYielded = ( sequencer.stepToReporter(thread, inputBlockId, inputName) ); // If the reporter yielded, return immediately; // it needs time to finish and report its value. if (reporterYielded) { return; } } argValues[inputName] = currentStackFrame.reported[inputName]; } // 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 // (e.g., on return from a substack) gets fresh inputs. currentStackFrame.reported = {}; if (DEBUG_BLOCK_CALLS) { console.groupCollapsed('Executing: ' + opcode); console.log('with arguments: ', argValues); console.log('and stack frame: ', currentStackFrame); } var primitiveReportedValue = null; primitiveReportedValue = blockFunction(argValues, { yield: thread.yield.bind(thread), yieldAndBlock: thread.yieldAndBlock.bind(thread), done: function() { sequencer.proceedThread(thread); }, timeout: thread.addTimeout.bind(thread), stackFrame: currentStackFrame.executionContext, startSubstack: function (substackNum) { sequencer.stepToSubstack(thread, substackNum); } }); // Deal with any reported value. // If it's a promise, wait until promise resolves. var isPromise = ( primitiveReportedValue && primitiveReportedValue.then && typeof primitiveReportedValue.then === 'function' ); if (isPromise) { primitiveReportedValue.then(function(resolvedValue) { // Promise resolved: the primitive reported a value. if (DEBUG_BLOCK_CALLS) { console.log('reporting value: ', resolvedValue); } thread.pushReportedValue(resolvedValue); sequencer.proceedThread(thread); }, function(rejectionReason) { // Promise rejected: the primitive had some error. // Log it and proceed. if (DEBUG_BLOCK_CALLS) { console.warn('primitive rejected promise: ', rejectionReason); } sequencer.proceedThread(thread); } ); } else if (thread.status === Thread.STATUS_RUNNING) { if (DEBUG_BLOCK_CALLS) { console.log('reporting value: ', primitiveReportedValue); } thread.pushReportedValue(primitiveReportedValue); } if (DEBUG_BLOCK_CALLS) { console.log('ending stack frame: ', currentStackFrame); console.groupEnd(); } }; module.exports = execute;