| 404 | * @returns ParseResult with root ElementNode (or null) and metadata |
| 405 | */ |
| 406 | export function parse(input: string, cat: ParamMap, rootName?: string): ParseResult { |
| 407 | const trimmed = preprocess(input); |
| 408 | if (!trimmed) return emptyResult(); |
| 409 | |
| 410 | const { text, wasIncomplete } = autoClose(trimmed); |
| 411 | const stmts = split(tokenize(text)); |
| 412 | if (!stmts.length) return emptyResult(wasIncomplete); |
| 413 | |
| 414 | const stmtMap = new Map<string, Statement>(); |
| 415 | let firstId = ""; |
| 416 | for (const s of stmts) { |
| 417 | const expr = parseExpression(s.tokens); |
| 418 | const stmt = classifyStatement(s, expr); |
| 419 | stmtMap.set(s.id, stmt); |
| 420 | if (!firstId) firstId = s.id; |
| 421 | } |
| 422 | // Derive from map to deduplicate — Map.set overwrites duplicates |
| 423 | const typedStmts = [...stmtMap.values()]; |
| 424 | |
| 425 | return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName); |
| 426 | } |
| 427 | |
| 428 | export interface StreamParser { |
| 429 | /** Feed the next SSE/stream chunk and get the latest ParseResult. */ |