First cut at turning lists into typed variables

This commit is contained in:
Karishma Chadha 2017-11-09 17:19:34 -05:00
parent a735459427
commit 70959cc7f5
9 changed files with 55 additions and 80 deletions

View file

@ -52,8 +52,8 @@ class Scratch3DataBlocks {
// If it is, report contents joined together with no separator. // If it is, report contents joined together with no separator.
// If it's not, report contents joined together with a space. // If it's not, report contents joined together with a space.
let allSingleLetters = true; let allSingleLetters = true;
for (let i = 0; i < list.contents.length; i++) { for (let i = 0; i < list.value.length; i++) {
const listItem = list.contents[i]; const listItem = list.value[i];
if (!((typeof listItem === 'string') && if (!((typeof listItem === 'string') &&
(listItem.length === 1))) { (listItem.length === 1))) {
allSingleLetters = false; allSingleLetters = false;
@ -61,73 +61,73 @@ class Scratch3DataBlocks {
} }
} }
if (allSingleLetters) { if (allSingleLetters) {
return list.contents.join(''); return list.value.join('');
} }
return list.contents.join(' '); return list.value.join(' ');
} }
addToList (args, util) { addToList (args, util) {
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
list.contents.push(args.ITEM); list.value.push(args.ITEM);
} }
deleteOfList (args, util) { deleteOfList (args, util) {
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
const index = Cast.toListIndex(args.INDEX, list.contents.length); const index = Cast.toListIndex(args.INDEX, list.value.length);
if (index === Cast.LIST_INVALID) { if (index === Cast.LIST_INVALID) {
return; return;
} else if (index === Cast.LIST_ALL) { } else if (index === Cast.LIST_ALL) {
list.contents = []; list.value = [];
return; return;
} }
list.contents.splice(index - 1, 1); list.value.splice(index - 1, 1);
} }
insertAtList (args, util) { insertAtList (args, util) {
const item = args.ITEM; const item = args.ITEM;
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
const index = Cast.toListIndex(args.INDEX, list.contents.length + 1); const index = Cast.toListIndex(args.INDEX, list.value.length + 1);
if (index === Cast.LIST_INVALID) { if (index === Cast.LIST_INVALID) {
return; return;
} }
list.contents.splice(index - 1, 0, item); list.value.splice(index - 1, 0, item);
} }
replaceItemOfList (args, util) { replaceItemOfList (args, util) {
const item = args.ITEM; const item = args.ITEM;
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
const index = Cast.toListIndex(args.INDEX, list.contents.length); const index = Cast.toListIndex(args.INDEX, list.value.length);
if (index === Cast.LIST_INVALID) { if (index === Cast.LIST_INVALID) {
return; return;
} }
list.contents.splice(index - 1, 1, item); list.value.splice(index - 1, 1, item);
} }
getItemOfList (args, util) { getItemOfList (args, util) {
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
const index = Cast.toListIndex(args.INDEX, list.contents.length); const index = Cast.toListIndex(args.INDEX, list.value.length);
if (index === Cast.LIST_INVALID) { if (index === Cast.LIST_INVALID) {
return ''; return '';
} }
return list.contents[index - 1]; return list.value[index - 1];
} }
lengthOfList (args, util) { lengthOfList (args, util) {
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
return list.contents.length; return list.value.length;
} }
listContainsItem (args, util) { listContainsItem (args, util) {
const item = args.ITEM; const item = args.ITEM;
const list = util.target.lookupOrCreateList(args.LIST); const list = util.target.lookupOrCreateList(args.LIST);
if (list.contents.indexOf(item) >= 0) { if (list.value.indexOf(item) >= 0) {
return true; return true;
} }
// Try using Scratch comparison operator on each item. // Try using Scratch comparison operator on each item.
// (Scratch considers the string '123' equal to the number 123). // (Scratch considers the string '123' equal to the number 123).
for (let i = 0; i < list.contents.length; i++) { for (let i = 0; i < list.value.length; i++) {
if (Cast.compare(list.contents[i], item) === 0) { if (Cast.compare(list.value[i], item) === 0) {
return true; return true;
} }
} }

View file

@ -260,7 +260,7 @@ class Blocks {
// If not, create it on the stage. // If not, create it on the stage.
// TODO create global and local variables when UI provides a way. // TODO create global and local variables when UI provides a way.
if (!optRuntime.getEditingTarget().lookupVariableById(e.varId)) { if (!optRuntime.getEditingTarget().lookupVariableById(e.varId)) {
stage.createVariable(e.varId, e.varName); stage.createVariable(e.varId, e.varName, e.varType);
} }
break; break;
case 'var_rename': case 'var_rename':
@ -310,7 +310,7 @@ class Blocks {
case 'field': case 'field':
// Update block value // Update block value
if (!block.fields[args.name]) return; if (!block.fields[args.name]) return;
if (args.name === 'VARIABLE') { if (args.name === 'VARIABLE' || args.name === 'LIST') {
// Get variable name using the id in args.value. // Get variable name using the id in args.value.
const variable = optRuntime.getEditingTarget().lookupVariableById(args.value); const variable = optRuntime.getEditingTarget().lookupVariableById(args.value);
if (variable) { if (variable) {

View file

@ -138,7 +138,7 @@ const execute = function (sequencer, thread) {
// Add all fields on this block to the argValues. // Add all fields on this block to the argValues.
for (const fieldName in fields) { for (const fieldName in fields) {
if (!fields.hasOwnProperty(fieldName)) continue; if (!fields.hasOwnProperty(fieldName)) continue;
if (fieldName === 'VARIABLE') { if (fieldName === 'VARIABLE' || fieldName === 'LIST') {
argValues[fieldName] = fields[fieldName].id; argValues[fieldName] = fields[fieldName].id;
} else { } else {
argValues[fieldName] = fields[fieldName].value; argValues[fieldName] = fields[fieldName].value;

View file

@ -1,18 +0,0 @@
/**
* @fileoverview
* Object representing a Scratch list.
*/
/**
* @param {!string} name Name of the list.
* @param {Array} contents Contents of the list, as an array.
* @constructor
*/
class List {
constructor (name, contents) {
this.name = name;
this.contents = contents;
}
}
module.exports = List;

View file

@ -2,7 +2,6 @@ const EventEmitter = require('events');
const Blocks = require('./blocks'); const Blocks = require('./blocks');
const Variable = require('../engine/variable'); const Variable = require('../engine/variable');
const List = require('../engine/list');
const uid = require('../util/uid'); const uid = require('../util/uid');
const {Map} = require('immutable'); const {Map} = require('immutable');
@ -88,7 +87,7 @@ class Target extends EventEmitter {
const variable = this.lookupVariableById(id); const variable = this.lookupVariableById(id);
if (variable) return variable; if (variable) return variable;
// No variable with this name exists - create it locally. // No variable with this name exists - create it locally.
const newVariable = new Variable(id, name, 0, false); const newVariable = new Variable(id, name, "", false);
this.variables[id] = newVariable; this.variables[id] = newVariable;
return newVariable; return newVariable;
} }
@ -117,24 +116,16 @@ class Target extends EventEmitter {
/** /**
* Look up a list object for this target, and create it if one doesn't exist. * Look up a list object for this target, and create it if one doesn't exist.
* Search begins for local lists; then look for globals. * Search begins for local lists; then look for globals.
* @param {!string} id Id of the list.
* @param {!string} name Name of the list. * @param {!string} name Name of the list.
* @return {!List} List object. * @return {!List} List object.
*/ */
lookupOrCreateList (name) { lookupOrCreateList (id, name) {
// If we have a local copy, return it. const list = this.lookupVariableById(id);
if (this.lists.hasOwnProperty(name)) { if (list) return list;
return this.lists[name]; // No variable with this name exists - create it locally.
} const newList = new Variable(id, name, "list", false);
// If the stage has a global copy, return it. this.variables[id] = newList;
if (this.runtime && !this.isStage) {
const stage = this.runtime.getTargetForStage();
if (stage.lists.hasOwnProperty(name)) {
return stage.lists[name];
}
}
// No list with this name exists - create it locally.
const newList = new List(name, []);
this.lists[name] = newList;
return newList; return newList;
} }
@ -143,11 +134,11 @@ class Target extends EventEmitter {
* dictionary of variables. * dictionary of variables.
* @param {string} id Id of variable * @param {string} id Id of variable
* @param {string} name Name of variable. * @param {string} name Name of variable.
* @param {string} type Type of variable, one of string, number, list
*/ */
createVariable (id, name) { createVariable (id, name, type) {
if (!this.variables.hasOwnProperty(id)) { if (!this.variables.hasOwnProperty(id)) {
const newVariable = new Variable(id, name, 0, const newVariable = new Variable(id, name, type, false);
false);
this.variables[id] = newVariable; this.variables[id] = newVariable;
} }
} }

View file

@ -9,19 +9,27 @@ class Variable {
/** /**
* @param {string} id Id of the variable. * @param {string} id Id of the variable.
* @param {string} name Name of the variable. * @param {string} name Name of the variable.
* @param {(string|number)} value Value of the variable. * @param {(string|number)} type Type of the variable, one of "" or "list"
* @param {boolean} isCloud Whether the variable is stored in the cloud. * @param {boolean} isCloud Whether the variable is stored in the cloud.
* @constructor * @constructor
*/ */
constructor (id, name, value, isCloud) { constructor (id, name, type, isCloud) {
this.id = id || uid(); this.id = id || uid();
this.name = name; this.name = name;
this.value = value; this.type = type;
this.isCloud = isCloud; this.isCloud = isCloud;
switch (this.type) {
case "":
this.value = 0;
break;
case "list":
this.value = [];
break;
}
} }
toXML () { toXML () {
return `<variable type="" id="${this.id}">${this.name}</variable>`; return `<variable type="${this.type}" id="${this.id}">${this.name}</variable>`;
} }
} }

View file

@ -13,7 +13,6 @@ const log = require('../util/log');
const uid = require('../util/uid'); const uid = require('../util/uid');
const specMap = require('./sb2_specmap'); const specMap = require('./sb2_specmap');
const Variable = require('../engine/variable'); const Variable = require('../engine/variable');
const List = require('../engine/list');
const {loadCostume} = require('../import/load-costume.js'); const {loadCostume} = require('../import/load-costume.js');
const {loadSound} = require('../import/load-sound.js'); const {loadSound} = require('../import/load-sound.js');
@ -235,9 +234,10 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
const newVariable = new Variable( const newVariable = new Variable(
getVariableId(variable.name), getVariableId(variable.name),
variable.name, variable.name,
variable.value, "",
variable.isPersistent variable.isPersistent
); );
newVariable.value = variable.value;
target.variables[newVariable.id] = newVariable; target.variables[newVariable.id] = newVariable;
} }
} }
@ -251,10 +251,14 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
for (let k = 0; k < object.lists.length; k++) { for (let k = 0; k < object.lists.length; k++) {
const list = object.lists[k]; const list = object.lists[k];
// @todo: monitor properties. // @todo: monitor properties.
target.lists[list.listName] = new List( const newVariable = new Variable(
getVariableId(list.listName),
list.listName, list.listName,
list.contents "list",
list.isPersistent
); );
newVariable.value = list.contents;
target.variables[newVariable.id] = newVariable;
} }
} }
if (object.hasOwnProperty('scratchX')) { if (object.hasOwnProperty('scratchX')) {

View file

@ -1252,7 +1252,7 @@ const specMap = {
] ]
}, },
'contentsOfList:': { 'contentsOfList:': {
opcode: 'data_list', opcode: 'data_listcontents',
argMap: [ argMap: [
{ {
type: 'field', type: 'field',

View file

@ -8,7 +8,6 @@ const vmPackage = require('../../package.json');
const Blocks = require('../engine/blocks'); const Blocks = require('../engine/blocks');
const Sprite = require('../sprites/sprite'); const Sprite = require('../sprites/sprite');
const Variable = require('../engine/variable'); const Variable = require('../engine/variable');
const List = require('../engine/list');
const {loadCostume} = require('../import/load-costume.js'); const {loadCostume} = require('../import/load-costume.js');
const {loadSound} = require('../import/load-sound.js'); const {loadSound} = require('../import/load-sound.js');
@ -125,22 +124,13 @@ const parseScratchObject = function (object, runtime, extensions) {
const newVariable = new Variable( const newVariable = new Variable(
variable.id, variable.id,
variable.name, variable.name,
variable.value, variable.type,
variable.isPersistent variable.isPersistent
); );
newVariable.value = variable.value;
target.variables[newVariable.id] = newVariable; target.variables[newVariable.id] = newVariable;
} }
} }
if (object.hasOwnProperty('lists')) {
for (let k = 0; k < object.lists.length; k++) {
const list = object.lists[k];
// @todo: monitor properties.
target.lists[list.listName] = new List(
list.listName,
list.contents
);
}
}
if (object.hasOwnProperty('x')) { if (object.hasOwnProperty('x')) {
target.x = object.x; target.x = object.x;
} }