Spaces:
Runtime error
Runtime error
| import {_, nil, Code, Name} from "./code" | |
| interface NameGroup { | |
| prefix: string | |
| index: number | |
| } | |
| export interface NameValue { | |
| ref: ValueReference // this is the reference to any value that can be referred to from generated code via `globals` var in the closure | |
| key?: unknown // any key to identify a global to avoid duplicates, if not passed ref is used | |
| 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`) | |
| } | |
| export type ValueReference = unknown // possibly make CodeGen parameterized type on this type | |
| class ValueError extends Error { | |
| readonly value?: NameValue | |
| constructor(name: ValueScopeName) { | |
| super(`CodeGen: "code" for ${name} not defined`) | |
| this.value = name.value | |
| } | |
| } | |
| interface ScopeOptions { | |
| prefixes?: Set<string> | |
| parent?: Scope | |
| } | |
| interface ValueScopeOptions extends ScopeOptions { | |
| scope: ScopeStore | |
| es5?: boolean | |
| lines?: boolean | |
| } | |
| export type ScopeStore = Record<string, ValueReference[] | undefined> | |
| type ScopeValues = { | |
| [Prefix in string]?: Map<unknown, ValueScopeName> | |
| } | |
| export type ScopeValueSets = { | |
| [Prefix in string]?: Set<ValueScopeName> | |
| } | |
| export enum UsedValueState { | |
| Started, | |
| Completed, | |
| } | |
| export type UsedScopeValues = { | |
| [Prefix in string]?: Map<ValueScopeName, UsedValueState | undefined> | |
| } | |
| export const varKinds = { | |
| const: new Name("const"), | |
| let: new Name("let"), | |
| var: new Name("var"), | |
| } | |
| export class Scope { | |
| protected readonly _names: {[Prefix in string]?: NameGroup} = {} | |
| protected readonly _prefixes?: Set<string> | |
| protected readonly _parent?: Scope | |
| constructor({prefixes, parent}: ScopeOptions = {}) { | |
| this._prefixes = prefixes | |
| this._parent = parent | |
| } | |
| toName(nameOrPrefix: Name | string): Name { | |
| return nameOrPrefix instanceof Name ? nameOrPrefix : this.name(nameOrPrefix) | |
| } | |
| name(prefix: string): Name { | |
| return new Name(this._newName(prefix)) | |
| } | |
| protected _newName(prefix: string): string { | |
| const ng = this._names[prefix] || this._nameGroup(prefix) | |
| return `${prefix}${ng.index++}` | |
| } | |
| private _nameGroup(prefix: string): NameGroup { | |
| if (this._parent?._prefixes?.has(prefix) || (this._prefixes && !this._prefixes.has(prefix))) { | |
| throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`) | |
| } | |
| return (this._names[prefix] = {prefix, index: 0}) | |
| } | |
| } | |
| interface ScopePath { | |
| property: string | |
| itemIndex: number | |
| } | |
| export class ValueScopeName extends Name { | |
| readonly prefix: string | |
| value?: NameValue | |
| scopePath?: Code | |
| constructor(prefix: string, nameStr: string) { | |
| super(nameStr) | |
| this.prefix = prefix | |
| } | |
| setValue(value: NameValue, {property, itemIndex}: ScopePath): void { | |
| this.value = value | |
| this.scopePath = _`.${new Name(property)}[${itemIndex}]` | |
| } | |
| } | |
| interface VSOptions extends ValueScopeOptions { | |
| _n: Code | |
| } | |
| const line = _`\n` | |
| export class ValueScope extends Scope { | |
| protected readonly _values: ScopeValues = {} | |
| protected readonly _scope: ScopeStore | |
| readonly opts: VSOptions | |
| constructor(opts: ValueScopeOptions) { | |
| super(opts) | |
| this._scope = opts.scope | |
| this.opts = {...opts, _n: opts.lines ? line : nil} | |
| } | |
| get(): ScopeStore { | |
| return this._scope | |
| } | |
| name(prefix: string): ValueScopeName { | |
| return new ValueScopeName(prefix, this._newName(prefix)) | |
| } | |
| value(nameOrPrefix: ValueScopeName | string, value: NameValue): ValueScopeName { | |
| if (value.ref === undefined) throw new Error("CodeGen: ref must be passed in value") | |
| const name = this.toName(nameOrPrefix) as ValueScopeName | |
| const {prefix} = name | |
| const valueKey = value.key ?? value.ref | |
| let vs = this._values[prefix] | |
| if (vs) { | |
| const _name = vs.get(valueKey) | |
| if (_name) return _name | |
| } else { | |
| vs = this._values[prefix] = new Map() | |
| } | |
| vs.set(valueKey, name) | |
| const s = this._scope[prefix] || (this._scope[prefix] = []) | |
| const itemIndex = s.length | |
| s[itemIndex] = value.ref | |
| name.setValue(value, {property: prefix, itemIndex}) | |
| return name | |
| } | |
| getValue(prefix: string, keyOrRef: unknown): ValueScopeName | undefined { | |
| const vs = this._values[prefix] | |
| if (!vs) return | |
| return vs.get(keyOrRef) | |
| } | |
| scopeRefs(scopeName: Name, values: ScopeValues | ScopeValueSets = this._values): Code { | |
| return this._reduceValues(values, (name: ValueScopeName) => { | |
| if (name.scopePath === undefined) throw new Error(`CodeGen: name "${name}" has no value`) | |
| return _`${scopeName}${name.scopePath}` | |
| }) | |
| } | |
| scopeCode( | |
| values: ScopeValues | ScopeValueSets = this._values, | |
| usedValues?: UsedScopeValues, | |
| getCode?: (n: ValueScopeName) => Code | undefined | |
| ): Code { | |
| return this._reduceValues( | |
| values, | |
| (name: ValueScopeName) => { | |
| if (name.value === undefined) throw new Error(`CodeGen: name "${name}" has no value`) | |
| return name.value.code | |
| }, | |
| usedValues, | |
| getCode | |
| ) | |
| } | |
| private _reduceValues( | |
| values: ScopeValues | ScopeValueSets, | |
| valueCode: (n: ValueScopeName) => Code | undefined, | |
| usedValues: UsedScopeValues = {}, | |
| getCode?: (n: ValueScopeName) => Code | undefined | |
| ): Code { | |
| let code: Code = nil | |
| for (const prefix in values) { | |
| const vs = values[prefix] | |
| if (!vs) continue | |
| const nameSet = (usedValues[prefix] = usedValues[prefix] || new Map()) | |
| vs.forEach((name: ValueScopeName) => { | |
| if (nameSet.has(name)) return | |
| nameSet.set(name, UsedValueState.Started) | |
| let c = valueCode(name) | |
| if (c) { | |
| const def = this.opts.es5 ? varKinds.var : varKinds.const | |
| code = _`${code}${def} ${name} = ${c};${this.opts._n}` | |
| } else if ((c = getCode?.(name))) { | |
| code = _`${code}${c}${this.opts._n}` | |
| } else { | |
| throw new ValueError(name) | |
| } | |
| nameSet.set(name, UsedValueState.Completed) | |
| }) | |
| } | |
| return code | |
| } | |
| } | |