perf(inline): replace PEG inline grammar with a hand-written linear scanner
The interpreted PEG grammar paid ~20 recursive peg--parse calls per plain character, which dominated render cost. Replace parse-inline with a single linear scan: each construct (code, emphasis, image, link, escape, text run) is dispatched by its unique starter character and consumed in one pass. The scanner emits the exact same capture list the grammar produced, verified byte-identical against golden snapshots of the original parser across the edge-case corpus (unclosed/empty/nested emphasis, empty backticks, links without closing paren, image vs bang, escapes at EOL).
Native (cumulative vs original baseline): mixed 2.6KB 161 -> 6.96 ms (23x) large 26KB 1797 -> 60.0 ms (30x) parse-inline plain text 70 -> 0.22 us/char (~320x)
parse-blocks (still PEG-based line classification) is now the dominant phase.
src/sigil/markdown.sgl | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------
1 file changed, 169 insertions(+), 93 deletions(-)src/sigil/markdown.sglmodified
(para-loop (cdr rest) (cons (car rest) para-lines))))))))))) ;; ========== Inline PEG Grammar ========== ;; cmt handlers produce SXML elements from captures (define (md-code . captures) (list 'code (car captures))) (define (md-strong . captures) (cons 'strong (parse-inline (car captures)))) (define (md-em . captures) (cons 'em (parse-inline (car captures)))) (define (md-link . captures) (list 'a (list '@ (list 'href (cadr captures))) (car captures))) (define (md-image . captures) (list 'img (list '@ (list 'src (cadr captures)) (list 'alt (car captures))))) (define (md-escape . captures) (car captures)) ;; Predicate for a plain-text character: one that cannot begin any ;; inline construct. The special starters are backtick (code), '*' and ;; '_' (emphasis), '[' (link), and '\\' (escape). A bare '!' is plain ;; text; only " (image (cmt (seq "![" (<- (* (seq (! "]") any-char))) "](" (<- (* (seq (! ")") any-char))) ")") ,md-image)) ;; Link: [text](url) (link (cmt (seq "[" (<- (* (seq (! "]") any-char))) "](" (<- (* (seq (! ")") any-char))) ")") ,md-link)) ;; Backslash escape (escape (cmt (seq "\\" (<- any-char)) ,md-escape)) ;; Run of plain text (excludes all special characters). ;; A single char-set predicate replaces the former chain of ;; per-character negative lookaheads; the extra (! "![") ;; keeps a bare '!' as plain text while still stopping the ;; run before an image marker. Same stop set, far fewer ;; interpreted-PEG steps per character. (text-run (<- (+ (seq (! "![") (char-set ,plain-text-char?))))) ;; Fallback: any single character not matched above (any-char-capture (<- any-char)))) md-inline-grammar))) ;; ========== Inline Parser ========== ;; Inline code: `code` — one or more non-backtick chars between backticks. ;; Literal content (not re-parsed). Fails on empty (``) or missing close. (define (scan-code text i len) (let loop ((k (+ i 1))) (cond ((>= k len) #f) ((eq? (string-ref text k) #\`) (if (> k (+ i 1)) (cons (list 'code (substring text (+ i 1) k)) (+ k 1)) #f)) (else (loop (+ k 1)))))) ;; Double-delimiter emphasis: **strong** / __strong__. Requires the ;; doubled opener (caller has already seen the first delimiter char), ;; one or more inner chars, and a doubled closer. Inner is re-parsed. (define (scan-delim-double text i len ch tag) (if (and (< (+ i 1) len) (eq? (string-ref text (+ i 1)) ch)) (let loop ((k (+ i 2))) (cond ((>= k len) #f) ((and (eq? (string-ref text k) ch) (< (+ k 1) len) (eq? (string-ref text (+ k 1)) ch)) (if (> k (+ i 2)) (cons (cons tag (parse-inline (substring text (+ i 2) k))) (+ k 2)) #f)) (else (loop (+ k 1))))) #f)) ;; Single-delimiter emphasis: *em* / _em_. Only when the opener is NOT ;; doubled (grammar lookahead (& (seq D (! D)))); one or more inner chars ;; then a closing delimiter. Inner is re-parsed. (define (scan-delim-single text i len ch tag) (if (or (>= (+ i 1) len) (not (eq? (string-ref text (+ i 1)) ch))) (let loop ((k (+ i 1))) (cond ((>= k len) #f) ((eq? (string-ref text k) ch) (if (> k (+ i 1)) (cons (cons tag (parse-inline (substring text (+ i 1) k))) (+ k 1)) #f)) (else (loop (+ k 1))))) #f)) ;; Emphasis dispatch for a delimiter char: try bold (double) then italic ;; (single), matching the grammar's ordered choice. (define (scan-emphasis text i len ch) (or (scan-delim-double text i len ch 'strong) (scan-delim-single text i len ch 'em))) ;; Bracketed construct helper: given the position of the first inner char ;; (just past '[' or '!['), scan a label up to the first ']', require an ;; immediately-following '(', scan a target up to the first ')', and call ;; MAKE with (label-start label-end target-start target-end). Returns ;; `(result . next-pos)` or #f. Labels/targets are literal (not re-parsed), ;; matching the grammar's greedy, non-backtracking `*` captures. (define (scan-bracketed text lbl-start len make) (let scan-label ((k lbl-start)) (cond ((>= k len) #f) ((eq? (string-ref text k) #\]) (if (and (< (+ k 1) len) (eq? (string-ref text (+ k 1)) #\()) (let scan-target ((m (+ k 2))) (cond ((>= m len) #f) ((eq? (string-ref text m) #\)) (cons (make lbl-start k (+ k 2) m) (+ m 1))) (else (scan-target (+ m 1))))) #f)) (else (scan-label (+ k 1)))))) ;; Image: . alt/src may be empty. -> (img (@ (src S) (alt A))). (define (scan-image text i len) (scan-bracketed text (+ i 2) len (lambda (a-start a-end s-start s-end) (list 'img (list '@ (list 'src (substring text s-start s-end)) (list 'alt (substring text a-start a-end))))))) ;; Link: [text](url). text/url may be empty; text is literal (not ;; re-parsed, per the grammar). -> (a (@ (href U)) TEXT). (define (scan-link text i len) (scan-bracketed text (+ i 1) len (lambda (t-start t-end u-start u-end) (list 'a (list '@ (list 'href (substring text u-start u-end))) (substring text t-start t-end))))) ;; Run of plain text. Advances while the char is plain and not the start ;; of an image marker " — only when followed by '[' ((and (eq? c #\!) (< (+ i 1) len) (eq? (string-ref text (+ i 1)) #\[)) (let ((r (scan-image text i len))) (if r (loop (cdr r) (cons (car r) acc)) (loop (+ i 1) (cons "!" acc))))) ;; [text](url) ((eq? c #\[) (let ((r (scan-link text i len))) (if r (loop (cdr r) (cons (car r) acc)) (loop (+ i 1) (cons "[" acc))))) ;; \escape — drops backslash, keeps next char literally ((eq? c #\\) (if (< (+ i 1) len) (loop (+ i 2) (cons (substring text (+ i 1) (+ i 2)) acc)) (loop (+ i 1) (cons "\\" acc)))) ;; plain-text run (includes a bare '!') (else (let ((end (scan-text-run text i len))) (loop end (cons (substring text i end) acc))))))))))) ;; ========== Block to SXML Conversion ==========