Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new blocks + Overhaul palette (vm part) #196

Closed
wants to merge 13 commits into from
12 changes: 6 additions & 6 deletions src/blocks/scratch3_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,13 @@ class Scratch3ControlBlocks {
}

allAtOnce (args, util) {
// Since the "all at once" block is implemented for compatiblity with
// Scratch 2.0 projects, it behaves the same way it did in 2.0, which
// is to simply run the contained script (like "if 1 = 1").
// (In early versions of Scratch 2.0, it would work the same way as
// "run without screen refresh" custom blocks do now, but this was
// removed before the release of 2.0.)
// In Scratch 3.0 and TurboWarp, this would simply
// run the contained substack. In Unsandboxed,
// we've reimplemented the intended functionality
// of running the stack all in one frame.
util.thread.peekStackFrame().warpMode = false;
util.startBranch(1, false);
util.thread.peekStackFrame().warpMode = true;
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/blocks/scratch3_operators.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
operator_gt: this.gt,
operator_and: this.and,
operator_or: this.or,
operator_xor: this.xor,
operator_not: this.not,
operator_random: this.random,
operator_join: this.join,
operator_letter_of: this.letterOf,
operator_letters_of: this.lettersOf,
operator_length: this.length,
operator_contains: this.contains,
operator_mod: this.mod,
Expand Down Expand Up @@ -73,6 +75,10 @@
return Cast.toBoolean(args.OPERAND1) || Cast.toBoolean(args.OPERAND2);
}

xor (args) {
return Cast.toBoolean(args.OPERAND1) !== Cast.toBoolean(args.OPERAND2);
}

not (args) {
return !Cast.toBoolean(args.OPERAND);
}
Expand Down Expand Up @@ -107,6 +113,12 @@
return str.charAt(index);
}

lettersOf (args) {
const index1 = Cast.toNumber(args.LETTER1) - 1;
const index2 = Cast.toNumber(args.LETTER2) - 1;
return str.slice(Math.max(index1, 1), Math.min(str.length, index2));

Check failure on line 119 in src/blocks/scratch3_operators.js

View workflow job for this annotation

GitHub Actions / build

'str' is not defined

Check failure on line 119 in src/blocks/scratch3_operators.js

View workflow job for this annotation

GitHub Actions / build

'str' is not defined
}

