scope.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 const varKinds = {
  36. const: new Name("const"),
  37. let: new Name("let"),
  38. var: new Name("var"),
  39. }
  40. export class Scope {
  41. protected readonly _names: {[Prefix in string]?: NameGroup} = {}
  42. protected readonly _prefixes?: Set<string>
  43. protected readonly _parent?: Scope
  44. constructor({prefixes, parent}: ScopeOptions = {}) {
  45. this._prefixes = prefixes
  46. this._parent = parent
  47. }
  48. toName(nameOrPrefix: Name | string): Name {
  49. return nameOrPrefix instanceof Name ? nameOrPrefix : this.name(nameOrPrefix)
  50. }
  51. name(prefix: string): Name {
  52. return new Name(this._newName(prefix))
  53. }
  54. protected _newName(prefix: string): string {
  55. const ng = this._names[prefix] || this._nameGroup(prefix)
  56. return `${prefix}${ng.index++}`
  57. }
  58. private _nameGroup(prefix: string): NameGroup {
  59. if (this._parent?._prefixes?.has(prefix) || (this._prefixes && !this._prefixes.has(prefix))) {
  60. throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`)
  61. }
  62. return (this._names[prefix] = {prefix, index: 0})
  63. }
  64. }
  65. interface ScopePath {
  66. property: string
  67. itemIndex: number
  68. }
  69. export class ValueScopeName extends Name {
  70. readonly prefix: string
  71. value?: NameValue
  72. scopePath?: Code
  73. constructor(prefix: string, nameStr: string) {
  74. super(nameStr)
  75. this.prefix = prefix
  76. }
  77. setValue(value: NameValue, {property, itemIndex}: ScopePath): void {
  78. this.value = value
  79. this.scopePath = _`.${new Name(property)}[${itemIndex}]`
  80. }
  81. }
  82. interface VSOptions extends ValueScopeOptions {
  83. _n: Code
  84. }
  85. const line = _`\n`
  86. export class ValueScope extends Scope {
  87. protected readonly _values: ScopeValues = {}
  88. protected readonly _scope: ScopeStore
  89. readonly opts: VSOptions
  90. constructor(opts: ValueScopeOptions) {
  91. super(opts)
  92. this._scope = opts.scope
  93. this.opts = {...opts, _n: opts.lines ? line : nil}
  94. }
  95. get(): ScopeStore {
  96. return this._scope
  97. }
  98. name(prefix: string): ValueScopeName {
  99. return new ValueScopeName(prefix, this._newName(prefix))
  100. }
  101. value(nameOrPrefix: ValueScopeName | string, value: NameValue): ValueScopeName {
  102. if (value.ref === undefined) throw new Error("CodeGen: ref must be passed in value")
  103. const name = this.toName(nameOrPrefix) as ValueScopeName
  104. const {prefix} = name
  105. const valueKey = value.key ?? value.ref
  106. let vs = this._values[prefix]
  107. if (vs) {
  108. const _name = vs.get(valueKey)
  109. if (_name) return _name
  110. } else {
  111. vs = this._values[prefix] = new Map()
  112. }
  113. vs.set(valueKey, name)
  114. const s = this._scope[prefix] || (this._scope[prefix] = [])
  115. const itemIndex = s.length
  116. s[itemIndex] = value.ref
  117. name.setValue(value, {property: prefix, itemIndex})
  118. return name
  119. }
  120. getValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined {
  121. const vs = this._values[prefix]
  122. if (!vs) return
  123. return vs.get(keyOrRef)
  124. }
  125. scopeRefs(scopeName: Name, values: ScopeValues | ScopeValueSets = this._values): Code {
  126. return this._reduceValues(values, (name: ValueScopeName) => {
  127. if (name.scopePath === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
  128. return _`${scopeName}${name.scopePath}`
  129. })
  130. }
  131. scopeCode(
  132. values: ScopeValues | ScopeValueSets = this._values,
  133. usedValues?: ScopeValueSets,
  134. getCode?: (n: ValueScopeName) => Code | undefined
  135. ): Code {
  136. return this._reduceValues(
  137. values,
  138. (name: ValueScopeName) => {
  139. if (name.value === undefined) throw new Error(`CodeGen: name "${name}" has no value`)
  140. return name.value.code
  141. },
  142. usedValues,
  143. getCode
  144. )
  145. }
  146. private _reduceValues(
  147. values: ScopeValues | ScopeValueSets,
  148. valueCode: (n: ValueScopeName) => Code | undefined,
  149. usedValues: ScopeValueSets = {},
  150. getCode?: (n: ValueScopeName) => Code | undefined
  151. ): Code {
  152. let code: Code = nil
  153. for (const prefix in values) {
  154. const vs = values[prefix]
  155. if (!vs) continue
  156. const nameSet = (usedValues[prefix] = usedValues[prefix] || new Set())
  157. vs.forEach((name: ValueScopeName) => {
  158. if (nameSet.has(name)) return
  159. nameSet.add(name)
  160. let c = valueCode(name)
  161. if (c) {
  162. const def = this.opts.es5 ? varKinds.var : varKinds.const
  163. code = _`${code}${def} ${name} = ${c};${this.opts._n}`
  164. } else if ((c = getCode?.(name))) {
  165. code = _`${code}${c}${this.opts._n}`
  166. } else {
  167. throw new ValueError(name)
  168. }
  169. })
  170. }
  171. return code
  172. }
  173. }