Skip to content

Commit 6c118cf

Browse files
authored
Merge pull request scratchfoundation#1301 from kchadha/variable-scope
Variable scope
2 parents 26e5996 + a0a11f2 commit 6c118cf

File tree

5 files changed

+63
-29
lines changed

5 files changed

+63
-29
lines changed

src/engine/blocks.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ class Blocks {
263263
return;
264264
}
265265
const stage = optRuntime.getTargetForStage();
266+
const editingTarget = optRuntime.getEditingTarget();
266267

267268
// UI event: clicked scripts toggle in the runtime.
268269
if (e.element === 'stackclick') {
@@ -330,35 +331,52 @@ class Blocks {
330331
this.deleteBlock(e.blockId);
331332
break;
332333
case 'var_create':
333-
// New variables being created by the user are all global.
334-
// Check if this variable exists on the current target or stage.
335-
// If not, create it on the stage.
336-
// TODO create global and local variables when UI provides a way.
337-
if (optRuntime.getEditingTarget()) {
338-
if (!optRuntime.getEditingTarget().lookupVariableById(e.varId)) {
339-
stage.createVariable(e.varId, e.varName, e.varType);
334+
// Check if the variable being created is global or local
335+
// If local, create a local var on the current editing target, as long
336+
// as there are no conflicts, and the current target is actually a sprite
337+
// If global or if the editing target is not present or we somehow got
338+
// into a state where a local var was requested for the stage,
339+
// create a stage (global) var after checking for name conflicts
340+
// on all the sprites.
341+
if (e.isLocal && editingTarget && !editingTarget.isStage) {
342+
if (!editingTarget.lookupVariableById(e.varId)) {
343+
editingTarget.createVariable(e.varId, e.varName, e.varType);
344+
}
345+
} else {
346+
// Check for name conflicts in all of the targets
347+
const allTargets = optRuntime.targets.filter(t => t.isOriginal);
348+
for (const target of allTargets) {
349+
if (target.lookupVariableByNameAndType(e.varName, e.varType, true)) {
350+
return;
351+
}
340352
}
341-
} else if (!stage.lookupVariableById(e.varId)) {
342-
// Since getEditingTarget returned null, we now need to
343-
// explicitly check if the stage has the variable, and
344-
// create one if not.
345353
stage.createVariable(e.varId, e.varName, e.varType);
346354
}
347355
break;
348356
case 'var_rename':
349-
stage.renameVariable(e.varId, e.newName);
350-
// Update all the blocks that use the renamed variable.
351-
if (optRuntime) {
357+
if (editingTarget && editingTarget.hasOwnProperty(e.varId)) {
358+
// This is a local variable, rename on the current target
359+
editingTarget.renameVariable(e.varId, e.newName);
360+
// Update all the blocks on the current target that use
361+
// this variable
362+
editingTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);
363+
} else {
364+
// This is a global variable
365+
stage.renameVariable(e.varId, e.newName);
366+
// Update all blocks on all targets that use the renamed variable
352367
const targets = optRuntime.targets;
353368
for (let i = 0; i < targets.length; i++) {
354369
const currTarget = targets[i];
355370
currTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);
356371
}
357372
}
358373
break;
359-
case 'var_delete':
360-
stage.deleteVariable(e.varId);
374+
case 'var_delete': {
375+
const target = (editingTarget && editingTarget.hasOwnProperty(e.varId)) ?
376+
editingTarget : stage;
377+
target.deleteVariable(e.varId);
361378
break;
379+
}
362380
case 'comment_create':
363381
if (optRuntime && optRuntime.getEditingTarget()) {
364382
const currTarget = optRuntime.getEditingTarget();

src/engine/runtime.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,15 @@ class Runtime extends EventEmitter {
17151715
return this._editingTarget;
17161716
}
17171717

1718+
getAllVarNamesOfType (varType) {
1719+
let varNames = [];
1720+
for (const target of this.targets) {
1721+
const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true);
1722+
varNames = varNames.concat(targetVarNames);
1723+
}
1724+
return varNames;
1725+
}
1726+
17181727
/**
17191728
* Tell the runtime to request a redraw.
17201729
* Use after a clone/sprite has completed some visible operation on the stage.

src/engine/target.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,19 @@ class Target extends EventEmitter {
8383
/**
8484
* Get the names of all the variables of the given type that are in scope for this target.
8585
* For targets that are not the stage, this includes any target-specific
86-
* variables as well as any stage variables.
86+
* variables as well as any stage variables unless the skipStage flag is true.
8787
* For the stage, this is all stage variables.
8888
* @param {string} type The variable type to search for; defaults to Variable.SCALAR_TYPE
89+
* @param {?bool} skipStage Optional flag to skip the stage.
8990
* @return {Array<string>} A list of variable names
9091
*/
91-
getAllVariableNamesInScopeByType (type) {
92+
getAllVariableNamesInScopeByType (type, skipStage) {
9293
if (typeof type !== 'string') type = Variable.SCALAR_TYPE;
94+
skipStage = skipStage || false;
9395
const targetVariables = Object.values(this.variables)
9496
.filter(v => v.type === type)
9597
.map(variable => variable.name);
96-
if (this.isStage || !this.runtime) {
98+
if (skipStage || this.isStage || !this.runtime) {
9799
return targetVariables;
98100
}
99101
const stage = this.runtime.getTargetForStage();
@@ -194,11 +196,13 @@ class Target extends EventEmitter {
194196
* was not found.
195197
* @param {string} name Name of the variable.
196198
* @param {string} type Type of the variable. Defaults to Variable.SCALAR_TYPE.
199+
* @param {?bool} skipStage Optional flag to skip checking the stage
197200
* @return {?Variable} Variable object if found, or null if not.
198201
*/
199-
lookupVariableByNameAndType (name, type) {
202+
lookupVariableByNameAndType (name, type, skipStage) {
200203
if (typeof name !== 'string') return;
201204
if (typeof type !== 'string') type = Variable.SCALAR_TYPE;
205+
skipStage = skipStage || false;
202206

203207
for (const varId in this.variables) {
204208
const currVar = this.variables[varId];
@@ -207,7 +211,7 @@ class Target extends EventEmitter {
207211
}
208212
}
209213

210-
if (this.runtime && !this.isStage) {
214+
if (!skipStage && this.runtime && !this.isStage) {
211215
const stage = this.runtime.getTargetForStage();
212216
if (stage) {
213217
for (const varId in stage.variables) {

src/engine/variable.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ class Variable {
3333
}
3434
}
3535

36-
toXML () {
37-
return `<variable type="${this.type}" id="${this.id}">${this.name}</variable>`;
36+
toXML (isLocal) {
37+
isLocal = (isLocal === true);
38+
return `<variable type="${this.type}" id="${this.id}" islocal="${isLocal}">${this.name}</variable>`;
3839
}
3940

4041
/**

src/virtual-machine.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,19 +1074,21 @@ class VirtualMachine extends EventEmitter {
10741074
const id = messageIds[i];
10751075
delete this.runtime.getTargetForStage().variables[id];
10761076
}
1077-
const variableMap = Object.assign({},
1078-
this.runtime.getTargetForStage().variables,
1079-
this.editingTarget.variables
1080-
);
1077+
const globalVarMap = Object.assign({}, this.runtime.getTargetForStage().variables);
1078+
const localVarMap = this.editingTarget.isStage ?
1079+
Object.create(null) :
1080+
Object.assign({}, this.editingTarget.variables);
10811081

1082-
const variables = Object.keys(variableMap).map(k => variableMap[k]);
1082+
const globalVariables = Object.keys(globalVarMap).map(k => globalVarMap[k]);
1083+
const localVariables = Object.keys(localVarMap).map(k => localVarMap[k]);
10831084
const workspaceComments = Object.keys(this.editingTarget.comments)
10841085
.map(k => this.editingTarget.comments[k])
10851086
.filter(c => c.blockId === null);
10861087

10871088
const xmlString = `<xml xmlns="http://www.w3.org/1999/xhtml">
10881089
<variables>
1089-
${variables.map(v => v.toXML()).join()}
1090+
${globalVariables.map(v => v.toXML()).join()}
1091+
${localVariables.map(v => v.toXML(true)).join()}
10901092
</variables>
10911093
${workspaceComments.map(c => c.toXML()).join()}
10921094
${this.editingTarget.blocks.toXML(this.editingTarget.comments)}

0 commit comments

Comments
 (0)