import type { ScopeValueSets , NameValue , ValueScope , ValueScopeName } from "./scope" ; import { _ , nil , _Code , Code , Name , UsedNames , CodeItem , addCodeArg , _CodeOrName } from "./code" ; import { Scope , varKinds } from "./scope" ; export { _ , str , strConcat , nil , getProperty , stringify , regexpCode , Name , Code } from "./code" ; export { Scope , ScopeStore , ValueScope , ValueScopeName , ScopeValueSets , varKinds } from "./scope" ; export type SafeExpr = Code | number | boolean | null ; export type Block = Code | ( ( ) => void ) ; export const operators = { GT : new _Code ( ">" ) , GTE : new _Code ( ">=" ) , LT : new _Code ( "<" ) , LTE : new _Code ( "<=" ) , EQ : new _Code ( "===" ) , NEQ : new _Code ( "!==" ) , NOT : new _Code ( "!" ) , OR : new _Code ( "||" ) , AND : new _Code ( "&&" ) , ADD : new _Code ( "+" ) , } ; abstract class Node { abstract readonly names : UsedNames ; optimizeNodes ( ) { return this ; } optimizeNames ( _names , _constants ) { return this ; } } class Def extends Node { constructor ( private readonly varKind , private readonly name , private rhs ? ) { super ( ) ; } render ( { es5 , _n } ) { const varKind = es5 ? varKinds . var : this . varKind ; const rhs = this . rhs === undefined ? "" : ` = ${ this . rhs }` ; return `${ varKind } ${ this . name }${ rhs };` + _n ; } optimizeNames ( names , constants ) { if ( ! names [ this . name . str ] ) return ; if ( this . rhs ) this . rhs = optimizeExpr ( this . rhs , names , constants ) ; return this ; } get names ( ) : UsedNames { return this . rhs instanceof _CodeOrName ? this . rhs . names : { } ; } } class Assign extends Node { constructor ( readonly lhs , public rhs , private readonly sideEffects ? ) { super ( ) ; } render ( { _n } ) { return `${ this . lhs } = ${ this . rhs };` + _n ; } optimizeNames ( names , constants ) { if ( this . lhs instanceof Name && ! names [ this . lhs . str ] && ! this . sideEffects ) return ; this . rhs = optimizeExpr ( this . rhs , names , constants ) ; return this ; } get names ( ) : UsedNames { const names = this . lhs instanceof Name ? { } : { ... this . lhs . names } ; return addExprNames ( names , this . rhs ) ; } } class AssignOp extends Assign { constructor ( lhs , private readonly op , rhs , sideEffects ? ) { super ( lhs , rhs , sideEffects ) ; } render ( { _n } ) { return `${ this . lhs } ${ this . op }= ${ this . rhs };` + _n ; } } class Label extends Node { readonly names : UsedNames = { } ; constructor ( readonly label ) { super ( ) ; } render ( { _n } ) { return `${ this . label }:` + _n ; } } class Break extends Node { readonly names : UsedNames = { } ; constructor ( readonly label ? ) { super ( ) ; } render ( { _n } ) { const label = this . label ? ` ${ this . label }` : "" ; return `break${ label };` + _n ; } } class Throw extends Node { constructor ( readonly error ) { super ( ) ; } render ( { _n } ) { return `throw ${ this . error };` + _n ; } get names ( ) : UsedNames { return this . error . names ; } } class AnyCode extends Node { constructor ( private code ) { super ( ) ; } render ( { _n } ) { return `${ this . code };` + _n ; } optimizeNodes ( ) { return `${ this . code }` ? this : undefined ; } optimizeNames ( names , constants ) { this . code = optimizeExpr ( this . code , names , constants ) ; return this ; } get names ( ) : UsedNames { return this . code instanceof _CodeOrName ? this . code . names : { } ; } } abstract class ParentNode extends Node { constructor ( readonly nodes = [ ] ) { super ( ) ; } render ( opts ) { return this . nodes . reduce ( ( code , n ) => code + n . render ( opts ) , "" ) ; } optimizeNodes ( ) { const { nodes } = this ; let i = nodes . length ; while ( i -- ) { const n = nodes [ i ] . optimizeNodes ( ) ; if ( Array . isArray ( n ) ) nodes . splice ( i , 1 , ... n ) ; else if ( n ) nodes [ i ] = n ; else nodes . splice ( i , 1 ) ; } return nodes . length > 0 ? this : undefined ; } optimizeNames ( names , constants ) { const { nodes } = this ; let i = nodes . length ; while ( i -- ) { const n = nodes [ i ] ; if ( n . optimizeNames ( names , constants ) ) continue ; subtractNames ( names , n . names ) ; nodes . splice ( i , 1 ) ; } return nodes . length > 0 ? this : undefined ; } get names ( ) : UsedNames { return this . nodes . reduce ( ( names , n ) => addNames ( names , n . names ) , { } ) ; } } abstract class BlockNode extends ParentNode { render ( opts ) { return "{" + opts . _n + super . render ( opts ) + "}" + opts . _n ; } } class Root extends ParentNode { } class Else extends BlockNode { static readonly kind = "else" ; } class If extends BlockNode { static readonly kind = "if" ; else ? : If | Else ; constructor ( private condition , nodes ? ) { super ( nodes ) ; } render ( opts ) { let code = `if(${ this . condition })` + super . render ( opts ) ; if ( this . else ) code += "else " + this . else . render ( opts ) ; return code ; } optimizeNodes ( ) { super . optimizeNodes ( ) ; const cond = this . condition ; if ( cond === true ) return this . nodes ; let e = this . else ; if ( e ) { const ns = e . optimizeNodes ( ) ; e = this . else = Array . isArray ( ns ) ? new Else ( ns ) : ( ns as Else | undefined ) ; } if ( e ) { if ( cond === false ) return e instanceof If ? e : e . nodes ; if ( this . nodes . length ) return this ; return new If ( not ( cond ) , e instanceof If ? [ e ] : e . nodes ) ; } if ( cond === false || ! this . nodes . length ) return undefined ; return this ; } optimizeNames ( names , constants ) { this . else = this . else ?. optimizeNames ( names , constants ) ; if ( ! ( super . optimizeNames ( names , constants ) || this . else ) ) return ; this . condition = optimizeExpr ( this . condition , names , constants ) ; return this ; } get names ( ) : UsedNames { const names = super . names ; addExprNames ( names , this . condition ) ; if ( this . else ) addNames ( names , this . else . names ) ; return names ; } } abstract class For extends BlockNode { static readonly kind = "for" ; } class ForLoop extends For { constructor ( private iteration ) { super ( ) ; } render ( opts ) { return `for(${ this . iteration })` + super . render ( opts ) ; } optimizeNames ( names , constants ) { if ( ! super . optimizeNames ( names , constants ) ) return ; this . iteration = optimizeExpr ( this . iteration , names , constants ) ; return this ; } get names ( ) : UsedNames { return addNames ( super . names , this . iteration . names ) ; } } class ForRange extends For { constructor ( private readonly varKind , private readonly name , private readonly from , private readonly to ) { super ( ) ; } render ( opts ) { const varKind = opts . es5 ? varKinds . var : this . varKind ; const { name , from , to } = this ; return `for(${ varKind } ${ name }=${ from }; ${ name }<${ to }; ${ name }++)` + super . render ( opts ) ; } get names ( ) : UsedNames { const names = addExprNames ( super . names , this . from ) ; return addExprNames ( names , this . to ) ; } } class ForIter extends For { constructor ( private readonly loop , private readonly varKind , private readonly name , private iterable ) { super ( ) ; } render ( opts ) { return `for(${ this . varKind } ${ this . name } ${ this . loop } ${ this . iterable })` + super . render ( opts ) ; } optimizeNames ( names , constants ) { if ( ! super . optimizeNames ( names , constants ) ) return ; this . iterable = optimizeExpr ( this . iterable , names , constants ) ; return this ; } get names ( ) : UsedNames { return addNames ( super . names , this . iterable . names ) ; } } class Func extends BlockNode { static readonly kind = "func" ; constructor ( public name , public args , public async ? ) { super ( ) ; } render ( opts ) { const _async = this . async ? "async " : "" ; return `${ _async }function ${ this . name }(${ this . args })` + super . render ( opts ) ; } } class Return extends ParentNode { static readonly kind = "return" ; render ( opts ) { return "return " + super . render ( opts ) ; } } class Try extends BlockNode { catch ? : Catch ; finally ? : Finally ; render ( opts ) { let code = "try" + super . render ( opts ) ; if ( this . catch ) code += this . catch . render ( opts ) ; if ( this . finally ) code += this . finally . render ( opts ) ; return code ; } optimizeNodes ( ) { super . optimizeNodes ( ) ; this . catch ?. optimizeNodes ( ) as Catch | undefined ; this . finally ?. optimizeNodes ( ) as Finally | undefined ; return this ; } optimizeNames ( names , constants ) { super . optimizeNames ( names , constants ) ; this . catch ?. optimizeNames ( names , constants ) ; this . finally ?. optimizeNames ( names , constants ) ; return this ; } get names ( ) : UsedNames { const names = super . names ; if ( this . catch ) addNames ( names , this . catch . names ) ; if ( this . finally ) addNames ( names , this . finally . names ) ; return names ; } } class Catch extends BlockNode { static readonly kind = "catch" ; constructor ( readonly error ) { super ( ) ; } render ( opts ) { return `catch(${ this . error })` + super . render ( opts ) ; } } class Finally extends BlockNode { static readonly kind = "finally" ; render ( opts ) { return "finally" + super . render ( opts ) ; } } type StartBlockNode = If | For | Func | Return | Try ; type LeafNode = Def | Assign | Label | Break | Throw | AnyCode ; type ChildNode = StartBlockNode | LeafNode ; type EndBlockNodeType = typeof If | typeof Else | typeof For | typeof Func | typeof Return | typeof Catch | typeof Finally ; type Constants = Record < string , SafeExpr | undefined > ; export interface CodeGenOptions { es5 ? : boolean ; lines ? : boolean ; ownProperties ? : boolean ; } interface CGOptions extends CodeGenOptions { _n : "\n" | "" ; } export class CodeGen { readonly _scope : Scope ; readonly _extScope : ValueScope ; readonly _values : ScopeValueSets = { } ; private readonly _nodes : ParentNode [ ] ; private readonly _blockStarts : number [ ] = [ ] ; private readonly _constants : Constants = { } ; private readonly opts : CGOptions ; constructor ( extScope , opts = { } ) { this . opts = { ... opts , _n : opts . lines ? "\n" : "" } ; this . _extScope = extScope ; this . _scope = new Scope ( { parent : extScope } ) ; this . _nodes = [ new Root ( ) ] ; } toString ( ) { return this . _root . render ( this . opts ) ; } name ( prefix ) { return this . _scope . name ( prefix ) ; } scopeName ( prefix ) { return this . _extScope . name ( prefix ) ; } scopeValue ( prefixOrName , value ) { const name = this . _extScope . value ( prefixOrName , value ) ; const vs = this . _values [ name . prefix ] || ( this . _values [ name . prefix ] = new Set ( ) ) ; vs . add ( name ) ; return name ; } getScopeValue ( prefix , keyOrRef ) { return this . _extScope . getValue ( prefix , keyOrRef ) ; } scopeRefs ( scopeName ) { return this . _extScope . scopeRefs ( scopeName , this . _values ) ; } scopeCode ( ) { return this . _extScope . scopeCode ( this . _values ) ; } private _def ( varKind , nameOrPrefix , rhs ? , constant ? ) { const name = this . _scope . toName ( nameOrPrefix ) ; if ( rhs !== undefined && constant ) this . _constants [ name . str ] = rhs ; this . _leafNode ( new Def ( varKind , name , rhs ) ) ; return name ; } const ( nameOrPrefix , rhs , _constant ? ) { return this . _def ( varKinds . const , nameOrPrefix , rhs , _constant ) ; } let ( nameOrPrefix , rhs ? , _constant ? ) { return this . _def ( varKinds . let , nameOrPrefix , rhs , _constant ) ; } var ( nameOrPrefix , rhs ? , _constant ? ) { return this . _def ( varKinds . var , nameOrPrefix , rhs , _constant ) ; } assign ( lhs , rhs , sideEffects ? ) { return this . _leafNode ( new Assign ( lhs , rhs , sideEffects ) ) ; } add ( lhs , rhs ) { return this . _leafNode ( new AssignOp ( lhs , operators . ADD , rhs ) ) ; } code ( c ) { if ( typeof c == "function" ) c ( ) ; else if ( c !== nil ) this . _leafNode ( new AnyCode ( c ) ) ; return this ; } object ( ... keyValues ) { const code = [ "{" ] ; for ( const [ key , value ] of keyValues ) { if ( code . length > 1 ) code . push ( "," ) ; code . push ( key ) ; if ( key !== value || this . opts . es5 ) { code . push ( ":" ) ; addCodeArg ( code , value ) ; } } code . push ( "}" ) ; return new _Code ( code ) ; } if ( condition , thenBody ? , elseBody ? ) { this . _blockNode ( new If ( condition ) ) ; if ( thenBody && elseBody ) { this . code ( thenBody ) . else ( ) . code ( elseBody ) . endIf ( ) ; } else if ( thenBody ) { this . code ( thenBody ) . endIf ( ) ; } else if ( elseBody ) { throw new Error ( 'CodeGen: "else" body without "then" body' ) ; } return this ; } elseIf ( condition ) { return this . _elseNode ( new If ( condition ) ) ; } else ( ) { return this . _elseNode ( new Else ( ) ) ; } endIf ( ) { return this . _endBlockNode ( If , Else ) ; } private _for ( node , forBody ? ) { this . _blockNode ( node ) ; if ( forBody ) this . code ( forBody ) . endFor ( ) ; return this ; } for ( iteration , forBody ? ) { return this . _for ( new ForLoop ( iteration ) , forBody ) ; } forRange ( nameOrPrefix , from , to , forBody , varKind = this . opts . es5 ? varKinds . var : varKinds . let ) { const name = this . _scope . toName ( nameOrPrefix ) ; return this . _for ( new ForRange ( varKind , name , from , to ) , ( ) => forBody ( name ) ) ; } forOf ( nameOrPrefix , iterable , forBody , varKind = varKinds . const ) { const name = this . _scope . toName ( nameOrPrefix ) ; if ( this . opts . es5 ) { const arr = iterable instanceof Name ? iterable : this . var ( "_arr" , iterable ) ; return this . forRange ( "_i" , 0 , _ `${ arr }.length` , ( i ) => { this . var ( name , _ `${ arr }[${ i }]` ) ; forBody ( name ) ; } ) ; } return this . _for ( new ForIter ( "of" , varKind , name , iterable ) , ( ) => forBody ( name ) ) ; } forIn ( nameOrPrefix , obj , forBody , varKind = this . opts . es5 ? varKinds . var : varKinds . const ) { if ( this . opts . ownProperties ) { return this . forOf ( nameOrPrefix , _ `Object.keys(${ obj })` , forBody ) ; } const name = this . _scope . toName ( nameOrPrefix ) ; return this . _for ( new ForIter ( "in" , varKind , name , obj ) , ( ) => forBody ( name ) ) ; } endFor ( ) { return this . _endBlockNode ( For ) ; } label ( label ) { return this . _leafNode ( new Label ( label ) ) ; } break ( label ? ) { return this . _leafNode ( new Break ( label ) ) ; } return ( value ) { const node = new Return ( ) ; this . _blockNode ( node ) ; this . code ( value ) ; if ( node . nodes . length !== 1 ) throw new Error ( 'CodeGen: "return" should have one node' ) ; return this . _endBlockNode ( Return ) ; } try ( tryBody , catchCode ? , finallyCode ? ) { if ( ! catchCode && ! finallyCode ) throw new Error ( 'CodeGen: "try" without "catch" and "finally"' ) ; const node = new Try ( ) ; this . _blockNode ( node ) ; this . code ( tryBody ) ; if ( catchCode ) { const error = this . name ( "e" ) ; this . _currNode = node . catch = new Catch ( error ) ; catchCode ( error ) ; } if ( finallyCode ) { this . _currNode = node . finally = new Finally ( ) ; this . code ( finallyCode ) ; } return this . _endBlockNode ( Catch , Finally ) ; } throw ( error ) { return this . _leafNode ( new Throw ( error ) ) ; } block ( body ? , nodeCount ? ) { this . _blockStarts . push ( this . _nodes . length ) ; if ( body ) this . code ( body ) . endBlock ( nodeCount ) ; return this ; } endBlock ( nodeCount ? ) { const len = this . _blockStarts . pop ( ) ; if ( len === undefined ) throw new Error ( "CodeGen: not in self-balancing block" ) ; const toClose = this . _nodes . length - len ; if ( toClose < 0 || ( nodeCount !== undefined && toClose !== nodeCount ) ) { throw new Error ( `CodeGen: wrong number of nodes: ${ toClose } vs ${ nodeCount } expected` ) ; } this . _nodes . length = len ; return this ; } func ( name , args = nil , async ? , funcBody ? ) { this . _blockNode ( new Func ( name , args , async ) ) ; if ( funcBody ) this . code ( funcBody ) . endFunc ( ) ; return this ; } endFunc ( ) { return this . _endBlockNode ( Func ) ; } optimize ( n = 1 ) { while ( n -- > 0 ) { this . _root . optimizeNodes ( ) ; this . _root . optimizeNames ( this . _root . names , this . _constants ) ; } } private _leafNode ( node ) { this . _currNode . nodes . push ( node ) ; return this ; } private _blockNode ( node ) { this . _currNode . nodes . push ( node ) ; this . _nodes . push ( node ) ; } private _endBlockNode ( N1 , N2 ? ) { const n = this . _currNode ; if ( n instanceof N1 || ( N2 && n instanceof N2 ) ) { this . _nodes . pop ( ) ; return this ; } throw new Error ( `CodeGen: not in block "${ N2 ? `${ N1 . kind }/${ N2 . kind }` : N1 . kind }"` ) ; } private _elseNode ( node ) { const n = this . _currNode ; if ( ! ( n instanceof If ) ) { throw new Error ( 'CodeGen: "else" without "if"' ) ; } this . _currNode = n . else = node ; return this ; } private get _root ( ) : Root { return this . _nodes [ 0 ] as Root ; } private get _currNode ( ) : ParentNode { const ns = this . _nodes ; return ns [ ns . length - 1 ] ; } private set _currNode ( node ) { const ns = this . _nodes ; ns [ ns . length - 1 ] = node ; } } function addNames ( names , from ) { for ( const n in from ) names [ n ] = ( names [ n ] || 0 ) + ( from [ n ] || 0 ) ; return names ; } function addExprNames ( names , from ) { return from instanceof _CodeOrName ? addNames ( names , from . names ) : names ; } function optimizeExpr < T extends SafeExpr | Code > ( expr , names , constants ) ; function optimizeExpr ( expr , names , constants ) { if ( expr instanceof Name ) return replaceName ( expr ) ; if ( ! canOptimize ( expr ) ) return expr ; return new _Code ( expr . _items . reduce ( ( items , c ) => { if ( c instanceof Name ) c = replaceName ( c ) ; if ( c instanceof _Code ) items . push ( ... c . _items ) ; else items . push ( c ) ; return items ; } , [ ] ) ) ; function replaceName ( n ) { const c = constants [ n . str ] ; if ( c === undefined || names [ n . str ] !== 1 ) return n ; delete names [ n . str ] ; return c ; } function canOptimize ( e ) { return ( e instanceof _Code && e . _items . some ( ( c ) => c instanceof Name && names [ c . str ] === 1 && constants [ c . str ] !== undefined ) ) ; } } function subtractNames ( names , from ) { for ( const n in from ) names [ n ] = ( names [ n ] || 0 ) - ( from [ n ] || 0 ) ; } export function not < T extends Code | SafeExpr > ( x ) ; export function not ( x ) { return typeof x == "boolean" || typeof x == "number" || x === null ? ! x : _ `!${ par ( x ) }` ; } const andCode = mappend ( operators . AND ) ; export function and ( ... args ) { return args . reduce ( andCode ) ; } const orCode = mappend ( operators . OR ) ; export function or ( ... args ) { return args . reduce ( orCode ) ; } type MAppend = ( x : Code , y : Code ) => Code ; function mappend ( op ) { return ( x , y ) => ( x === nil ? y : y === nil ? x : _ `${ par ( x ) } ${ op } ${ par ( y ) }` ) ; } function par ( x ) { return x instanceof Name ? x : _ `(${ x })` ; }