Add procedure support

- when a call block is executed, the block is copied, any parameters are
   evaluated and stored on the copy, then the copy is pushed on the stack
- when a getParam block is executed it finds the last call block on the
  stack, and then gets the already evaluated parameters from there.
- based on PR  by @bobbybee, passes all of @nathan's tests there
This commit is contained in:
Chad Walker 2014-09-08 10:34:08 -05:00
parent 318f5833bf
commit 41b68edd90
5 changed files with 92 additions and 2 deletions

View file

@ -29,9 +29,22 @@ var Block = function(opAndArgs, optionalSubstack) {
this.subStack2 = null;
this.nextBlock = null;
this.tmp = -1;
// used in procedure blocks and call blocks
// in procedure blocks it's a map of parameter name to array [position, default value]
// in call blocks it's a map of parameter name to evaluated value
this.namedParams = {};
this.asyncFlag = false; // used in procedure hats
interp.fixArgs(this);
};
Block.copyBlock = function (srcBlock) {
var dstBlock = new Block([srcBlock.op]);
['primFcn', 'args', 'isLoop', 'substack', 'subStack2', 'nextBlock', 'tmp', 'namedParams', 'asyncFlag'].forEach(function (property) {
dstBlock[property] = srcBlock[property];
});
return dstBlock;
};
var Thread = function(block, target) {
this.nextBlock = block; // next block to run; null when thread is finished
this.firstBlock = block;
@ -306,6 +319,11 @@ Interpreter.prototype.initPrims = function() {
this.primitiveTable['timerReset'] = function(b) { interp.timerBase = Date.now(); };
this.primitiveTable['timer'] = function(b) { return (Date.now() - interp.timerBase) / 1000; };
// procedure primatives
this.primitiveTable['procDef'] = this.primProcDef;
this.primitiveTable["getParam"] = this.primGetParam;
this.primitiveTable['call'] = this.primCall;
new Primitives().addPrimsTo(this.primitiveTable);
};
@ -391,3 +409,66 @@ Interpreter.prototype.startSubstack = function(b, isLoop, secondSubstack) {
this.activeThread.nextBlock = b.substack2;
}
};
Interpreter.prototype.primProcDef = function (b) {
var namedParams = b.args.slice(1); // proc name was 0, and async flag is last
b.asyncFlag = namedParams.pop();
var i;
for (i = 0; i < namedParams.length; i += 2) {
b.namedParams[namedParams[i].op] = [i / 2, namedParams[i + 1].op];
}
};
Interpreter.prototype.primGetParam = function (b) {
// the last call block on the stack is the one with the parameters
var callBlock;
for (var i = interp.activeThread.stack.length - 1; i >= 0; i--) {
if (interp.activeThread.stack[i].op === 'call') {
callBlock = interp.activeThread.stack[i];
break;
}
}
if (!callBlock) {
console.log('failed to find a callBlock to get parameters from');
return;
}
var paramName = interp.arg(b, 0);
if (!callBlock.namedParams.hasOwnProperty(paramName)) {
console.log('got getParam block missing parameter name', paramName);
return;
}
return callBlock.namedParams[paramName];
};
Interpreter.prototype.primCall = function (b) {
// first make a copy of the block (so recursive calls can evaluate
// parameters as different values)
b = Block.copyBlock(b);
var procedure = interp.targetSprite().procedures[interp.arg(b, 0)];
// any parameterss for the procedure will be in the block at the top of
// the stack, but if there are any blocks as parameters, we need to
// evaluate them now, and save the values in place in the call block
var paramNames = Object.keys(procedure.namedParams);
var args = b.args.slice(1);
paramNames.forEach(function (paramName) {
var i = procedure.namedParams[paramName][0];
// searching for paramName
if (i < args.length) {
b.namedParams[paramName] = interp.arg(b, i + 1);
} else {
b.namedParams[paramName] = procedure.namedParams[paramName][1];
}
});
// push the copy block onto the stack, so when the call returns we can continue
interp.activeThread.stack.push(b);
// jump to the procedure, should probably check args first
interp.activeThread.nextBlock = procedure;
};

View file

@ -70,6 +70,13 @@ Runtime.prototype.loadStart = function() {
setTimeout(function(runtime) { runtime.loadStart(); }, 50, this);
return;
}
runtime.allStacksDo(function (stack, target) {
if (stack.op === 'procDef') {
target.procedures[stack.args[0]] = stack;
stack.primFcn(stack); // initialize parameters
}
});
$('#preloader').css('display', 'none');
setInterval(this.step, 33);
this.projectLoaded = true;

View file

@ -111,6 +111,9 @@ var Sprite = function(data) {
// Stacks to be pushed to the interpreter and run
this.stacks = [];
// Procedures by name
this.procedures = {};
};
// Attaches a Sprite (<img>) to a Scratch scene

View file

@ -51,7 +51,7 @@ Stage.prototype.attachPenLayer = function(scene) {
};
Stage.prototype.isLoaded = function() {
return this.penLayerLoaded && this.costumesLoaded == this.costumes.length && this.soundsLoaded == Object.keys(this.sounds).length;
return this.penLayerLoaded && Sprite.prototype.isLoaded.call(this);
};
// Pen functions

View file

@ -105,4 +105,3 @@ Primitives.prototype.primMathFunction = function(b) {
}
return 0;
}