Merge pull request #1301 from kchadha/variable-scope

Variable scope
This commit is contained in:
kchadha 2018-07-06 14:39:56 -04:00 committed by GitHub
commit 6c118cf8e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 29 deletions

View file

@ -263,6 +263,7 @@ class Blocks {
return; return;
} }
const stage = optRuntime.getTargetForStage(); const stage = optRuntime.getTargetForStage();
const editingTarget = optRuntime.getEditingTarget();
// UI event: clicked scripts toggle in the runtime. // UI event: clicked scripts toggle in the runtime.
if (e.element === 'stackclick') { if (e.element === 'stackclick') {
@ -330,25 +331,39 @@ class Blocks {
this.deleteBlock(e.blockId); this.deleteBlock(e.blockId);
break; break;
case 'var_create': case 'var_create':
// New variables being created by the user are all global. // Check if the variable being created is global or local
// Check if this variable exists on the current target or stage. // If local, create a local var on the current editing target, as long
// If not, create it on the stage. // as there are no conflicts, and the current target is actually a sprite
// TODO create global and local variables when UI provides a way. // If global or if the editing target is not present or we somehow got
if (optRuntime.getEditingTarget()) { // into a state where a local var was requested for the stage,
if (!optRuntime.getEditingTarget().lookupVariableById(e.varId)) { // create a stage (global) var after checking for name conflicts
stage.createVariable(e.varId, e.varName, e.varType); // on all the sprites.
if (e.isLocal && editingTarget && !editingTarget.isStage) {
if (!editingTarget.lookupVariableById(e.varId)) {
editingTarget.createVariable(e.varId, e.varName, e.varType);
}
} else {
// Check for name conflicts in all of the targets
const allTargets = optRuntime.targets.filter(t => t.isOriginal);
for (const target of allTargets) {
if (target.lookupVariableByNameAndType(e.varName, e.varType, true)) {
return;
}
} }
} else if (!stage.lookupVariableById(e.varId)) {
// Since getEditingTarget returned null, we now need to
// explicitly check if the stage has the variable, and
// create one if not.
stage.createVariable(e.varId, e.varName, e.varType); stage.createVariable(e.varId, e.varName, e.varType);
} }
break; break;
case 'var_rename': case 'var_rename':
stage.renameVariable(e.varId, e.newName); if (editingTarget && editingTarget.hasOwnProperty(e.varId)) {
// Update all the blocks that use the renamed variable. // This is a local variable, rename on the current target
if (optRuntime) { editingTarget.renameVariable(e.varId, e.newName);
// Update all the blocks on the current target that use
// this variable
editingTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);
} else {
// This is a global variable
stage.renameVariable(e.varId, e.newName);
// Update all blocks on all targets that use the renamed variable
const targets = optRuntime.targets; const targets = optRuntime.targets;
for (let i = 0; i < targets.length; i++) { for (let i = 0; i < targets.length; i++) {
const currTarget = targets[i]; const currTarget = targets[i];
@ -356,9 +371,12 @@ class Blocks {
} }
} }
break; break;
case 'var_delete': case 'var_delete': {
stage.deleteVariable(e.varId); const target = (editingTarget && editingTarget.hasOwnProperty(e.varId)) ?
editingTarget : stage;
target.deleteVariable(e.varId);
break; break;
}
case 'comment_create': case 'comment_create':
if (optRuntime && optRuntime.getEditingTarget()) { if (optRuntime && optRuntime.getEditingTarget()) {
const currTarget = optRuntime.getEditingTarget(); const currTarget = optRuntime.getEditingTarget();

View file

@ -1715,6 +1715,15 @@ class Runtime extends EventEmitter {
return this._editingTarget; return this._editingTarget;
} }
getAllVarNamesOfType (varType) {
let varNames = [];
for (const target of this.targets) {
const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true);
varNames = varNames.concat(targetVarNames);
}
return varNames;
}
/** /**
* Tell the runtime to request a redraw. * Tell the runtime to request a redraw.
* Use after a clone/sprite has completed some visible operation on the stage. * Use after a clone/sprite has completed some visible operation on the stage.

View file

@ -83,17 +83,19 @@ class Target extends EventEmitter {
/** /**
* Get the names of all the variables of the given type that are in scope for this target. * Get the names of all the variables of the given type that are in scope for this target.
* For targets that are not the stage, this includes any target-specific * For targets that are not the stage, this includes any target-specific
* variables as well as any stage variables. * variables as well as any stage variables unless the skipStage flag is true.
* For the stage, this is all stage variables. * For the stage, this is all stage variables.
* @param {string} type The variable type to search for; defaults to Variable.SCALAR_TYPE * @param {string} type The variable type to search for; defaults to Variable.SCALAR_TYPE
* @param {?bool} skipStage Optional flag to skip the stage.
* @return {Array<string>} A list of variable names * @return {Array<string>} A list of variable names
*/ */
getAllVariableNamesInScopeByType (type) { getAllVariableNamesInScopeByType (type, skipStage) {
if (typeof type !== 'string') type = Variable.SCALAR_TYPE; if (typeof type !== 'string') type = Variable.SCALAR_TYPE;
skipStage = skipStage || false;
const targetVariables = Object.values(this.variables) const targetVariables = Object.values(this.variables)
.filter(v => v.type === type) .filter(v => v.type === type)
.map(variable => variable.name); .map(variable => variable.name);
if (this.isStage || !this.runtime) { if (skipStage || this.isStage || !this.runtime) {
return targetVariables; return targetVariables;
} }
const stage = this.runtime.getTargetForStage(); const stage = this.runtime.getTargetForStage();
@ -194,11 +196,13 @@ class Target extends EventEmitter {
* was not found. * was not found.
* @param {string} name Name of the variable. * @param {string} name Name of the variable.
* @param {string} type Type of the variable. Defaults to Variable.SCALAR_TYPE. * @param {string} type Type of the variable. Defaults to Variable.SCALAR_TYPE.
* @param {?bool} skipStage Optional flag to skip checking the stage
* @return {?Variable} Variable object if found, or null if not. * @return {?Variable} Variable object if found, or null if not.
*/ */
lookupVariableByNameAndType (name, type) { lookupVariableByNameAndType (name, type, skipStage) {
if (typeof name !== 'string') return; if (typeof name !== 'string') return;
if (typeof type !== 'string') type = Variable.SCALAR_TYPE; if (typeof type !== 'string') type = Variable.SCALAR_TYPE;
skipStage = skipStage || false;
for (const varId in this.variables) { for (const varId in this.variables) {
const currVar = this.variables[varId]; const currVar = this.variables[varId];
@ -207,7 +211,7 @@ class Target extends EventEmitter {
} }
} }
if (this.runtime && !this.isStage) { if (!skipStage && this.runtime && !this.isStage) {
const stage = this.runtime.getTargetForStage(); const stage = this.runtime.getTargetForStage();
if (stage) { if (stage) {
for (const varId in stage.variables) { for (const varId in stage.variables) {

View file

@ -33,8 +33,9 @@ class Variable {
} }
} }
toXML () { toXML (isLocal) {
return `<variable type="${this.type}" id="${this.id}">${this.name}</variable>`; isLocal = (isLocal === true);
return `<variable type="${this.type}" id="${this.id}" islocal="${isLocal}">${this.name}</variable>`;
} }
/** /**

View file

@ -1074,19 +1074,21 @@ class VirtualMachine extends EventEmitter {
const id = messageIds[i]; const id = messageIds[i];
delete this.runtime.getTargetForStage().variables[id]; delete this.runtime.getTargetForStage().variables[id];
} }
const variableMap = Object.assign({}, const globalVarMap = Object.assign({}, this.runtime.getTargetForStage().variables);
this.runtime.getTargetForStage().variables, const localVarMap = this.editingTarget.isStage ?
this.editingTarget.variables Object.create(null) :
); Object.assign({}, this.editingTarget.variables);
const variables = Object.keys(variableMap).map(k => variableMap[k]); const globalVariables = Object.keys(globalVarMap).map(k => globalVarMap[k]);
const localVariables = Object.keys(localVarMap).map(k => localVarMap[k]);
const workspaceComments = Object.keys(this.editingTarget.comments) const workspaceComments = Object.keys(this.editingTarget.comments)
.map(k => this.editingTarget.comments[k]) .map(k => this.editingTarget.comments[k])
.filter(c => c.blockId === null); .filter(c => c.blockId === null);
const xmlString = `<xml xmlns="http://www.w3.org/1999/xhtml"> const xmlString = `<xml xmlns="http://www.w3.org/1999/xhtml">
<variables> <variables>
${variables.map(v => v.toXML()).join()} ${globalVariables.map(v => v.toXML()).join()}
${localVariables.map(v => v.toXML(true)).join()}
</variables> </variables>
${workspaceComments.map(c => c.toXML()).join()} ${workspaceComments.map(c => c.toXML()).join()}
${this.editingTarget.blocks.toXML(this.editingTarget.comments)} ${this.editingTarget.blocks.toXML(this.editingTarget.comments)}