Skip to content

Commit 358e2f1

Browse files
committed
feat: Tables (currently broken) (scratch-vm)
1 parent 0651e62 commit 358e2f1

File tree

6 files changed

+196
-26
lines changed

6 files changed

+196
-26
lines changed

src/blocks/scratch3_data.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class Scratch3DataBlocks {
3131
data_lengthoflist: this.lengthOfList,
3232
data_listcontainsitem: this.listContainsItem,
3333
data_hidelist: this.hideList,
34-
data_showlist: this.showList
34+
data_showlist: this.showList,
35+
data_tablecontents: this.getTableContents
3536
};
3637
}
3738

@@ -120,7 +121,6 @@ class Scratch3DataBlocks {
120121
return list.value.join('');
121122
}
122123
return list.value.map(item => Cast.toString(item)).join(' ');
123-
124124
}
125125

126126
addToList (args, util) {
@@ -235,6 +235,38 @@ class Scratch3DataBlocks {
235235
}
236236
return false;
237237
}
238+
239+
getTableContents (args, util) {
240+
const table = util.target.lookupOrCreateTable(
241+
args.TABLE.id, args.TABLE.name);
242+
243+
// If block is running for monitors, return copy of list as an array if changed.
244+
if (util.thread.updateMonitor) {
245+
// Return original list value if up-to-date, which doesn't trigger monitor update.
246+
if (table._monitorUpToDate) return table.value;
247+
// If value changed, reset the flag and return a copy to trigger monitor update.
248+
// Because monitors use Immutable data structures, only new objects trigger updates.
249+
table._monitorUpToDate = true;
250+
return table.value.slice();
251+
}
252+
253+
// Determine if the list is all single letters.
254+
// If it is, report contents joined together with no separator.
255+
// If it's not, report contents joined together with a space.
256+
let allSingleLetters = true;
257+
for (let i = 0; i < table.value.length; i++) {
258+
const tableItem = table.value[i];
259+
if (!((typeof tableItem === 'string') &&
260+
(tableItem.length === 1))) {
261+
allSingleLetters = false;
262+
break;
263+
}
264+
}
265+
if (allSingleLetters) {
266+
return table.value.join('');
267+
}
268+
return table.value.map(item => Cast.toString(item)).join(' ');
269+
}
238270
}
239271

240272
module.exports = Scratch3DataBlocks;

src/engine/blocks.js

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -685,8 +685,11 @@ class Blocks {
685685

686686
// Update block value
687687
if (!block.fields[args.name]) return;
688-
if (args.name === 'VARIABLE' || args.name === 'LIST' ||
689-
args.name === 'BROADCAST_OPTION') {
688+
if (args.name === 'VARIABLE' ||
689+
args.name === 'LIST' ||
690+
args.name === 'TABLE' ||
691+
args.name === 'BROADCAST_OPTION'
692+
) {
690693
// Get variable name using the id in args.value.
691694
const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);
692695
if (variable) {
@@ -726,7 +729,10 @@ class Blocks {
726729
// block but in the case of monitored reporters that have arguments,
727730
// map the old id to a new id, creating a new monitor block if necessary
728731
if (block.fields && Object.keys(block.fields).length > 0 &&
729-
block.opcode !== 'data_variable' && block.opcode !== 'data_listcontents') {
732+
block.opcode !== 'data_variable' &&
733+
block.opcode !== 'data_listcontents' &&
734+
block.opcode !== 'data_tablecontents'
735+
) {
730736

731737
// This block has an argument which needs to get separated out into
732738
// multiple monitor blocks with ids based on the selected argument
@@ -754,6 +760,8 @@ class Blocks {
754760
isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.VARIABLE.id]);
755761
} else if (block.opcode === 'data_listcontents') {
756762
isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.LIST.id]);
763+
} else if (block.opcode === 'data_tablecontents') {
764+
isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.TABLE.id]);
757765
}
758766

