mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-11 10:39:56 -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() {
|
Scratch3ProcedureBlocks.prototype.getPrimitives = function() {
|
||||||
return {
|
return {
|
||||||
'procedures_defnoreturn': this.defNoReturn,
|
'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) {
|
Scratch3ProcedureBlocks.prototype.callNoReturn = function (args, util) {
|
||||||
if (!util.stackFrame.executed) {
|
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.stackFrame.executed = true;
|
||||||
util.startProcedure(procedureName);
|
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;
|
module.exports = Scratch3ProcedureBlocks;
|
||||||
|
|
|
@ -151,13 +151,30 @@ Blocks.prototype.getProcedureDefinition = function (name) {
|
||||||
var block = this._blocks[id];
|
var block = this._blocks[id];
|
||||||
if ((block.opcode == 'procedures_defnoreturn' ||
|
if ((block.opcode == 'procedures_defnoreturn' ||
|
||||||
block.opcode == 'procedures_defreturn') &&
|
block.opcode == 'procedures_defreturn') &&
|
||||||
block.fields['NAME'].value == name) {
|
block.mutation.proccode == name) {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
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;
|
var mutationString = '<' + mutation.tagName;
|
||||||
for (var prop in mutation) {
|
for (var prop in mutation) {
|
||||||
if (prop == 'children' || prop == 'tagName') continue;
|
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 += '>';
|
mutationString += '>';
|
||||||
for (var i = 0; i < mutation.children.length; i++) {
|
for (var i = 0; i < mutation.children.length; i++) {
|
||||||
|
|
|
@ -164,6 +164,15 @@ var execute = function (sequencer, thread) {
|
||||||
startProcedure: function (procedureName) {
|
startProcedure: function (procedureName) {
|
||||||
sequencer.stepToProcedure(thread, 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) {
|
startHats: function(requestedHat, opt_matchFields, opt_target) {
|
||||||
return (
|
return (
|
||||||
runtime.startHats(requestedHat, opt_matchFields, opt_target)
|
runtime.startHats(requestedHat, opt_matchFields, opt_target)
|
||||||
|
|
|
@ -132,6 +132,11 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
||||||
Sequencer.prototype.stepToProcedure = function (thread, procedureName) {
|
Sequencer.prototype.stepToProcedure = function (thread, procedureName) {
|
||||||
var definition = thread.target.blocks.getProcedureDefinition(procedureName);
|
var definition = thread.target.blocks.getProcedureDefinition(procedureName);
|
||||||
thread.pushStack(definition);
|
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({
|
this.stackFrames.push({
|
||||||
reported: {}, // Collects reported input values.
|
reported: {}, // Collects reported input values.
|
||||||
waitingReporter: null, // Name of waiting reporter.
|
waitingReporter: null, // Name of waiting reporter.
|
||||||
|
params: {}, // Procedure parameters.
|
||||||
executionContext: {} // A context passed to block implementations.
|
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.
|
* 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.
|
* @return {Boolean} True if execution is at top of the stack.
|
||||||
|
|
|
@ -203,6 +203,40 @@ function flatten (blocks) {
|
||||||
return finalBlocks;
|
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.
|
* Parse a single SB2 JSON-formatted block and its children.
|
||||||
* @param {!Object} sb2block SB2 JSON-formatted block.
|
* @param {!Object} sb2block SB2 JSON-formatted block.
|
||||||
|
@ -227,6 +261,10 @@ function parseBlock (sb2block) {
|
||||||
shadow: false, // No shadow blocks in an SB2 by default.
|
shadow: false, // No shadow blocks in an SB2 by default.
|
||||||
children: [] // Store any generated children, flattened in `flatten`.
|
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.`
|
// Look at the expected arguments in `blockMetadata.argMap.`
|
||||||
// The basic problem here is to turn positional SB2 arguments into
|
// The basic problem here is to turn positional SB2 arguments into
|
||||||
// non-positional named Scratch VM arguments.
|
// non-positional named Scratch VM arguments.
|
||||||
|
@ -328,11 +366,43 @@ function parseBlock (sb2block) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Special cases to generate mutations.
|
// 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 = {
|
activeBlock.mutation = {
|
||||||
tagName: 'mutation',
|
tagName: 'mutation',
|
||||||
children: [],
|
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;
|
return activeBlock;
|
||||||
|
|
|
@ -752,9 +752,8 @@ var specMap = {
|
||||||
'opcode':'control_stop',
|
'opcode':'control_stop',
|
||||||
'argMap':[
|
'argMap':[
|
||||||
{
|
{
|
||||||
'type':'input',
|
'type':'field',
|
||||||
'inputOp':'control_stop_menu',
|
'fieldName':'STOP_OPTION'
|
||||||
'inputName':'STOP_OPTION'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1375,15 +1374,10 @@ var specMap = {
|
||||||
},
|
},
|
||||||
'procDef':{
|
'procDef':{
|
||||||
'opcode':'procedures_defnoreturn',
|
'opcode':'procedures_defnoreturn',
|
||||||
'argMap':[
|
'argMap':[]
|
||||||
{
|
|
||||||
'type':'field',
|
|
||||||
'fieldName':'NAME'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
'getParam':{
|
'getParam':{
|
||||||
'opcode':'proc_param',
|
'opcode':'procedures_param',
|
||||||
'argMap':[]
|
'argMap':[]
|
||||||
},
|
},
|
||||||
'call':{
|
'call':{
|
||||||
|
|
Loading…
Reference in a new issue