length (args) {
return Cast.toString(args.STRING).length;
}
Expand Down
11 changes: 5 additions & 6 deletions src/compiler/irgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@
const blockInfo = this.getBlockInfo(block.opcode);
if (blockInfo) {
const type = blockInfo.info.blockType;
if (type === BlockType.REPORTER || type === BlockType.BOOLEAN) {
if (type === BlockType.REPORTER || type === BlockType.BOOLEAN || type === BlockType.INLINE) {
return this.descendCompatLayer(block);
}
}
Expand Down Expand Up @@ -694,15 +694,14 @@
descendStackedBlock (block) {
switch (block.opcode) {
case 'control_all_at_once':
// In Scratch 3, this block behaves like "if 1 = 1"
// In Unsandboxed, attempts to run the script in 1 frame.
return {
kind: 'control.if',
kind: 'control.allAtOnce',
condition: {
kind: 'constant',
value: true
},
whenTrue: this.descendSubstack(block, 'SUBSTACK'),
whenFalse: []
code: this.descendSubstack(block, 'SUBSTACK'),

Check failure on line 704 in src/compiler/irgen.js

View workflow job for this annotation

GitHub Actions / build

Unexpected trailing comma
};
case 'control_clear_counter':
return {
Expand Down Expand Up @@ -1415,7 +1414,7 @@
const blockInfo = this.getBlockInfo(block.opcode);
const blockType = (blockInfo && blockInfo.info && blockInfo.info.blockType) || BlockType.COMMAND;
const substacks = {};
if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP) {
if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP || blockType === BlockType.INLINE) {
for (const inputName in block.inputs) {
if (!inputName.startsWith('SUBSTACK')) continue;
const branchNum = inputName === 'SUBSTACK' ? 1 : +inputName.substring('SUBSTACK'.length);
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/jsgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,13 @@
this.source += `}\n`;
break;

case 'control.allAtOnce':
const previousWarp = this.isWarp;

Check failure on line 880 in src/compiler/jsgen.js

View workflow job for this annotation

GitHub Actions / build

Unexpected lexical declaration in case block
this.isWarp = true;
this.descendStack(node.code, new Frame(false, 'control.allAtOnce'));
this.isWarp = previousWarp;
break;

case 'counter.clear':
this.source += 'runtime.ext_scratch3_control._counter = 0;\n';
break;
Expand Down
42 changes: 36 additions & 6 deletions src/engine/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ const ArgumentTypeMap = (() => {
fieldName: 'SOUND_MENU'
}
};
map[ArgumentType.VARIABLE] = {
fieldType: 'field_variable',
fieldName: 'VARIABLE'
};
return map;
})();

Expand Down Expand Up @@ -1219,14 +1223,15 @@ class Runtime extends EventEmitter {
type: menuId,
inputsInline: true,
output: 'String',
colour: categoryInfo.color1,
colourSecondary: categoryInfo.color2,
colourTertiary: categoryInfo.color3,
colour: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color1,
colourSecondary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color2,
colourTertiary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color3,
outputShape: menuInfo.acceptReporters ?
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
args0: [
{
type: 'field_dropdown',
{// to do: we could reimplement field_numberdropdown here really easily
type: menuInfo.acceptText ?
'field_textdropdown' : 'field_dropdown',
name: menuName,
options: menuItems
}
Expand Down Expand Up @@ -1414,6 +1419,11 @@ class Runtime extends EventEmitter {
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
}
break;
case BlockType.INLINE:
blockInfo.branchCount = blockInfo.branchCount || 1;
blockJSON.output = blockInfo.allowDropAnywhere ? null : 'String'; // TODO: distinguish number & string here?
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
break;
}

const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
Expand Down Expand Up @@ -1561,7 +1571,7 @@ class Runtime extends EventEmitter {
}

/**
* Helper for _convertPlaceholdes which handles inline images which are a specialized case of block "arguments".
* Helper for _convertPlaceholders which handles inline images which are a specialized case of block "arguments".
* @param {object} argInfo Metadata about the inline image as specified by the extension
* @return {object} JSON blob for a scratch-blocks image field.
* @private
Expand All @@ -1583,6 +1593,24 @@ class Runtime extends EventEmitter {
};
}

/**
* Helper for _convertPlaceholders which handles variable fields which are a specialized case of block "arguments".
* @param {object} argInfo Metadata about the inline image as specified by the extension
* @return {object} JSON blob for a scratch-blocks variable field.
* @private
*/
_constructVariableJson (argInfo, placeholder) {
return {
type: 'field_variable',
name: placeholder,
variableTypes:
// eslint-disable-next-line max-len
argInfo.variableTypes ? (Array.isArray(argInfo.variableTypes) ? argInfo.variableTypes : [argInfo.variableTypes]) : [''],
variable: (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
filter: argInfo.filter ?? []
};
}

/**
* Helper for _convertForScratchBlocks which handles linearization of argument placeholders. Called as a callback
* from string#replace. In addition to the return value the JSON and XML items in the context will be filled.
Expand Down Expand Up @@ -1610,6 +1638,8 @@ class Runtime extends EventEmitter {
// check if this is not one of those cases. E.g. an inline image on a block.
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
} else if (argTypeInfo.fieldType === 'field_variable') {
argJSON = this._constructVariableJson(argInfo, placeholder);
} else {
// Construct input value

Expand Down
7 changes: 6 additions & 1 deletion src/extension-support/argument-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ const ArgumentType = {
/**
* Name of sound in the current target
*/
SOUND: 'sound'
SOUND: 'sound',

/**
* Name of variable in the current specified target(s)
*/
VARIABLE: 'variable'
};

module.exports = ArgumentType;
8 changes: 7 additions & 1 deletion src/extension-support/block-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ const BlockType = {
/**
* Arbitrary scratch-blocks XML.
*/
XML: 'xml'
XML: 'xml',

/**
* Specialized reporter block that allows for the insertion and evaluation
* of a substack.
*/
INLINE: 'inline'
};

module.exports = BlockType;
3 changes: 2 additions & 1 deletion src/extension-support/extension-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,8 @@ class ExtensionManager {
});

if (!menuItems || menuItems.length < 1) {
throw new Error(`Extension menu returned no items: ${menuItemFunctionName}`);
console.warn(`Extension menu returned no items: ${menuItemFunctionName}`);
return [''];
}
return menuItems;
}
Expand Down
Loading