scope.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import {_, nil, Code, Name} from "./code"
  2. interface NameGroup {
  3. prefix: string
  4. index: number
  5. }
  6. export interface NameValue {
  7. ref: ValueReference // this is the reference to any value that can be referred to from generated code via `globals` var in the closure
  8. key?: unknown // any key to identify a global to avoid duplicates, if not passed ref is used
  9. 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`)
  10. }
  11. export type ValueReference = unknown // possibly make CodeGen parameterized type on this type
  12. class ValueError extends Error {
  13. readonly value?: NameValue
  14. constructor(name: ValueScopeName) {
  15. super(`CodeGen: "code" for ${name} not defined`)
  16. this.value = name.value
  17. }
  18. }
  19. interface ScopeOptions {
  20. prefixes?: Set<string>
  21. parent?: Scope
  22. }
  23. interface ValueScopeOptions extends ScopeOptions {
  24. scope: ScopeStore
  25. es5?: boolean
  26. lines?: boolean
  27. }
  28. export type ScopeStore = Record<string, ValueReference[] | undefined>
  29. type ScopeValues = {
  30. [Prefix in string]?: Map<unknown, ValueScopeName>
  31. }
  32. export type ScopeValueSets = {
  33. [Prefix in string]?: Set<ValueScopeName>
  34. }
  35. export enum UsedValueState {
  36. Started,
  37. Completed,
  38. }
  39. export type UsedScopeValues = {
  40. [Prefix in string]?: Map<ValueScopeName, UsedValueState | undefined>
  41. }
  42. export const varKinds = {
  43. const: new Name("const"),
  44. let: new Name("let"),
  45. var: new Name("var"),
  46. }
  47. export class Scope {
  48. protected readonly _names: {[Prefix in string]?: NameGroup} = {}
  49. protected readonly _prefixes?: Set<string>
  50. protected readonly _parent?: Scope
  51. constructor({prefixes, parent}: ScopeOptions = {}) {
  52. this._prefixes = prefixes
  53. this._parent = parent
  54. }
  55. toName(nameOrPrefix: Name | string): Name {
  56. return nameOrPrefix instanceof Name ? nameOrPrefix : this.name(nameOrPrefix)
  57. }
  58. name(prefix: string): Name {
  59. return new Name(this._newName(prefix))
  60. }
  61. protected _newName(prefix: string): string {
  62. const ng = this._names[prefix] || this._nameGroup(prefix)
  63. return `${prefix}${ng.index++}`
  64. }
  65. private _nameGroup(prefix: string): NameGroup {
  66. if (this._parent?._prefixes?.has(prefix) || (this._prefixes && !this._prefixes.has(prefix))) {
  67. throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`)
  68. }
  69. return (this._names[prefix] = {prefix, index: 0})
  70. }
  71. }
  72. interface ScopePath {
  73. property: string
  74. itemIndex: number
  75. }
  76. export class ValueScopeName extends Name {
  77. readonly prefix: string
  78. value?: NameValue
  79. scopePath?: Code
  80. constructor(prefix: string, nameStr: string) {
  81. super(nameStr)
  82. this.prefix = prefix
  83. }
  84. setValue(value: NameValue, {property, itemIndex}: ScopePath): void {
  85. this.value = value
  86. this.scopePath = _`.${new Name(property)}[${itemIndex}]`
  87. }
  88. }
  89. interface VSOptions extends ValueScopeOptions {
  90. _n: Code
  91. }
  92. const line = _`\n`
  93. export class ValueScope extends Scope {
  94. protected readonly _values: ScopeValues = {}
  95. protected readonly _scope: ScopeStore
  96. readonly opts: VSOptions
  97. constructor(opts: ValueScopeOptions) {
  98. super(opts)
  99. this._scope = opts.scope
  100. this.opts = {...opts, _n: opts.lines ? line : nil}
  101. }
  102. get(): ScopeStore {
  103. return this._scope
  104. }
  105. name(prefix: string): ValueScopeName {
  106. return new ValueScopeName(prefix, this._newName(prefix))
  107. }
  108. value(nameOrPrefix: ValueScopeName | string, value: NameValue): ValueScopeName {
  109. if (value.ref === undefined) throw new Error("CodeGen: ref must be passed in value")
  110. const name = this.toName(nameOrPrefix) as ValueScopeName
  111. const {prefix} = name
  112. const valueKey = value.key ?? value.ref
  113. let vs = this._values[prefix]
  114. if (vs) {
  115. const _name = vs.get(valueKey)
  116. if (_name) return _name
  117. } else {
  118. vs = this._values[prefix] = new Map()
  119. }
  120. vs.set(valueKey, name)
  121. const s = this._scope[prefix] || (this._scope[prefix] = [])
  122. const itemIndex = s.length
  123. s[itemIndex] = value.ref
  124. name.setValue(value, {property: prefix, itemIndex})
  125. return name
  126. }
  127. getValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined {
  128. const vs = this._values[prefix]
  129. if (!vs) return
  130. return vs.get(keyOrRef)
  131. }
  132. scopeRefs(scopeName: Name, values: ScopeValues | ScopeValueSets = this._values): Code {
  133. return this._reduceValues(values, (name: ValueScopeName) => {
  134. if (name.scopePath === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
  135. return _`${scopeName}${name.scopePath}`
  136. })
  137. }
  138. scopeCode(
  139. values: ScopeValues | ScopeValueSets = this._values,
  140. usedValues?: UsedScopeValues,
  141. getCode?: (n: ValueScopeName) => Code | undefined
  142. ): Code {
  143. return this._reduceValues(
  144. values,
  145. (name: ValueScopeName) => {
  146. if (name.value === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
  147. return name.value.code
  148. },
  149. usedValues,
  150. getCode
  151. )
  152. }
  153. private _reduceValues(
  154. values: ScopeValues | ScopeValueSets,
  155. valueCode: (n: ValueScopeName) => Code | undefined,
  156. usedValues: UsedScopeValues = {},
  157. getCode?: (n: ValueScopeName) => Code | undefined
  158. ): Code {
  159. let code: Code = nil
  160. for (const prefix in values) {
  161. const vs = values[prefix]
  162. if (!vs) continue
  163. const nameSet = (usedValues[prefix] = usedValues[prefix] || new Map())
  164. vs.forEach((name: ValueScopeName) => {
  165. if (nameSet.has(name)) return
  166. nameSet.set(name, UsedValueState.Started)
  167. let c = valueCode(name)
  168. if (c) {
  169. const def = this.opts.es5 ? varKinds.var : varKinds.const
  170. code = _`${code}${def} ${name} = ${c};${this.opts._n}`
  171. } else if ((c = getCode?.(name))) {
  172. code = _`${code}${c}${this.opts._n}`
  173. } else {
  174. throw new ValueError(name)
  175. }
  176. nameSet.set(name, UsedValueState.Completed)
  177. })
  178. }
  179. return code
  180. }
  181. }