import { _ , nil , Code , Name } from "./code" ; interface NameGroup { prefix : string ; index : number ; } export interface NameValue { ref : ValueReference ; key ? : unknown ; code ? : Code ; } export type ValueReference = unknown ; class ValueError extends Error { readonly value ? : NameValue ; constructor ( name ) { super ( `CodeGen: "code" for ${ name } not defined` ) ; this . value = name . value ; } } interface ScopeOptions { prefixes ? : Set < string > ; parent ? : Scope ; } interface ValueScopeOptions extends ScopeOptions { scope : ScopeStore ; es5 ? : boolean ; lines ? : boolean ; } export type ScopeStore = Record < string , ValueReference [ ] | undefined > ; type ScopeValues = { [ Prefix in string ] ? : Map < unknown , ValueScopeName > ; } ; export type ScopeValueSets = { [ Prefix in string ] ? : Set < ValueScopeName > ; } ; export enum UsedValueState { Started , Completed } export type UsedScopeValues = { [ Prefix in string ] ? : Map < ValueScopeName , UsedValueState | undefined > ; } ; export const varKinds = { const : new Name ( "const" ) , let : new Name ( "let" ) , var : new Name ( "var" ) , } ; export class Scope { protected readonly _names : { [ Prefix in string ] ? : NameGroup ; } = { } ; protected readonly _prefixes ? : Set < string > ; protected readonly _parent ? : Scope ; constructor ( { prefixes , parent } = { } ) { this . _prefixes = prefixes ; this . _parent = parent ; } toName ( nameOrPrefix ) { return nameOrPrefix instanceof Name ? nameOrPrefix : this . name ( nameOrPrefix ) ; } name ( prefix ) { return new Name ( this . _newName ( prefix ) ) ; } protected _newName ( prefix ) { const ng = this . _names [ prefix ] || this . _nameGroup ( prefix ) ; return `${ prefix }${ ng . index ++ }` ; } private _nameGroup ( prefix ) { if ( this . _parent ?. _prefixes ?. has ( prefix ) || ( this . _prefixes && ! this . _prefixes . has ( prefix ) ) ) { throw new Error ( `CodeGen: prefix "${ prefix }" is not allowed in this scope` ) ; } return ( this . _names [ prefix ] = { prefix , index : 0 } ) ; } } interface ScopePath { property : string ; itemIndex : number ; } export class ValueScopeName extends Name { readonly prefix : string ; value ? : NameValue ; scopePath ? : Code ; constructor ( prefix , nameStr ) { super ( nameStr ) ; this . prefix = prefix ; } setValue ( value , { property , itemIndex } ) { this . value = value ; this . scopePath = _ `.${ new Name ( property ) }[${ itemIndex }]` ; } } interface VSOptions extends ValueScopeOptions { _n : Code ; } const line = _ `\n` ; export class ValueScope extends Scope { protected readonly _values : ScopeValues = { } ; protected readonly _scope : ScopeStore ; readonly opts : VSOptions ; constructor ( opts ) { super ( opts ) ; this . _scope = opts . scope ; this . opts = { ... opts , _n : opts . lines ? line : nil } ; } get ( ) { return this . _scope ; } name ( prefix ) { return new ValueScopeName ( prefix , this . _newName ( prefix ) ) ; } value ( nameOrPrefix , value ) { if ( value . ref === undefined ) throw new Error ( "CodeGen: ref must be passed in value" ) ; const name = this . toName ( nameOrPrefix ) as ValueScopeName ; const { prefix } = name ; const valueKey = value . key ?? value . ref ; let vs = this . _values [ prefix ] ; if ( vs ) { const _name = vs . get ( valueKey ) ; if ( _name ) return _name ; } else { vs = this . _values [ prefix ] = new Map ( ) ; } vs . set ( valueKey , name ) ; const s = this . _scope [ prefix ] || ( this . _scope [ prefix ] = [ ] ) ; const itemIndex = s . length ; s [ itemIndex ] = value . ref ; name . setValue ( value , { property : prefix , itemIndex } ) ; return name ; } getValue ( prefix , keyOrRef ) { const vs = this . _values [ prefix ] ; if ( ! vs ) return ; return vs . get ( keyOrRef ) ; } scopeRefs ( scopeName , values = this . _values ) { return this . _reduceValues ( values , ( name ) => { if ( name . scopePath === undefined ) throw new Error ( `CodeGen: name "${ name }" has no value` ) ; return _ `${ scopeName }${ name . scopePath }` ; } ) ; } scopeCode ( values = this . _values , usedValues ? , getCode ? ) { return this . _reduceValues ( values , ( name ) => { if ( name . value === undefined ) throw new Error ( `CodeGen: name "${ name }" has no value` ) ; return name . value . code ; } , usedValues , getCode ) ; } private _reduceValues ( values , valueCode , usedValues = { } , getCode ? ) { let code = nil ; for ( const prefix in values ) { const vs = values [ prefix ] ; if ( ! vs ) continue ; const nameSet = ( usedValues [ prefix ] = usedValues [ prefix ] || new Map ( ) ) ; vs . forEach ( ( name ) => { if ( nameSet . has ( name ) ) return ; nameSet . set ( name , UsedValueState . Started ) ; let c = valueCode ( name ) ; if ( c ) { const def = this . opts . es5 ? varKinds . var : varKinds . const ; code = _ `${ code }${ def } ${ name } = ${ c };${ this . opts . _n }` ; } else if ( ( c = getCode ?. ( name ) ) ) { code = _ `${ code }${ c }${ this . opts . _n }` ; } else { throw new ValueError ( name ) ; } nameSet . set ( name , UsedValueState . Completed ) ; } ) ; } return code ; } }