You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					216 lines
				
				5.8 KiB
			
		
		
			
		
	
	
					216 lines
				
				5.8 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								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
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |