.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / table / node_modules / ajv / lib / compile / codegen / scope.ts
1 import {_, nil, Code, Name} from "./code"
2
3 interface NameGroup {
4   prefix: string
5   index: number
6 }
7
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`)
12 }
13
14 export type ValueReference = unknown // possibly make CodeGen parameterized type on this type
15
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
21   }
22 }
23
24 interface ScopeOptions {
25   prefixes?: Set<string>
26   parent?: Scope
27 }
28
29 interface ValueScopeOptions extends ScopeOptions {
30   scope: ScopeStore
31   es5?: boolean
32   lines?: boolean
33 }
34
35 export type ScopeStore = Record<string, ValueReference[] | undefined>
36
37 type ScopeValues = {
38   [Prefix in string]?: Map<unknown, ValueScopeName>
39 }
40
41 export type ScopeValueSets = {
42   [Prefix in string]?: Set<ValueScopeName>
43 }
44
45 export enum UsedValueState {
46   Started,
47   Completed,
48 }
49
50 export type UsedScopeValues = {
51   [Prefix in string]?: Map<ValueScopeName, UsedValueState | undefined>
52 }
53
54 export const varKinds = {
55   const: new Name("const"),
56   let: new Name("let"),
57   var: new Name("var"),
58 }
59
60 export 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
91 interface ScopePath {
92   property: string
93   itemIndex: number
94 }
95
96 export 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
112 interface VSOptions extends ValueScopeOptions {
113   _n: Code
114 }
115
116 const line = _`\n`
117
118 export 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 }