123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- 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: 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
- }
- }
|