mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Procedure blocks (#264)
This commit is contained in:
parent
c9c2cc560e
commit
8c654bbe60
7 changed files with 144 additions and 16 deletions
|
@ -13,7 +13,8 @@ function Scratch3ProcedureBlocks(runtime) {
|
|||
Scratch3ProcedureBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'procedures_defnoreturn': this.defNoReturn,
|
||||
'procedures_callnoreturn': this.callNoReturn
|
||||
'procedures_callnoreturn': this.callNoReturn,
|
||||
'procedures_param': this.param
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -23,10 +24,24 @@ Scratch3ProcedureBlocks.prototype.defNoReturn = function () {
|
|||
|
||||
Scratch3ProcedureBlocks.prototype.callNoReturn = function (args, util) {
|
||||
if (!util.stackFrame.executed) {
|
||||
var procedureName = args.mutation.name;
|
||||
var procedureName = args.mutation.proccode;
|
||||
var paramNames = util.getProcedureParamNames(procedureName);
|
||||
for (var i = 0; i < paramNames.length; i++) {
|
||||
if (args.hasOwnProperty('input' + i)) {
|
||||
util.pushParam(paramNames[i], args['input' + i]);
|
||||
}
|
||||
}
|
||||
util.stackFrame.executed = true;
|
||||
util.startProcedure(procedureName);
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ProcedureBlocks.prototype.param = function (args, util) {
|
||||
var value = util.getParam(args.mutation.paramname);
|
||||
if (!value) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
module.exports = Scratch3ProcedureBlocks;
|
||||
|
|
|
@ -151,13 +151,30 @@ Blocks.prototype.getProcedureDefinition = function (name) {
|
|||
var block = this._blocks[id];
|
||||
if ((block.opcode == 'procedures_defnoreturn' ||
|
||||
block.opcode == 'procedures_defreturn') &&
|
||||
block.fields['NAME'].value == name) {
|
||||
block.mutation.proccode == name) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the procedure definition for a given name.
|
||||
* @param {?string} name Name of procedure to query.
|
||||
* @return {?string} ID of procedure definition.
|
||||
*/
|
||||
Blocks.prototype.getProcedureParamNames = function (name) {
|
||||
for (var id in this._blocks) {
|
||||
var block = this._blocks[id];
|
||||
if ((block.opcode == 'procedures_defnoreturn' ||
|
||||
block.opcode == 'procedures_defreturn') &&
|
||||
block.mutation.proccode == name) {
|
||||
return JSON.parse(block.mutation.argumentnames);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
@ -434,7 +451,9 @@ Blocks.prototype.mutationToXML = function (mutation) {
|
|||
var mutationString = '<' + mutation.tagName;
|
||||
for (var prop in mutation) {
|
||||
if (prop == 'children' || prop == 'tagName') continue;
|
||||
mutationString += ' ' + prop + '="' + mutation[prop] + '"';
|
||||
var mutationValue = (typeof mutation[prop] === 'string') ?
|
||||
xmlEscape(mutation[prop]) : mutation[prop];
|
||||
mutationString += ' ' + prop + '="' + mutationValue + '"';
|
||||
}
|
||||
mutationString += '>';
|
||||
for (var i = 0; i < mutation.children.length; i++) {
|
||||
|
|
|
@ -164,6 +164,15 @@ var execute = function (sequencer, thread) {
|
|||
startProcedure: function (procedureName) {
|
||||
sequencer.stepToProcedure(thread, procedureName);
|
||||
},
|
||||
getProcedureParamNames: function (procedureName) {
|
||||
return thread.target.blocks.getProcedureParamNames(procedureName);
|
||||
},
|
||||
pushParam: function (paramName, paramValue) {
|
||||
thread.pushParam(paramName, paramValue);
|
||||
},
|
||||
getParam: function (paramName) {
|
||||
return thread.getParam(paramName);
|
||||
},
|
||||
startHats: function(requestedHat, opt_matchFields, opt_target) {
|
||||
return (
|
||||
runtime.startHats(requestedHat, opt_matchFields, opt_target)
|
||||
|
|
|
@ -132,6 +132,11 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
|||
Sequencer.prototype.stepToProcedure = function (thread, procedureName) {
|
||||
var definition = thread.target.blocks.getProcedureDefinition(procedureName);
|
||||
thread.pushStack(definition);
|
||||
// Check if the call is recursive. If so, yield.
|
||||
// @todo: Have behavior match Scratch 2.0.
|
||||
if (thread.stack.indexOf(definition) > -1) {
|
||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -83,6 +83,7 @@ Thread.prototype.pushStack = function (blockId) {
|
|||
this.stackFrames.push({
|
||||
reported: {}, // Collects reported input values.
|
||||
waitingReporter: null, // Name of waiting reporter.
|
||||
params: {}, // Procedure parameters.
|
||||
executionContext: {} // A context passed to block implementations.
|
||||
});
|
||||
}
|
||||
|
@ -135,6 +136,21 @@ Thread.prototype.pushReportedValue = function (value) {
|
|||
}
|
||||
};
|
||||
|
||||
Thread.prototype.pushParam = function (paramName, value) {
|
||||
var stackFrame = this.peekStackFrame();
|
||||
stackFrame.params[paramName] = value;
|
||||
};
|
||||
|
||||
Thread.prototype.getParam = function (paramName) {
|
||||
for (var i = this.stackFrames.length - 1; i >= 0; i--) {
|
||||
var frame = this.stackFrames[i];
|
||||
if (frame.params.hasOwnProperty(paramName)) {
|
||||
return frame.params[paramName];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the current execution of a thread is at the top of the stack.
|
||||
* @return {Boolean} True if execution is at top of the stack.
|
||||
|
|
|
@ -203,6 +203,40 @@ function flatten (blocks) {
|
|||
return finalBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Scratch 2.0 procedure string (e.g., "my_procedure %s %b %n")
|
||||
* into an argument map. This allows us to provide the expected inputs
|
||||
* to a mutated procedure call.
|
||||
* @param {string} procCode Scratch 2.0 procedure string.
|
||||
* @return {Object} Argument map compatible with those in sb2specmap.
|
||||
*/
|
||||
function parseProcedureArgMap (procCode) {
|
||||
var argMap = [
|
||||
{} // First item in list is op string.
|
||||
];
|
||||
var INPUT_PREFIX = 'input';
|
||||
var inputCount = 0;
|
||||
// Split by %n, %b, %s.
|
||||
var parts = procCode.split(/(?=[^\\]\%[nbs])/);
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i].trim();
|
||||
if (part.substring(0, 1) == '%') {
|
||||
var argType = part.substring(1, 2);
|
||||
var arg = {
|
||||
type: 'input',
|
||||
inputName: INPUT_PREFIX + (inputCount++)
|
||||
};
|
||||
if (argType == 'n') {
|
||||
arg.inputOp = 'math_number';
|
||||
} else if (argType == 's') {
|
||||
arg.inputOp = 'text';
|
||||
}
|
||||
argMap.push(arg);
|
||||
}
|
||||
}
|
||||
return argMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single SB2 JSON-formatted block and its children.
|
||||
* @param {!Object} sb2block SB2 JSON-formatted block.
|
||||
|
@ -227,6 +261,10 @@ function parseBlock (sb2block) {
|
|||
shadow: false, // No shadow blocks in an SB2 by default.
|
||||
children: [] // Store any generated children, flattened in `flatten`.
|
||||
};
|
||||
// For a procedure call, generate argument map from proc string.
|
||||
if (oldOpcode == 'call') {
|
||||
blockMetadata.argMap = parseProcedureArgMap(sb2block[1]);
|
||||
}
|
||||
// Look at the expected arguments in `blockMetadata.argMap.`
|
||||
// The basic problem here is to turn positional SB2 arguments into
|
||||
// non-positional named Scratch VM arguments.
|
||||
|
@ -328,11 +366,43 @@ function parseBlock (sb2block) {
|
|||
}
|
||||
}
|
||||
// Special cases to generate mutations.
|
||||
if (oldOpcode == 'call') {
|
||||
if (oldOpcode == 'stopScripts') {
|
||||
// Mutation for stop block: if the argument is 'other scripts',
|
||||
// the block needs a next connection.
|
||||
if (sb2block[1] == 'other scripts in sprite') {
|
||||
activeBlock.mutation = {
|
||||
tagName: 'mutation',
|
||||
hasnext: 'true',
|
||||
children: []
|
||||
};
|
||||
}
|
||||
} else if (oldOpcode == 'procDef') {
|
||||
// Mutation for procedure definition:
|
||||
// store all 2.0 proc data.
|
||||
var procData = sb2block.slice(1);
|
||||
activeBlock.mutation = {
|
||||
tagName: 'mutation',
|
||||
proccode: procData[0], // e.g., "abc %n %b %s"
|
||||
argumentnames: JSON.stringify(procData[1]), // e.g. ['arg1', 'arg2']
|
||||
argumentdefaults: JSON.stringify(procData[2]), // e.g., [1, 'abc']
|
||||
warp: procData[3], // Warp mode, e.g., true/false.
|
||||
children: []
|
||||
};
|
||||
} else if (oldOpcode == 'call') {
|
||||
// Mutation for procedure call:
|
||||
// string for proc code (e.g., "abc %n %b %s").
|
||||
activeBlock.mutation = {
|
||||
tagName: 'mutation',
|
||||
children: [],
|
||||
name: sb2block[1]
|
||||
proccode: sb2block[1]
|
||||
};
|
||||
} else if (oldOpcode == 'getParam') {
|
||||
// Mutation for procedure parameter.
|
||||
activeBlock.mutation = {
|
||||
tagName: 'mutation',
|
||||
children: [],
|
||||
paramname: sb2block[1], // Name of parameter.
|
||||
shape: sb2block[2] // Shape - in 2.0, 'r' or 'b'.
|
||||
};
|
||||
}
|
||||
return activeBlock;
|
||||
|
|
|
@ -752,9 +752,8 @@ var specMap = {
|
|||
'opcode':'control_stop',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'control_stop_menu',
|
||||
'inputName':'STOP_OPTION'
|
||||
'type':'field',
|
||||
'fieldName':'STOP_OPTION'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1375,15 +1374,10 @@ var specMap = {
|
|||
},
|
||||
'procDef':{
|
||||
'opcode':'procedures_defnoreturn',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'field',
|
||||
'fieldName':'NAME'
|
||||
}
|
||||
]
|
||||
'argMap':[]
|
||||
},
|
||||
'getParam':{
|
||||
'opcode':'proc_param',
|
||||
'opcode':'procedures_param',
|
||||
'argMap':[]
|
||||
},
|
||||
'call':{
|
||||
|
|
Loading…
Reference in a new issue