759767
const isSpriteSpecific = isSpriteLocalVariable ||
@@ -772,6 +780,18 @@ class Blocks {
772780
this.runtime.requestHideMonitor(block.id);
773781
} else if (!wasMonitored && block.isMonitored) {
774782
// Tries to show the monitor for specified block. If it doesn't exist, add the monitor.
783+
let mode;
784+
switch (block.opcode) {
785+
case 'data_tablecontents':
786+
mode = 'table';
787+
break;
788+
case 'data_listcontents':
789+
mode = 'list';
790+
break;
791+
default:
792+
mode = 'default';
793+
break;
794+
}
775795
if (!this.runtime.requestShowMonitor(block.id)) {
776796
this.runtime.requestAddMonitor(MonitorRecord({
777797
id: block.id,
@@ -781,7 +801,7 @@ class Blocks {
781801
params: this._getBlockParams(block),
782802
// @todo(vm#565) for numerical values with decimals, some countries use comma
783803
value: '',
784-
mode: block.opcode === 'data_listcontents' ? 'list' : 'default'
804+
mode
785805
}));
786806
}
787807
}
@@ -961,28 +981,31 @@ class Blocks {
961981
const blocks = optBlocks ? optBlocks : this._blocks;
962982
const allReferences = Object.create(null);
963983
for (const blockId in blocks) {
964-
let varOrListField = null;
984+
let varTypeField = null;
965985
let varType = null;
966986
if (blocks[blockId].fields.VARIABLE) {
967-
varOrListField = blocks[blockId].fields.VARIABLE;
987+
varTypeField = blocks[blockId].fields.VARIABLE;
968988
varType = Variable.SCALAR_TYPE;
969989
} else if (blocks[blockId].fields.LIST) {
970-
varOrListField = blocks[blockId].fields.LIST;
990+
varTypeField = blocks[blockId].fields.LIST;
971991
varType = Variable.LIST_TYPE;
992+
} else if (blocks[blockId].fields.TABLE) {
993+
varTypeField = blocks[blockId].fields.TABLE;
994+
varType = Variable.TABLE_TYPE;
972995
} else if (optIncludeBroadcast && blocks[blockId].fields.BROADCAST_OPTION) {
973-
varOrListField = blocks[blockId].fields.BROADCAST_OPTION;
996+
varTypeField = blocks[blockId].fields.BROADCAST_OPTION;
974997
varType = Variable.BROADCAST_MESSAGE_TYPE;
975998
}
976-
if (varOrListField) {
977-
const currVarId = varOrListField.id;
999+
if (varTypeField) {
1000+
const currVarId = varTypeField.id;
9781001
if (allReferences[currVarId]) {
9791002
allReferences[currVarId].push({
980-
referencingField: varOrListField,
1003+
referencingField: varTypeField,
9811004
type: varType
9821005
});
9831006
} else {
9841007
allReferences[currVarId] = [{
985-
referencingField: varOrListField,
1008+
referencingField: varTypeField,
9861009
type: varType
9871010
}];
9881011
}
@@ -999,16 +1022,18 @@ class Blocks {
9991022
updateBlocksAfterVarRename (varId, newName) {
10001023
const blocks = this._blocks;
10011024
for (const blockId in blocks) {
1002-
let varOrListField = null;
1025+
let varTypeField = null;
10031026
if (blocks[blockId].fields.VARIABLE) {
1004-
varOrListField = blocks[blockId].fields.VARIABLE;
1027+
varTypeField = blocks[blockId].fields.VARIABLE;
10051028
} else if (blocks[blockId].fields.LIST) {
1006-
varOrListField = blocks[blockId].fields.LIST;
1029+
varTypeField = blocks[blockId].fields.LIST;
1030+
} else if (blocks[blockId].fields.TABLE) {
1031+
varTypeField = blocks[blockId].fields.TABLE;
10071032
}
1008-
if (varOrListField) {
1009-
const currFieldId = varOrListField.id;
1033+
if (varTypeField) {
1034+
const currFieldId = varTypeField.id;
10101035
if (varId === currFieldId) {
1011-
varOrListField.value = newName;
1036+
varTypeField.value = newName;
10121037
}
10131038
}
10141039
}

src/engine/target.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,26 @@ class Target extends EventEmitter {
260260
return newList;
261261
}
262262

263+
/**
264+
* Look up a table object for this target, and create it if one doesn't exist.
265+
* Search begins for local tables; then look for globals.
266+
* @param {!string} id Id of the table.
267+
* @param {!string} name Name of the table.
268+
* @return {!Varible} Variable object representing the found/created table.
269+
*/
270+
lookupOrCreateTable (id, name) {
271+
let table = this.lookupVariableById(id);
272+
if (table) return table;
273+
274+
table = this.lookupVariableByNameAndType(name, Variable.TABLE_TYPE);
275+
if (table) return table;
276+
277+
// No variable with this name exists - create it locally.
278+
const newTable = new Variable(id, name, Variable.TABLE_TYPE, false);
279+
this.variables[id] = newTable;
280+
return newTable;
281+
}
282+
263283
/**
264284
* Creates a variable with the given id and name and adds it to the
265285
* dictionary of variables.
@@ -343,11 +363,23 @@ class Target extends EventEmitter {
343363
if (blockUpdated) this.runtime.requestBlocksUpdate();
344364
}
345365

366+
let name;
367+
switch (variable.type) {
368+
case Variable.TABLE_TYPE:
369+
name = 'TABLE';
370+
break;
371+
case Variable.LIST_TYPE:
372+
name = 'LIST';
373+
break;
374+
default:
375+
name = 'VARIABLE';
376+
break;
377+
}
346378
const blocks = this.runtime.monitorBlocks;
347379
blocks.changeBlock({
348380
id: id,
349381
element: 'field',
350-
name: variable.type === Variable.LIST_TYPE ? 'LIST' : 'VARIABLE',
382+
name,
351383
value: id
352384
}, this.runtime);
353385
const monitorBlock = blocks.getBlock(variable.id);
@@ -424,6 +456,8 @@ class Target extends EventEmitter {
424456
);
425457
if (newVariable.type === Variable.LIST_TYPE) {
426458
newVariable.value = originalVariable.value.slice(0);
459+
} else if (newVariable.type === Variable.TABLE_TYPE) {
460+
newVariable.value = originalVariable.value.slice(0);
427461
} else {
428462
newVariable.value = originalVariable.value;
429463
}

src/engine/variable.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Variable {
1010
/**
1111
* @param {string} id Id of the variable.
1212
* @param {string} name Name of the variable.
13-
* @param {string} type Type of the variable, one of '' or 'list'
13+
* @param {string} type Type of the variable, one of '', 'list', or 'table'
1414
* @param {boolean} isCloud Whether the variable is stored in the cloud.
1515
* @constructor
1616
*/
@@ -26,6 +26,9 @@ class Variable {
2626
case Variable.LIST_TYPE:
2727
this.value = [];
2828
break;
29+
case Variable.TABLE_TYPE:
30+
this.value = [];
31+
break;
2932
case Variable.BROADCAST_MESSAGE_TYPE:
3033
this.value = this.name;
3134
break;
@@ -58,6 +61,14 @@ class Variable {
5861
return 'list'; // used by compiler
5962
}
6063

64+
/**
65+
* Type representation for table variables.
66+
* @const {string}
67+
*/
68+
static get TABLE_TYPE () {
69+
return 'table'; // used by compiler
70+
}
71+
6172
/**
6273
* Type representation for list variables.
6374
* @const {string}

0 commit comments

Comments
 (0)