mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40:08 +01:00
272 lines
8.6 KiB
TypeScript
272 lines
8.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2012 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Utility functions for handling variable and procedure names.
|
|
*
|
|
* @class
|
|
*/
|
|
// Former goog.module ID: Blockly.Names
|
|
|
|
import {Msg} from './msg.js';
|
|
// import * as Procedures from './procedures.js';
|
|
import type {VariableMap} from './variable_map.js';
|
|
import * as Variables from './variables.js';
|
|
import type {Workspace} from './workspace.js';
|
|
|
|
/**
|
|
* Class for a database of entity names (variables, procedures, etc).
|
|
*/
|
|
export class Names {
|
|
static DEVELOPER_VARIABLE_TYPE: NameType;
|
|
private readonly variablePrefix: string;
|
|
|
|
/** A set of reserved words. */
|
|
private readonly reservedWords: Set<string>;
|
|
|
|
/**
|
|
* A map from type (e.g. name, procedure) to maps from names to generated
|
|
* names.
|
|
*/
|
|
private readonly db = new Map<string, Map<string, string>>();
|
|
|
|
/** A set of used names to avoid collisions. */
|
|
private readonly dbReverse = new Set<string>();
|
|
|
|
/**
|
|
* The variable map from the workspace, containing Blockly variable models.
|
|
*/
|
|
private variableMap: VariableMap | null = null;
|
|
|
|
/**
|
|
* @param reservedWordsList A comma-separated string of words that are illegal
|
|
* for use as names in a language (e.g. 'new,if,this,...').
|
|
* @param opt_variablePrefix Some languages need a '$' or a namespace before
|
|
* all variable names (but not procedure names).
|
|
*/
|
|
constructor(reservedWordsList: string, opt_variablePrefix?: string) {
|
|
/** The prefix to attach to variable names in generated code. */
|
|
this.variablePrefix = opt_variablePrefix || '';
|
|
|
|
this.reservedWords = new Set<string>(
|
|
reservedWordsList ? reservedWordsList.split(',') : [],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Empty the database and start from scratch. The reserved words are kept.
|
|
*/
|
|
reset() {
|
|
this.db.clear();
|
|
this.dbReverse.clear();
|
|
this.variableMap = null;
|
|
}
|
|
|
|
/**
|
|
* Set the variable map that maps from variable name to variable object.
|
|
*
|
|
* @param map The map to track.
|
|
*/
|
|
setVariableMap(map: VariableMap) {
|
|
this.variableMap = map;
|
|
}
|
|
|
|
/**
|
|
* Get the name for a user-defined variable, based on its ID.
|
|
* This should only be used for variables of NameType VARIABLE.
|
|
*
|
|
* @param id The ID to look up in the variable map.
|
|
* @returns The name of the referenced variable, or null if there was no
|
|
* variable map or the variable was not found in the map.
|
|
*/
|
|
private getNameForUserVariable(id: string): string | null {
|
|
if (!this.variableMap) {
|
|
console.warn(
|
|
'Deprecated call to Names.prototype.getName without ' +
|
|
'defining a variable map. To fix, add the following code in your ' +
|
|
"generator's init() function:\n" +
|
|
'Blockly.YourGeneratorName.nameDB_.setVariableMap(' +
|
|
'workspace.getVariableMap());',
|
|
);
|
|
return null;
|
|
}
|
|
const variable = this.variableMap.getVariableById(id);
|
|
if (variable) {
|
|
return variable.name;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Generate names for user variables, but only ones that are being used.
|
|
*
|
|
* @param workspace Workspace to generate variables from.
|
|
*/
|
|
populateVariables(workspace: Workspace) {
|
|
const variables = Variables.allUsedVarModels(workspace);
|
|
for (let i = 0; i < variables.length; i++) {
|
|
this.getName(variables[i].getId(), NameType.VARIABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate names for procedures.
|
|
*
|
|
* @param workspace Workspace to generate procedures from.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
populateProcedures(workspace: Workspace) {
|
|
throw new Error(
|
|
'The implementation of populateProcedures should be ' +
|
|
'monkey-patched in by blockly.ts',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Convert a Blockly entity name to a legal exportable entity name.
|
|
*
|
|
* @param nameOrId The Blockly entity name (no constraints) or variable ID.
|
|
* @param type The type of the name in Blockly ('VARIABLE', 'PROCEDURE',
|
|
* 'DEVELOPER_VARIABLE', etc...).
|
|
* @returns An entity name that is legal in the exported language.
|
|
*/
|
|
getName(nameOrId: string, type: NameType | string): string {
|
|
let name = nameOrId;
|
|
if (type === NameType.VARIABLE) {
|
|
const varName = this.getNameForUserVariable(nameOrId);
|
|
if (varName) {
|
|
// Successful ID lookup.
|
|
name = varName;
|
|
}
|
|
}
|
|
const normalizedName = name.toLowerCase();
|
|
|
|
const isVar =
|
|
type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE;
|
|
|
|
const prefix = isVar ? this.variablePrefix : '';
|
|
if (!this.db.has(type)) {
|
|
this.db.set(type, new Map<string, string>());
|
|
}
|
|
const typeDb = this.db.get(type);
|
|
if (typeDb!.has(normalizedName)) {
|
|
return prefix + typeDb!.get(normalizedName);
|
|
}
|
|
const safeName = this.getDistinctName(name, type);
|
|
typeDb!.set(normalizedName, safeName.substr(prefix.length));
|
|
return safeName;
|
|
}
|
|
|
|
/**
|
|
* Return a list of all known user-created names of a specified name type.
|
|
*
|
|
* @param type The type of entity in Blockly ('VARIABLE', 'PROCEDURE',
|
|
* 'DEVELOPER_VARIABLE', etc...).
|
|
* @returns A list of Blockly entity names (no constraints).
|
|
*/
|
|
getUserNames(type: NameType | string): string[] {
|
|
const userNames = this.db.get(type)?.keys();
|
|
return userNames ? Array.from(userNames) : [];
|
|
}
|
|
|
|
/**
|
|
* Convert a Blockly entity name to a legal exportable entity name.
|
|
* Ensure that this is a new name not overlapping any previously defined name.
|
|
* Also check against list of reserved words for the current language and
|
|
* ensure name doesn't collide.
|
|
*
|
|
* @param name The Blockly entity name (no constraints).
|
|
* @param type The type of entity in Blockly ('VARIABLE', 'PROCEDURE',
|
|
* 'DEVELOPER_VARIABLE', etc...).
|
|
* @returns An entity name that is legal in the exported language.
|
|
*/
|
|
getDistinctName(name: string, type: NameType | string): string {
|
|
let safeName = this.safeName(name);
|
|
let i: number | null = null;
|
|
while (
|
|
this.dbReverse.has(safeName + (i ?? '')) ||
|
|
this.reservedWords.has(safeName + (i ?? ''))
|
|
) {
|
|
// Collision with existing name. Create a unique name.
|
|
i = i ? i + 1 : 2;
|
|
}
|
|
safeName += i ?? '';
|
|
this.dbReverse.add(safeName);
|
|
const isVar =
|
|
type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE;
|
|
const prefix = isVar ? this.variablePrefix : '';
|
|
return prefix + safeName;
|
|
}
|
|
|
|
/**
|
|
* Given a proposed entity name, generate a name that conforms to the
|
|
* [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for
|
|
* variable and function names.
|
|
*
|
|
* @param name Potentially illegal entity name.
|
|
* @returns Safe entity name.
|
|
*/
|
|
private safeName(name: string): string {
|
|
if (!name) {
|
|
name = Msg['UNNAMED_KEY'] || 'unnamed';
|
|
} else {
|
|
// Unfortunately names in non-latin characters will look like
|
|
// _E9_9F_B3_E4_B9_90 which is pretty meaningless.
|
|
// https://github.com/google/blockly/issues/1654
|
|
name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_');
|
|
// Most languages don't allow names with leading numbers.
|
|
if ('0123456789'.includes(name[0])) {
|
|
name = 'my_' + name;
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Do the given two entity names refer to the same entity?
|
|
* Blockly names are case-insensitive.
|
|
*
|
|
* @param name1 First name.
|
|
* @param name2 Second name.
|
|
* @returns True if names are the same.
|
|
*/
|
|
static equals(name1: string, name2: string): boolean {
|
|
// name1.localeCompare(name2) is slower.
|
|
return name1.toLowerCase() === name2.toLowerCase();
|
|
}
|
|
}
|
|
|
|
export namespace Names {
|
|
/**
|
|
* Enum for the type of a name. Different name types may have different rules
|
|
* about collisions.
|
|
* When JavaScript (or most other languages) is generated, variable 'foo' and
|
|
* procedure 'foo' would collide. However, Blockly has no such problems since
|
|
* variable get 'foo' and procedure call 'foo' are unambiguous.
|
|
* Therefore, Blockly keeps a separate name type to disambiguate.
|
|
* getName('foo', 'VARIABLE') = 'foo'
|
|
* getName('foo', 'PROCEDURE') = 'foo2'
|
|
*
|
|
*/
|
|
export enum NameType {
|
|
DEVELOPER_VARIABLE = 'DEVELOPER_VARIABLE',
|
|
VARIABLE = 'VARIABLE',
|
|
PROCEDURE = 'PROCEDURE',
|
|
}
|
|
}
|
|
|
|
export type NameType = Names.NameType;
|
|
export const NameType = Names.NameType;
|
|
|
|
/**
|
|
* Constant to separate developer variable names from user-defined variable
|
|
* names when running generators.
|
|
* A developer variable will be declared as a global in the generated code, but
|
|
* will never be shown to the user in the workspace or stored in the variable
|
|
* map.
|
|
*/
|
|
Names.DEVELOPER_VARIABLE_TYPE = NameType.DEVELOPER_VARIABLE;
|