refactor(generators): Migrate PHP generators to TypeScript (#7647)

* refactor(generators): Migrate dart_generator.js to TypeScript

* refactor(generators): Simplify getAdjusted

  Slightly simplify the implementation of getAdjusted, in part to
  make it more readable.  Also improve its JSDoc comment.

* refactor(generators): Migrate generators/php/* to TypeScript

  First pass doing very mechanistic migration, not attempting to fix
  all the resulting type errors.

* fix(generators): Fix type errors in generator functions

  This consists almost entirely of adding casts, so the code output
  by tsc should be as similar as possible to the pre-migration .js
  source files.

* fix(generators): Fix more minor inconsistencies in JS and Python

  The migration of the JavaScript and Python generators
  inadvertently introduced some inconsistencies in the code,
  e.g. putting executable code before the initial comment line that
  most generator functions begin with.  This fixes another instance
  of this (but n.b. that these inline comments should have been
  JSDocs and a task has been added to #7600 to convert them).

  Additionally, I noticed while doing the PHP migration that
  ORDER_OVERRIDES was not typed as specifically as it could be,
  in previous migrations, so this is fixed here (along with the
  formatting of the associated JSDoc, which can fit on one line
  now.)

* refactor(generators): Migrate generators/php.js to TypeScript

  The way the generator functions are added to
  phpGenerator.forBlock has been modified so that incorrect
  generator function signatures will cause tsc to generate a type
  error.

* chore(generator): Format

  One block protected with // prettier-ignore to preserve careful
  comment formatting.

  Where there are repeated concatenations prettier has made a pretty
  mess of things, but the correct fix is probably to use template
  literals instead (rather than just locally disabling prettier).
  This is one of the items in the to-do list in #7600.

* docs(generators): @fileoverview -> @file

  With an update to the wording for generators/php.ts.

* fix(generators): Fixes for PR #7647.

  - Don't declare unused wherePascalCase dictionary.
  - Don't allow null in OPERATOR dictionary when not needed.
  - Fix return type (and documentation thereof) of getAdjusted.
This commit is contained in:
Christopher Allen
2023-11-28 16:59:19 +00:00
committed by GitHub
parent b198e2f4ae
commit 7bda30924a
16 changed files with 1107 additions and 756 deletions

View File

@@ -66,10 +66,8 @@ export enum Order {
* JavaScript code generator class. * JavaScript code generator class.
*/ */
export class JavascriptGenerator extends CodeGenerator { export class JavascriptGenerator extends CodeGenerator {
/** /** List of outer-inner pairings that do NOT require parentheses. */
* List of outer-inner pairings that do NOT require parentheses. ORDER_OVERRIDES: [Order, Order][] = [
*/
ORDER_OVERRIDES: number[][] = [
// (foo()).bar -> foo().bar // (foo()).bar -> foo().bar
// (foo())[0] -> foo()[0] // (foo())[0] -> foo()[0]
[Order.FUNCTION_CALL, Order.MEMBER], [Order.FUNCTION_CALL, Order.MEMBER],

View File

@@ -302,6 +302,7 @@ export function lists_getSublist(
block: Block, block: Block,
generator: JavascriptGenerator, generator: JavascriptGenerator,
): [string, Order] { ): [string, Order] {
// Get sublist.
// Dictionary of WHEREn field choices and their CamelCase equivalents. // Dictionary of WHEREn field choices and their CamelCase equivalents.
const wherePascalCase = { const wherePascalCase = {
'FIRST': 'First', 'FIRST': 'First',
@@ -310,7 +311,6 @@ export function lists_getSublist(
'FROM_END': 'FromEnd', 'FROM_END': 'FromEnd',
}; };
type WhereOption = keyof typeof wherePascalCase; type WhereOption = keyof typeof wherePascalCase;
// Get sublist.
const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]';
const where1 = block.getFieldValue('WHERE1') as WhereOption; const where1 = block.getFieldValue('WHERE1') as WhereOption;
const where2 = block.getFieldValue('WHERE2') as WhereOption; const where2 = block.getFieldValue('WHERE2') as WhereOption;

View File

@@ -5,9 +5,9 @@
*/ */
/** /**
* @fileoverview Complete helper functions for generating PHP for * @file Instantiate a PhpGenerator and populate it with the complete
* blocks. This is the entrypoint for php_compressed.js. * set of block generator functions for PHP. This is the entrypoint
* @suppress {extraRequire} * for php_compressed.js.
*/ */
// Former goog.module ID: Blockly.PHP.all // Former goog.module ID: Blockly.PHP.all
@@ -32,8 +32,17 @@ export * from './php/php_generator.js';
export const phpGenerator = new PhpGenerator(); export const phpGenerator = new PhpGenerator();
// Install per-block-type generator functions: // Install per-block-type generator functions:
Object.assign( const generators: typeof phpGenerator.forBlock = {
phpGenerator.forBlock, ...colour,
colour, lists, logic, loops, math, procedures, ...lists,
text, variables, variablesDynamic ...logic,
); ...loops,
...math,
...procedures,
...text,
...variables,
...variablesDynamic,
};
for (const name in generators) {
phpGenerator.forBlock[name] = generators[name];
}

View File

@@ -5,37 +5,52 @@
*/ */
/** /**
* @fileoverview Generating PHP for colour blocks. * @file Generating PHP for colour blocks.
*/ */
// Former goog.module ID: Blockly.PHP.colour // Former goog.module ID: Blockly.PHP.colour
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function colour_picker(
export function colour_picker(block, generator) { block: Block,
generator: PhpGenerator,
): [string, Order] {
// Colour picker. // Colour picker.
const code = generator.quote_(block.getFieldValue('COLOUR')); const code = generator.quote_(block.getFieldValue('COLOUR'));
return [code, Order.ATOMIC]; return [code, Order.ATOMIC];
}; }
export function colour_random(block, generator) { export function colour_random(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Generate a random colour. // Generate a random colour.
const functionName = generator.provideFunction_('colour_random', ` const functionName = generator.provideFunction_(
'colour_random',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { function ${generator.FUNCTION_NAME_PLACEHOLDER_}() {
return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
} }
`); `,
);
const code = functionName + '()'; const code = functionName + '()';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function colour_rgb(block, generator) { export function colour_rgb(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Compose a colour from RGB components expressed as percentages. // Compose a colour from RGB components expressed as percentages.
const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; const red = generator.valueToCode(block, 'RED', Order.NONE) || 0;
const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0;
const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0;
const functionName = generator.provideFunction_('colour_rgb', ` const functionName = generator.provideFunction_(
'colour_rgb',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) {
$r = round(max(min($r, 100), 0) * 2.55); $r = round(max(min($r, 100), 0) * 2.55);
$g = round(max(min($g, 100), 0) * 2.55); $g = round(max(min($g, 100), 0) * 2.55);
@@ -46,19 +61,23 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) {
$hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT); $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT);
return $hex; return $hex;
} }
`); `,
);
const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function colour_blend(block, generator) { export function colour_blend(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Blend two colours together. // Blend two colours together.
const c1 = const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'";
generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'";
const c2 =
generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'";
const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5;
const functionName = generator.provideFunction_('colour_blend', ` const functionName = generator.provideFunction_(
'colour_blend',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) {
$ratio = max(min($ratio, 1), 0); $ratio = max(min($ratio, 1), 0);
$r1 = hexdec(substr($c1, 1, 2)); $r1 = hexdec(substr($c1, 1, 2));
@@ -76,7 +95,8 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) {
$hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT); $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT);
return $hex; return $hex;
} }
`); `,
);
const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }

View File

@@ -5,7 +5,7 @@
*/ */
/** /**
* @fileoverview Generating PHP for list blocks. * @file Generating PHP for list blocks.
*/ */
/** /**
@@ -22,27 +22,42 @@
// Former goog.module ID: Blockly.generator.lists // Former goog.module ID: Blockly.generator.lists
import * as stringUtils from '../../core/utils/string.js'; import * as stringUtils from '../../core/utils/string.js';
import type {Block} from '../../core/block.js';
import type {CreateWithBlock} from '../../blocks/lists.js';
import {NameType} from '../../core/names.js'; import {NameType} from '../../core/names.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function lists_create_empty(block, generator) { export function lists_create_empty(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Create an empty list. // Create an empty list.
return ['array()', Order.FUNCTION_CALL]; return ['array()', Order.FUNCTION_CALL];
}; }
export function lists_create_with(block, generator) { export function lists_create_with(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Create a list with any number of elements of any type. // Create a list with any number of elements of any type.
let code = new Array(block.itemCount_); const createWithBlock = block as CreateWithBlock;
for (let i = 0; i < block.itemCount_; i++) { const elements = new Array(createWithBlock.itemCount_);
code[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null'; for (let i = 0; i < createWithBlock.itemCount_; i++) {
elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null';
} }
code = 'array(' + code.join(', ') + ')'; const code = 'array(' + elements.join(', ') + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function lists_repeat(block, generator) { export function lists_repeat(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Create a list with one element repeated. // Create a list with one element repeated.
const functionName = generator.provideFunction_('lists_repeat', ` const functionName = generator.provideFunction_(
'lists_repeat',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) {
$array = array(); $array = array();
for ($index = 0; $index < $count; $index++) { for ($index = 0; $index < $count; $index++) {
@@ -50,16 +65,22 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) {
} }
return $array; return $array;
} }
`); `,
);
const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'null';
const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0';
const code = functionName + '(' + element + ', ' + repeatCount + ')'; const code = functionName + '(' + element + ', ' + repeatCount + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function lists_length(block, generator) { export function lists_length(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// String or array length. // String or array length.
const functionName = generator.provideFunction_('length', ` const functionName = generator.provideFunction_(
'length',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) {
if (is_string($value)) { if (is_string($value)) {
return strlen($value); return strlen($value);
@@ -67,24 +88,29 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) {
return count($value); return count($value);
} }
} }
`); `,
);
const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
return [functionName + '(' + list + ')', Order.FUNCTION_CALL]; return [functionName + '(' + list + ')', Order.FUNCTION_CALL];
}; }
export function lists_isEmpty(block, generator) { export function lists_isEmpty(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Is the string null or array empty? // Is the string null or array empty?
const argument0 = const argument0 =
generator.valueToCode(block, 'VALUE', Order.FUNCTION_CALL) generator.valueToCode(block, 'VALUE', Order.FUNCTION_CALL) || 'array()';
|| 'array()';
return ['empty(' + argument0 + ')', Order.FUNCTION_CALL]; return ['empty(' + argument0 + ')', Order.FUNCTION_CALL];
}; }
export function lists_indexOf(block, generator) { export function lists_indexOf(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Find an item in the list. // Find an item in the list.
const argument0 = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const argument0 = generator.valueToCode(block, 'FIND', Order.NONE) || "''";
const argument1 = const argument1 = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]';
generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]';
let errorIndex = ' -1'; let errorIndex = ' -1';
let indexAdjustment = ''; let indexAdjustment = '';
if (block.workspace.options.oneBasedIndex) { if (block.workspace.options.oneBasedIndex) {
@@ -94,17 +120,22 @@ export function lists_indexOf(block, generator) {
let functionName; let functionName;
if (block.getFieldValue('END') === 'FIRST') { if (block.getFieldValue('END') === 'FIRST') {
// indexOf // indexOf
functionName = generator.provideFunction_('indexOf', ` functionName = generator.provideFunction_(
'indexOf',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) {
for ($index = 0; $index < count($haystack); $index++) { for ($index = 0; $index < count($haystack); $index++) {
if ($haystack[$index] == $needle) return $index${indexAdjustment}; if ($haystack[$index] == $needle) return $index${indexAdjustment};
} }
return ${errorIndex}; return ${errorIndex};
} }
`); `,
);
} else { } else {
// lastIndexOf // lastIndexOf
functionName = generator.provideFunction_('lastIndexOf', ` functionName = generator.provideFunction_(
'lastIndexOf',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) {
$last = ${errorIndex}; $last = ${errorIndex};
for ($index = 0; $index < count($haystack); $index++) { for ($index = 0; $index < count($haystack); $index++) {
@@ -112,14 +143,18 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) {
} }
return $last; return $last;
} }
`); `,
);
} }
const code = functionName + '(' + argument1 + ', ' + argument0 + ')'; const code = functionName + '(' + argument1 + ', ' + argument0 + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function lists_getIndex(block, generator) { export function lists_getIndex(
block: Block,
generator: PhpGenerator,
): [string, Order] | string {
// Get element at index. // Get element at index.
const mode = block.getFieldValue('MODE') || 'GET'; const mode = block.getFieldValue('MODE') || 'GET';
const where = block.getFieldValue('WHERE') || 'FROM_START'; const where = block.getFieldValue('WHERE') || 'FROM_START';
@@ -127,34 +162,34 @@ export function lists_getIndex(block, generator) {
case 'FIRST': case 'FIRST':
if (mode === 'GET') { if (mode === 'GET') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()'; generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()';
const code = list + '[0]'; const code = list + '[0]';
return [code, Order.MEMBER]; return [code, Order.MEMBER];
} else if (mode === 'GET_REMOVE') { } else if (mode === 'GET_REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'array_shift(' + list + ')'; const code = 'array_shift(' + list + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') { } else if (mode === 'REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
return 'array_shift(' + list + ');\n'; return 'array_shift(' + list + ');\n';
} }
break; break;
case 'LAST': case 'LAST':
if (mode === 'GET') { if (mode === 'GET') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'end(' + list + ')'; const code = 'end(' + list + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'GET_REMOVE') { } else if (mode === 'GET_REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'array_pop(' + list + ')'; const code = 'array_pop(' + list + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') { } else if (mode === 'REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
return 'array_pop(' + list + ');\n'; return 'array_pop(' + list + ');\n';
} }
break; break;
@@ -162,17 +197,17 @@ export function lists_getIndex(block, generator) {
const at = generator.getAdjusted(block, 'AT'); const at = generator.getAdjusted(block, 'AT');
if (mode === 'GET') { if (mode === 'GET') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()'; generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()';
const code = list + '[' + at + ']'; const code = list + '[' + at + ']';
return [code, Order.MEMBER]; return [code, Order.MEMBER];
} else if (mode === 'GET_REMOVE') { } else if (mode === 'GET_REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'array_splice(' + list + ', ' + at + ', 1)[0]'; const code = 'array_splice(' + list + ', ' + at + ', 1)[0]';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') { } else if (mode === 'REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
return 'array_splice(' + list + ', ' + at + ', 1);\n'; return 'array_splice(' + list + ', ' + at + ', 1);\n';
} }
break; break;
@@ -180,17 +215,22 @@ export function lists_getIndex(block, generator) {
case 'FROM_END': case 'FROM_END':
if (mode === 'GET') { if (mode === 'GET') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const at = generator.getAdjusted(block, 'AT', 1, true); const at = generator.getAdjusted(block, 'AT', 1, true);
const code = 'array_slice(' + list + ', ' + at + ', 1)[0]'; const code = 'array_slice(' + list + ', ' + at + ', 1)[0]';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'GET_REMOVE' || mode === 'REMOVE') { } else if (mode === 'GET_REMOVE' || mode === 'REMOVE') {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const at = const at = generator.getAdjusted(
generator.getAdjusted(block, 'AT', 1, false, Order.SUBTRACTION); block,
const code = 'array_splice(' + list + ', count(' + list + ') - ' + at + 'AT',
', 1)[0]'; 1,
false,
Order.SUBTRACTION,
);
const code =
'array_splice(' + list + ', count(' + list + ') - ' + at + ', 1)[0]';
if (mode === 'GET_REMOVE') { if (mode === 'GET_REMOVE') {
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') { } else if (mode === 'REMOVE') {
@@ -200,58 +240,65 @@ export function lists_getIndex(block, generator) {
break; break;
case 'RANDOM': { case 'RANDOM': {
const list = const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
if (mode === 'GET') { if (mode === 'GET') {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_get_random_item', ` 'lists_get_random_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) {
return $list[rand(0,count($list)-1)]; return $list[rand(0,count($list)-1)];
} }
`); `,
);
const code = functionName + '(' + list + ')'; const code = functionName + '(' + list + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'GET_REMOVE') { } else if (mode === 'GET_REMOVE') {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_get_remove_random_item', ` 'lists_get_remove_random_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) {
$x = rand(0,count($list)-1); $x = rand(0,count($list)-1);
unset($list[$x]); unset($list[$x]);
return array_values($list); return array_values($list);
} }
`); `,
);
const code = functionName + '(' + list + ')'; const code = functionName + '(' + list + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') { } else if (mode === 'REMOVE') {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_remove_random_item', ` 'lists_remove_random_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) {
unset($list[rand(0,count($list)-1)]); unset($list[rand(0,count($list)-1)]);
} }
`); `,
);
return functionName + '(' + list + ');\n'; return functionName + '(' + list + ');\n';
} }
break; break;
} }
} }
throw Error('Unhandled combination (lists_getIndex).'); throw Error('Unhandled combination (lists_getIndex).');
}; }
export function lists_setIndex(block, generator) { export function lists_setIndex(block: Block, generator: PhpGenerator) {
// Set element at index. // Set element at index.
// Note: Until February 2013 this block did not have MODE or WHERE inputs. // Note: Until February 2013 this block did not have MODE or WHERE inputs.
const mode = block.getFieldValue('MODE') || 'GET'; const mode = block.getFieldValue('MODE') || 'GET';
const where = block.getFieldValue('WHERE') || 'FROM_START'; const where = block.getFieldValue('WHERE') || 'FROM_START';
const value = const value = generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null';
generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null';
// Cache non-trivial values to variables to prevent repeated look-ups. // Cache non-trivial values to variables to prevent repeated look-ups.
// Closure, which accesses and modifies 'list'. // Closure, which accesses and modifies 'list'.
let cachedList; let cachedList: string;
function cacheList() { function cacheList() {
if (cachedList.match(/^\$\w+$/)) { if (cachedList.match(/^\$\w+$/)) {
return ''; return '';
} }
const listVar = const listVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); 'tmp_list',
NameType.VARIABLE,
);
const code = listVar + ' = &' + cachedList + ';\n'; const code = listVar + ' = &' + cachedList + ';\n';
cachedList = listVar; cachedList = listVar;
return code; return code;
@@ -260,24 +307,26 @@ export function lists_setIndex(block, generator) {
case 'FIRST': case 'FIRST':
if (mode === 'SET') { if (mode === 'SET') {
const list = const list =
generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()'; generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()';
return list + '[0] = ' + value + ';\n'; return list + '[0] = ' + value + ';\n';
} else if (mode === 'INSERT') { } else if (mode === 'INSERT') {
const list = const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
return 'array_unshift(' + list + ', ' + value + ');\n'; return 'array_unshift(' + list + ', ' + value + ');\n';
} }
break; break;
case 'LAST': { case 'LAST': {
const list = const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
if (mode === 'SET') { if (mode === 'SET') {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_set_last_item', ` 'lists_set_last_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) {
$list[count($list) - 1] = $value; $list[count($list) - 1] = $value;
} }
`); `,
);
return functionName + '(' + list + ', ' + value + ');\n'; return functionName + '(' + list + ', ' + value + ');\n';
} else if (mode === 'INSERT') { } else if (mode === 'INSERT') {
return 'array_push(' + list + ', ' + value + ');\n'; return 'array_push(' + list + ', ' + value + ');\n';
@@ -288,45 +337,51 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) {
const at = generator.getAdjusted(block, 'AT'); const at = generator.getAdjusted(block, 'AT');
if (mode === 'SET') { if (mode === 'SET') {
const list = const list =
generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()'; generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()';
return list + '[' + at + '] = ' + value + ';\n'; return list + '[' + at + '] = ' + value + ';\n';
} else if (mode === 'INSERT') { } else if (mode === 'INSERT') {
const list = const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
return 'array_splice(' + list + ', ' + at + ', 0, ' + value + ');\n'; return 'array_splice(' + list + ', ' + at + ', 0, ' + value + ');\n';
} }
break; break;
} }
case 'FROM_END': { case 'FROM_END': {
const list = const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
const at = generator.getAdjusted(block, 'AT', 1); const at = generator.getAdjusted(block, 'AT', 1);
if (mode === 'SET') { if (mode === 'SET') {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_set_from_end', ` 'lists_set_from_end',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) {
$list[count($list) - $at] = $value; $list[count($list) - $at] = $value;
} }
`); `,
);
return functionName + '(' + list + ', ' + at + ', ' + value + ');\n'; return functionName + '(' + list + ', ' + at + ', ' + value + ');\n';
} else if (mode === 'INSERT') { } else if (mode === 'INSERT') {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_insert_from_end', ` 'lists_insert_from_end',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) {
return array_splice($list, count($list) - $at, 0, $value); return array_splice($list, count($list) - $at, 0, $value);
} }
`); `,
);
return functionName + '(' + list + ', ' + at + ', ' + value + ');\n'; return functionName + '(' + list + ', ' + at + ', ' + value + ');\n';
} }
break; break;
} }
case 'RANDOM': case 'RANDOM':
cachedList = cachedList =
generator.valueToCode(block, 'LIST', Order.REFERENCE) || 'array()'; generator.valueToCode(block, 'LIST', Order.REFERENCE) || 'array()';
let code = cacheList(); let code = cacheList();
const list = cachedList; const list = cachedList;
const xVar = const xVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); 'tmp_x',
NameType.VARIABLE,
);
code += xVar + ' = rand(0, count(' + list + ')-1);\n'; code += xVar + ' = rand(0, count(' + list + ')-1);\n';
if (mode === 'SET') { if (mode === 'SET') {
code += list + '[' + xVar + '] = ' + value + ';\n'; code += list + '[' + xVar + '] = ' + value + ';\n';
@@ -338,9 +393,12 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) {
break; break;
} }
throw Error('Unhandled combination (lists_setIndex).'); throw Error('Unhandled combination (lists_setIndex).');
}; }
export function lists_getSublist(block, generator) { export function lists_getSublist(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Get sublist. // Get sublist.
const list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; const list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
const where1 = block.getFieldValue('WHERE1'); const where1 = block.getFieldValue('WHERE1');
@@ -349,8 +407,9 @@ export function lists_getSublist(block, generator) {
if (where1 === 'FIRST' && where2 === 'LAST') { if (where1 === 'FIRST' && where2 === 'LAST') {
code = list; code = list;
} else if ( } else if (
list.match(/^\$\w+$/) || list.match(/^\$\w+$/) ||
(where1 !== 'FROM_END' && where2 === 'FROM_START')) { (where1 !== 'FROM_END' && where2 === 'FROM_START')
) {
// If the list is a simple value or doesn't require a call for length, don't // If the list is a simple value or doesn't require a call for length, don't
// generate a helper function. // generate a helper function.
let at1; let at1;
@@ -359,8 +418,7 @@ export function lists_getSublist(block, generator) {
at1 = generator.getAdjusted(block, 'AT1'); at1 = generator.getAdjusted(block, 'AT1');
break; break;
case 'FROM_END': case 'FROM_END':
at1 = at1 = generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION);
generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION);
at1 = 'count(' + list + ') - ' + at1; at1 = 'count(' + list + ') - ' + at1;
break; break;
case 'FIRST': case 'FIRST':
@@ -373,11 +431,12 @@ export function lists_getSublist(block, generator) {
let length; let length;
switch (where2) { switch (where2) {
case 'FROM_START': case 'FROM_START':
at2 = at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
length = at2 + ' - '; length = at2 + ' - ';
if (stringUtils.isNumber(String(at1)) || if (
String(at1).match(/^\(.+\)$/)) { stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)
) {
length += at1; length += at1;
} else { } else {
length += '(' + at1 + ')'; length += '(' + at1 + ')';
@@ -385,11 +444,12 @@ export function lists_getSublist(block, generator) {
length += ' + 1'; length += ' + 1';
break; break;
case 'FROM_END': case 'FROM_END':
at2 = at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
length = 'count(' + list + ') - ' + at2 + ' - '; length = 'count(' + list + ') - ' + at2 + ' - ';
if (stringUtils.isNumber(String(at1)) || if (
String(at1).match(/^\(.+\)$/)) { stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)
) {
length += at1; length += at1;
} else { } else {
length += '(' + at1 + ')'; length += '(' + at1 + ')';
@@ -397,8 +457,10 @@ export function lists_getSublist(block, generator) {
break; break;
case 'LAST': case 'LAST':
length = 'count(' + list + ') - '; length = 'count(' + list + ') - ';
if (stringUtils.isNumber(String(at1)) || if (
String(at1).match(/^\(.+\)$/)) { stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)
) {
length += at1; length += at1;
} else { } else {
length += '(' + at1 + ')'; length += '(' + at1 + ')';
@@ -411,8 +473,9 @@ export function lists_getSublist(block, generator) {
} else { } else {
const at1 = generator.getAdjusted(block, 'AT1'); const at1 = generator.getAdjusted(block, 'AT1');
const at2 = generator.getAdjusted(block, 'AT2'); const at2 = generator.getAdjusted(block, 'AT2');
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('lists_get_sublist', ` 'lists_get_sublist',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) {
if ($where1 == 'FROM_END') { if ($where1 == 'FROM_END') {
$at1 = count($list) - 1 - $at1; $at1 = count($list) - 1 - $at1;
@@ -433,20 +496,37 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2,
} }
return array_slice($list, $at1, $length); return array_slice($list, $at1, $length);
} }
`); `,
code = functionName + '(' + list + ', \'' + where1 + '\', ' + at1 + ', \'' + );
where2 + '\', ' + at2 + ')'; code =
functionName +
'(' +
list +
", '" +
where1 +
"', " +
at1 +
", '" +
where2 +
"', " +
at2 +
')';
} }
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function lists_sort(block, generator) { export function lists_sort(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Block for sorting a list. // Block for sorting a list.
const listCode = const listCode =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1;
const type = block.getFieldValue('TYPE'); const type = block.getFieldValue('TYPE');
const functionName = generator.provideFunction_('lists_sort', ` const functionName = generator.provideFunction_(
'lists_sort',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) {
$sortCmpFuncs = array( $sortCmpFuncs = array(
'NUMERIC' => 'strnatcasecmp', 'NUMERIC' => 'strnatcasecmp',
@@ -461,17 +541,20 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) {
} }
return $list2; return $list2;
} }
`); `,
);
const sortCode = const sortCode =
functionName + '(' + listCode + ', "' + type + '", ' + direction + ')'; functionName + '(' + listCode + ', "' + type + '", ' + direction + ')';
return [sortCode, Order.FUNCTION_CALL]; return [sortCode, Order.FUNCTION_CALL];
}; }
export function lists_split(block, generator) { export function lists_split(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Block for splitting text into a list, or joining a list into text. // Block for splitting text into a list, or joining a list into text.
let value_input = generator.valueToCode(block, 'INPUT', Order.NONE); let value_input = generator.valueToCode(block, 'INPUT', Order.NONE);
const value_delim = const value_delim = generator.valueToCode(block, 'DELIM', Order.NONE) || "''";
generator.valueToCode(block, 'DELIM', Order.NONE) || "''";
const mode = block.getFieldValue('MODE'); const mode = block.getFieldValue('MODE');
let functionName; let functionName;
if (mode === 'SPLIT') { if (mode === 'SPLIT') {
@@ -489,11 +572,14 @@ export function lists_split(block, generator) {
} }
const code = functionName + '(' + value_delim + ', ' + value_input + ')'; const code = functionName + '(' + value_delim + ', ' + value_input + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function lists_reverse(block, generator) { export function lists_reverse(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Block for reversing a list. // Block for reversing a list.
const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
const code = 'array_reverse(' + list + ')'; const code = 'array_reverse(' + list + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }

View File

@@ -5,35 +5,43 @@
*/ */
/** /**
* @fileoverview Generating PHP for logic blocks. * @file Generating PHP for logic blocks.
*/ */
// Former goog.module ID: Blockly.PHP.logic // Former goog.module ID: Blockly.PHP.logic
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function controls_if(block: Block, generator: PhpGenerator) {
export function controls_if(block, generator) {
// If/elseif/else condition. // If/elseif/else condition.
let n = 0; let n = 0;
let code = '', branchCode, conditionCode; let code = '',
branchCode,
conditionCode;
if (generator.STATEMENT_PREFIX) { if (generator.STATEMENT_PREFIX) {
// Automatic prefix insertion is switched off for this block. Add manually. // Automatic prefix insertion is switched off for this block. Add manually.
code += generator.injectId(generator.STATEMENT_PREFIX, block); code += generator.injectId(generator.STATEMENT_PREFIX, block);
} }
do { do {
conditionCode = conditionCode =
generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false'; generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false';
branchCode = generator.statementToCode(block, 'DO' + n); branchCode = generator.statementToCode(block, 'DO' + n);
if (generator.STATEMENT_SUFFIX) { if (generator.STATEMENT_SUFFIX) {
branchCode = branchCode =
generator.prefixLines( generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block), generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT) + generator.INDENT,
branchCode; ) + branchCode;
} }
code += (n > 0 ? ' else ' : '') + 'if (' + conditionCode + ') {\n' + code +=
branchCode + '}'; (n > 0 ? ' else ' : '') +
'if (' +
conditionCode +
') {\n' +
branchCode +
'}';
n++; n++;
} while (block.getInput('IF' + n)); } while (block.getInput('IF' + n));
@@ -41,36 +49,48 @@ export function controls_if(block, generator) {
branchCode = generator.statementToCode(block, 'ELSE'); branchCode = generator.statementToCode(block, 'ELSE');
if (generator.STATEMENT_SUFFIX) { if (generator.STATEMENT_SUFFIX) {
branchCode = branchCode =
generator.prefixLines( generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block), generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT) + generator.INDENT,
branchCode; ) + branchCode;
} }
code += ' else {\n' + branchCode + '}'; code += ' else {\n' + branchCode + '}';
} }
return code + '\n'; return code + '\n';
}; }
export const controls_ifelse = controls_if; export const controls_ifelse = controls_if;
export function logic_compare(block, generator) { export function logic_compare(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Comparison operator. // Comparison operator.
const OPERATORS = const OPERATORS = {
{'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; 'EQ': '==',
const operator = OPERATORS[block.getFieldValue('OP')]; 'NEQ': '!=',
const order = (operator === '==' || operator === '!=') ? Order.EQUALITY : 'LT': '<',
Order.RELATIONAL; 'LTE': '<=',
'GT': '>',
'GTE': '>=',
};
type OperatorOption = keyof typeof OPERATORS;
const operator = OPERATORS[block.getFieldValue('OP') as OperatorOption];
const order =
operator === '==' || operator === '!=' ? Order.EQUALITY : Order.RELATIONAL;
const argument0 = generator.valueToCode(block, 'A', order) || '0'; const argument0 = generator.valueToCode(block, 'A', order) || '0';
const argument1 = generator.valueToCode(block, 'B', order) || '0'; const argument1 = generator.valueToCode(block, 'B', order) || '0';
const code = argument0 + ' ' + operator + ' ' + argument1; const code = argument0 + ' ' + operator + ' ' + argument1;
return [code, order]; return [code, order];
}; }
export function logic_operation(block, generator) { export function logic_operation(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Operations 'and', 'or'. // Operations 'and', 'or'.
const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const operator = block.getFieldValue('OP') === 'AND' ? '&&' : '||';
const order = const order = operator === '&&' ? Order.LOGICAL_AND : Order.LOGICAL_OR;
(operator === '&&') ? Order.LOGICAL_AND : Order.LOGICAL_OR;
let argument0 = generator.valueToCode(block, 'A', order); let argument0 = generator.valueToCode(block, 'A', order);
let argument1 = generator.valueToCode(block, 'B', order); let argument1 = generator.valueToCode(block, 'B', order);
if (!argument0 && !argument1) { if (!argument0 && !argument1) {
@@ -79,7 +99,7 @@ export function logic_operation(block, generator) {
argument1 = 'false'; argument1 = 'false';
} else { } else {
// Single missing arguments have no effect on the return value. // Single missing arguments have no effect on the return value.
const defaultArgument = (operator === '&&') ? 'true' : 'false'; const defaultArgument = operator === '&&' ? 'true' : 'false';
if (!argument0) { if (!argument0) {
argument0 = defaultArgument; argument0 = defaultArgument;
} }
@@ -89,35 +109,47 @@ export function logic_operation(block, generator) {
} }
const code = argument0 + ' ' + operator + ' ' + argument1; const code = argument0 + ' ' + operator + ' ' + argument1;
return [code, order]; return [code, order];
}; }
export function logic_negate(block, generator) { export function logic_negate(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Negation. // Negation.
const order = Order.LOGICAL_NOT; const order = Order.LOGICAL_NOT;
const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true'; const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true';
const code = '!' + argument0; const code = '!' + argument0;
return [code, order]; return [code, order];
}; }
export function logic_boolean(block, generator) { export function logic_boolean(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Boolean values true and false. // Boolean values true and false.
const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; const code = block.getFieldValue('BOOL') === 'TRUE' ? 'true' : 'false';
return [code, Order.ATOMIC]; return [code, Order.ATOMIC];
}; }
export function logic_null(block, generator) { export function logic_null(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Null data type. // Null data type.
return ['null', Order.ATOMIC]; return ['null', Order.ATOMIC];
}; }
export function logic_ternary(block, generator) { export function logic_ternary(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Ternary operator. // Ternary operator.
const value_if = const value_if =
generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false';
const value_then = const value_then =
generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null'; generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null';
const value_else = const value_else =
generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'null'; generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'null';
const code = value_if + ' ? ' + value_then + ' : ' + value_else; const code = value_if + ' ? ' + value_then + ' : ' + value_else;
return [code, Order.CONDITIONAL]; return [code, Order.CONDITIONAL];
}; }

View File

@@ -5,17 +5,19 @@
*/ */
/** /**
* @fileoverview Generating PHP for loop blocks. * @file Generating PHP for loop blocks.
*/ */
// Former goog.module ID: Blockly.PHP.loops // Former goog.module ID: Blockly.PHP.loops
import * as stringUtils from '../../core/utils/string.js'; import * as stringUtils from '../../core/utils/string.js';
import type {Block} from '../../core/block.js';
import type {ControlFlowInLoopBlock} from '../../blocks/loops.js';
import {NameType} from '../../core/names.js'; import {NameType} from '../../core/names.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function controls_repeat_ext(block: Block, generator: PhpGenerator) {
export function controls_repeat_ext(block, generator) {
// Repeat n times. // Repeat n times.
let repeats; let repeats;
if (block.getField('TIMES')) { if (block.getField('TIMES')) {
@@ -28,55 +30,80 @@ export function controls_repeat_ext(block, generator) {
let branch = generator.statementToCode(block, 'DO'); let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block); branch = generator.addLoopTrap(branch, block);
let code = ''; let code = '';
const loopVar = const loopVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName('count', NameType.VARIABLE); 'count',
NameType.VARIABLE,
);
let endVar = repeats; let endVar = repeats;
if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) { if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) {
endVar = endVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE); 'repeat_end',
NameType.VARIABLE,
);
code += endVar + ' = ' + repeats + ';\n'; code += endVar + ' = ' + repeats + ';\n';
} }
code += 'for (' + loopVar + ' = 0; ' + loopVar + ' < ' + endVar + '; ' + code +=
loopVar + '++) {\n' + branch + '}\n'; 'for (' +
loopVar +
' = 0; ' +
loopVar +
' < ' +
endVar +
'; ' +
loopVar +
'++) {\n' +
branch +
'}\n';
return code; return code;
}; }
export const controls_repeat = controls_repeat_ext; export const controls_repeat = controls_repeat_ext;
export function controls_whileUntil(block, generator) { export function controls_whileUntil(block: Block, generator: PhpGenerator) {
// Do while/until loop. // Do while/until loop.
const until = block.getFieldValue('MODE') === 'UNTIL'; const until = block.getFieldValue('MODE') === 'UNTIL';
let argument0 = let argument0 =
generator.valueToCode( generator.valueToCode(
block, 'BOOL', until ? Order.LOGICAL_NOT : Order.NONE) || block,
'false'; 'BOOL',
until ? Order.LOGICAL_NOT : Order.NONE,
) || 'false';
let branch = generator.statementToCode(block, 'DO'); let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block); branch = generator.addLoopTrap(branch, block);
if (until) { if (until) {
argument0 = '!' + argument0; argument0 = '!' + argument0;
} }
return 'while (' + argument0 + ') {\n' + branch + '}\n'; return 'while (' + argument0 + ') {\n' + branch + '}\n';
}; }
export function controls_for(block, generator) { export function controls_for(block: Block, generator: PhpGenerator) {
// For loop. // For loop.
const variable0 = const variable0 = generator.getVariableName(block.getFieldValue('VAR'));
generator.getVariableName(block.getFieldValue('VAR'));
const argument0 = const argument0 =
generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0'; generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0';
const argument1 = const argument1 = generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0';
generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0'; const increment = generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1';
const increment =
generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1';
let branch = generator.statementToCode(block, 'DO'); let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block); branch = generator.addLoopTrap(branch, block);
let code; let code;
if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) && if (
stringUtils.isNumber(increment)) { stringUtils.isNumber(argument0) &&
stringUtils.isNumber(argument1) &&
stringUtils.isNumber(increment)
) {
// All arguments are simple numbers. // All arguments are simple numbers.
const up = Number(argument0) <= Number(argument1); const up = Number(argument0) <= Number(argument1);
code = 'for (' + variable0 + ' = ' + argument0 + '; ' + variable0 + code =
(up ? ' <= ' : ' >= ') + argument1 + '; ' + variable0; 'for (' +
variable0 +
' = ' +
argument0 +
'; ' +
variable0 +
(up ? ' <= ' : ' >= ') +
argument1 +
'; ' +
variable0;
const step = Math.abs(Number(increment)); const step = Math.abs(Number(increment));
if (step === 1) { if (step === 1) {
code += up ? '++' : '--'; code += up ? '++' : '--';
@@ -89,55 +116,78 @@ export function controls_for(block, generator) {
// Cache non-trivial values to variables to prevent repeated look-ups. // Cache non-trivial values to variables to prevent repeated look-ups.
let startVar = argument0; let startVar = argument0;
if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) { if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) {
startVar = startVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName( variable0 + '_start',
variable0 + '_start', NameType.VARIABLE); NameType.VARIABLE,
);
code += startVar + ' = ' + argument0 + ';\n'; code += startVar + ' = ' + argument0 + ';\n';
} }
let endVar = argument1; let endVar = argument1;
if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) { if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) {
endVar = endVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName( variable0 + '_end',
variable0 + '_end', NameType.VARIABLE); NameType.VARIABLE,
);
code += endVar + ' = ' + argument1 + ';\n'; code += endVar + ' = ' + argument1 + ';\n';
} }
// Determine loop direction at start, in case one of the bounds // Determine loop direction at start, in case one of the bounds
// changes during loop execution. // changes during loop execution.
const incVar = const incVar = generator.nameDB_!.getDistinctName(
generator.nameDB_.getDistinctName( variable0 + '_inc',
variable0 + '_inc', NameType.VARIABLE); NameType.VARIABLE,
);
code += incVar + ' = '; code += incVar + ' = ';
if (stringUtils.isNumber(increment)) { if (stringUtils.isNumber(increment)) {
code += Math.abs(increment) + ';\n'; code += Math.abs(Number(increment)) + ';\n';
} else { } else {
code += 'abs(' + increment + ');\n'; code += 'abs(' + increment + ');\n';
} }
code += 'if (' + startVar + ' > ' + endVar + ') {\n'; code += 'if (' + startVar + ' > ' + endVar + ') {\n';
code += generator.INDENT + incVar + ' = -' + incVar + ';\n'; code += generator.INDENT + incVar + ' = -' + incVar + ';\n';
code += '}\n'; code += '}\n';
code += 'for (' + variable0 + ' = ' + startVar + '; ' + incVar + code +=
' >= 0 ? ' + variable0 + ' <= ' + endVar + ' : ' + variable0 + 'for (' +
' >= ' + endVar + '; ' + variable0 + ' += ' + incVar + ') {\n' + variable0 +
branch + '}\n'; ' = ' +
startVar +
'; ' +
incVar +
' >= 0 ? ' +
variable0 +
' <= ' +
endVar +
' : ' +
variable0 +
' >= ' +
endVar +
'; ' +
variable0 +
' += ' +
incVar +
') {\n' +
branch +
'}\n';
} }
return code; return code;
}; }
export function controls_forEach(block, generator) { export function controls_forEach(block: Block, generator: PhpGenerator) {
// For each loop. // For each loop.
const variable0 = const variable0 = generator.getVariableName(block.getFieldValue('VAR'));
generator.getVariableName(block.getFieldValue('VAR'));
const argument0 = const argument0 =
generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]'; generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]';
let branch = generator.statementToCode(block, 'DO'); let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block); branch = generator.addLoopTrap(branch, block);
let code = ''; let code = '';
code += code +=
'foreach (' + argument0 + ' as ' + variable0 + ') {\n' + branch + '}\n'; 'foreach (' + argument0 + ' as ' + variable0 + ') {\n' + branch + '}\n';
return code; return code;
}; }
export function controls_flow_statements(block, generator) { export function controls_flow_statements(
block: Block,
generator: PhpGenerator,
) {
// Flow statements: continue, break. // Flow statements: continue, break.
let xfix = ''; let xfix = '';
if (generator.STATEMENT_PREFIX) { if (generator.STATEMENT_PREFIX) {
@@ -150,7 +200,7 @@ export function controls_flow_statements(block, generator) {
xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); xfix += generator.injectId(generator.STATEMENT_SUFFIX, block);
} }
if (generator.STATEMENT_PREFIX) { if (generator.STATEMENT_PREFIX) {
const loop = block.getSurroundLoop(); const loop = (block as ControlFlowInLoopBlock).getSurroundLoop();
if (loop && !loop.suppressPrefixSuffix) { if (loop && !loop.suppressPrefixSuffix) {
// Inject loop's statement prefix here since the regular one at the end // Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if 'continue' is triggered. // of the loop will not get executed if 'continue' is triggered.
@@ -165,4 +215,4 @@ export function controls_flow_statements(block, generator) {
return xfix + 'continue;\n'; return xfix + 'continue;\n';
} }
throw Error('Unknown flow statement.'); throw Error('Unknown flow statement.');
}; }

View File

@@ -5,45 +5,55 @@
*/ */
/** /**
* @fileoverview Generating PHP for math blocks. * @file Generating PHP for math blocks.
*/ */
// Former goog.module ID: Blockly.PHP.math // Former goog.module ID: Blockly.PHP.math
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function math_number(
export function math_number(block, generator) { block: Block,
generator: PhpGenerator,
): [string, Order] {
// Numeric value. // Numeric value.
let code = Number(block.getFieldValue('NUM')); let number = Number(block.getFieldValue('NUM'));
const order = code >= 0 ? Order.ATOMIC : Order.UNARY_NEGATION; if (number === Infinity) {
if (code === Infinity) { return ['INF', Order.ATOMIC];
code = 'INF'; } else if (number === -Infinity) {
} else if (code === -Infinity) { return ['-INF', Order.UNARY_NEGATION];
code = '-INF';
} }
return [code, order]; return [String(number), number >= 0 ? Order.ATOMIC : Order.UNARY_NEGATION];
}; }
export function math_arithmetic(block, generator) { export function math_arithmetic(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Basic arithmetic operators, and power. // Basic arithmetic operators, and power.
const OPERATORS = { const OPERATORS: Record<string, [string, Order]> = {
'ADD': [' + ', Order.ADDITION], 'ADD': [' + ', Order.ADDITION],
'MINUS': [' - ', Order.SUBTRACTION], 'MINUS': [' - ', Order.SUBTRACTION],
'MULTIPLY': [' * ', Order.MULTIPLICATION], 'MULTIPLY': [' * ', Order.MULTIPLICATION],
'DIVIDE': [' / ', Order.DIVISION], 'DIVIDE': [' / ', Order.DIVISION],
'POWER': [' ** ', Order.POWER], 'POWER': [' ** ', Order.POWER],
}; };
const tuple = OPERATORS[block.getFieldValue('OP')]; type OperatorOption = keyof typeof OPERATORS;
const tuple = OPERATORS[block.getFieldValue('OP') as OperatorOption];
const operator = tuple[0]; const operator = tuple[0];
const order = tuple[1]; const order = tuple[1];
const argument0 = generator.valueToCode(block, 'A', order) || '0'; const argument0 = generator.valueToCode(block, 'A', order) || '0';
const argument1 = generator.valueToCode(block, 'B', order) || '0'; const argument1 = generator.valueToCode(block, 'B', order) || '0';
const code = argument0 + operator + argument1; const code = argument0 + operator + argument1;
return [code, order]; return [code, order];
}; }
export function math_single(block, generator) { export function math_single(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Math operators with single operand. // Math operators with single operand.
const operator = block.getFieldValue('OP'); const operator = block.getFieldValue('OP');
let code; let code;
@@ -122,11 +132,14 @@ export function math_single(block, generator) {
throw Error('Unknown math operator: ' + operator); throw Error('Unknown math operator: ' + operator);
} }
return [code, Order.DIVISION]; return [code, Order.DIVISION];
}; }
export function math_constant(block, generator) { export function math_constant(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
const CONSTANTS = { const CONSTANTS: Record<string, [string, Order]> = {
'PI': ['M_PI', Order.ATOMIC], 'PI': ['M_PI', Order.ATOMIC],
'E': ['M_E', Order.ATOMIC], 'E': ['M_E', Order.ATOMIC],
'GOLDEN_RATIO': ['(1 + sqrt(5)) / 2', Order.DIVISION], 'GOLDEN_RATIO': ['(1 + sqrt(5)) / 2', Order.DIVISION],
@@ -134,13 +147,20 @@ export function math_constant(block, generator) {
'SQRT1_2': ['M_SQRT1_2', Order.ATOMIC], 'SQRT1_2': ['M_SQRT1_2', Order.ATOMIC],
'INFINITY': ['INF', Order.ATOMIC], 'INFINITY': ['INF', Order.ATOMIC],
}; };
return CONSTANTS[block.getFieldValue('CONSTANT')]; type ConstantOption = keyof typeof CONSTANTS;
}; return CONSTANTS[block.getFieldValue('CONSTANT') as ConstantOption];
}
export function math_number_property(block, generator) { export function math_number_property(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Check if a number is even, odd, prime, whole, positive, or negative // Check if a number is even, odd, prime, whole, positive, or negative
// or if it is divisible by certain number. Returns true or false. // or if it is divisible by certain number. Returns true or false.
const PROPERTIES = { const PROPERTIES: Record<
string,
[string, string, Order, Order] | [null, null, Order, Order]
> = {
'EVEN': ['', ' % 2 == 0', Order.MODULUS, Order.EQUALITY], 'EVEN': ['', ' % 2 == 0', Order.MODULUS, Order.EQUALITY],
'ODD': ['', ' % 2 == 1', Order.MODULUS, Order.EQUALITY], 'ODD': ['', ' % 2 == 1', Order.MODULUS, Order.EQUALITY],
'WHOLE': ['is_int(', ')', Order.NONE, Order.FUNCTION_CALL], 'WHOLE': ['is_int(', ')', Order.NONE, Order.FUNCTION_CALL],
@@ -149,15 +169,18 @@ export function math_number_property(block, generator) {
'DIVISIBLE_BY': [null, null, Order.MODULUS, Order.EQUALITY], 'DIVISIBLE_BY': [null, null, Order.MODULUS, Order.EQUALITY],
'PRIME': [null, null, Order.NONE, Order.FUNCTION_CALL], 'PRIME': [null, null, Order.NONE, Order.FUNCTION_CALL],
}; };
const dropdownProperty = block.getFieldValue('PROPERTY'); type PropertyOption = keyof typeof PROPERTIES;
const dropdownProperty = block.getFieldValue('PROPERTY') as PropertyOption;
const [prefix, suffix, inputOrder, outputOrder] = const [prefix, suffix, inputOrder, outputOrder] =
PROPERTIES[dropdownProperty]; PROPERTIES[dropdownProperty];
const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK', const numberToCheck =
inputOrder) || '0'; generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0';
let code; let code;
if (dropdownProperty === 'PRIME') { if (dropdownProperty === 'PRIME') {
// Prime is a special case as it is not a one-liner test. // Prime is a special case as it is not a one-liner test.
const functionName = generator.provideFunction_('math_isPrime', ` const functionName = generator.provideFunction_(
'math_isPrime',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($n) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($n) {
// https://en.wikipedia.org/wiki/Primality_test#Naive_methods // https://en.wikipedia.org/wiki/Primality_test#Naive_methods
if ($n == 2 || $n == 3) { if ($n == 2 || $n == 3) {
@@ -176,37 +199,39 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($n) {
} }
return true; return true;
} }
`); `,
);
code = functionName + '(' + numberToCheck + ')'; code = functionName + '(' + numberToCheck + ')';
} else if (dropdownProperty === 'DIVISIBLE_BY') { } else if (dropdownProperty === 'DIVISIBLE_BY') {
const divisor = generator.valueToCode(block, 'DIVISOR', const divisor =
Order.MODULUS) || '0'; generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0';
if (divisor === '0') { if (divisor === '0') {
return ['false', Order.ATOMIC]; return ['false', Order.ATOMIC];
} }
code = numberToCheck + ' % ' + divisor + ' == 0'; code = numberToCheck + ' % ' + divisor + ' == 0';
} else { } else {
code = prefix + numberToCheck + suffix; code = prefix + numberToCheck + suffix;
} }
return [code, outputOrder]; return [code, outputOrder];
}; }
export function math_change(block, generator) { export function math_change(block: Block, generator: PhpGenerator) {
// Add to a variable in place. // Add to a variable in place.
const argument0 = const argument0 =
generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0'; generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0';
const varName = const varName = generator.getVariableName(block.getFieldValue('VAR'));
generator.getVariableName(block.getFieldValue('VAR'));
return varName + ' += ' + argument0 + ';\n'; return varName + ' += ' + argument0 + ';\n';
}; }
// Rounding functions have a single operand. // Rounding functions have a single operand.
export const math_round = math_single; export const math_round = math_single;
// Trigonometry functions have a single operand. // Trigonometry functions have a single operand.
export const math_trig = math_single; export const math_trig = math_single;
export function math_on_list(block, generator) { export function math_on_list(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Math functions for lists. // Math functions for lists.
const func = block.getFieldValue('OP'); const func = block.getFieldValue('OP');
let list; let list;
@@ -214,40 +239,43 @@ export function math_on_list(block, generator) {
switch (func) { switch (func) {
case 'SUM': case 'SUM':
list = list =
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || 'array()';
|| 'array()';
code = 'array_sum(' + list + ')'; code = 'array_sum(' + list + ')';
break; break;
case 'MIN': case 'MIN':
list = list =
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || 'array()';
|| 'array()';
code = 'min(' + list + ')'; code = 'min(' + list + ')';
break; break;
case 'MAX': case 'MAX':
list = list =
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || 'array()';
|| 'array()';
code = 'max(' + list + ')'; code = 'max(' + list + ')';
break; break;
case 'AVERAGE': { case 'AVERAGE': {
const functionName = generator.provideFunction_('math_mean', ` const functionName = generator.provideFunction_(
'math_mean',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($myList) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($myList) {
return array_sum($myList) / count($myList); return array_sum($myList) / count($myList);
} }
`); `,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
code = functionName + '(' + list + ')'; code = functionName + '(' + list + ')';
break; break;
} }
case 'MEDIAN': { case 'MEDIAN': {
const functionName = generator.provideFunction_('math_median', ` const functionName = generator.provideFunction_(
'math_median',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($arr) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($arr) {
sort($arr,SORT_NUMERIC); sort($arr,SORT_NUMERIC);
return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] : return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] :
($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2; ($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2;
} }
`); `,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')'; code = functionName + '(' + list + ')';
break; break;
@@ -256,7 +284,9 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($arr) {
// As a list of numbers can contain more than one mode, // As a list of numbers can contain more than one mode,
// the returned result is provided as an array. // the returned result is provided as an array.
// Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1].
const functionName = generator.provideFunction_('math_modes', ` const functionName = generator.provideFunction_(
'math_modes',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($values) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($values) {
if (empty($values)) return array(); if (empty($values)) return array();
$counts = array_count_values($values); $counts = array_count_values($values);
@@ -264,14 +294,16 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($values) {
$modes = array_keys($counts, current($counts), true); $modes = array_keys($counts, current($counts), true);
return $modes; return $modes;
} }
`); `,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')'; code = functionName + '(' + list + ')';
break; break;
} }
case 'STD_DEV': { case 'STD_DEV': {
const functionName = const functionName = generator.provideFunction_(
generator.provideFunction_('math_standard_deviation', ` 'math_standard_deviation',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($numbers) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($numbers) {
$n = count($numbers); $n = count($numbers);
if (!$n) return null; if (!$n) return null;
@@ -279,18 +311,22 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($numbers) {
foreach($numbers as $key => $num) $devs[$key] = pow($num - $mean, 2); foreach($numbers as $key => $num) $devs[$key] = pow($num - $mean, 2);
return sqrt(array_sum($devs) / (count($devs) - 1)); return sqrt(array_sum($devs) / (count($devs) - 1));
} }
`); `,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')'; code = functionName + '(' + list + ')';
break; break;
} }
case 'RANDOM': { case 'RANDOM': {
const functionName = generator.provideFunction_('math_random_list', ` const functionName = generator.provideFunction_(
'math_random_list',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) {
$x = rand(0, count($list)-1); $x = rand(0, count($list)-1);
return $list[$x]; return $list[$x];
} }
`); `,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')'; code = functionName + '(' + list + ')';
break; break;
@@ -299,56 +335,74 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) {
throw Error('Unknown operator: ' + func); throw Error('Unknown operator: ' + func);
} }
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function math_modulo(block, generator) { export function math_modulo(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Remainder computation. // Remainder computation.
const argument0 = const argument0 =
generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0'; generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0';
const argument1 = const argument1 =
generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0'; generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0';
const code = argument0 + ' % ' + argument1; const code = argument0 + ' % ' + argument1;
return [code, Order.MODULUS]; return [code, Order.MODULUS];
}; }
export function math_constrain(block, generator) { export function math_constrain(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Constrain a number between two limits. // Constrain a number between two limits.
const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0';
const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0';
const argument2 = const argument2 =
generator.valueToCode(block, 'HIGH', Order.NONE) || 'Infinity'; generator.valueToCode(block, 'HIGH', Order.NONE) || 'Infinity';
const code = const code =
'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; 'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function math_random_int(block, generator) { export function math_random_int(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Random integer between [X] and [Y]. // Random integer between [X] and [Y].
const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0';
const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0';
const functionName = generator.provideFunction_('math_random_int', ` const functionName = generator.provideFunction_(
'math_random_int',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($a, $b) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($a, $b) {
if ($a > $b) { if ($a > $b) {
return rand($b, $a); return rand($b, $a);
} }
return rand($a, $b); return rand($a, $b);
} }
`); `,
);
const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; const code = functionName + '(' + argument0 + ', ' + argument1 + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function math_random_float(block, generator) { export function math_random_float(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Random fraction between 0 and 1. // Random fraction between 0 and 1.
return ['(float)rand()/(float)getrandmax()', Order.FUNCTION_CALL]; return ['(float)rand()/(float)getrandmax()', Order.FUNCTION_CALL];
}; }
export function math_atan2(block, generator) { export function math_atan2(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Arctangent of point (X, Y) in degrees from -180 to 180. // Arctangent of point (X, Y) in degrees from -180 to 180.
const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0';
const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0';
return [ return [
'atan2(' + argument1 + ', ' + argument0 + ') / pi() * 180', 'atan2(' + argument1 + ', ' + argument0 + ') / pi() * 180',
Order.DIVISION Order.DIVISION,
]; ];
}; }

View File

@@ -1,307 +0,0 @@
/**
* @license
* Copyright 2015 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Helper functions for generating PHP for blocks.
* @suppress {checkTypes|globalThis}
*/
// Former goog.module ID: Blockly.PHP
import * as stringUtils from '../../core/utils/string.js';
// import type {Block} from '../../core/block.js';
import {CodeGenerator} from '../../core/generator.js';
import {Names} from '../../core/names.js';
// import type {Workspace} from '../../core/workspace.js';
import {inputTypes} from '../../core/inputs/input_types.js';
/**
* Order of operation ENUMs.
* http://php.net/manual/en/language.operators.precedence.php
* @enum {number}
*/
export const Order = {
ATOMIC: 0, // 0 "" ...
CLONE: 1, // clone
NEW: 1, // new
MEMBER: 2.1, // []
FUNCTION_CALL: 2.2, // ()
POWER: 3, // **
INCREMENT: 4, // ++
DECREMENT: 4, // --
BITWISE_NOT: 4, // ~
CAST: 4, // (int) (float) (string) (array) ...
SUPPRESS_ERROR: 4, // @
INSTANCEOF: 5, // instanceof
LOGICAL_NOT: 6, // !
UNARY_PLUS: 7.1, // +
UNARY_NEGATION: 7.2, // -
MULTIPLICATION: 8.1, // *
DIVISION: 8.2, // /
MODULUS: 8.3, // %
ADDITION: 9.1, // +
SUBTRACTION: 9.2, // -
STRING_CONCAT: 9.3, // .
BITWISE_SHIFT: 10, // << >>
RELATIONAL: 11, // < <= > >=
EQUALITY: 12, // == != === !== <> <=>
REFERENCE: 13, // &
BITWISE_AND: 13, // &
BITWISE_XOR: 14, // ^
BITWISE_OR: 15, // |
LOGICAL_AND: 16, // &&
LOGICAL_OR: 17, // ||
IF_NULL: 18, // ??
CONDITIONAL: 19, // ?:
ASSIGNMENT: 20, // = += -= *= /= %= <<= >>= ...
LOGICAL_AND_WEAK: 21, // and
LOGICAL_XOR: 22, // xor
LOGICAL_OR_WEAK: 23, // or
NONE: 99, // (...)
};
export class PhpGenerator extends CodeGenerator {
/**
* List of outer-inner pairings that do NOT require parentheses.
* @type {!Array<!Array<number>>}
*/
ORDER_OVERRIDES = [
// (foo()).bar() -> foo().bar()
// (foo())[0] -> foo()[0]
[Order.MEMBER, Order.FUNCTION_CALL],
// (foo[0])[1] -> foo[0][1]
// (foo.bar).baz -> foo.bar.baz
[Order.MEMBER, Order.MEMBER],
// !(!foo) -> !!foo
[Order.LOGICAL_NOT, Order.LOGICAL_NOT],
// a * (b * c) -> a * b * c
[Order.MULTIPLICATION, Order.MULTIPLICATION],
// a + (b + c) -> a + b + c
[Order.ADDITION, Order.ADDITION],
// a && (b && c) -> a && b && c
[Order.LOGICAL_AND, Order.LOGICAL_AND],
// a || (b || c) -> a || b || c
[Order.LOGICAL_OR, Order.LOGICAL_OR]
];
constructor(name) {
super(name ?? 'PHP');
this.isInitialized = false;
// Copy Order values onto instance for backwards compatibility
// while ensuring they are not part of the publically-advertised
// API.
//
// TODO(#7085): deprecate these in due course. (Could initially
// replace data properties with get accessors that call
// deprecate.warn().)
for (const key in Order) {
this['ORDER_' + key] = Order[key];
}
// List of illegal variable names. This is not intended to be a
// security feature. Blockly is 100% client-side, so bypassing
// this list is trivial. This is intended to prevent users from
// accidentally clobbering a built-in object or function.
this.addReservedWords(
// http://php.net/manual/en/reserved.keywords.php
'__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,' +
'clone,const,continue,declare,default,die,do,echo,else,elseif,empty,' +
'enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,' +
'extends,final,for,foreach,function,global,goto,if,implements,include,' +
'include_once,instanceof,insteadof,interface,isset,list,namespace,new,' +
'or,print,private,protected,public,require,require_once,return,static,' +
'switch,throw,trait,try,unset,use,var,while,xor,' +
// http://php.net/manual/en/reserved.constants.php
'PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,' +
'PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,' +
'PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,' +
'PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,' +
'PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,' +
'PHP_SYSCONFDIR,PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,' +
'PHP_CONFIG_FILE_SCAN_DIR,PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,' +
'E_NOTICE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,' +
'E_COMPILE_WARNING,E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE,' +
'E_DEPRECATED,E_USER_DEPRECATED,E_ALL,E_STRICT,' +
'__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,__FILE__,' +
'__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__'
);
}
/**
* Initialise the database of variable names.
* @param {!Workspace} workspace Workspace to generate code from.
*/
init(workspace) {
super.init(workspace);
if (!this.nameDB_) {
this.nameDB_ = new Names(this.RESERVED_WORDS_, '$');
} else {
this.nameDB_.reset();
}
this.nameDB_.setVariableMap(workspace.getVariableMap());
this.nameDB_.populateVariables(workspace);
this.nameDB_.populateProcedures(workspace);
this.isInitialized = true;
};
/**
* Prepend the generated code with the variable definitions.
* @param {string} code Generated code.
* @return {string} Completed code.
*/
finish(code) {
// Convert the definitions dictionary into a list.
const definitions = Object.values(this.definitions_);
// Call Blockly.CodeGenerator's finish.
code = super.finish(code);
this.isInitialized = false;
this.nameDB_.reset();
return definitions.join('\n\n') + '\n\n\n' + code;
};
/**
* Naked values are top-level blocks with outputs that aren't plugged into
* anything. A trailing semicolon is needed to make this legal.
* @param {string} line Line of generated code.
* @return {string} Legal line of code.
*/
scrubNakedValue(line) {
return line + ';\n';
};
/**
* Encode a string as a properly escaped PHP string, complete with
* quotes.
* @param {string} string Text to encode.
* @return {string} PHP string.
*/
quote_(string) {
string = string.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\\n')
.replace(/'/g, '\\\'');
return '\'' + string + '\'';
};
/**
* Encode a string as a properly escaped multiline PHP string, complete with
* quotes.
* @param {string} string Text to encode.
* @return {string} PHP string.
*/
multiline_quote_(string) {
const lines = string.split(/\n/g).map(this.quote_);
// Join with the following, plus a newline:
// . "\n" .
// Newline escaping only works in double-quoted strings.
return lines.join(' . \"\\n\" .\n');
};
/**
* Common tasks for generating PHP from blocks.
* Handles comments for the specified block and any connected value blocks.
* Calls any statements following this block.
* @param {!Block} block The current block.
* @param {string} code The PHP code created for this block.
* @param {boolean=} opt_thisOnly True to generate code for only this
* statement.
* @return {string} PHP code with comments and subsequent blocks added.
* @protected
*/
scrub_(block, code, opt_thisOnly) {
let commentCode = '';
// Only collect comments for blocks that aren't inline.
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
let comment = block.getCommentText();
if (comment) {
comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3);
commentCode += this.prefixLines(comment, '// ') + '\n';
}
// Collect comments for all value arguments.
// Don't collect comments for nested statements.
for (let i = 0; i < block.inputList.length; i++) {
if (block.inputList[i].type === inputTypes.VALUE) {
const childBlock = block.inputList[i].connection.targetBlock();
if (childBlock) {
comment = this.allNestedComments(childBlock);
if (comment) {
commentCode += this.prefixLines(comment, '// ');
}
}
}
}
}
const nextBlock =
block.nextConnection && block.nextConnection.targetBlock();
const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock);
return commentCode + code + nextCode;
};
/**
* Gets a property and adjusts the value while taking into account indexing.
* @param {!Block} block The block.
* @param {string} atId The property ID of the element to get.
* @param {number=} opt_delta Value to add.
* @param {boolean=} opt_negate Whether to negate the value.
* @param {number=} opt_order The highest order acting on this value.
* @return {string|number}
*/
getAdjusted(block, atId, opt_delta, opt_negate, opt_order) {
let delta = opt_delta || 0;
let order = opt_order || this.ORDER_NONE;
if (block.workspace.options.oneBasedIndex) {
delta--;
}
let defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0';
let outerOrder = order;
let innerOrder;
if (delta > 0) {
outerOrder = this.ORDER_ADDITION;
innerOrder = this.ORDER_ADDITION;
} else if (delta < 0) {
outerOrder = this.ORDER_SUBTRACTION;
innerOrder = this.ORDER_SUBTRACTION;
} else if (opt_negate) {
outerOrder = this.ORDER_UNARY_NEGATION;
innerOrder = this.ORDER_UNARY_NEGATION;
}
let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex;
if (stringUtils.isNumber(at)) {
// If the index is a naked number, adjust it right now.
at = Number(at) + delta;
if (opt_negate) {
at = -at;
}
} else {
// If the index is dynamic, adjust it in code.
if (delta > 0) {
at = at + ' + ' + delta;
} else if (delta < 0) {
at = at + ' - ' + -delta;
}
if (opt_negate) {
if (delta) {
at = '-(' + at + ')';
} else {
at = '-' + at;
}
}
innerOrder = Math.floor(innerOrder);
order = Math.floor(order);
if (innerOrder && order >= innerOrder) {
at = '(' + at + ')';
}
}
return at;
};
}

View File

@@ -0,0 +1,320 @@
/**
* @license
* Copyright 2015 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file PHP code generator class, including helper methods for
* generating PHP for blocks.
*/
// Former goog.module ID: Blockly.PHP
import * as stringUtils from '../../core/utils/string.js';
import type {Block} from '../../core/block.js';
import {CodeGenerator} from '../../core/generator.js';
import {Names} from '../../core/names.js';
import type {Workspace} from '../../core/workspace.js';
import {inputTypes} from '../../core/inputs/input_types.js';
/**
* Order of operation ENUMs.
* http://php.net/manual/en/language.operators.precedence.php
*/
// prettier-ignore
export enum Order {
ATOMIC = 0, // 0 "" ...
CLONE = 1, // clone
NEW = 1, // new
MEMBER = 2.1, // []
FUNCTION_CALL = 2.2, // ()
POWER = 3, // **
INCREMENT = 4, // ++
DECREMENT = 4, // --
BITWISE_NOT = 4, // ~
CAST = 4, // (int) (float) (string) (array) ...
SUPPRESS_ERROR = 4, // @
INSTANCEOF = 5, // instanceof
LOGICAL_NOT = 6, // !
UNARY_PLUS = 7.1, // +
UNARY_NEGATION = 7.2, // -
MULTIPLICATION = 8.1, // *
DIVISION = 8.2, // /
MODULUS = 8.3, // %
ADDITION = 9.1, // +
SUBTRACTION = 9.2, // -
STRING_CONCAT = 9.3, // .
BITWISE_SHIFT = 10, // << >>
RELATIONAL = 11, // < <= > >=
EQUALITY = 12, // == != === !== <> <=>
REFERENCE = 13, // &
BITWISE_AND = 13, // &
BITWISE_XOR = 14, // ^
BITWISE_OR = 15, // |
LOGICAL_AND = 16, // &&
LOGICAL_OR = 17, // ||
IF_NULL = 18, // ??
CONDITIONAL = 19, // ?:
ASSIGNMENT = 20, // = += -= *= /= %= <<= >>= ...
LOGICAL_AND_WEAK = 21, // and
LOGICAL_XOR = 22, // xor
LOGICAL_OR_WEAK = 23, // or
NONE = 99, // (...)
}
export class PhpGenerator extends CodeGenerator {
/** List of outer-inner pairings that do NOT require parentheses. */
ORDER_OVERRIDES: [Order, Order][] = [
// (foo()).bar() -> foo().bar()
// (foo())[0] -> foo()[0]
[Order.MEMBER, Order.FUNCTION_CALL],
// (foo[0])[1] -> foo[0][1]
// (foo.bar).baz -> foo.bar.baz
[Order.MEMBER, Order.MEMBER],
// !(!foo) -> !!foo
[Order.LOGICAL_NOT, Order.LOGICAL_NOT],
// a * (b * c) -> a * b * c
[Order.MULTIPLICATION, Order.MULTIPLICATION],
// a + (b + c) -> a + b + c
[Order.ADDITION, Order.ADDITION],
// a && (b && c) -> a && b && c
[Order.LOGICAL_AND, Order.LOGICAL_AND],
// a || (b || c) -> a || b || c
[Order.LOGICAL_OR, Order.LOGICAL_OR],
];
/** @param name Name of the language the generator is for. */
constructor(name = 'PHP') {
super(name);
this.isInitialized = false;
// Copy Order values onto instance for backwards compatibility
// while ensuring they are not part of the publically-advertised
// API.
//
// TODO(#7085): deprecate these in due course. (Could initially
// replace data properties with get accessors that call
// deprecate.warn().)
for (const key in Order) {
// Must assign Order[key] to a temporary to get the type guard to work;
// see https://github.com/microsoft/TypeScript/issues/10530.
const value = Order[key];
// Skip reverse-lookup entries in the enum. Due to
// https://github.com/microsoft/TypeScript/issues/55713 this (as
// of TypeScript 5.5.2) actually narrows the type of value to
// never - but that still allows the following assignment to
// succeed.
if (typeof value === 'string') continue;
(this as unknown as Record<string, Order>)['ORDER_' + key] = value;
}
// List of illegal variable names. This is not intended to be a
// security feature. Blockly is 100% client-side, so bypassing
// this list is trivial. This is intended to prevent users from
// accidentally clobbering a built-in object or function.
this.addReservedWords(
// http://php.net/manual/en/reserved.keywords.php
'__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,' +
'clone,const,continue,declare,default,die,do,echo,else,elseif,empty,' +
'enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,' +
'extends,final,for,foreach,function,global,goto,if,implements,include,' +
'include_once,instanceof,insteadof,interface,isset,list,namespace,new,' +
'or,print,private,protected,public,require,require_once,return,static,' +
'switch,throw,trait,try,unset,use,var,while,xor,' +
// http://php.net/manual/en/reserved.constants.php
'PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,' +
'PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,' +
'PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,' +
'PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,' +
'PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,' +
'PHP_SYSCONFDIR,PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,' +
'PHP_CONFIG_FILE_SCAN_DIR,PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,' +
'E_NOTICE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,' +
'E_COMPILE_WARNING,E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE,' +
'E_DEPRECATED,E_USER_DEPRECATED,E_ALL,E_STRICT,' +
'__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,__FILE__,' +
'__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__',
);
}
/**
* Initialise the database of variable names.
*
* @param workspace Workspace to generate code from.
*/
init(workspace: Workspace) {
super.init(workspace);
if (!this.nameDB_) {
this.nameDB_ = new Names(this.RESERVED_WORDS_, '$');
} else {
this.nameDB_.reset();
}
this.nameDB_.setVariableMap(workspace.getVariableMap());
this.nameDB_.populateVariables(workspace);
this.nameDB_.populateProcedures(workspace);
this.isInitialized = true;
}
/**
* Prepend the generated code with the variable definitions.
*
* @param code Generated code.
* @returns Completed code.
*/
finish(code: string): string {
// Convert the definitions dictionary into a list.
const definitions = Object.values(this.definitions_);
// Call Blockly.CodeGenerator's finish.
code = super.finish(code);
this.isInitialized = false;
this.nameDB_!.reset();
return definitions.join('\n\n') + '\n\n\n' + code;
}
/**
* Naked values are top-level blocks with outputs that aren't plugged into
* anything.
*
* @param line Line of generated code.
* @returns Legal line of code.
*/
scrubNakedValue(line: string): string {
return line + ';\n';
}
/**
* Encode a string as a properly escaped PHP string, complete with
* quotes.
*
* @param string Text to encode.
* @returns PHP string.
*/
quote_(string: string): string {
string = string
.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\\n')
.replace(/'/g, "\\'");
return "'" + string + "'";
}
/**
* Encode a string as a properly escaped multiline PHP string, complete with
* quotes.
* @param string Text to encode.
* @returns PHP string.
*/
multiline_quote_(string: string): string {
const lines = string.split(/\n/g).map(this.quote_);
// Join with the following, plus a newline:
// . "\n" .
// Newline escaping only works in double-quoted strings.
return lines.join(' . "\\n" .\n');
}
/**
* Common tasks for generating PHP from blocks.
* Handles comments for the specified block and any connected value blocks.
* Calls any statements following this block.
*
* @param block The current block.
* @param code The PHP code created for this block.
* @param thisOnly True to generate code for only this statement.
* @returns PHP code with comments and subsequent blocks added.
*/
scrub_(block: Block, code: string, thisOnly = false): string {
let commentCode = '';
// Only collect comments for blocks that aren't inline.
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
let comment = block.getCommentText();
if (comment) {
comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3);
commentCode += this.prefixLines(comment, '// ') + '\n';
}
// Collect comments for all value arguments.
// Don't collect comments for nested statements.
for (let i = 0; i < block.inputList.length; i++) {
if (block.inputList[i].type === inputTypes.VALUE) {
const childBlock = block.inputList[i].connection!.targetBlock();
if (childBlock) {
comment = this.allNestedComments(childBlock);
if (comment) {
commentCode += this.prefixLines(comment, '// ');
}
}
}
}
}
const nextBlock =
block.nextConnection && block.nextConnection.targetBlock();
const nextCode = thisOnly ? '' : this.blockToCode(nextBlock);
return commentCode + code + nextCode;
}
/**
* Generate code representing the specified value input, adjusted to take into
* account indexing (zero- or one-based) and optionally by a specified delta
* and/or by negation.
*
* @param block The block.
* @param atId The ID of the input block to get (and adjust) the value of.
* @param delta Value to add.
* @param negate Whether to negate the value.
* @param order The highest order acting on this value.
* @returns The adjusted value or code that evaluates to it.
*/
getAdjusted(
block: Block,
atId: string,
delta = 0,
negate = false,
order = Order.NONE,
): string {
if (block.workspace.options.oneBasedIndex) {
delta--;
}
let defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0';
let orderForInput = order;
if (delta > 0) {
orderForInput = Order.ADDITION;
} else if (delta < 0) {
orderForInput = Order.SUBTRACTION;
} else if (negate) {
orderForInput = Order.UNARY_NEGATION;
}
let at = this.valueToCode(block, atId, orderForInput) || defaultAtIndex;
// Easy case: no adjustments.
if (delta === 0 && !negate) {
return at;
}
// If the index is a naked number, adjust it right now.
if (stringUtils.isNumber(at)) {
at = String(Number(at) + delta);
if (negate) {
at = String(-Number(at));
}
return at;
}
// If the index is dynamic, adjust it in code.
if (delta > 0) {
at = `${at} + ${delta}`;
} else if (delta < 0) {
at = `${at} - ${-delta}`;
}
if (negate) {
at = delta ? `-(${at})` : `-${at}`;
}
if (Math.floor(order) >= Math.floor(orderForInput)) {
at = `(${at})`;
}
return at;
}
}

View File

@@ -5,17 +5,19 @@
*/ */
/** /**
* @fileoverview Generating PHP for procedure blocks. * @file Generating PHP for procedure blocks.
*/ */
// Former goog.module ID: Blockly.PHP.procedures // Former goog.module ID: Blockly.PHP.procedures
import * as Variables from '../../core/variables.js'; import * as Variables from '../../core/variables.js';
import type {Block} from '../../core/block.js';
import type {IfReturnBlock} from '../../blocks/procedures.js';
import {NameType} from '../../core/names.js'; import {NameType} from '../../core/names.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function procedures_defreturn(block: Block, generator: PhpGenerator) {
export function procedures_defreturn(block, generator) {
// Define a procedure with a return value. // Define a procedure with a return value.
// First, add a 'global' statement for every variable that is not shadowed by // First, add a 'global' statement for every variable that is not shadowed by
// a local parameter. // a local parameter.
@@ -33,15 +35,14 @@ export function procedures_defreturn(block, generator) {
const devVarList = Variables.allDeveloperVariables(workspace); const devVarList = Variables.allDeveloperVariables(workspace);
for (let i = 0; i < devVarList.length; i++) { for (let i = 0; i < devVarList.length; i++) {
globals.push( globals.push(
generator.nameDB_.getName( generator.nameDB_!.getName(devVarList[i], NameType.DEVELOPER_VARIABLE),
devVarList[i], NameType.DEVELOPER_VARIABLE)); );
} }
const globalStr = const globalStr = globals.length
globals.length ? ? generator.INDENT + 'global ' + globals.join(', ') + ';\n'
generator.INDENT + 'global ' + globals.join(', ') + ';\n' : ''; : '';
const funcName = const funcName = generator.getProcedureName(block.getFieldValue('NAME'));
generator.getProcedureName(block.getFieldValue('NAME'));
let xfix1 = ''; let xfix1 = '';
if (generator.STATEMENT_PREFIX) { if (generator.STATEMENT_PREFIX) {
xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block);
@@ -55,8 +56,9 @@ export function procedures_defreturn(block, generator) {
let loopTrap = ''; let loopTrap = '';
if (generator.INFINITE_LOOP_TRAP) { if (generator.INFINITE_LOOP_TRAP) {
loopTrap = generator.prefixLines( loopTrap = generator.prefixLines(
generator.injectId(generator.INFINITE_LOOP_TRAP, block), generator.injectId(generator.INFINITE_LOOP_TRAP, block),
generator.INDENT); generator.INDENT,
);
} }
const branch = generator.statementToCode(block, 'STACK'); const branch = generator.statementToCode(block, 'STACK');
let returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || '';
@@ -73,22 +75,37 @@ export function procedures_defreturn(block, generator) {
for (let i = 0; i < variables.length; i++) { for (let i = 0; i < variables.length; i++) {
args[i] = generator.getVariableName(variables[i]); args[i] = generator.getVariableName(variables[i]);
} }
let code = 'function ' + funcName + '(' + args.join(', ') + ') {\n' + let code =
globalStr + xfix1 + loopTrap + branch + xfix2 + returnValue + '}'; 'function ' +
funcName +
'(' +
args.join(', ') +
') {\n' +
globalStr +
xfix1 +
loopTrap +
branch +
xfix2 +
returnValue +
'}';
code = generator.scrub_(block, code); code = generator.scrub_(block, code);
// Add % so as not to collide with helper functions in definitions list. // Add % so as not to collide with helper functions in definitions list.
generator.definitions_['%' + funcName] = code; // TODO(#7600): find better approach than casting to any to override
// CodeGenerator declaring .definitions protected.
(generator as AnyDuringMigration).definitions_['%' + funcName] = code;
return null; return null;
}; }
// Defining a procedure without a return value uses the same generator as // Defining a procedure without a return value uses the same generator as
// a procedure with a return value. // a procedure with a return value.
export const procedures_defnoreturn = procedures_defreturn; export const procedures_defnoreturn = procedures_defreturn;
export function procedures_callreturn(block, generator) { export function procedures_callreturn(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Call a procedure with a return value. // Call a procedure with a return value.
const funcName = const funcName = generator.getProcedureName(block.getFieldValue('NAME'));
generator.getProcedureName(block.getFieldValue('NAME'));
const args = []; const args = [];
const variables = block.getVars(); const variables = block.getVars();
for (let i = 0; i < variables.length; i++) { for (let i = 0; i < variables.length; i++) {
@@ -96,30 +113,33 @@ export function procedures_callreturn(block, generator) {
} }
const code = funcName + '(' + args.join(', ') + ')'; const code = funcName + '(' + args.join(', ') + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function procedures_callnoreturn(block, generator) { export function procedures_callnoreturn(block: Block, generator: PhpGenerator) {
// Call a procedure with no return value. // Call a procedure with no return value.
// Generated code is for a function call as a statement is the same as a // Generated code is for a function call as a statement is the same as a
// function call as a value, with the addition of line ending. // function call as a value, with the addition of line ending.
const tuple = generator.forBlock['procedures_callreturn'](block, generator); const tuple = generator.forBlock['procedures_callreturn'](
block,
generator,
) as [string, Order];
return tuple[0] + ';\n'; return tuple[0] + ';\n';
}; }
export function procedures_ifreturn(block, generator) { export function procedures_ifreturn(block: Block, generator: PhpGenerator) {
// Conditionally return value from a procedure. // Conditionally return value from a procedure.
const condition = const condition =
generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false';
let code = 'if (' + condition + ') {\n'; let code = 'if (' + condition + ') {\n';
if (generator.STATEMENT_SUFFIX) { if (generator.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end // Inject any statement suffix here since the regular one at the end
// will not get executed if the return is triggered. // will not get executed if the return is triggered.
code += code += generator.prefixLines(
generator.prefixLines( generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.injectId(generator.STATEMENT_SUFFIX, block), generator.INDENT,
generator.INDENT); );
} }
if (block.hasReturnValue_) { if ((block as IfReturnBlock).hasReturnValue_) {
const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'null'; const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'null';
code += generator.INDENT + 'return ' + value + ';\n'; code += generator.INDENT + 'return ' + value + ';\n';
} else { } else {
@@ -127,4 +147,4 @@ export function procedures_ifreturn(block, generator) {
} }
code += '}\n'; code += '}\n';
return code; return code;
}; }

View File

@@ -5,87 +5,104 @@
*/ */
/** /**
* @fileoverview Generating PHP for text blocks. * @file Generating PHP for text blocks.
*/ */
// Former goog.module ID: Blockly.PHP.texts // Former goog.module ID: Blockly.PHP.texts
import type {Block} from '../../core/block.js';
import type {JoinMutatorBlock} from '../../blocks/text.js';
import {Order} from './php_generator.js'; import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function text(block: Block, generator: PhpGenerator): [string, Order] {
export function text(block, generator) {
// Text value. // Text value.
const code = generator.quote_(block.getFieldValue('TEXT')); const code = generator.quote_(block.getFieldValue('TEXT'));
return [code, Order.ATOMIC]; return [code, Order.ATOMIC];
}; }
export function text_multiline(block, generator) { export function text_multiline(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Text value. // Text value.
const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const code = generator.multiline_quote_(block.getFieldValue('TEXT'));
const order = const order = code.indexOf('.') !== -1 ? Order.STRING_CONCAT : Order.ATOMIC;
code.indexOf('.') !== -1 ? Order.STRING_CONCAT : Order.ATOMIC;
return [code, order]; return [code, order];
}; }
export function text_join(block, generator) { export function text_join(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Create a string made up of any number of elements of any type. // Create a string made up of any number of elements of any type.
if (block.itemCount_ === 0) { const joinBlock = block as JoinMutatorBlock;
if (joinBlock.itemCount_ === 0) {
return ["''", Order.ATOMIC]; return ["''", Order.ATOMIC];
} else if (block.itemCount_ === 1) { } else if (joinBlock.itemCount_ === 1) {
const element = generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; const element = generator.valueToCode(block, 'ADD0', Order.NONE) || "''";
const code = element; const code = element;
return [code, Order.NONE]; return [code, Order.NONE];
} else if (block.itemCount_ === 2) { } else if (joinBlock.itemCount_ === 2) {
const element0 = const element0 =
generator.valueToCode(block, 'ADD0', Order.STRING_CONCAT) || "''"; generator.valueToCode(block, 'ADD0', Order.STRING_CONCAT) || "''";
const element1 = const element1 =
generator.valueToCode(block, 'ADD1', Order.STRING_CONCAT) || "''"; generator.valueToCode(block, 'ADD1', Order.STRING_CONCAT) || "''";
const code = element0 + ' . ' + element1; const code = element0 + ' . ' + element1;
return [code, Order.STRING_CONCAT]; return [code, Order.STRING_CONCAT];
} else { } else {
const elements = new Array(block.itemCount_); const elements = new Array(joinBlock.itemCount_);
for (let i = 0; i < block.itemCount_; i++) { for (let i = 0; i < joinBlock.itemCount_; i++) {
elements[i] = elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''";
generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''";
} }
const code = 'implode(\'\', array(' + elements.join(',') + '))'; const code = "implode('', array(" + elements.join(',') + '))';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} }
}; }
export function text_append(block, generator) { export function text_append(block: Block, generator: PhpGenerator) {
// Append to a variable in place. // Append to a variable in place.
const varName = const varName = generator.getVariableName(block.getFieldValue('VAR'));
generator.getVariableName(block.getFieldValue('VAR')); const value = generator.valueToCode(block, 'TEXT', Order.ASSIGNMENT) || "''";
const value =
generator.valueToCode(block, 'TEXT', Order.ASSIGNMENT) || "''";
return varName + ' .= ' + value + ';\n'; return varName + ' .= ' + value + ';\n';
}; }
export function text_length(block, generator) { export function text_length(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// String or array length. // String or array length.
const functionName = generator.provideFunction_('length', ` const functionName = generator.provideFunction_(
'length',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) {
if (is_string($value)) { if (is_string($value)) {
return strlen($value); return strlen($value);
} }
return count($value); return count($value);
} }
`); `,
);
const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
return [functionName + '(' + text + ')', Order.FUNCTION_CALL]; return [functionName + '(' + text + ')', Order.FUNCTION_CALL];
}; }
export function text_isEmpty(block, generator) { export function text_isEmpty(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Is the string null or array empty? // Is the string null or array empty?
const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
return ['empty(' + text + ')', Order.FUNCTION_CALL]; return ['empty(' + text + ')', Order.FUNCTION_CALL];
}; }
export function text_indexOf(block, generator) { export function text_indexOf(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Search the text for a substring. // Search the text for a substring.
const operator = const operator =
block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos'; block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos';
const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''";
const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
let errorIndex = ' -1'; let errorIndex = ' -1';
@@ -95,22 +112,27 @@ export function text_indexOf(block, generator) {
indexAdjustment = ' + 1'; indexAdjustment = ' + 1';
} }
const functionName = generator.provideFunction_( const functionName = generator.provideFunction_(
block.getFieldValue('END') === 'FIRST' ? 'text_indexOf' : block.getFieldValue('END') === 'FIRST'
'text_lastIndexOf', ? 'text_indexOf'
` : 'text_lastIndexOf',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $search) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $search) {
$pos = ${operator}($text, $search); $pos = ${operator}($text, $search);
return $pos === false ? ${errorIndex} : $pos${indexAdjustment}; return $pos === false ? ${errorIndex} : $pos${indexAdjustment};
} }
`); `,
);
const code = functionName + '(' + text + ', ' + substring + ')'; const code = functionName + '(' + text + ', ' + substring + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function text_charAt(block, generator) { export function text_charAt(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Get letter at index. // Get letter at index.
const where = block.getFieldValue('WHERE') || 'FROM_START'; const where = block.getFieldValue('WHERE') || 'FROM_START';
const textOrder = (where === 'RANDOM') ? Order.NONE : Order.NONE; const textOrder = where === 'RANDOM' ? Order.NONE : Order.NONE;
const text = generator.valueToCode(block, 'VALUE', textOrder) || "''"; const text = generator.valueToCode(block, 'VALUE', textOrder) || "''";
switch (where) { switch (where) {
case 'FIRST': { case 'FIRST': {
@@ -132,19 +154,25 @@ export function text_charAt(block, generator) {
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} }
case 'RANDOM': { case 'RANDOM': {
const functionName = generator.provideFunction_('text_random_letter', ` const functionName = generator.provideFunction_(
'text_random_letter',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text) {
return $text[rand(0, strlen($text) - 1)]; return $text[rand(0, strlen($text) - 1)];
} }
`); `,
);
const code = functionName + '(' + text + ')'; const code = functionName + '(' + text + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} }
} }
throw Error('Unhandled option (text_charAt).'); throw Error('Unhandled option (text_charAt).');
}; }
export function text_getSubstring(block, generator) { export function text_getSubstring(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Get substring. // Get substring.
const where1 = block.getFieldValue('WHERE1'); const where1 = block.getFieldValue('WHERE1');
const where2 = block.getFieldValue('WHERE2'); const where2 = block.getFieldValue('WHERE2');
@@ -155,7 +183,9 @@ export function text_getSubstring(block, generator) {
} else { } else {
const at1 = generator.getAdjusted(block, 'AT1'); const at1 = generator.getAdjusted(block, 'AT1');
const at2 = generator.getAdjusted(block, 'AT2'); const at2 = generator.getAdjusted(block, 'AT2');
const functionName = generator.provideFunction_('text_get_substring', ` const functionName = generator.provideFunction_(
'text_get_substring',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, $at2) { function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, $at2) {
if ($where1 == 'FROM_END') { if ($where1 == 'FROM_END') {
$at1 = strlen($text) - 1 - $at1; $at1 = strlen($text) - 1 - $at1;
@@ -176,14 +206,29 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2,
} }
return substr($text, $at1, $length); return substr($text, $at1, $length);
} }
`); `,
const code = functionName + '(' + text + ', \'' + where1 + '\', ' + at1 + );
', \'' + where2 + '\', ' + at2 + ')'; const code =
functionName +
'(' +
text +
", '" +
where1 +
"', " +
at1 +
", '" +
where2 +
"', " +
at2 +
')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
} }
}; }
export function text_changeCase(block, generator) { export function text_changeCase(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Change capitalization. // Change capitalization.
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
let code; let code;
@@ -194,24 +239,31 @@ export function text_changeCase(block, generator) {
} else if (block.getFieldValue('CASE') === 'TITLECASE') { } else if (block.getFieldValue('CASE') === 'TITLECASE') {
code = 'ucwords(strtolower(' + text + '))'; code = 'ucwords(strtolower(' + text + '))';
} }
return [code, Order.FUNCTION_CALL]; return [code as string, Order.FUNCTION_CALL];
}; }
export function text_trim(block, generator) { export function text_trim(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Trim spaces. // Trim spaces.
const OPERATORS = {'LEFT': 'ltrim', 'RIGHT': 'rtrim', 'BOTH': 'trim'}; const OPERATORS = {'LEFT': 'ltrim', 'RIGHT': 'rtrim', 'BOTH': 'trim'};
const operator = OPERATORS[block.getFieldValue('MODE')]; type OperatorOption = keyof typeof OPERATORS;
const operator = OPERATORS[block.getFieldValue('MODE') as OperatorOption];
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
return [operator + '(' + text + ')', Order.FUNCTION_CALL]; return [operator + '(' + text + ')', Order.FUNCTION_CALL];
}; }
export function text_print(block, generator) { export function text_print(block: Block, generator: PhpGenerator) {
// Print statement. // Print statement.
const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
return 'print(' + msg + ');\n'; return 'print(' + msg + ');\n';
}; }
export function text_prompt_ext(block, generator) { export function text_prompt_ext(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Prompt function. // Prompt function.
let msg; let msg;
if (block.getField('TEXT')) { if (block.getField('TEXT')) {
@@ -227,29 +279,47 @@ export function text_prompt_ext(block, generator) {
code = 'floatval(' + code + ')'; code = 'floatval(' + code + ')';
} }
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export const text_prompt = text_prompt_ext; export const text_prompt = text_prompt_ext;
export function text_count(block, generator) { export function text_count(
block: Block,
generator: PhpGenerator,
): [string, Order] {
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''";
const code = 'strlen(' + sub + ') === 0' + const code =
' ? strlen(' + text + ') + 1' + 'strlen(' +
' : substr_count(' + text + ', ' + sub + ')'; sub +
') === 0' +
' ? strlen(' +
text +
') + 1' +
' : substr_count(' +
text +
', ' +
sub +
')';
return [code, Order.CONDITIONAL]; return [code, Order.CONDITIONAL];
}; }
export function text_replace(block, generator) { export function text_replace(
block: Block,
generator: PhpGenerator,
): [string, Order] {
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''";
const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; const to = generator.valueToCode(block, 'TO', Order.NONE) || "''";
const code = 'str_replace(' + from + ', ' + to + ', ' + text + ')'; const code = 'str_replace(' + from + ', ' + to + ', ' + text + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }
export function text_reverse(block, generator) { export function text_reverse(
block: Block,
generator: PhpGenerator,
): [string, Order] {
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
const code = 'strrev(' + text + ')'; const code = 'strrev(' + text + ')';
return [code, Order.FUNCTION_CALL]; return [code, Order.FUNCTION_CALL];
}; }

View File

@@ -1,30 +0,0 @@
/**
* @license
* Copyright 2015 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Generating PHP for variable blocks.
*/
// Former goog.module ID: Blockly.PHP.variables
import {Order} from './php_generator.js';
export function variables_get(block, generator) {
// Variable getter.
const code =
generator.getVariableName(block.getFieldValue('VAR'));
return [code, Order.ATOMIC];
};
export function variables_set(block, generator) {
// Variable setter.
const argument0 =
generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0';
const varName =
generator.getVariableName(block.getFieldValue('VAR'));
return varName + ' = ' + argument0 + ';\n';
};

View File

@@ -0,0 +1,32 @@
/**
* @license
* Copyright 2015 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file Generating PHP for variable blocks.
*/
// Former goog.module ID: Blockly.PHP.variables
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function variables_get(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Variable getter.
const code = generator.getVariableName(block.getFieldValue('VAR'));
return [code, Order.ATOMIC];
}
export function variables_set(block: Block, generator: PhpGenerator) {
// Variable setter.
const argument0 =
generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0';
const varName = generator.getVariableName(block.getFieldValue('VAR'));
return varName + ' = ' + argument0 + ';\n';
}

View File

@@ -5,12 +5,11 @@
*/ */
/** /**
* @fileoverview Generating PHP for dynamic variable blocks. * @file Generating PHP for dynamic variable blocks.
*/ */
// Former goog.module ID: Blockly.PHP.variablesDynamic // Former goog.module ID: Blockly.PHP.variablesDynamic
// generator is dynamically typed. // generator is dynamically typed.
export { export {
variables_get as variables_get_dynamic, variables_get as variables_get_dynamic,

View File

@@ -52,10 +52,8 @@ export enum Order {
* PythonScript code generator class. * PythonScript code generator class.
*/ */
export class PythonGenerator extends CodeGenerator { export class PythonGenerator extends CodeGenerator {
/** /** List of outer-inner pairings that do NOT require parentheses. */
* List of outer-inner pairings that do NOT require parentheses. ORDER_OVERRIDES: [Order, Order][] = [
*/
ORDER_OVERRIDES: number[][] = [
// (foo()).bar -> foo().bar // (foo()).bar -> foo().bar
// (foo())[0] -> foo()[0] // (foo())[0] -> foo()[0]
[Order.FUNCTION_CALL, Order.MEMBER], [Order.FUNCTION_CALL, Order.MEMBER],