2023-10-03 11:14:36 +08:00
import { _ , nil , Code , Name } from "./code"
interface NameGroup {
prefix : string
index : number
}
export interface NameValue {
ref : ValueReference // this is the reference to any value that can be referred to from generated code via `globals` var in the closure
key? : unknown // any key to identify a global to avoid duplicates, if not passed ref is used
code? : Code // this is the code creating the value needed for standalone code wit_out closure - can be a primitive value, function or import (`require`)
}
export type ValueReference = unknown // possibly make CodeGen parameterized type on this type
class ValueError extends Error {
readonly value? : NameValue
constructor ( name : ValueScopeName ) {
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 } : ScopeOptions = { } ) {
this . _prefixes = prefixes
this . _parent = parent
}
toName ( nameOrPrefix : Name | string ) : Name {
return nameOrPrefix instanceof Name ? nameOrPrefix : this.name ( nameOrPrefix )
}
name ( prefix : string ) : Name {
return new Name ( this . _newName ( prefix ) )
}
protected _newName ( prefix : string ) : string {
const ng = this . _names [ prefix ] || this . _nameGroup ( prefix )
return ` ${ prefix } ${ ng . index ++ } `
}
private _nameGroup ( prefix : string ) : NameGroup {
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 : string , nameStr : string ) {
super ( nameStr )
this . prefix = prefix
}
setValue ( value : NameValue , { property , itemIndex } : ScopePath ) : void {
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 : ValueScopeOptions ) {
super ( opts )
this . _scope = opts . scope
this . opts = { . . . opts , _n : opts.lines ? line : nil }
}
get ( ) : ScopeStore {
return this . _scope
}
name ( prefix : string ) : ValueScopeName {
return new ValueScopeName ( prefix , this . _newName ( prefix ) )
}
value ( nameOrPrefix : ValueScopeName | string , value : NameValue ) : ValueScopeName {
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 : string , keyOrRef : unknown ) : ValueScopeName | undefined {
const vs = this . _values [ prefix ]
if ( ! vs ) return
return vs . get ( keyOrRef )
}
scopeRefs ( scopeName : Name , values : ScopeValues | ScopeValueSets = this . _values ) : Code {
return this . _reduceValues ( values , ( name : ValueScopeName ) = > {
if ( name . scopePath === undefined ) throw new Error ( ` CodeGen: name " ${ name } " has no value ` )
return _ ` ${ scopeName } ${ name . scopePath } `
} )
}
scopeCode (
values : ScopeValues | ScopeValueSets = this . _values ,
usedValues? : UsedScopeValues ,
getCode ? : ( n : ValueScopeName ) = > Code | undefined
) : Code {
return this . _reduceValues (
values ,
( name : ValueScopeName ) = > {
if ( name . value === undefined ) throw new Error ( ` CodeGen: name " ${ name } " has no value ` )
return name . value . code
} ,
usedValues ,
getCode
)
}
private _reduceValues (
values : ScopeValues | ScopeValueSets ,
valueCode : ( n : ValueScopeName ) = > Code | undefined ,
usedValues : UsedScopeValues = { } ,
getCode ? : ( n : ValueScopeName ) = > Code | undefined
) : Code {
let code : 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 : ValueScopeName ) = > {
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
}
}