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.
*/
export class JavascriptGenerator extends CodeGenerator {
/**
* List of outer-inner pairings that do NOT require parentheses.
*/
ORDER_OVERRIDES: number[][] = [
/** List of outer-inner pairings that do NOT require parentheses. */
ORDER_OVERRIDES: [Order, Order][] = [
// (foo()).bar -> foo().bar
// (foo())[0] -> foo()[0]
[Order.FUNCTION_CALL, Order.MEMBER],

View File

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

View File

@@ -5,9 +5,9 @@
*/
/**
* @fileoverview Complete helper functions for generating PHP for
* blocks. This is the entrypoint for php_compressed.js.
* @suppress {extraRequire}
* @file Instantiate a PhpGenerator and populate it with the complete
* set of block generator functions for PHP. This is the entrypoint
* for php_compressed.js.
*/
// Former goog.module ID: Blockly.PHP.all
@@ -32,8 +32,17 @@ export * from './php/php_generator.js';
export const phpGenerator = new PhpGenerator();
// Install per-block-type generator functions:
Object.assign(
phpGenerator.forBlock,
colour, lists, logic, loops, math, procedures,
text, variables, variablesDynamic
);
const generators: typeof phpGenerator.forBlock = {
...colour,
...lists,
...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
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function colour_picker(block, generator) {
export function colour_picker(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Colour picker.
const code = generator.quote_(block.getFieldValue('COLOUR'));
return [code, Order.ATOMIC];
};
}
export function colour_random(block, generator) {
export function colour_random(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Generate a random colour.
const functionName = generator.provideFunction_('colour_random', `
const functionName = generator.provideFunction_(
'colour_random',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}() {
return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
}
`);
`,
);
const code = functionName + '()';
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.
const red = generator.valueToCode(block, 'RED', Order.NONE) || 0;
const green = generator.valueToCode(block, 'GREEN', 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) {
$r = round(max(min($r, 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);
return $hex;
}
`);
`,
);
const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')';
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.
const c1 =
generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'";
const c2 =
generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'";
const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'";
const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'";
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) {
$ratio = max(min($ratio, 1), 0);
$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);
return $hex;
}
`);
`,
);
const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')';
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
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 {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.
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.
let code = new Array(block.itemCount_);
for (let i = 0; i < block.itemCount_; i++) {
code[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null';
const createWithBlock = block as CreateWithBlock;
const elements = new Array(createWithBlock.itemCount_);
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];
};
}
export function lists_repeat(block, generator) {
export function lists_repeat(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// 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) {
$array = array();
for ($index = 0; $index < $count; $index++) {
@@ -50,16 +65,22 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) {
}
return $array;
}
`);
`,
);
const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'null';
const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0';
const code = functionName + '(' + element + ', ' + repeatCount + ')';
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.
const functionName = generator.provideFunction_('length', `
const functionName = generator.provideFunction_(
'length',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) {
if (is_string($value)) {
return strlen($value);
@@ -67,24 +88,29 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) {
return count($value);
}
}
`);
`,
);
const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
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?
const argument0 =
generator.valueToCode(block, 'VALUE', Order.FUNCTION_CALL)
|| 'array()';
generator.valueToCode(block, 'VALUE', Order.FUNCTION_CALL) || 'array()';
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.
const argument0 = generator.valueToCode(block, 'FIND', Order.NONE) || "''";
const argument1 =
generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]';
const argument1 = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]';
let errorIndex = ' -1';
let indexAdjustment = '';
if (block.workspace.options.oneBasedIndex) {
@@ -94,17 +120,22 @@ export function lists_indexOf(block, generator) {
let functionName;
if (block.getFieldValue('END') === 'FIRST') {
// indexOf
functionName = generator.provideFunction_('indexOf', `
functionName = generator.provideFunction_(
'indexOf',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) {
for ($index = 0; $index < count($haystack); $index++) {
if ($haystack[$index] == $needle) return $index${indexAdjustment};
}
return ${errorIndex};
}
`);
`,
);
} else {
// lastIndexOf
functionName = generator.provideFunction_('lastIndexOf', `
functionName = generator.provideFunction_(
'lastIndexOf',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) {
$last = ${errorIndex};
for ($index = 0; $index < count($haystack); $index++) {
@@ -112,14 +143,18 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) {
}
return $last;
}
`);
`,
);
}
const code = functionName + '(' + argument1 + ', ' + argument0 + ')';
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.
const mode = block.getFieldValue('MODE') || 'GET';
const where = block.getFieldValue('WHERE') || 'FROM_START';
@@ -127,34 +162,34 @@ export function lists_getIndex(block, generator) {
case 'FIRST':
if (mode === 'GET') {
const list =
generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()';
generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()';
const code = list + '[0]';
return [code, Order.MEMBER];
} else if (mode === 'GET_REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'array_shift(' + list + ')';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
return 'array_shift(' + list + ');\n';
}
break;
case 'LAST':
if (mode === 'GET') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'end(' + list + ')';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'GET_REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'array_pop(' + list + ')';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
return 'array_pop(' + list + ');\n';
}
break;
@@ -162,17 +197,17 @@ export function lists_getIndex(block, generator) {
const at = generator.getAdjusted(block, 'AT');
if (mode === 'GET') {
const list =
generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()';
generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()';
const code = list + '[' + at + ']';
return [code, Order.MEMBER];
} else if (mode === 'GET_REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const code = 'array_splice(' + list + ', ' + at + ', 1)[0]';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
return 'array_splice(' + list + ', ' + at + ', 1);\n';
}
break;
@@ -180,17 +215,22 @@ export function lists_getIndex(block, generator) {
case 'FROM_END':
if (mode === 'GET') {
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 code = 'array_slice(' + list + ', ' + at + ', 1)[0]';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'GET_REMOVE' || mode === 'REMOVE') {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const at =
generator.getAdjusted(block, 'AT', 1, false, Order.SUBTRACTION);
const code = 'array_splice(' + list + ', count(' + list + ') - ' + at +
', 1)[0]';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
const at = generator.getAdjusted(
block,
'AT',
1,
false,
Order.SUBTRACTION,
);
const code =
'array_splice(' + list + ', count(' + list + ') - ' + at + ', 1)[0]';
if (mode === 'GET_REMOVE') {
return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') {
@@ -200,58 +240,65 @@ export function lists_getIndex(block, generator) {
break;
case 'RANDOM': {
const list =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()';
if (mode === 'GET') {
const functionName =
generator.provideFunction_('lists_get_random_item', `
const functionName = generator.provideFunction_(
'lists_get_random_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) {
return $list[rand(0,count($list)-1)];
}
`);
`,
);
const code = functionName + '(' + list + ')';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'GET_REMOVE') {
const functionName =
generator.provideFunction_('lists_get_remove_random_item', `
const functionName = generator.provideFunction_(
'lists_get_remove_random_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) {
$x = rand(0,count($list)-1);
unset($list[$x]);
return array_values($list);
}
`);
`,
);
const code = functionName + '(' + list + ')';
return [code, Order.FUNCTION_CALL];
} else if (mode === 'REMOVE') {
const functionName =
generator.provideFunction_('lists_remove_random_item', `
const functionName = generator.provideFunction_(
'lists_remove_random_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) {
unset($list[rand(0,count($list)-1)]);
}
`);
`,
);
return functionName + '(' + list + ');\n';
}
break;
}
}
throw Error('Unhandled combination (lists_getIndex).');
};
}
export function lists_setIndex(block, generator) {
export function lists_setIndex(block: Block, generator: PhpGenerator) {
// Set element at index.
// Note: Until February 2013 this block did not have MODE or WHERE inputs.
const mode = block.getFieldValue('MODE') || 'GET';
const where = block.getFieldValue('WHERE') || 'FROM_START';
const value =
generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null';
const value = generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null';
// Cache non-trivial values to variables to prevent repeated look-ups.
// Closure, which accesses and modifies 'list'.
let cachedList;
let cachedList: string;
function cacheList() {
if (cachedList.match(/^\$\w+$/)) {
return '';
}
const listVar =
generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE);
const listVar = generator.nameDB_!.getDistinctName(
'tmp_list',
NameType.VARIABLE,
);
const code = listVar + ' = &' + cachedList + ';\n';
cachedList = listVar;
return code;
@@ -260,24 +307,26 @@ export function lists_setIndex(block, generator) {
case 'FIRST':
if (mode === 'SET') {
const list =
generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()';
generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()';
return list + '[0] = ' + value + ';\n';
} else if (mode === 'INSERT') {
const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
return 'array_unshift(' + list + ', ' + value + ');\n';
}
break;
case 'LAST': {
const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
if (mode === 'SET') {
const functionName =
generator.provideFunction_('lists_set_last_item', `
const functionName = generator.provideFunction_(
'lists_set_last_item',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) {
$list[count($list) - 1] = $value;
}
`);
`,
);
return functionName + '(' + list + ', ' + value + ');\n';
} else if (mode === 'INSERT') {
return 'array_push(' + list + ', ' + value + ');\n';
@@ -288,45 +337,51 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) {
const at = generator.getAdjusted(block, 'AT');
if (mode === 'SET') {
const list =
generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()';
generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()';
return list + '[' + at + '] = ' + value + ';\n';
} else if (mode === 'INSERT') {
const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
return 'array_splice(' + list + ', ' + at + ', 0, ' + value + ');\n';
}
break;
}
case 'FROM_END': {
const list =
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
const at = generator.getAdjusted(block, 'AT', 1);
if (mode === 'SET') {
const functionName =
generator.provideFunction_('lists_set_from_end', `
const functionName = generator.provideFunction_(
'lists_set_from_end',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) {
$list[count($list) - $at] = $value;
}
`);
`,
);
return functionName + '(' + list + ', ' + at + ', ' + value + ');\n';
} else if (mode === 'INSERT') {
const functionName =
generator.provideFunction_('lists_insert_from_end', `
const functionName = generator.provideFunction_(
'lists_insert_from_end',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) {
return array_splice($list, count($list) - $at, 0, $value);
}
`);
`,
);
return functionName + '(' + list + ', ' + at + ', ' + value + ');\n';
}
break;
}
case 'RANDOM':
cachedList =
generator.valueToCode(block, 'LIST', Order.REFERENCE) || 'array()';
generator.valueToCode(block, 'LIST', Order.REFERENCE) || 'array()';
let code = cacheList();
const list = cachedList;
const xVar =
generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE);
const xVar = generator.nameDB_!.getDistinctName(
'tmp_x',
NameType.VARIABLE,
);
code += xVar + ' = rand(0, count(' + list + ')-1);\n';
if (mode === 'SET') {
code += list + '[' + xVar + '] = ' + value + ';\n';
@@ -338,9 +393,12 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) {
break;
}
throw Error('Unhandled combination (lists_setIndex).');
};
}
export function lists_getSublist(block, generator) {
export function lists_getSublist(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Get sublist.
const list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
const where1 = block.getFieldValue('WHERE1');
@@ -349,8 +407,9 @@ export function lists_getSublist(block, generator) {
if (where1 === 'FIRST' && where2 === 'LAST') {
code = list;
} else if (
list.match(/^\$\w+$/) ||
(where1 !== 'FROM_END' && where2 === 'FROM_START')) {
list.match(/^\$\w+$/) ||
(where1 !== 'FROM_END' && where2 === 'FROM_START')
) {
// If the list is a simple value or doesn't require a call for length, don't
// generate a helper function.
let at1;
@@ -359,8 +418,7 @@ export function lists_getSublist(block, generator) {
at1 = generator.getAdjusted(block, 'AT1');
break;
case 'FROM_END':
at1 =
generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION);
at1 = generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION);
at1 = 'count(' + list + ') - ' + at1;
break;
case 'FIRST':
@@ -373,11 +431,12 @@ export function lists_getSublist(block, generator) {
let length;
switch (where2) {
case 'FROM_START':
at2 =
generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
length = at2 + ' - ';
if (stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)) {
if (
stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)
) {
length += at1;
} else {
length += '(' + at1 + ')';
@@ -385,11 +444,12 @@ export function lists_getSublist(block, generator) {
length += ' + 1';
break;
case 'FROM_END':
at2 =
generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION);
length = 'count(' + list + ') - ' + at2 + ' - ';
if (stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)) {
if (
stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)
) {
length += at1;
} else {
length += '(' + at1 + ')';
@@ -397,8 +457,10 @@ export function lists_getSublist(block, generator) {
break;
case 'LAST':
length = 'count(' + list + ') - ';
if (stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)) {
if (
stringUtils.isNumber(String(at1)) ||
String(at1).match(/^\(.+\)$/)
) {
length += at1;
} else {
length += '(' + at1 + ')';
@@ -411,8 +473,9 @@ export function lists_getSublist(block, generator) {
} else {
const at1 = generator.getAdjusted(block, 'AT1');
const at2 = generator.getAdjusted(block, 'AT2');
const functionName =
generator.provideFunction_('lists_get_sublist', `
const functionName = generator.provideFunction_(
'lists_get_sublist',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) {
if ($where1 == 'FROM_END') {
$at1 = count($list) - 1 - $at1;
@@ -433,20 +496,37 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2,
}
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];
};
}
export function lists_sort(block, generator) {
export function lists_sort(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Block for sorting a list.
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 type = block.getFieldValue('TYPE');
const functionName = generator.provideFunction_('lists_sort', `
const functionName = generator.provideFunction_(
'lists_sort',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) {
$sortCmpFuncs = array(
'NUMERIC' => 'strnatcasecmp',
@@ -461,17 +541,20 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) {
}
return $list2;
}
`);
`,
);
const sortCode =
functionName + '(' + listCode + ', "' + type + '", ' + direction + ')';
functionName + '(' + listCode + ', "' + type + '", ' + direction + ')';
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.
let value_input = generator.valueToCode(block, 'INPUT', Order.NONE);
const value_delim =
generator.valueToCode(block, 'DELIM', Order.NONE) || "''";
const value_delim = generator.valueToCode(block, 'DELIM', Order.NONE) || "''";
const mode = block.getFieldValue('MODE');
let functionName;
if (mode === 'SPLIT') {
@@ -489,11 +572,14 @@ export function lists_split(block, generator) {
}
const code = functionName + '(' + value_delim + ', ' + value_input + ')';
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.
const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
const code = 'array_reverse(' + list + ')';
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
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function controls_if(block, generator) {
export function controls_if(block: Block, generator: PhpGenerator) {
// If/elseif/else condition.
let n = 0;
let code = '', branchCode, conditionCode;
let code = '',
branchCode,
conditionCode;
if (generator.STATEMENT_PREFIX) {
// Automatic prefix insertion is switched off for this block. Add manually.
code += generator.injectId(generator.STATEMENT_PREFIX, block);
}
do {
conditionCode =
generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false';
generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false';
branchCode = generator.statementToCode(block, 'DO' + n);
if (generator.STATEMENT_SUFFIX) {
branchCode =
generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT) +
branchCode;
generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT,
) + branchCode;
}
code += (n > 0 ? ' else ' : '') + 'if (' + conditionCode + ') {\n' +
branchCode + '}';
code +=
(n > 0 ? ' else ' : '') +
'if (' +
conditionCode +
') {\n' +
branchCode +
'}';
n++;
} while (block.getInput('IF' + n));
@@ -41,36 +49,48 @@ export function controls_if(block, generator) {
branchCode = generator.statementToCode(block, 'ELSE');
if (generator.STATEMENT_SUFFIX) {
branchCode =
generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT) +
branchCode;
generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT,
) + branchCode;
}
code += ' else {\n' + branchCode + '}';
}
return code + '\n';
};
}
export const controls_ifelse = controls_if;
export function logic_compare(block, generator) {
export function logic_compare(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Comparison operator.
const OPERATORS =
{'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='};
const operator = OPERATORS[block.getFieldValue('OP')];
const order = (operator === '==' || operator === '!=') ? Order.EQUALITY :
Order.RELATIONAL;
const OPERATORS = {
'EQ': '==',
'NEQ': '!=',
'LT': '<',
'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 argument1 = generator.valueToCode(block, 'B', order) || '0';
const code = argument0 + ' ' + operator + ' ' + argument1;
return [code, order];
};
}
export function logic_operation(block, generator) {
export function logic_operation(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Operations 'and', 'or'.
const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||';
const order =
(operator === '&&') ? Order.LOGICAL_AND : Order.LOGICAL_OR;
const operator = block.getFieldValue('OP') === 'AND' ? '&&' : '||';
const order = operator === '&&' ? Order.LOGICAL_AND : Order.LOGICAL_OR;
let argument0 = generator.valueToCode(block, 'A', order);
let argument1 = generator.valueToCode(block, 'B', order);
if (!argument0 && !argument1) {
@@ -79,7 +99,7 @@ export function logic_operation(block, generator) {
argument1 = 'false';
} else {
// Single missing arguments have no effect on the return value.
const defaultArgument = (operator === '&&') ? 'true' : 'false';
const defaultArgument = operator === '&&' ? 'true' : 'false';
if (!argument0) {
argument0 = defaultArgument;
}
@@ -89,35 +109,47 @@ export function logic_operation(block, generator) {
}
const code = argument0 + ' ' + operator + ' ' + argument1;
return [code, order];
};
}
export function logic_negate(block, generator) {
export function logic_negate(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Negation.
const order = Order.LOGICAL_NOT;
const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true';
const code = '!' + argument0;
return [code, order];
};
}
export function logic_boolean(block, generator) {
export function logic_boolean(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// 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];
};
}
export function logic_null(block, generator) {
export function logic_null(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Null data type.
return ['null', Order.ATOMIC];
};
}
export function logic_ternary(block, generator) {
export function logic_ternary(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Ternary operator.
const value_if =
generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false';
generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false';
const value_then =
generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null';
generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null';
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;
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
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 {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function controls_repeat_ext(block, generator) {
export function controls_repeat_ext(block: Block, generator: PhpGenerator) {
// Repeat n times.
let repeats;
if (block.getField('TIMES')) {
@@ -28,55 +30,80 @@ export function controls_repeat_ext(block, generator) {
let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block);
let code = '';
const loopVar =
generator.nameDB_.getDistinctName('count', NameType.VARIABLE);
const loopVar = generator.nameDB_!.getDistinctName(
'count',
NameType.VARIABLE,
);
let endVar = repeats;
if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) {
endVar =
generator.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE);
endVar = generator.nameDB_!.getDistinctName(
'repeat_end',
NameType.VARIABLE,
);
code += endVar + ' = ' + repeats + ';\n';
}
code += 'for (' + loopVar + ' = 0; ' + loopVar + ' < ' + endVar + '; ' +
loopVar + '++) {\n' + branch + '}\n';
code +=
'for (' +
loopVar +
' = 0; ' +
loopVar +
' < ' +
endVar +
'; ' +
loopVar +
'++) {\n' +
branch +
'}\n';
return code;
};
}
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.
const until = block.getFieldValue('MODE') === 'UNTIL';
let argument0 =
generator.valueToCode(
block, 'BOOL', until ? Order.LOGICAL_NOT : Order.NONE) ||
'false';
generator.valueToCode(
block,
'BOOL',
until ? Order.LOGICAL_NOT : Order.NONE,
) || 'false';
let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block);
if (until) {
argument0 = '!' + argument0;
}
return 'while (' + argument0 + ') {\n' + branch + '}\n';
};
}
export function controls_for(block, generator) {
export function controls_for(block: Block, generator: PhpGenerator) {
// For loop.
const variable0 =
generator.getVariableName(block.getFieldValue('VAR'));
const variable0 = generator.getVariableName(block.getFieldValue('VAR'));
const argument0 =
generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0';
const argument1 =
generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0';
const increment =
generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1';
generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0';
const argument1 = generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0';
const increment = generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1';
let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block);
let code;
if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) &&
stringUtils.isNumber(increment)) {
if (
stringUtils.isNumber(argument0) &&
stringUtils.isNumber(argument1) &&
stringUtils.isNumber(increment)
) {
// All arguments are simple numbers.
const up = Number(argument0) <= Number(argument1);
code = 'for (' + variable0 + ' = ' + argument0 + '; ' + variable0 +
(up ? ' <= ' : ' >= ') + argument1 + '; ' + variable0;
code =
'for (' +
variable0 +
' = ' +
argument0 +
'; ' +
variable0 +
(up ? ' <= ' : ' >= ') +
argument1 +
'; ' +
variable0;
const step = Math.abs(Number(increment));
if (step === 1) {
code += up ? '++' : '--';
@@ -89,55 +116,78 @@ export function controls_for(block, generator) {
// Cache non-trivial values to variables to prevent repeated look-ups.
let startVar = argument0;
if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) {
startVar =
generator.nameDB_.getDistinctName(
variable0 + '_start', NameType.VARIABLE);
startVar = generator.nameDB_!.getDistinctName(
variable0 + '_start',
NameType.VARIABLE,
);
code += startVar + ' = ' + argument0 + ';\n';
}
let endVar = argument1;
if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) {
endVar =
generator.nameDB_.getDistinctName(
variable0 + '_end', NameType.VARIABLE);
endVar = generator.nameDB_!.getDistinctName(
variable0 + '_end',
NameType.VARIABLE,
);
code += endVar + ' = ' + argument1 + ';\n';
}
// Determine loop direction at start, in case one of the bounds
// changes during loop execution.
const incVar =
generator.nameDB_.getDistinctName(
variable0 + '_inc', NameType.VARIABLE);
const incVar = generator.nameDB_!.getDistinctName(
variable0 + '_inc',
NameType.VARIABLE,
);
code += incVar + ' = ';
if (stringUtils.isNumber(increment)) {
code += Math.abs(increment) + ';\n';
code += Math.abs(Number(increment)) + ';\n';
} else {
code += 'abs(' + increment + ');\n';
}
code += 'if (' + startVar + ' > ' + endVar + ') {\n';
code += generator.INDENT + incVar + ' = -' + incVar + ';\n';
code += '}\n';
code += 'for (' + variable0 + ' = ' + startVar + '; ' + incVar +
' >= 0 ? ' + variable0 + ' <= ' + endVar + ' : ' + variable0 +
' >= ' + endVar + '; ' + variable0 + ' += ' + incVar + ') {\n' +
branch + '}\n';
code +=
'for (' +
variable0 +
' = ' +
startVar +
'; ' +
incVar +
' >= 0 ? ' +
variable0 +
' <= ' +
endVar +
' : ' +
variable0 +
' >= ' +
endVar +
'; ' +
variable0 +
' += ' +
incVar +
') {\n' +
branch +
'}\n';
}
return code;
};
}
export function controls_forEach(block, generator) {
export function controls_forEach(block: Block, generator: PhpGenerator) {
// For each loop.
const variable0 =
generator.getVariableName(block.getFieldValue('VAR'));
const variable0 = generator.getVariableName(block.getFieldValue('VAR'));
const argument0 =
generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]';
generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]';
let branch = generator.statementToCode(block, 'DO');
branch = generator.addLoopTrap(branch, block);
let code = '';
code +=
'foreach (' + argument0 + ' as ' + variable0 + ') {\n' + branch + '}\n';
'foreach (' + argument0 + ' as ' + variable0 + ') {\n' + branch + '}\n';
return code;
};
}
export function controls_flow_statements(block, generator) {
export function controls_flow_statements(
block: Block,
generator: PhpGenerator,
) {
// Flow statements: continue, break.
let xfix = '';
if (generator.STATEMENT_PREFIX) {
@@ -150,7 +200,7 @@ export function controls_flow_statements(block, generator) {
xfix += generator.injectId(generator.STATEMENT_SUFFIX, block);
}
if (generator.STATEMENT_PREFIX) {
const loop = block.getSurroundLoop();
const loop = (block as ControlFlowInLoopBlock).getSurroundLoop();
if (loop && !loop.suppressPrefixSuffix) {
// Inject loop's statement prefix here since the regular one at the end
// 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';
}
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
import type {Block} from '../../core/block.js';
import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function math_number(block, generator) {
export function math_number(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Numeric value.
let code = Number(block.getFieldValue('NUM'));
const order = code >= 0 ? Order.ATOMIC : Order.UNARY_NEGATION;
if (code === Infinity) {
code = 'INF';
} else if (code === -Infinity) {
code = '-INF';
let number = Number(block.getFieldValue('NUM'));
if (number === Infinity) {
return ['INF', Order.ATOMIC];
} else if (number === -Infinity) {
return ['-INF', Order.UNARY_NEGATION];
}
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.
const OPERATORS = {
const OPERATORS: Record<string, [string, Order]> = {
'ADD': [' + ', Order.ADDITION],
'MINUS': [' - ', Order.SUBTRACTION],
'MULTIPLY': [' * ', Order.MULTIPLICATION],
'DIVIDE': [' / ', Order.DIVISION],
'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 order = tuple[1];
const argument0 = generator.valueToCode(block, 'A', order) || '0';
const argument1 = generator.valueToCode(block, 'B', order) || '0';
const code = argument0 + operator + argument1;
return [code, order];
};
}
export function math_single(block, generator) {
export function math_single(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Math operators with single operand.
const operator = block.getFieldValue('OP');
let code;
@@ -122,11 +132,14 @@ export function math_single(block, generator) {
throw Error('Unknown math operator: ' + operator);
}
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.
const CONSTANTS = {
const CONSTANTS: Record<string, [string, Order]> = {
'PI': ['M_PI', Order.ATOMIC],
'E': ['M_E', Order.ATOMIC],
'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],
'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
// 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],
'ODD': ['', ' % 2 == 1', Order.MODULUS, Order.EQUALITY],
'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],
'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] =
PROPERTIES[dropdownProperty];
const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK',
inputOrder) || '0';
PROPERTIES[dropdownProperty];
const numberToCheck =
generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0';
let code;
if (dropdownProperty === 'PRIME') {
// 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) {
// https://en.wikipedia.org/wiki/Primality_test#Naive_methods
if ($n == 2 || $n == 3) {
@@ -176,37 +199,39 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($n) {
}
return true;
}
`);
`,
);
code = functionName + '(' + numberToCheck + ')';
} else if (dropdownProperty === 'DIVISIBLE_BY') {
const divisor = generator.valueToCode(block, 'DIVISOR',
Order.MODULUS) || '0';
const divisor =
generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0';
if (divisor === '0') {
return ['false', Order.ATOMIC];
}
code = numberToCheck + ' % ' + divisor + ' == 0';
} else {
code = prefix + numberToCheck + suffix;
}
return [code, outputOrder];
};
}
export function math_change(block, generator) {
export function math_change(block: Block, generator: PhpGenerator) {
// Add to a variable in place.
const argument0 =
generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0';
const varName =
generator.getVariableName(block.getFieldValue('VAR'));
generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0';
const varName = generator.getVariableName(block.getFieldValue('VAR'));
return varName + ' += ' + argument0 + ';\n';
};
}
// Rounding functions have a single operand.
export const math_round = math_single;
// Trigonometry functions have a single operand.
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.
const func = block.getFieldValue('OP');
let list;
@@ -214,40 +239,43 @@ export function math_on_list(block, generator) {
switch (func) {
case 'SUM':
list =
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL)
|| 'array()';
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || 'array()';
code = 'array_sum(' + list + ')';
break;
case 'MIN':
list =
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL)
|| 'array()';
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || 'array()';
code = 'min(' + list + ')';
break;
case 'MAX':
list =
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL)
|| 'array()';
generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || 'array()';
code = 'max(' + list + ')';
break;
case 'AVERAGE': {
const functionName = generator.provideFunction_('math_mean', `
const functionName = generator.provideFunction_(
'math_mean',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($myList) {
return array_sum($myList) / count($myList);
}
`);
`,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()';
code = functionName + '(' + list + ')';
break;
}
case 'MEDIAN': {
const functionName = generator.provideFunction_('math_median', `
const functionName = generator.provideFunction_(
'math_median',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($arr) {
sort($arr,SORT_NUMERIC);
return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] :
($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2;
}
`);
`,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')';
break;
@@ -256,7 +284,9 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($arr) {
// As a list of numbers can contain more than one mode,
// the returned result is provided as an array.
// 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) {
if (empty($values)) return array();
$counts = array_count_values($values);
@@ -264,14 +294,16 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($values) {
$modes = array_keys($counts, current($counts), true);
return $modes;
}
`);
`,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')';
break;
}
case 'STD_DEV': {
const functionName =
generator.provideFunction_('math_standard_deviation', `
const functionName = generator.provideFunction_(
'math_standard_deviation',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($numbers) {
$n = count($numbers);
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);
return sqrt(array_sum($devs) / (count($devs) - 1));
}
`);
`,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')';
break;
}
case 'RANDOM': {
const functionName = generator.provideFunction_('math_random_list', `
const functionName = generator.provideFunction_(
'math_random_list',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) {
$x = rand(0, count($list)-1);
return $list[$x];
}
`);
`,
);
list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]';
code = functionName + '(' + list + ')';
break;
@@ -299,56 +335,74 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) {
throw Error('Unknown operator: ' + func);
}
return [code, Order.FUNCTION_CALL];
};
}
export function math_modulo(block, generator) {
export function math_modulo(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Remainder computation.
const argument0 =
generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0';
generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0';
const argument1 =
generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0';
generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0';
const code = argument0 + ' % ' + argument1;
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.
const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0';
const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0';
const argument2 =
generator.valueToCode(block, 'HIGH', Order.NONE) || 'Infinity';
generator.valueToCode(block, 'HIGH', Order.NONE) || 'Infinity';
const code =
'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')';
'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')';
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].
const argument0 = generator.valueToCode(block, 'FROM', 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) {
if ($a > $b) {
return rand($b, $a);
}
return rand($a, $b);
}
`);
`,
);
const code = functionName + '(' + argument0 + ', ' + argument1 + ')';
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.
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.
const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0';
const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0';
return [
'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
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 {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function procedures_defreturn(block, generator) {
export function procedures_defreturn(block: Block, generator: PhpGenerator) {
// Define a procedure with a return value.
// First, add a 'global' statement for every variable that is not shadowed by
// a local parameter.
@@ -33,15 +35,14 @@ export function procedures_defreturn(block, generator) {
const devVarList = Variables.allDeveloperVariables(workspace);
for (let i = 0; i < devVarList.length; i++) {
globals.push(
generator.nameDB_.getName(
devVarList[i], NameType.DEVELOPER_VARIABLE));
generator.nameDB_!.getName(devVarList[i], NameType.DEVELOPER_VARIABLE),
);
}
const globalStr =
globals.length ?
generator.INDENT + 'global ' + globals.join(', ') + ';\n' : '';
const globalStr = globals.length
? generator.INDENT + 'global ' + globals.join(', ') + ';\n'
: '';
const funcName =
generator.getProcedureName(block.getFieldValue('NAME'));
const funcName = generator.getProcedureName(block.getFieldValue('NAME'));
let xfix1 = '';
if (generator.STATEMENT_PREFIX) {
xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block);
@@ -55,8 +56,9 @@ export function procedures_defreturn(block, generator) {
let loopTrap = '';
if (generator.INFINITE_LOOP_TRAP) {
loopTrap = generator.prefixLines(
generator.injectId(generator.INFINITE_LOOP_TRAP, block),
generator.INDENT);
generator.injectId(generator.INFINITE_LOOP_TRAP, block),
generator.INDENT,
);
}
const branch = generator.statementToCode(block, 'STACK');
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++) {
args[i] = generator.getVariableName(variables[i]);
}
let code = 'function ' + funcName + '(' + args.join(', ') + ') {\n' +
globalStr + xfix1 + loopTrap + branch + xfix2 + returnValue + '}';
let code =
'function ' +
funcName +
'(' +
args.join(', ') +
') {\n' +
globalStr +
xfix1 +
loopTrap +
branch +
xfix2 +
returnValue +
'}';
code = generator.scrub_(block, code);
// 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;
};
}
// Defining a procedure without a return value uses the same generator as
// a procedure with a return value.
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.
const funcName =
generator.getProcedureName(block.getFieldValue('NAME'));
const funcName = generator.getProcedureName(block.getFieldValue('NAME'));
const args = [];
const variables = block.getVars();
for (let i = 0; i < variables.length; i++) {
@@ -96,30 +113,33 @@ export function procedures_callreturn(block, generator) {
}
const code = funcName + '(' + args.join(', ') + ')';
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.
// 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.
const tuple = generator.forBlock['procedures_callreturn'](block, generator);
const tuple = generator.forBlock['procedures_callreturn'](
block,
generator,
) as [string, Order];
return tuple[0] + ';\n';
};
}
export function procedures_ifreturn(block, generator) {
export function procedures_ifreturn(block: Block, generator: PhpGenerator) {
// Conditionally return value from a procedure.
const condition =
generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false';
generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false';
let code = 'if (' + condition + ') {\n';
if (generator.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end
// will not get executed if the return is triggered.
code +=
generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT);
code += generator.prefixLines(
generator.injectId(generator.STATEMENT_SUFFIX, block),
generator.INDENT,
);
}
if (block.hasReturnValue_) {
if ((block as IfReturnBlock).hasReturnValue_) {
const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'null';
code += generator.INDENT + 'return ' + value + ';\n';
} else {
@@ -127,4 +147,4 @@ export function procedures_ifreturn(block, generator) {
}
code += '}\n';
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
import type {Block} from '../../core/block.js';
import type {JoinMutatorBlock} from '../../blocks/text.js';
import {Order} from './php_generator.js';
import type {PhpGenerator} from './php_generator.js';
export function text(block, generator) {
export function text(block: Block, generator: PhpGenerator): [string, Order] {
// Text value.
const code = generator.quote_(block.getFieldValue('TEXT'));
return [code, Order.ATOMIC];
};
}
export function text_multiline(block, generator) {
export function text_multiline(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Text value.
const code = generator.multiline_quote_(block.getFieldValue('TEXT'));
const order =
code.indexOf('.') !== -1 ? Order.STRING_CONCAT : Order.ATOMIC;
const order = code.indexOf('.') !== -1 ? Order.STRING_CONCAT : Order.ATOMIC;
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.
if (block.itemCount_ === 0) {
const joinBlock = block as JoinMutatorBlock;
if (joinBlock.itemCount_ === 0) {
return ["''", Order.ATOMIC];
} else if (block.itemCount_ === 1) {
} else if (joinBlock.itemCount_ === 1) {
const element = generator.valueToCode(block, 'ADD0', Order.NONE) || "''";
const code = element;
return [code, Order.NONE];
} else if (block.itemCount_ === 2) {
} else if (joinBlock.itemCount_ === 2) {
const element0 =
generator.valueToCode(block, 'ADD0', Order.STRING_CONCAT) || "''";
generator.valueToCode(block, 'ADD0', Order.STRING_CONCAT) || "''";
const element1 =
generator.valueToCode(block, 'ADD1', Order.STRING_CONCAT) || "''";
generator.valueToCode(block, 'ADD1', Order.STRING_CONCAT) || "''";
const code = element0 + ' . ' + element1;
return [code, Order.STRING_CONCAT];
} else {
const elements = new Array(block.itemCount_);
for (let i = 0; i < block.itemCount_; i++) {
elements[i] =
generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''";
const elements = new Array(joinBlock.itemCount_);
for (let i = 0; i < joinBlock.itemCount_; i++) {
elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''";
}
const code = 'implode(\'\', array(' + elements.join(',') + '))';
const code = "implode('', array(" + elements.join(',') + '))';
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.
const varName =
generator.getVariableName(block.getFieldValue('VAR'));
const value =
generator.valueToCode(block, 'TEXT', Order.ASSIGNMENT) || "''";
const varName = generator.getVariableName(block.getFieldValue('VAR'));
const value = generator.valueToCode(block, 'TEXT', Order.ASSIGNMENT) || "''";
return varName + ' .= ' + value + ';\n';
};
}
export function text_length(block, generator) {
export function text_length(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// String or array length.
const functionName = generator.provideFunction_('length', `
const functionName = generator.provideFunction_(
'length',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) {
if (is_string($value)) {
return strlen($value);
}
return count($value);
}
`);
`,
);
const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
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?
const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
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.
const operator =
block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos';
block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos';
const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''";
const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''";
let errorIndex = ' -1';
@@ -95,22 +112,27 @@ export function text_indexOf(block, generator) {
indexAdjustment = ' + 1';
}
const functionName = generator.provideFunction_(
block.getFieldValue('END') === 'FIRST' ? 'text_indexOf' :
'text_lastIndexOf',
`
block.getFieldValue('END') === 'FIRST'
? 'text_indexOf'
: 'text_lastIndexOf',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $search) {
$pos = ${operator}($text, $search);
return $pos === false ? ${errorIndex} : $pos${indexAdjustment};
}
`);
`,
);
const code = functionName + '(' + text + ', ' + substring + ')';
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.
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) || "''";
switch (where) {
case 'FIRST': {
@@ -132,19 +154,25 @@ export function text_charAt(block, generator) {
return [code, Order.FUNCTION_CALL];
}
case 'RANDOM': {
const functionName = generator.provideFunction_('text_random_letter', `
const functionName = generator.provideFunction_(
'text_random_letter',
`
function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text) {
return $text[rand(0, strlen($text) - 1)];
}
`);
`,
);
const code = functionName + '(' + text + ')';
return [code, Order.FUNCTION_CALL];
}
}
throw Error('Unhandled option (text_charAt).');
};
}
export function text_getSubstring(block, generator) {
export function text_getSubstring(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Get substring.
const where1 = block.getFieldValue('WHERE1');
const where2 = block.getFieldValue('WHERE2');
@@ -155,7 +183,9 @@ export function text_getSubstring(block, generator) {
} else {
const at1 = generator.getAdjusted(block, 'AT1');
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) {
if ($where1 == 'FROM_END') {
$at1 = strlen($text) - 1 - $at1;
@@ -176,14 +206,29 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2,
}
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];
}
};
}
export function text_changeCase(block, generator) {
export function text_changeCase(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Change capitalization.
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
let code;
@@ -194,24 +239,31 @@ export function text_changeCase(block, generator) {
} else if (block.getFieldValue('CASE') === 'TITLECASE') {
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.
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) || "''";
return [operator + '(' + text + ')', Order.FUNCTION_CALL];
};
}
export function text_print(block, generator) {
export function text_print(block: Block, generator: PhpGenerator) {
// Print statement.
const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
return 'print(' + msg + ');\n';
};
}
export function text_prompt_ext(block, generator) {
export function text_prompt_ext(
block: Block,
generator: PhpGenerator,
): [string, Order] {
// Prompt function.
let msg;
if (block.getField('TEXT')) {
@@ -227,29 +279,47 @@ export function text_prompt_ext(block, generator) {
code = 'floatval(' + code + ')';
}
return [code, Order.FUNCTION_CALL];
};
}
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 sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''";
const code = 'strlen(' + sub + ') === 0' +
' ? strlen(' + text + ') + 1' +
' : substr_count(' + text + ', ' + sub + ')';
const code =
'strlen(' +
sub +
') === 0' +
' ? strlen(' +
text +
') + 1' +
' : substr_count(' +
text +
', ' +
sub +
')';
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 from = generator.valueToCode(block, 'FROM', Order.NONE) || "''";
const to = generator.valueToCode(block, 'TO', Order.NONE) || "''";
const code = 'str_replace(' + from + ', ' + to + ', ' + text + ')';
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 code = 'strrev(' + text + ')';
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
// generator is dynamically typed.
export {
variables_get as variables_get_dynamic,

View File

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