tatara-lisp 0.3.10

Homoiconic S-expression reader + macroexpander — the pleme-io Lisp authoring surface, hermetic-build standalone
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
//! Source-position rendering for `LispError`.
//!
//! `tatara-lisp` errors carry byte offsets through the reader (see
//! `reader.rs` and `LispError::position`). This module is the projection
//! step: it converts a byte offset into a 1-based `(line, column)` and
//! renders a rustc-style diagnostic with a caret pointing at the
//! failure. `tatara-lispc`, `tatara-check`, the REPL, and the future
//! LSP all funnel through `format_diagnostic` so authoring surfaces
//! point at the byte that broke instead of leaving the operator to
//! hunt for it.
//!
//! Theory grounding: THEORY.md §V.1 — knowable platform / constructive
//! diagnostics. An error whose location cannot be projected to source
//! is not knowable. Inspiration: rustc's `DiagnosticBuilder` snippet
//! format; translation through pleme-io primitives is byte-offset
//! spans on the existing `LispError`, no new IR layer.

use crate::error::LispError;

/// `writeln!` into a writer whose `fmt::Write` impl is infallible.
///
/// `format_diagnostic` assembles its rustc-style snippet by emitting
/// four formatted lines into a `String`. `String`'s `fmt::Write` impl
/// is total — `impl fmt::Write for String { fn write_str(&mut self, s)
/// { self.push_str(s); Ok(()) } }` — so every `writeln!`/`write!` into
/// it returns `Ok(())`; the inline `.expect("writes to a String never
/// fail")` triple recurred at four sites (THEORY.md §VI.1
/// three-times rule, crossed decisively).
///
/// Lifting it into ONE macro centralizes the canonical panic message:
/// a typo in the expect-string can never drift across the four
/// emission sites at runtime. Sibling of `infallible_write!` for the
/// non-newline-terminated single-write case (the trailing caret line
/// in `format_diagnostic`).
///
/// Theory grounding: THEORY.md §VI.1 — the four-times duplication of
/// `.expect("writes to a String never fail")` collapses into one
/// named primitive. The macro names the invariant ("infallible write
/// to String") as a primitive of the diagnostic-rendering substrate,
/// so future writer-type changes (e.g., `String` → a typed builder)
/// land in ONE place — every call site picks up the new emission
/// posture mechanically.
macro_rules! infallible_writeln {
    ($out:expr, $($t:tt)*) => {{
        // Hygienically bring `fmt::Write::write_fmt` into scope so
        // call sites don't need a separate `use std::fmt::Write as _`
        // import — the macro is self-contained.
        use ::std::fmt::Write as _;
        ::std::writeln!($out, $($t)*).expect("writes to a String never fail")
    }};
}

/// `write!` into a writer whose `fmt::Write` impl is infallible — the
/// non-newline-terminated sibling of `infallible_writeln!`. Used by
/// `format_diagnostic` for its trailing caret line, which must not
/// emit a closing newline (so consumers concatenating the rendered
/// diagnostic into a longer message see the caret as the final
/// character, not the line after it). Same `String`-infallibility
/// invariant; same canonical panic message; same theory-anchor
/// (THEORY.md §VI.1).
macro_rules! infallible_write {
    ($out:expr, $($t:tt)*) => {{
        use ::std::fmt::Write as _;
        ::std::write!($out, $($t)*).expect("writes to a String never fail")
    }};
}

/// 1-based line + column. `line_col` walks the source up to a byte
/// offset; `\n` increments `line` and resets `column` to 1.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LineCol {
    pub line: usize,
    pub column: usize,
}

