| 209 | } |
| 210 | |
| 211 | export function hoist<A, E, T extends Tag, const Items extends Replacements = readonly []>( |
| 212 | root: Node<A, E, any>, |
| 213 | tag: T, |
| 214 | replacements?: ValidReplacements<Items>, |
| 215 | ): { |
| 216 | readonly node: Node<A, E> |
| 217 | readonly hoisted: Node<unknown, E> |
| 218 | } { |
| 219 | const hoisted = new Map<string, AnyNode>() |
| 220 | const replacementMap = replacementMapFrom(replacements) |
| 221 | |
| 222 | const node = walk<AnyNode>( |
| 223 | root, |
| 224 | (node, context) => { |
| 225 | if (node.kind === "group") { |
| 226 | return { ...node, dependencies: node.dependencies.map(context.visit) } |
| 227 | } |
| 228 | if (node.tag === tag) { |
| 229 | const existing = hoisted.get(node.name) |
| 230 | if (existing && existing !== node) { |
| 231 | throw new Error(`Tag ${tag} has conflicting implementations for ${node.name}`) |
| 232 | } |
| 233 | hoisted.set(node.name, node) |
| 234 | return group([]) |
| 235 | } |
| 236 | if (node.kind === "unbound") { |
| 237 | return node |
| 238 | } |
| 239 | return { ...node, dependencies: node.dependencies.map(context.visit) } |
| 240 | }, |
| 241 | { resolve: (node) => replacementMap.get(node.name) ?? node }, |
| 242 | ) |
| 243 | |
| 244 | return { |
| 245 | node: node as Node<A, E>, |
| 246 | hoisted: group(Array.from(hoisted.values())) as Node<unknown, E>, |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | export function compile<A, E, const Items extends Replacements = readonly []>( |
| 251 | root: Node<A, E, any>, |