UNPKG

5.98 kBPlain TextView Raw
1import {_, nil, Code, Name} from "./code"
2
3interface NameGroup {
4 prefix: string
5 index: number
6}
7
8export 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`)
12}
13
14export type ValueReference = unknown // possibly make CodeGen parameterized type on this type
15
16class ValueError extends Error {
17 readonly value?: NameValue
18 constructor(name: ValueScopeName) {
19 super(`CodeGen: "code" for ${name} not defined`)
20 this.value = name.value
21 }
22}
23
24interface ScopeOptions {
25 prefixes?: Set<string>
26 parent?: Scope
27}
28
29interface ValueScopeOptions extends ScopeOptions {
30 scope: ScopeStore
31 es5?: boolean
32 lines?: boolean
33}
34
35export type ScopeStore = Record<string, ValueReference[] | undefined>
36
37type ScopeValues = {
38 [Prefix in string]?: Map<unknown, ValueScopeName>
39}
40
41export type ScopeValueSets = {
42 [Prefix in string]?: Set<ValueScopeName>
43}
44
45export enum UsedValueState {
46 Started,
47 Completed,
48}
49
50export type UsedScopeValues = {
51 [Prefix in string]?: Map<ValueScopeName, UsedValueState | undefined>
52}
53
54export const varKinds = {
55 const: new Name("const"),
56 let: new Name("let"),
57 var: new Name("var"),
58}
59
60export class Scope {
61 protected readonly _names: {[Prefix in string]?: NameGroup} = {}
62 protected readonly _prefixes?: Set<string>
63 protected readonly _parent?: Scope
64
65 constructor({prefixes, parent}: ScopeOptions = {}) {
66 this._prefixes = prefixes
67 this._parent = parent
68 }
69
70 toName(nameOrPrefix: Name | string): Name {
71 return nameOrPrefix instanceof Name ? nameOrPrefix : this.name(nameOrPrefix)
72 }
73
74 name(prefix: string): Name {
75 return new Name(this._newName(prefix))
76 }
77
78 protected _newName(prefix: string): string {
79 const ng = this._names[prefix] || this._nameGroup(prefix)
80 return `${prefix}${ng.index++}`
81 }
82
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`)
86 }
87 return (this._names[prefix] = {prefix, index: 0})
88 }
89}
90
91interface ScopePath {
92 property: string
93 itemIndex: number
94}
95
96export class ValueScopeName extends Name {
97 readonly prefix: string
98 value?: NameValue
99 scopePath?: Code
100
101 constructor(prefix: string, nameStr: string) {
102 super(nameStr)
103 this.prefix = prefix
104 }
105
106 setValue(value: NameValue, {property, itemIndex}: ScopePath): void {
107 this.value = value
108 this.scopePath = _`.${new Name(property)}[${itemIndex}]`
109 }
110}
111
112interface VSOptions extends ValueScopeOptions {
113 _n: Code
114}
115
116const line = _`\n`
117
118export class ValueScope extends Scope {
119 protected readonly _values: ScopeValues = {}
120 protected readonly _scope: ScopeStore
121 readonly opts: VSOptions
122
123 constructor(opts: ValueScopeOptions) {
124 super(opts)
125 this._scope = opts.scope
126 this.opts = {...opts, _n: opts.lines ? line : nil}
127 }
128
129 get(): ScopeStore {
130 return this._scope
131 }
132
133 name(prefix: string): ValueScopeName {
134 return new ValueScopeName(prefix, this._newName(prefix))
135 }
136
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]
143 if (vs) {
144 const _name = vs.get(valueKey)
145 if (_name) return _name
146 } else {
147 vs = this._values[prefix] = new Map()
148 }
149 vs.set(valueKey, name)
150
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})
155 return name
156 }
157
158 getValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined {
159 const vs = this._values[prefix]
160 if (!vs) return
161 return vs.get(keyOrRef)
162 }
163
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}`
168 })
169 }
170
171 scopeCode(
172 values: ScopeValues | ScopeValueSets = this._values,
173 usedValues?: UsedScopeValues,
174 getCode?: (n: ValueScopeName) => Code | undefined
175 ): Code {
176 return this._reduceValues(
177 values,
178 (name: ValueScopeName) => {
179 if (name.value === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
180 return name.value.code
181 },
182 usedValues,
183 getCode
184 )
185 }
186
187 private _reduceValues(
188 values: ScopeValues | ScopeValueSets,
189 valueCode: (n: ValueScopeName) => Code | undefined,
190 usedValues: UsedScopeValues = {},
191 getCode?: (n: ValueScopeName) => Code | undefined
192 ): Code {
193 let code: Code = nil
194 for (const prefix in values) {
195 const vs = values[prefix]
196 if (!vs) continue
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)
202 if (c) {
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}`
207 } else {
208 throw new ValueError(name)
209 }
210 nameSet.set(name, UsedValueState.Completed)
211 })
212 }
213 return code
214 }
215}