(cat: ParamMap, rootName?: string)
| 436 | } |
| 437 | |
| 438 | export function createStreamParser(cat: ParamMap, rootName?: string): StreamParser { |
| 439 | let buf = ""; // raw accumulated input (kept for set() diffing) |
| 440 | // Preprocessed view of `buf` (fences + comments stripped, same as parse()'s |
| 441 | // preprocess). The completed-statement scan runs over THIS, never the raw |
| 442 | // buffer — so leading markdown prose / ```fences``` (e.g. an apostrophe in |
| 443 | // "You're …" before the program) can't corrupt statement-boundary detection. |
| 444 | let cleaned = ""; |
| 445 | let completedEnd = 0; // watermark: how far into `cleaned` is already completed |
| 446 | const completedStmtMap = new Map<string, Statement>(); |
| 447 | |
| 448 | let completedCount = 0; |
| 449 | let firstId = ""; |
| 450 | |
| 451 | function addStmt(text: string) { |
| 452 | // `text` is sliced from `cleaned`, so it's already fence/comment-free. |
| 453 | const t = text.trim(); |
| 454 | if (!t) return; |
| 455 | for (const s of split(tokenize(t))) { |
| 456 | const expr = parseExpression(s.tokens); |
| 457 | const stmt = classifyStatement(s, expr); |
| 458 | completedStmtMap.set(s.id, stmt); |
| 459 | completedCount++; |
| 460 | if (!firstId) firstId = s.id; |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | // Recompute `cleaned` from `buf`. If the already-completed prefix is no longer |
| 465 | // a prefix of the new cleaned text (e.g. an opening ```fence``` just appeared |
| 466 | // and shifted the stripped output), the watermark + cache are stale, so reset |
| 467 | // and re-scan. When the prefix is stable (the common streaming case) the cache |
| 468 | // is kept, so a partial trailing statement never blanks already-completed ones. |
| 469 | function refreshCleaned() { |
| 470 | const next = preprocess(buf); |
| 471 | if (!next.startsWith(cleaned.slice(0, completedEnd))) { |
| 472 | completedEnd = 0; |
| 473 | completedStmtMap.clear(); |
| 474 | completedCount = 0; |
| 475 | firstId = ""; |
| 476 | } |
| 477 | cleaned = next; |
| 478 | } |
| 479 | |
| 480 | function scanNewCompleted(): number { |
| 481 | let depth = 0, |
| 482 | ternaryDepth = 0, |
| 483 | inStr: false | '"' | "'" = false, |
| 484 | esc = false; |
| 485 | let stmtStart = completedEnd; |
| 486 | |
| 487 | for (let i = completedEnd; i < cleaned.length; i++) { |
| 488 | const c = cleaned[i]; |
| 489 | if (esc) { |
| 490 | esc = false; |
| 491 | continue; |
| 492 | } |
| 493 | if (c === "\\" && inStr) { |
| 494 | esc = true; |
| 495 | continue; |
no outgoing calls
no test coverage detected