1 import {_, nil, Code, Name} from "./code"
8 export interface NameValue {
9 ref: ValueReference // this is the reference to any value that can be referred to from generated code via `globals` var in the closure
10 key?: unknown // any key to identify a global to avoid duplicates, if not passed ref is used
11 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`)
14 export type ValueReference = unknown // possibly make CodeGen parameterized type on this type
16 class ValueError extends Error {
17 readonly value?: NameValue
18 constructor(name: ValueScopeName) {
19 super(`CodeGen: "code" for ${name} not defined`)
20 this.value = name.value
24 interface ScopeOptions {
25 prefixes?: Set<string>
29 interface ValueScopeOptions extends ScopeOptions {
35 export type ScopeStore = Record<string, ValueReference[] | undefined>
38 [Prefix in string]?: Map<unknown, ValueScopeName>
41 export type ScopeValueSets = {
42 [Prefix in string]?: Set<ValueScopeName>
45 export enum UsedValueState {
50 export type UsedScopeValues = {
51 [Prefix in string]?: Map<ValueScopeName, UsedValueState | undefined>
54 export const varKinds = {
55 const: new Name("const"),
61 protected readonly _names: {[Prefix in string]?: NameGroup} = {}
62 protected readonly _prefixes?: Set<string>
63 protected readonly _parent?: Scope
65 constructor({prefixes, parent}: ScopeOptions = {}) {
66 this._prefixes = prefixes
70 toName(nameOrPrefix: Name | string): Name {
71 return nameOrPrefix instanceof Name ? nameOrPrefix : this.name(nameOrPrefix)
74 name(prefix: string): Name {
75 return new Name(this._newName(prefix))
78 protected _newName(prefix: string): string {
79 const ng = this._names[prefix] || this._nameGroup(prefix)
80 return `${prefix}${ng.index++}`
83 private _nameGroup(prefix: string): NameGroup {
84 if (this._parent?._prefixes?.has(prefix) || (this._prefixes && !this._prefixes.has(prefix))) {
85 throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`)
87 return (this._names[prefix] = {prefix, index: 0})
96 export class ValueScopeName extends Name {
97 readonly prefix: string
101 constructor(prefix: string, nameStr: string) {
106 setValue(value: NameValue, {property, itemIndex}: ScopePath): void {
108 this.scopePath = _`.${new Name(property)}[${itemIndex}]`
112 interface VSOptions extends ValueScopeOptions {
118 export class ValueScope extends Scope {
119 protected readonly _values: ScopeValues = {}
120 protected readonly _scope: ScopeStore
121 readonly opts: VSOptions
123 constructor(opts: ValueScopeOptions) {
125 this._scope = opts.scope
126 this.opts = {...opts, _n: opts.lines ? line : nil}
133 name(prefix: string): ValueScopeName {
134 return new ValueScopeName(prefix, this._newName(prefix))
137 value(nameOrPrefix: ValueScopeName | string, value: NameValue): ValueScopeName {
138 if (value.ref === undefined) throw new Error("CodeGen: ref must be passed in value")
139 const name = this.toName(nameOrPrefix) as ValueScopeName
140 const {prefix} = name
141 const valueKey = value.key ?? value.ref
142 let vs = this._values[prefix]
144 const _name = vs.get(valueKey)
145 if (_name) return _name
147 vs = this._values[prefix] = new Map()
149 vs.set(valueKey, name)
151 const s = this._scope[prefix] || (this._scope[prefix] = [])
152 const itemIndex = s.length
153 s[itemIndex] = value.ref
154 name.setValue(value, {property: prefix, itemIndex})
158 getValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined {
159 const vs = this._values[prefix]
161 return vs.get(keyOrRef)
164 scopeRefs(scopeName: Name, values: ScopeValues | ScopeValueSets = this._values): Code {
165 return this._reduceValues(values, (name: ValueScopeName) => {
166 if (name.scopePath === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
167 return _`${scopeName}${name.scopePath}`
172 values: ScopeValues | ScopeValueSets = this._values,
173 usedValues?: UsedScopeValues,
174 getCode?: (n: ValueScopeName) => Code | undefined
176 return this._reduceValues(
178 (name: ValueScopeName) => {
179 if (name.value === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
180 return name.value.code
187 private _reduceValues(
188 values: ScopeValues | ScopeValueSets,
189 valueCode: (n: ValueScopeName) => Code | undefined,
190 usedValues: UsedScopeValues = {},
191 getCode?: (n: ValueScopeName) => Code | undefined
194 for (const prefix in values) {
195 const vs = values[prefix]
197 const nameSet = (usedValues[prefix] = usedValues[prefix] || new Map())
198 vs.forEach((name: ValueScopeName) => {
199 if (nameSet.has(name)) return
200 nameSet.set(name, UsedValueState.Started)
201 let c = valueCode(name)
203 const def = this.opts.es5 ? varKinds.var : varKinds.const
204 code = _`${code}${def} ${name} = ${c};${this.opts._n}`
205 } else if ((c = getCode?.(name))) {
206 code = _`${code}${c}${this.opts._n}`
208 throw new ValueError(name)
210 nameSet.set(name, UsedValueState.Completed)