/// Convert a byte offset into a 1-based `LineCol`. Offsets past EOF
/// clamp to the final position. `column` counts UTF-8 scalar
/// characters, not bytes — an `é` is one column, two bytes — so the
/// caret renders under the visible character a human sees.
#[must_use]
pub fn line_col(src: &str, byte_offset: usize) -> LineCol {
    let cap = byte_offset.min(src.len());
    let mut line = 1usize;
    let mut column = 1usize;
    let mut idx = 0usize;
    for c in src.chars() {
        if idx >= cap {
            break;
        }
        if c == '\n' {
            line += 1;
            column = 1;
        } else {
            column += 1;
        }
        idx += c.len_utf8();
    }
    LineCol { line, column }
}

/// Slice the line of `src` containing `byte_offset` (without its
/// trailing `\n`). Used by `format_diagnostic` to render the caret
/// underneath the right line.
///
/// Public because it is one half of the shared caret-rendering contract:
/// a consumer that frames its own diagnostic (see
/// `tatara_lisp_eval::EvalError::render`) still must slice the SAME line
/// this module's caret pad is computed against, or the caret and the
/// snippet disagree. Exposing the slicer alongside [`caret_run`] keeps
/// that pairing at one definition instead of two hand-rolled copies.
#[must_use]
pub fn line_at(src: &str, byte_offset: usize) -> &str {
    let cap = byte_offset.min(src.len());
    let start = src[..cap].rfind('\n').map_or(0, |i| i + 1);
    let end = src[start..].find('\n').map_or(src.len(), |i| start + i);
    &src[start..end]
}

/// Build a caret-pad string whose rendered visual width equals the
/// first `column - 1` chars of `line_text` under a fixed-width terminal.
///
/// Each source `\t` mirrors through as `\t`; every other char becomes
/// a space. This preserves caret alignment for tab-indented sources —
/// the pad and the source line consume the same tab-stops, so the
/// caret lands under the offending byte regardless of the terminal's
/// tab-stop setting. Pre-lift the caret pad was
/// `" ".repeat(column.saturating_sub(1))`, which silently drifted
/// under a tab-indented source (a `\t` renders as N columns of source
/// but as ONE space in the pad, so the caret slid left of the byte).
///
/// Named at the substrate level so a future range-underline
/// diagnostic (e.g. `let caret = mirror + "^".repeat(width)` for a
/// multi-column highlight, or a `note:` companion line pinned under a
/// different column of the SAME source line) inherits the tab-mirror
/// discipline mechanically — the visual-width invariant lives at ONE
/// projection on the diagnostic-rendering surface.
///
/// Theory anchor: THEORY.md §V.1 — knowable platform / constructive
/// diagnostics. A caret whose column drifts under a tab-indented
/// source is not knowable to the operator. Inspiration: rustc's
/// `SnippetData::render_source_line` tab-mirror idiom; translation
/// through pleme-io primitives is a chars-iterator over the source
/// line already in hand, no new IR layer.
fn mirror_source_prefix_as_pad(line_text: &str, column: usize) -> String {
    line_text
        .chars()
        .take(column.saturating_sub(1))
        .map(|c| if c == '\t' { '\t' } else { ' ' })
        .collect()
}

