Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: toString performance #6896

Merged
merged 5 commits into from
Mar 15, 2023
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 53 additions & 82 deletions core/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {Align, Input} from './input.js';
import {inputTypes} from './input_types.js';
import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
import type {IDeletable} from './interfaces/i_deletable.js';
import {ASTNode} from './keyboard_nav/ast_node.js';
import type {Mutator} from './mutator.js';
import * as Tooltip from './tooltip.js';
import * as arrayUtils from './utils/array.js';
Expand Down Expand Up @@ -1400,21 +1399,51 @@ export class Block implements IASTNodeLocation, IDeletable {
* Create a human-readable text representation of this block and any children.
*
* @param opt_maxLength Truncate the string to this length.
* @param opt_emptyToken The placeholder string used to denote an empty field.
* @param opt_emptyToken The placeholder string used to denote an empty input.
* If not specified, '?' is used.
* @returns Text of block.
*/
toString(opt_maxLength?: number, opt_emptyToken?: string): string {
const tokens = [];
const emptyFieldPlaceholder = opt_emptyToken || '?';
const tokens = this.toTokens(opt_emptyToken);

// Temporarily set flag to navigate to all fields.
const prevNavigateFields = ASTNode.NAVIGATE_ALL_FIELDS;
ASTNode.NAVIGATE_ALL_FIELDS = true;
// Run through our tokens array and simplify expression to remove
// parentheses around single field blocks.
// E.g. ['repeat', '(', '10', ')', 'times', 'do', '?']
for (let i = 2; i < tokens.length; i++) {
if (tokens[i - 2] === '(' && tokens[i] === ')') {
tokens[i - 2] = tokens[i - 1];
tokens.splice(i - 1, 2);
}
}

let node = ASTNode.createBlockNode(this);
const rootNode = node;
let prev = '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the comment too?

// Join the text array, removing the spaces around added parentheses.

let text: string = tokens.reduce((acc, curr) => {
const val = acc + ((prev === '(' || curr === ')') ? '' : ' ') + curr;
prev = curr[curr.length - 1];
return val;
}, '');

text = text.trim() || '???';
if (opt_maxLength) {
// TODO: Improve truncation so that text from this block is given
// priority. E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not
// "1+2+3+4+5...". E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
if (text.length > opt_maxLength) {
text = text.substring(0, opt_maxLength - 3) + '...';
}
}
return text;
}

/**
* Converts this block into string tokens.
*
* @param emptyToken The token to use in place of an empty input.
* Defaults to '?'.
* @returns The array of string tokens representing this block.
*/
private toTokens(emptyToken = '?'): string[] {
const tokens = [];
/**
* Whether or not to add parentheses around an input.
*
Expand All @@ -1430,84 +1459,26 @@ export class Block implements IASTNodeLocation, IDeletable {
(checks.indexOf('Boolean') !== -1 || checks.indexOf('Number') !== -1);
}

/** Check that we haven't circled back to the original root node. */
function checkRoot() {
if (node && node.getType() === rootNode?.getType() &&
node.getLocation() === rootNode?.getLocation()) {
node = null;
for (const input of this.inputList) {
if (input.name == constants.COLLAPSED_INPUT_NAME) {
continue;
}
}

// Traverse the AST building up our text string.
while (node) {
switch (node.getType()) {
case ASTNode.types.INPUT: {
const connection = node.getLocation() as Connection;
if (!node.in()) {
tokens.push(emptyFieldPlaceholder);
} else if (shouldAddParentheses(connection)) {
tokens.push('(');
}
break;
}
case ASTNode.types.FIELD: {
const field = node.getLocation() as Field;
if (field.name !== constants.COLLAPSED_FIELD_NAME) {
tokens.push(field.getText());
}
break;
}
for (const field of input.fieldRow) {
tokens.push(field.getText());
}

const current = node;
node = current.in() || current.next();
if (!node) {
// Can't go in or next, keep going out until we can go next.
node = current.out();
checkRoot();
while (node && !node.next()) {
node = node.out();
checkRoot();
// If we hit an input on the way up, possibly close out parentheses.
if (node && node.getType() === ASTNode.types.INPUT &&
shouldAddParentheses(node.getLocation() as Connection)) {
tokens.push(')');
}
}
if (node) {
node = node.next();
if (input.connection) {
const child = input.connection.targetBlock();
if (child) {
const shouldAddParens = shouldAddParentheses(input.connection);
if (shouldAddParens) tokens.push('(');
tokens.push(...child.toTokens(emptyToken));
if (shouldAddParens) tokens.push(')');
} else {
tokens.push(emptyToken);
}
}
}

// Restore state of NAVIGATE_ALL_FIELDS.
ASTNode.NAVIGATE_ALL_FIELDS = prevNavigateFields;

// Run through our text array and simplify expression to remove parentheses
// around single field blocks.
// E.g. ['repeat', '(', '10', ')', 'times', 'do', '?']
for (let i = 2; i < tokens.length; i++) {
if (tokens[i - 2] === '(' && tokens[i] === ')') {
tokens[i - 2] = tokens[i - 1];
tokens.splice(i - 1, 2);
}
}

// Join the text array, removing spaces around added parentheses.
let text: string = tokens.reduce(function(acc, value) {
return acc + (acc.substr(-1) === '(' || value === ')' ? '' : ' ') + value;
}, '');

text = text.trim() || '???';
if (opt_maxLength) {
// TODO: Improve truncation so that text from this block is given
// priority. E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not
// "1+2+3+4+5...". E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
if (text.length > opt_maxLength) {
text = text.substring(0, opt_maxLength - 3) + '...';
}
}
return text;
return tokens;
}

/**
Expand Down