This is the first of three issues created to discuss proposed changes to generators (and perhaps track implementation of those proposals, if agreed). This issue concerns proposed breaking changes to the CodeGenerator class in core/generator.ts.
In code examples in these bugs, the word "language" / "Language" / "LANGUAGE" should be read as a metasyntactic variable standing in for some particular language like JavaScript.
Background
The way code generators are currently defined involves creating a CodeGenerator instance for the particular language to generate code for, and then adding a large number of properties to that object. These can broadly be divided into three categories:
- Constants and similarly static (but not
static) properties. - Helper methods and overrides of methods declared on
CodeGenerator. - Per-block-type generator functions.
(See detailed example in background section of the second proposal, issue #7085.)
This is a perfectly good approach in JavaScript, but migrating this style of code to TypeScript poses some challenges because tsc generally complains about adding arbitrary properties to an object unless that object is declared as a dictionary.
The main proposal below addresses category 3, the per-block-type generator functions; the other two categories will be dealt with in the second proposal, #7085.
There are two supplementary proposals concerning other shortcomings in CodeGenerator which would require breaking changes to correct and which we may wish to address in the same release.
Main Proposal: Move generator functions to a dictionary object
At the moment CodeGenerator.prototype.blockToCode uses the CodeGenerator object itself as a dictionary to look up the generator function based on the block type:
classCodeGenerator{// ...blockToCode(block: Block|null,/* ... */): string{// ...constfunc=(thisasany)[block.type];Instead, have the CodeGenerator constructor create a dictionary object (perhaps named .blockGenerators) on each instance, and have blockToCode obtain the generator function there:
typeBlockGenerator=(block: Block)=>[string,number]|string|null;classCodeGenerator{// ...blockGenerators: {[type: string]: BlockGenerator}=Object.create(null);blockToCode(block: Block|null,/* ... */): string{// ...constfunc=this.blockGenerators[block.type];Breaking Changes
This will entail all code that creates generator objects (our own and external developers) to replace assignments of the form:
myGenerator['block_type']=function(block){/* ... */};with the trivially different:
myGenerator.blockGenerators['block_type']=function(block){/* ... */};This will also apply cases where generator functions are reused; i.e. JavaScript['controls_ifelse'] = JavaScript['controls_if'] will need to be updated to JavaScript.blockGenerators['controls_ifelse'] = JavaScript.blockGenerators['controls_if'].
Alternatives Considered
It seems like it should be possible to use declaration merging or intersection types to create a type that is both a CodeGenerator and dictionary of block generator functions (i.e., Record<string, BlockGenerator> or equivalently {[type: string]: BlockGenerator}). Unfortunately several attempts to create a proof-of-concept of this idea in the TypeScript Playground all fell foul of errors complaining that the various properties and methods of CodeGenerator did not conform to the BlockGenerator type. It appears that it would be necessary to declare a type for the keys of the generator function dictionary that either included every possible block type, or all strings except the names of methods declared on CodeGeneratorand any of its subclasses—but in either case this depends on creating a comprehensive list of names most of which are chosen by external developers, clearly an impossible task.
This points to another weakness of both this alternative and the status quo: that there is a danger of clashes between block types and the names of non-block-generator-function methods on CodeGenerator and its subclasses—a danger entirely obviated by the proposal of creating a separate dictionary for the latter.
Additional Info
The names BlockGenerator and blockGenerators have been chosen somewhat arbitrarily and better suggestions are welcome. The latter in particular will be used frequently when creating generators, so something shorter would be advantageous.
Supplementary Proposal 1: Consider renaming members to conform to style guide
The following properties and methods on CodeGenerator have names which do not conform to our current styleguide. If we are making breaking changes, we could consider fixing these at the same time:
name_FUNCTION_NAME_PLACEHOLDER_FUNCTION_NAME_PLACEHOLDER_REGEXP_INFINITE_LOOP_TRAPSTATEMENT_PREFIXSTATEMENT_SUFFIXINDENTCOMMENT_WRAPORDER_OVERRIDESRESERVED_WORDS_definitions_functionNames_protected nameDB_
Breaking Changes
This will entail updating all code that references these names, which is essentially all generator implementations. Since these changes are less trivial than the changes entailed by proposal 1 it may be better not to impose such costs for purely stylistic reasons. Nevertheless, if we did want to update these names it makes sense to do so at the same time as we make other breaking changes.
Supplementary Proposal 2: Consider changes to parenthesisation mechanism in valueToCode
The existence of the ORDER_OVERRIDES property seems like a code smell. It appears to be needed because generators do not provide information about the (left- or right-)associativity of the operators they generate, only a numerical 'order' value.
There are various possible ways this could be improved. At the simplest level, generator functions could return [code, order, associativity] instead of just [code, order]. A more satisfactory approach might be to provide a standardised way to declare an operator precedence table that includes associativity information, and have generator functions return [code, rootOperator] tuples.
This proposal would require further development.
This is the first of three issues created to discuss proposed changes to generators (and perhaps track implementation of those proposals, if agreed). This issue concerns proposed breaking changes to the
CodeGeneratorclass incore/generator.ts.In code examples in these bugs, the word "language" / "Language" / "LANGUAGE" should be read as a metasyntactic variable standing in for some particular language like
JavaScript.Background
The way code generators are currently defined involves creating a
CodeGeneratorinstance for the particular language to generate code for, and then adding a large number of properties to that object. These can broadly be divided into three categories:static) properties.CodeGenerator.(See detailed example in background section of the second proposal, issue #7085.)
This is a perfectly good approach in JavaScript, but migrating this style of code to TypeScript poses some challenges because
tscgenerally complains about adding arbitrary properties to an object unless that object is declared as a dictionary.The main proposal below addresses category 3, the per-block-type generator functions; the other two categories will be dealt with in the second proposal, #7085.
There are two supplementary proposals concerning other shortcomings in
CodeGeneratorwhich would require breaking changes to correct and which we may wish to address in the same release.Main Proposal: Move generator functions to a dictionary object
At the moment
CodeGenerator.prototype.blockToCodeuses theCodeGeneratorobject itself as a dictionary to look up the generator function based on the block type:Instead, have the
CodeGeneratorconstructor create a dictionary object (perhaps named.blockGenerators) on each instance, and haveblockToCodeobtain the generator function there:Breaking Changes
This will entail all code that creates generator objects (our own and external developers) to replace assignments of the form:
with the trivially different:
This will also apply cases where generator functions are reused; i.e.
JavaScript['controls_ifelse'] = JavaScript['controls_if']will need to be updated toJavaScript.blockGenerators['controls_ifelse'] = JavaScript.blockGenerators['controls_if'].Alternatives Considered
It seems like it should be possible to use declaration merging or intersection types to create a type that is both a
CodeGeneratorand dictionary of block generator functions (i.e.,Record<string, BlockGenerator>or equivalently{[type: string]: BlockGenerator}). Unfortunately several attempts to create a proof-of-concept of this idea in the TypeScript Playground all fell foul of errors complaining that the various properties and methods ofCodeGeneratordid not conform to theBlockGeneratortype. It appears that it would be necessary to declare a type for the keys of the generator function dictionary that either included every possible block type, or all strings except the names of methods declared onCodeGeneratorand any of its subclasses—but in either case this depends on creating a comprehensive list of names most of which are chosen by external developers, clearly an impossible task.This points to another weakness of both this alternative and the status quo: that there is a danger of clashes between block types and the names of non-block-generator-function methods on
CodeGeneratorand its subclasses—a danger entirely obviated by the proposal of creating a separate dictionary for the latter.Additional Info
The names
BlockGeneratorandblockGeneratorshave been chosen somewhat arbitrarily and better suggestions are welcome. The latter in particular will be used frequently when creating generators, so something shorter would be advantageous.Supplementary Proposal 1: Consider renaming members to conform to style guide
The following properties and methods on
CodeGeneratorhave names which do not conform to our current styleguide. If we are making breaking changes, we could consider fixing these at the same time:name_FUNCTION_NAME_PLACEHOLDER_FUNCTION_NAME_PLACEHOLDER_REGEXP_INFINITE_LOOP_TRAPSTATEMENT_PREFIXSTATEMENT_SUFFIXINDENTCOMMENT_WRAPORDER_OVERRIDESRESERVED_WORDS_definitions_functionNames_protected nameDB_Breaking Changes
This will entail updating all code that references these names, which is essentially all generator implementations. Since these changes are less trivial than the changes entailed by proposal 1 it may be better not to impose such costs for purely stylistic reasons. Nevertheless, if we did want to update these names it makes sense to do so at the same time as we make other breaking changes.
Supplementary Proposal 2: Consider changes to parenthesisation mechanism in
valueToCodeThe existence of the
ORDER_OVERRIDESproperty seems like a code smell. It appears to be needed because generators do not provide information about the (left- or right-)associativity of the operators they generate, only a numerical 'order' value.There are various possible ways this could be improved. At the simplest level, generator functions could return [code, order, associativity] instead of just [code, order]. A more satisfactory approach might be to provide a standardised way to declare an operator precedence table that includes associativity information, and have generator functions return [code, rootOperator] tuples.
This proposal would require further development.