/// THE caret renderer: the pad that walks `line_text`'s first
/// `column - 1` columns, followed by `width` carets.
///
/// This is the one place in the fleet that decides where a `^` lands
/// under a source line and how wide the underline runs. Two hand-written
/// copies existed before this lift — this module's single-caret pad and
/// `tatara_lisp_eval::EvalError::render`'s span underline — and they had
/// drifted apart on two axes that are BOTH bugs in the eval copy:
///
/// 1. **Tab handling.** The eval copy padded with
///    `" ".repeat(column - 1)`, so under a tab-indented source the caret
///    slid left of its byte (one space where the source spent a whole
///    tab-stop). The pad here mirrors tabs through
///    ([`mirror_source_prefix_as_pad`]), so pad and source consume the
///    same tab-stops on any terminal.
/// 2. **Caret width unit.** The eval copy computed its underline as
///    `span.end - span.start` — a BYTE count — while the column it
///    padded to was a CHAR count. A multi-byte subform therefore
///    over-underlined (a one-column `é` drew two carets). `width` here
///    is documented as, and must be passed as, a count of CHARS.
///
/// `width` is clamped up to 1: a zero-width span still gets a single
/// caret to point at, rather than rendering a bare pad with nothing
/// under it.
///
/// `width` is NOT clamped down to the remaining length of `line_text`.
/// A span covering several lines will therefore draw carets past the end
/// of the single line rendered above it. That is the pre-existing
/// behaviour of both copies, preserved deliberately rather than changed
/// under cover of this consolidation — see the `pending-caret-multiline`
/// note in `tatara_lisp_eval::EvalError::render`.
///
/// Theory anchor: THEORY.md §V.1 — knowable platform / constructive
/// diagnostics. A caret whose column or width drifts from the byte it
/// names is not knowable to the operator.
#[must_use]
pub fn caret_run(line_text: &str, column: usize, width: usize) -> String {
    let mut out = mirror_source_prefix_as_pad(line_text, column);
    for _ in 0..width.max(1) {
        out.push('^');
    }
    out
}

/// Render a `LispError` as a rustc-style diagnostic with a caret.
///
/// ```text
/// error: unmatched closing paren at position 3
///  --> file.lisp:1:4
///   |
/// 1 |    )
///   |    ^
/// ```
///
/// `label` is the file path or any identifier the caller wants in the
/// `--> label:line:col` line; pass `None` when there is no source name
/// (the REPL, an in-memory string) and the location renders as
/// `--> line N, column M`.
///
/// Errors whose `position()` is `None` (`Type`, `Compile`, …) render
/// as a single `error: <msg>` line — there is nothing to point at.
/// As more variants gain positions, those errors automatically pick
/// up the snippet rendering with no consumer changes.
#[must_use]
pub fn format_diagnostic(src: &str, err: &LispError, label: Option<&str>) -> String {
    let mut out = format!("error: {err}");
    let Some(pos) = err.position() else {
        return out;
    };
    let LineCol { line, column } = line_col(src, pos);
    let line_text = line_at(src, pos);
    let line_str = line.to_string();
    let gutter = " ".repeat(line_str.len());
    // A `LispError` names a single byte, not a range — hence width 1.
    // The width parameter is what lets `EvalError::render`, whose errors
    // DO carry a `[start, end)` span, share this exact renderer instead
    // of hand-rolling a second one.
    let caret = caret_run(line_text, column, 1);

    out.push('\n');
    match label {
        Some(label) => infallible_writeln!(out, "{gutter}--> {label}:{line}:{column}"),
        None => infallible_writeln!(out, "{gutter}--> line {line}, column {column}"),
    }
    infallible_writeln!(out, "{gutter} |");
    infallible_writeln!(out, "{line_str} | {line_text}");
    infallible_write!(out, "{gutter} | {caret}");
    out
}

#[cfg(test)]
mod tests {
    use super::{
        caret_run, format_diagnostic, line_at, line_col, mirror_source_prefix_as_pad, LineCol,
    };
    use crate::error::LispError;
    use crate::reader::read;

    // ── line_col ────────────────────────────────────────────────────

    #[test]
    fn line_col_at_start_of_input() {
        assert_eq!(line_col("abc", 0), LineCol { line: 1, column: 1 });
    }

    #[test]
    fn line_col_advances_columns_on_first_line() {
        assert_eq!(line_col("abc", 1), LineCol { line: 1, column: 2 });
        assert_eq!(line_col("abc", 2), LineCol { line: 1, column: 3 });
    }

    #[test]
    fn line_col_at_eof_is_one_past_last_char() {
        assert_eq!(line_col("abc", 3), LineCol { line: 1, column: 4 });
    }

    #[test]
    fn line_col_clamps_past_eof() {
        assert_eq!(line_col("abc", 999), LineCol { line: 1, column: 4 });
        assert_eq!(line_col("", 999), LineCol { line: 1, column: 1 });
    }

    #[test]
    fn line_col_advances_line_after_newline() {
        // `a\nb` — offset 0 = (1,1); 1 = (1,2) (still on line 1, after `a`);
        // 2 = (2,1) (after the `\n`); 3 = (2,2) (after `b`).
        assert_eq!(line_col("a\nb", 0), LineCol { line: 1, column: 1 });
        assert_eq!(line_col("a\nb", 1), LineCol { line: 1, column: 2 });
        assert_eq!(line_col("a\nb", 2), LineCol { line: 2, column: 1 });
        assert_eq!(line_col("a\nb", 3), LineCol { line: 2, column: 2 });
    }

    #[test]
    fn line_col_counts_chars_not_bytes_for_multibyte() {
        // `é` is two bytes (0xC3 0xA9) but one column. Offset = 2 lands
        // immediately after `é`, i.e. column 2 on line 1.
        assert_eq!(line_col("é", 2), LineCol { line: 1, column: 2 });
        assert_eq!(line_col("\né", 1), LineCol { line: 2, column: 1 });
        assert_eq!(line_col("\né", 3), LineCol { line: 2, column: 2 });
    }

    // ── line_at ─────────────────────────────────────────────────────

    #[test]
    fn line_at_returns_the_containing_line_without_newline() {
        let src = "alpha\nbeta\ngamma";
        assert_eq!(line_at(src, 0), "alpha");
        assert_eq!(line_at(src, 6), "beta"); // first char of line 2
        assert_eq!(line_at(src, 11), "gamma"); // first char of line 3
        assert_eq!(line_at(src, 16), "gamma"); // EOF still on line 3
    }

    // ── format_diagnostic ───────────────────────────────────────────

    #[test]
    fn format_diagnostic_renders_unmatched_paren_with_caret_under_offending_byte() {
        // `   )` — stray `)` at byte 3, which is column 4 on line 1.
        // The caret under the `)` proves the column math + line slicing
        // agree.
        let src = "   )";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("x.lisp"));
        let expected = "\
error: unmatched closing paren at position 3
 --> x.lisp:1:4
  |
1 |    )
  |    ^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }

    #[test]
    fn format_diagnostic_locates_paren_on_a_later_line() {
        // Two leading lines plus a stray `)` — confirms the line index
        // and the line-slicing both work past the first newline.
        let src = "(a b)\n(c d)\n   )\n";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("nested.lisp"));
        // The stray `)` is at byte 15 → (line 3, column 4).
        let expected = "\
error: unmatched closing paren at position 15
 --> nested.lisp:3:4
  |
3 |    )
  |    ^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }

    #[test]
    fn format_diagnostic_unmatched_open_points_at_the_unclosed_paren() {
        // `(a (b c` — inner `(` at byte 3 is the deepest unclosed open.
        let src = "(a (b c";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("open.lisp"));
        let expected = "\
error: unmatched opening paren at position 3
 --> open.lisp:1:4
  |
1 | (a (b c
  |    ^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }

    #[test]
    fn format_diagnostic_omits_label_when_none() {
        let err = read(")").unwrap_err();
        let rendered = format_diagnostic(")", &err, None);
        // No file path is known; still produce a structured location.
        let expected = "\
error: unmatched closing paren at position 0
 --> line 1, column 1
  |
1 | )
  | ^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }

    #[test]
    fn format_diagnostic_renders_eof_at_end_of_input() {
        // `(a b) '` — trailing quote with no datum runs the parser past
        // EOF; the caret renders one column past the last visible char.
        let src = "(a b) '";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("dangle.lisp"));
        let expected = "\
error: unexpected end of input at position 7
 --> dangle.lisp:1:8
  |
1 | (a b) '
  |        ^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }

    #[test]
    fn infallible_writeln_macro_appends_formatted_line_with_trailing_newline() {
        // Pin the macro's emission shape: `writeln!`-equivalent into a
        // `String`, no swallowed bytes, no missing newline. A regression
        // that drops the newline or mis-handles format-arg interpolation
        // fails-loudly here. The macro is the centralized substitute
        // for the four inline `.expect("writes to a String never
        // fail")` triples that recurred in `format_diagnostic`'s body
        // pre-lift.
        let mut out = String::new();
        infallible_writeln!(out, "hello {x}", x = 42);
        assert_eq!(out, "hello 42\n");
    }

    #[test]
    fn infallible_write_macro_appends_formatted_text_without_newline() {
        // Sibling of `infallible_writeln!` — non-newline-terminated
        // emission. Pin that the macro does NOT add a trailing newline
        // so the caret-line rendering in `format_diagnostic` stays
        // byte-for-byte stable. A regression that adds a newline here
        // fails-loudly via the existing `format_diagnostic_*` tests
        // AND this isolated unit-pin.
        let mut out = String::new();
        infallible_write!(out, "tail {y}", y = "value");
        assert_eq!(out, "tail value");
    }

    #[test]
    fn infallible_macros_preserve_format_diagnostic_byte_identity() {
        // The lift is a pure refactor — `format_diagnostic`'s rendered
        // output must be byte-for-byte identical to the pre-lift state
        // across every existing test case. The five `format_diagnostic_*`
        // tests below already pin specific expected strings; this test
        // re-asserts that path-uniformity at the macro-substitution
        // layer: emit one full diagnostic and confirm both the caret
        // line (the only `infallible_write!` site) AND the gutter
        // lines (three `infallible_writeln!` sites) render correctly
        // together.
        let src = "   )";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("macros.lisp"));
        assert!(rendered.starts_with("error: unmatched closing paren"));
        assert!(rendered.contains("\n --> macros.lisp:1:4\n"));
        assert!(rendered.ends_with("^"));
        assert!(
            !rendered.ends_with("^\n"),
            "trailing caret line must NOT emit a newline (would drift consumer concat)"
        );
    }

    #[test]
    fn format_diagnostic_falls_back_to_single_line_for_positionless_errors() {
        // A `Compile` error has no position today; it must still render
        // as a clean single line so downstream tools can dump it
        // unconditionally.
        let err = LispError::Compile {
            form: ":threshold".into(),
            message: "expected number".into(),
        };
        let rendered = format_diagnostic("(defmonitor :threshold #t)", &err, Some("m.lisp"));
        assert_eq!(
            rendered,
            "error: compile error in :threshold: expected number"
        );
        assert!(
            !rendered.contains('\n'),
            "single-line render must not introduce newlines"
        );
        assert!(
            !rendered.contains('^'),
            "no caret allowed without a position to point at"
        );
    }

    // ── mirror_source_prefix_as_pad ─────────────────────────────────
    //
    // The pre-lift `" ".repeat(column - 1)` caret pad drifted under a
    // tab-indented source (a `\t` renders as N columns of source but
    // as ONE space in the pad, so the caret slid left of the offending
    // byte). Post-lift the pad mirrors each source char — tabs stay
    // tabs, everything else becomes a space — so the pad and the
    // source line consume the SAME tab-stops on a fixed-width terminal.
    // The pins below anchor the four canonical fixpoints AND the
    // end-to-end composition through `format_diagnostic`.

    #[test]
    fn mirror_source_prefix_as_pad_at_column_one_is_empty() {
        // Column 1 means the caret sits under the FIRST char of the
        // source line — zero pad ahead of it. `saturating_sub(1)` guards
        // both column 0 (unreachable but defensively OK) and column 1.
        assert_eq!(mirror_source_prefix_as_pad("(a b)", 1), "");
        assert_eq!(mirror_source_prefix_as_pad("(a b)", 0), "");
    }

    #[test]
    fn mirror_source_prefix_as_pad_replaces_non_tab_chars_with_spaces() {
        // A tab-free source line reproduces the pre-lift behavior byte-
        // for-byte: N chars of source before the caret → N spaces of
        // pad. Load-bearing for the existing `format_diagnostic_*`
        // tests, which all pin space-only prefixes.
        assert_eq!(mirror_source_prefix_as_pad("   )", 4), "   ");
        assert_eq!(mirror_source_prefix_as_pad("(a b c)", 5), "    ");
        assert_eq!(
            mirror_source_prefix_as_pad("hello", 6),
            "     ",
            "column past-last-char pads with spaces for every source char",
        );
    }

    #[test]
    fn mirror_source_prefix_as_pad_preserves_tabs_verbatim() {
        // A single leading tab mirrors through as a tab — the caret pad
        // consumes the same tab-stop the source did, so `\t)` and `\t^`
        // land the `)` and the `^` at the SAME visual column regardless
        // of the terminal's tab-stop setting (2, 4, 8, whatever).
        assert_eq!(mirror_source_prefix_as_pad("\t)", 2), "\t");
        assert_eq!(mirror_source_prefix_as_pad("\t\t)", 3), "\t\t");
    }

    #[test]
    fn mirror_source_prefix_as_pad_mirrors_mixed_tab_and_space_prefix() {
        // Real-world indent shapes (space-then-tab, tab-then-space,
        // interleaved) must reproduce the source's exact whitespace
        // sequence in the pad. A regression that converts tabs to
        // spaces (or vice versa) fails HERE with a visible mismatch.
        assert_eq!(mirror_source_prefix_as_pad("  \t)", 4), "  \t");
        assert_eq!(mirror_source_prefix_as_pad("\t  )", 4), "\t  ");
        // `(` is a non-tab char — it becomes a space in the pad while
        // the surrounding tabs mirror through as tabs. Pin that the
        // interleaved `[tab, space, non-tab, tab]` prefix produces
        // `[tab, space, space, tab]` so the caret's tab-stop advances
        // remain aligned regardless of what non-tab chars precede it.
        assert_eq!(mirror_source_prefix_as_pad("\t (\t)", 5), "\t  \t");
    }

    #[test]
    fn mirror_source_prefix_as_pad_replaces_multibyte_chars_with_spaces() {
        // `é` is one char, one column. The pad counts CHARS (matching
        // `line_col`'s `column` accounting), so `é)` at column 2 →
        // ONE space of pad, not two (which the pre-lift byte-repeat
        // would have produced under a naive byte-count).
        assert_eq!(mirror_source_prefix_as_pad("é)", 2), " ");
        assert_eq!(mirror_source_prefix_as_pad("\téé)", 4), "\t  ");
    }

    #[test]
    fn mirror_source_prefix_as_pad_clamps_to_line_length_at_eof_column() {
        // `format_diagnostic_renders_eof_at_end_of_input` renders the
        // caret one column past the last visible char. Under this
        // logic the pad is `chars().take(N)` over an N-char line, which
        // yields exactly N mirrored chars — same as the pre-lift
        // `" ".repeat(N)` for a tab-free source. Pin the clamp so a
        // future refactor that swaps `take` for a slice-index panics
        // out at rustc / test time rather than silently mispadding
        // EOF errors.
        assert_eq!(mirror_source_prefix_as_pad("(a b) '", 8), "       ");
        assert_eq!(mirror_source_prefix_as_pad("\tfoo", 5), "\t   ");
    }

    // ── caret_run ───────────────────────────────────────────────────
    //
    // The shared caret renderer. These pins exist because `caret_run` is
    // the SINGLE definition both consumers now go through — this module's
    // `format_diagnostic` (width 1) and `EvalError::render` (width = the
    // span's CHAR count). A drift here breaks both at once, which is the
    // point of consolidating them.

    #[test]
    fn caret_run_width_one_reproduces_the_single_caret_pad() {
        // The `format_diagnostic` case: pad + exactly one `^`. Byte-
        // identical to the pre-lift `mirror_source_prefix_as_pad(..) +
        // "^"` expression it replaced.
        assert_eq!(caret_run("   )", 4, 1), "   ^");
        assert_eq!(caret_run("(a b)", 1, 1), "^");
    }

    #[test]
    fn caret_run_underlines_a_multi_column_span() {
        // The `EvalError::render` case, which the single-caret renderer
        // could not express at all before the width parameter. `(+ x foo
        // y)` with `foo` spanning columns 6..9 → five pad columns then
        // three carets.
        assert_eq!(caret_run("(+ x foo y)", 6, 3), "     ^^^");
    }

    #[test]
    fn caret_run_clamps_zero_width_up_to_one_caret() {
        // A zero-width span still names a point in the source; rendering
        // a bare pad with nothing under it would produce a diagnostic
        // whose caret line is invisible whitespace.
        assert_eq!(caret_run("(a b)", 3, 0), "  ^");
    }

    #[test]
    fn caret_run_mirrors_tabs_under_a_multi_column_underline() {
        // THE eval-side bug fix, at the primitive. Pre-lift the eval copy
        // padded with `" ".repeat(column - 1)`, so a tab-indented source
        // line put ONE space where the source spent a whole tab-stop and
        // the entire underline slid left of the span it names. Going
        // through the shared pad fixes it for every width.
        assert_eq!(caret_run("\tfoo", 2, 3), "\t^^^");
        assert_eq!(caret_run("  \tbar", 4, 3), "  \t^^^");
    }

    #[test]
    fn caret_run_counts_pad_in_chars_not_bytes_for_multibyte_prefix() {
        // Companion to the width-unit fix: the pad is a CHAR count, so a
        // multi-byte prefix advances the caret by columns-as-seen, not by
        // bytes. `é` is one column, two bytes — one space of pad.
        assert_eq!(caret_run("é)", 2, 1), " ^");
        assert_eq!(caret_run("ééx", 3, 1), "  ^");
    }

    #[test]
    fn format_diagnostic_caret_pad_mirrors_tab_indent_for_terminal_alignment() {
        // END-TO-END CONTRACT: a tab-indented source with a stray `)`
        // must render a caret pad whose leading tab matches the source
        // line's leading tab — so the terminal displays `^` under `)`
        // regardless of tab-stop setting. Pre-lift this rendered
        // `\t)` above `  ^` (two spaces where a tab belongs), which
        // slid the caret left of the `)` on every real terminal. Pin
        // the fix at the outer boundary so a regression in
        // `mirror_source_prefix_as_pad` surfaces through the diagnostic
        // consumer, not just its internal unit-pin.
        let src = "\t)";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("tabby.lisp"));
        let expected = "\
error: unmatched closing paren at position 1
 --> tabby.lisp:1:2
  |
1 | \t)
  | \t^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }

    #[test]
    fn format_diagnostic_caret_pad_mirrors_mixed_tab_space_indent() {
        // Deeper composition: mixed leading indent (space + tab +
        // space) with the caret on a nested `(` that stays unclosed.
        // Every prefix char round-trips into the pad — spaces stay
        // spaces, the tab stays a tab. A regression that homogenizes
        // the prefix to all-spaces fails HERE with a visible mismatch
        // on the tab position.
        let src = " \t (a b";
        let err = read(src).unwrap_err();
        let rendered = format_diagnostic(src, &err, Some("mixed.lisp"));
        // Unclosed `(` sits at byte 3, column 4 on line 1.
        let expected = "\
error: unmatched opening paren at position 3
 --> mixed.lisp:1:4
  |
1 |  \t (a b
  |  \t ^";
        assert_eq!(rendered, expected, "got:\n{rendered}");
    }
}