mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 06:23:37 -05:00
Mutations in block representation; an unfeatured procedure call (#212)
* Add scratch3_procedures and no-op for defnoreturn * Add mutation adapter to parse mutations in CREATE/CHANGE events * Add mutation-to-XML * Update spec map for Blockly procedure names * Placeholder for procedure special cases * Basic stepping to procedures * Remove extra case * Validation for changeBlock
This commit is contained in:
parent
dd624aea06
commit
0a66c62f6a
9 changed files with 171 additions and 8 deletions
32
src/blocks/scratch3_procedures.js
Normal file
32
src/blocks/scratch3_procedures.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
function Scratch3ProcedureBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3ProcedureBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'procedures_defnoreturn': this.defNoReturn,
|
||||
'procedures_callnoreturn': this.callNoReturn
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3ProcedureBlocks.prototype.defNoReturn = function () {
|
||||
// No-op: execute the blocks.
|
||||
};
|
||||
|
||||
Scratch3ProcedureBlocks.prototype.callNoReturn = function (args, util) {
|
||||
if (!util.stackFrame.executed) {
|
||||
var procedureName = args.mutation.name;
|
||||
util.stackFrame.executed = true;
|
||||
util.startProcedure(procedureName);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Scratch3ProcedureBlocks;
|
|
@ -1,3 +1,4 @@
|
|||
var mutationAdapter = require('./mutation-adapter');
|
||||
var html = require('htmlparser2');
|
||||
|
||||
/**
|
||||
|
@ -138,6 +139,9 @@ function domToBlock (blockDOM, blocks, isTopBlock, parent) {
|
|||
// Link next block to this block.
|
||||
block.next = childBlockNode.attribs.id;
|
||||
break;
|
||||
case 'mutation':
|
||||
block.mutation = mutationAdapter(xmlChild);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var adapter = require('./adapter');
|
||||
var mutationAdapter = require('./mutation-adapter');
|
||||
var xmlEscape = require('../util/xml-escape');
|
||||
|
||||
/**
|
||||
|
@ -116,6 +117,16 @@ Blocks.prototype.getInputs = function (id) {
|
|||
return inputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get mutation data for a block.
|
||||
* @param {?string} id ID of block to query.
|
||||
* @return {!Object} Mutation for the block.
|
||||
*/
|
||||
Blocks.prototype.getMutation = function (id) {
|
||||
if (typeof this._blocks[id] === 'undefined') return null;
|
||||
return this._blocks[id].mutation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the top-level script for a given block.
|
||||
* @param {?string} id ID of block to query.
|
||||
|
@ -130,6 +141,23 @@ Blocks.prototype.getTopLevelScript = function (id) {
|
|||
return block.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the procedure definition for a given name.
|
||||
* @param {?string} name Name of procedure to query.
|
||||
* @return {?string} ID of procedure definition.
|
||||
*/
|
||||
Blocks.prototype.getProcedureDefinition = function (name) {
|
||||
for (var id in this._blocks) {
|
||||
var block = this._blocks[id];
|
||||
if ((block.opcode == 'procedures_defnoreturn' ||
|
||||
block.opcode == 'procedures_defreturn') &&
|
||||
block.fields['NAME'].value == name) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
@ -226,12 +254,16 @@ Blocks.prototype.createBlock = function (block, opt_isFlyoutBlock) {
|
|||
*/
|
||||
Blocks.prototype.changeBlock = function (args) {
|
||||
// Validate
|
||||
if (args.element !== 'field') return;
|
||||
if (args.element !== 'field' && args.element !== 'mutation') return;
|
||||
if (typeof this._blocks[args.id] === 'undefined') return;
|
||||
if (typeof this._blocks[args.id].fields[args.name] === 'undefined') return;
|
||||
|
||||
if (args.element == 'field') {
|
||||
// Update block value
|
||||
if (!this._blocks[args.id].fields[args.name]) return;
|
||||
this._blocks[args.id].fields[args.name].value = args.value;
|
||||
} else if (args.element == 'mutation') {
|
||||
this._blocks[args.id].mutation = mutationAdapter(args.value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -355,6 +387,10 @@ Blocks.prototype.blockToXML = function (blockId) {
|
|||
' type="' + block.opcode + '"' +
|
||||
xy +
|
||||
'>';
|
||||
// Add any mutation. Must come before inputs.
|
||||
if (block.mutation) {
|
||||
xmlString += this.mutationToXML(block.mutation);
|
||||
}
|
||||
// Add any inputs on this block.
|
||||
for (var input in block.inputs) {
|
||||
var blockInput = block.inputs[input];
|
||||
|
@ -389,6 +425,25 @@ Blocks.prototype.blockToXML = function (blockId) {
|
|||
return xmlString;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively encode a mutation object to XML.
|
||||
* @param {!Object} mutation Object representing a mutation.
|
||||
* @return {string} XML string representing a mutation.
|
||||
*/
|
||||
Blocks.prototype.mutationToXML = function (mutation) {
|
||||
var mutationString = '<' + mutation.tagName;
|
||||
for (var prop in mutation) {
|
||||
if (prop == 'children' || prop == 'tagName') continue;
|
||||
mutationString += ' ' + prop + '="' + mutation[prop] + '"';
|
||||
}
|
||||
mutationString += '>';
|
||||
for (var i = 0; i < mutation.children.length; i++) {
|
||||
mutationString += this.mutationToXML(mutation.children[i]);
|
||||
}
|
||||
mutationString += '</' + mutation.tagName + '>';
|
||||
return mutationString;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
|
|
@ -132,6 +132,12 @@ var execute = function (sequencer, thread) {
|
|||
argValues[inputName] = currentStackFrame.reported[inputName];
|
||||
}
|
||||
|
||||
// Add any mutation to args (e.g., for procedures).
|
||||
var mutation = target.blocks.getMutation(currentBlockId);
|
||||
if (mutation) {
|
||||
argValues.mutation = mutation;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -155,6 +161,9 @@ var execute = function (sequencer, thread) {
|
|||
startBranch: function (branchNum) {
|
||||
sequencer.stepToBranch(thread, branchNum);
|
||||
},
|
||||
startProcedure: function (procedureName) {
|
||||
sequencer.stepToProcedure(thread, procedureName);
|
||||
},
|
||||
startHats: function(requestedHat, opt_matchFields, opt_target) {
|
||||
return (
|
||||
runtime.startHats(requestedHat, opt_matchFields, opt_target)
|
||||
|
|
39
src/engine/mutation-adapter.js
Normal file
39
src/engine/mutation-adapter.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
var html = require('htmlparser2');
|
||||
|
||||
/**
|
||||
* Adapter between mutator XML or DOM and block representation which can be
|
||||
* used by the Scratch runtime.
|
||||
* @param {(Object|string)} mutation Mutation XML string or DOM.
|
||||
* @return {Object} Object representing the mutation.
|
||||
*/
|
||||
module.exports = function (mutation) {
|
||||
var mutationParsed;
|
||||
// Check if the mutation is already parsed; if not, parse it.
|
||||
if (typeof mutation === 'object') {
|
||||
mutationParsed = mutation;
|
||||
} else {
|
||||
mutationParsed = html.parseDOM(mutation)[0];
|
||||
}
|
||||
return mutatorTagToObject(mutationParsed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a part of a mutation DOM to a mutation VM object, recursively.
|
||||
* @param {Object} dom DOM object for mutation tag.
|
||||
* @return {Object} Object representing useful parts of this mutation.
|
||||
*/
|
||||
function mutatorTagToObject (dom) {
|
||||
var obj = Object.create(null);
|
||||
obj.tagName = dom.name;
|
||||
obj.children = [];
|
||||
for (var prop in dom.attribs) {
|
||||
if (prop == 'xmlns') continue;
|
||||
obj[prop] = dom.attribs[prop];
|
||||
}
|
||||
for (var i = 0; i < dom.children.length; i++) {
|
||||
obj.children.push(
|
||||
mutatorTagToObject(dom.children[i])
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
|
@ -15,7 +15,8 @@ var defaultBlockPackages = {
|
|||
'scratch3_motion': require('../blocks/scratch3_motion'),
|
||||
'scratch3_operators': require('../blocks/scratch3_operators'),
|
||||
'scratch3_sensing': require('../blocks/scratch3_sensing'),
|
||||
'scratch3_data': require('../blocks/scratch3_data')
|
||||
'scratch3_data': require('../blocks/scratch3_data'),
|
||||
'scratch3_procedures': require('../blocks/scratch3_procedures')
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -124,6 +124,16 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Step a procedure.
|
||||
* @param {!Thread} thread Thread object to step to procedure.
|
||||
* @param {!string} procedureName Name of procedure defined in this target.
|
||||
*/
|
||||
Sequencer.prototype.stepToProcedure = function (thread, procedureName) {
|
||||
var definition = thread.target.blocks.getProcedureDefinition(procedureName);
|
||||
thread.pushStack(definition);
|
||||
};
|
||||
|
||||
/**
|
||||
* Step a thread into an input reporter, and manage its status appropriately.
|
||||
* @param {!Thread} thread Thread object to step to reporter.
|
||||
|
|
|
@ -327,6 +327,14 @@ function parseBlock (sb2block) {
|
|||
};
|
||||
}
|
||||
}
|
||||
// Special cases to generate mutations.
|
||||
if (oldOpcode == 'call') {
|
||||
activeBlock.mutation = {
|
||||
tagName: 'mutation',
|
||||
children: [],
|
||||
name: sb2block[1]
|
||||
};
|
||||
}
|
||||
return activeBlock;
|
||||
}
|
||||
|
||||
|
|
|
@ -1374,15 +1374,20 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'procDef':{
|
||||
'opcode':'proc_def',
|
||||
'argMap':[]
|
||||
'opcode':'procedures_defnoreturn',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'field',
|
||||
'fieldName':'NAME'
|
||||
}
|
||||
]
|
||||
},
|
||||
'getParam':{
|
||||
'opcode':'proc_param',
|
||||
'argMap':[]
|
||||
},
|
||||
'call':{
|
||||
'opcode':'proc_call',
|
||||
'opcode':'procedures_callnoreturn',
|
||||
'argMap':[]
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue