| 1 | import * as Effect from "../../Effect.ts" |
| 2 | import type { CompiledDecoder, Decode, Is, Make } from "../../schema/SchemaCompiler.ts" |
| 3 | import type * as SchemaAST from "../../SchemaAST.ts" |
| 4 | import type { Parser } from "../../SchemaParser.ts" |
| 5 | import * as Interpreter from "./interpreter.ts" |
| 6 | import * as InternalParser from "./parser.ts" |
| 7 | |
| 8 | /** @internal */ |
| 9 | export const invalid = Symbol() |
| 10 | |
| 11 | /** @internal */ |
| 12 | export type Resolve = (ast: SchemaAST.AST) => Entry |
| 13 | |
| 14 | /** @internal */ |
| 15 | export type DecoderSource = Partial<CompiledDecoder> |
| 16 | |
| 17 | /** @internal */ |
| 18 | export type DecoderOperation = keyof CompiledDecoder |
| 19 | |
| 20 | /** @internal */ |
| 21 | export type Compile = <K extends DecoderOperation>( |
| 22 | ast: SchemaAST.AST, |
| 23 | resolve: Resolve, |
| 24 | operation: K |
| 25 | ) => CompiledDecoder[K] | undefined |
| 26 | |
| 27 | /** @internal */ |
| 28 | export type CompileSource = (ast: SchemaAST.AST, resolve: Resolve) => DecoderSource | undefined |
| 29 | |
| 30 | /** @internal */ |
| 31 | export type Compiled = DecoderSource | Compile |
| 32 | |
| 33 | const cache = new WeakMap<SchemaAST.AST, Entry>() |
| 34 | let compiler: CompileSource | undefined |
| 35 | |
| 36 | /** @internal */ |
| 37 | export let compilerAdaptersEnabled = false |
| 38 | |
| 39 | function activateCompilerAdapters(): void { |
| 40 | compilerAdaptersEnabled = true |
| 41 | } |
| 42 | |
| 43 | const decodeChild = (ast: SchemaAST.AST): Parser => |
| 44 | compilerAdaptersEnabled |
| 45 | ? lazyParser(resolve, ast, "parser") |
| 46 | : resolve(ast).parser |
| 47 | const makeChild = (ast: SchemaAST.AST): Parser => |
| 48 | compilerAdaptersEnabled |
| 49 | ? lazyParser(resolve, ast, "makeEffect") |
| 50 | : resolve(ast).makeEffect |
| 51 | const makeField = (ast: SchemaAST.AST): Parser => Interpreter.compileField(ast, makeChild) |
| 52 | |
| 53 | /** @internal */ |
| 54 | export interface Entry { |
| 55 | readonly ast: SchemaAST.AST |
| 56 | readonly compiled?: Compiled | undefined |
| 57 | readonly resolve?: Resolve | undefined |
| 58 | readonly is?: Is | undefined |
| 59 | readonly decode?: Decode | undefined |
| 60 | readonly make?: Make | undefined |
| 61 | readonly decodeEffect: Parser |
| 62 | readonly parser: Parser |
| 63 | readonly makeEffect: Parser |
| 64 | } |
| 65 | |
| 66 | class InterpretedEntry implements Entry { |
| 67 | readonly ast: SchemaAST.AST |
| 68 | declare private cachedDecodeEffect: Parser | undefined |
| 69 | declare private cachedMakeEffect: Parser | undefined |
| 70 | |
| 71 | constructor(ast: SchemaAST.AST) { |
| 72 | this.ast = ast |
| 73 | } |
| 74 | |
| 75 | get decodeEffect(): Parser { |
| 76 | return this.cachedDecodeEffect ??= Interpreter.compile(this.ast, decodeChild) |
| 77 | } |
| 78 | |
| 79 | get parser(): Parser { |
| 80 | return this.decodeEffect |
| 81 | } |
| 82 | |
| 83 | get makeEffect(): Parser { |
| 84 | return this.cachedMakeEffect ??= Interpreter.compile(this.ast, makeChild, makeField) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | class CompilerEntry extends InterpretedEntry { |
| 89 | readonly compiled: Compiled | undefined |
| 90 | readonly resolve: Resolve |
| 91 | |
| 92 | constructor(ast: SchemaAST.AST, compiled: Compiled | undefined, resolve: Resolve) { |
| 93 | super(ast) |
| 94 | this.compiled = compiled |
| 95 | this.resolve = resolve |
| 96 | } |
| 97 | |
| 98 | private save<K extends keyof Entry>(key: K, value: Entry[K]): Entry[K] { |
| 99 | Object.defineProperty(this, key, { value }) |
| 100 | return value |
| 101 | } |
| 102 | |
| 103 | private operation<K extends DecoderOperation>(key: K): CompiledDecoder[K] | undefined { |
| 104 | const compiled = this.compiled |
| 105 | return typeof compiled === "function" ? compiled(this.ast, this.resolve, key) : compiled?.[key] |
| 106 | } |
| 107 | |
| 108 | get is(): Is | undefined { |
| 109 | return this.save("is", this.operation("is")) |
| 110 | } |
| 111 | |
| 112 | get decode(): Decode | undefined { |
| 113 | return this.save("decode", this.operation("decode")) |
| 114 | } |
| 115 | |
| 116 | get make(): Make | undefined { |
| 117 | return this.save("make", this.operation("make")) |
| 118 | } |
| 119 | |
| 120 | override get decodeEffect(): Parser { |
| 121 | return this.save( |
| 122 | "decodeEffect", |
| 123 | this.operation("decodeEffect") ?? Interpreter.compile(this.ast, (ast) => lazyParser(this.resolve, ast, "parser")) |
| 124 | ) |
| 125 | } |
| 126 | |
| 127 | override get parser(): Parser { |
| 128 | const decode = this.decode |
| 129 | return decode === undefined |
| 130 | ? this.decodeEffect |
| 131 | : this.save("parser", withDecode(decode, () => this.decodeEffect)) |
| 132 | } |
| 133 | |
| 134 | override get makeEffect(): Parser { |
| 135 | const makeEffect = this.operation("makeEffect") |
| 136 | if (makeEffect !== undefined) return this.save("makeEffect", makeEffect) |
| 137 | const child = (ast: SchemaAST.AST): Parser => lazyParser(this.resolve, ast, "makeEffect") |
| 138 | return this.save( |
| 139 | "makeEffect", |
| 140 | Interpreter.compile( |
| 141 | this.ast, |
| 142 | child, |
| 143 | (ast) => Interpreter.compileField(ast, child) |
| 144 | ) |
| 145 | ) |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /** @internal */ |
| 150 | export function withDecode(fastDecode: Decode, decodeEffect: () => Parser): Parser { |
| 151 | let detailed: Parser | undefined |
| 152 | return (input, options) => { |
| 153 | if (input !== InternalParser.missing) { |
| 154 | try { |
| 155 | const value = fastDecode(input, options) |
| 156 | if (value !== invalid) return value === input ? InternalParser.sameExit : InternalParser.succeed(value) |
| 157 | } catch (error) { |
| 158 | return Effect.die(error) |
| 159 | } |
| 160 | } |
| 161 | return (detailed ??= decodeEffect())(input, options) |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | /** @internal */ |
| 166 | export function lazyParser( |
| 167 | resolve: Resolve, |
| 168 | ast: SchemaAST.AST, |
| 169 | operation: "parser" | "decodeEffect" | "makeEffect" |
| 170 | ): Parser { |
| 171 | const entry = resolve(ast) |
| 172 | if (entry.compiled === undefined || Object.hasOwn(entry, operation)) { |
| 173 | return entry[operation] |
| 174 | } |
| 175 | let parser: Parser | undefined |
| 176 | return (input, options) => (parser ??= entry[operation])(input, options) |
| 177 | } |
| 178 | |
| 179 | /** @internal */ |
| 180 | export function resolve(ast: SchemaAST.AST): Entry { |
| 181 | const cached = cache.get(ast) |
| 182 | if (cached !== undefined) return cached |
| 183 | const entry = compiler === undefined |
| 184 | ? new InterpretedEntry(ast) |
| 185 | : new CompilerEntry(ast, compiler(ast, resolve), resolve) |
| 186 | cache.set(ast, entry) |
| 187 | return entry |
| 188 | } |
| 189 | |
| 190 | /** @internal */ |
| 191 | export function set(ast: SchemaAST.AST, decoder: DecoderSource | undefined, resolveChild: Resolve = resolve): Entry { |
| 192 | if (decoder !== undefined) activateCompilerAdapters() |
| 193 | const entry = new CompilerEntry(ast, decoder, resolveChild) |
| 194 | cache.set(ast, entry) |
| 195 | return entry |
| 196 | } |
| 197 | |
| 198 | /** @internal */ |
| 199 | export function setCompiler( |
| 200 | ast: SchemaAST.AST, |
| 201 | compile: Compile, |
| 202 | resolveChild: Resolve = resolve |
| 203 | ): Entry { |
| 204 | activateCompilerAdapters() |
| 205 | const entry = new CompilerEntry(ast, compile, resolveChild) |
| 206 | cache.set(ast, entry) |
| 207 | return entry |
| 208 | } |
| 209 | |
| 210 | /** @internal */ |
| 211 | export function install(compile: CompileSource): void { |
| 212 | activateCompilerAdapters() |
| 213 | compiler = compile |
| 214 | } |
| 215 | |
| 216 | /** @internal */ |
| 217 | export function enable(ast: SchemaAST.AST, compile: CompileSource): void { |
| 218 | activateCompilerAdapters() |
| 219 | const scoped: Resolve = (child) => { |
| 220 | const cached = cache.get(child) |
| 221 | return cached !== undefined && (cached.compiled !== undefined || cached.resolve === scoped) |
| 222 | ? cached |
| 223 | : set(child, compile(child, scoped), scoped) |
| 224 | } |
| 225 | const decoder = compile(ast, scoped) |
| 226 | const cached = cache.get(ast) |
| 227 | if (decoder !== undefined || cached?.compiled === undefined) { |
| 228 | set(ast, decoder, scoped) |
| 229 | } |
| 230 | } |