Skip to main content

toml_spanner/
emit.rs

1mod normalization;
2mod partition;
3mod reprojection;
4#[cfg(test)]
5pub(crate) mod test_data;
6#[cfg(test)]
7#[path = "emit_tests.rs"]
8mod tests;
9
10pub(crate) use normalization::NormalizedTable;
11pub(crate) use reprojection::reproject;
12pub(crate) use reprojection::reproject_with_span_identity;
13
14use crate::Array;
15use crate::Table;
16use crate::arena::Arena;
17use crate::item::{ArrayStyle, Item, Key, TableStyle, Value};
18use crate::span::Span;
19use std::io::Write;
20use std::mem::MaybeUninit;
21
22/// Stack-allocated linked list node for building key-path prefixes
23/// without heap allocation. Each node lives on the call stack.
24#[derive(Clone, Copy)]
25struct Prefix<'a, 'de> {
26    name: &'de str,
27    key_span: Span,
28    parent: Option<&'a Prefix<'a, 'de>>,
29}
30
31/// Indentation unit for expanded inline arrays.
32///
33/// Each nesting level repeats this unit once.
34#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub enum Indent {
36    /// N spaces per level (e.g. `Spaces(4)` for 4-space indent).
37    Spaces(u8),
38    /// One tab character per level.
39    Tab,
40}
41
42impl Default for Indent {
43    fn default() -> Self {
44        Indent::Spaces(4)
45    }
46}
47
48impl Indent {
49    fn write(&self, out: &mut Vec<u8>, depth: u32) {
50        match self {
51            Indent::Spaces(n) => {
52                let total = depth as usize * *n as usize;
53                for _ in 0..total {
54                    out.push(b' ');
55                }
56            }
57            Indent::Tab => {
58                for _ in 0..depth {
59                    out.push(b'\t');
60                }
61            }
62        }
63    }
64
65    fn width(&self) -> usize {
66        match self {
67            Indent::Spaces(n) => *n as usize,
68            Indent::Tab => 1,
69        }
70    }
71}
72
73/// Configuration for [`emit_with_config`].
74///
75/// When both fields are non-empty, scalar values whose reprojected source
76/// item has a valid span will be emitted verbatim from the original text,
77/// preserving formatting such as literal strings, hex/octal/binary
78/// integers, and underscored numbers.
79#[derive(Default)]
80pub(crate) struct EmitConfig<'a> {
81    pub projected_source_text: &'a str,
82    pub projected_source_items: &'a [&'a Item<'a>],
83    pub indent: Indent,
84}
85
86struct Emitter<'a, 'b> {
87    arena: &'b Arena,
88    src: &'a [u8],
89    src_items: &'a [&'a Item<'a>],
90    indent: Indent,
91}
92
93fn trim_trailing_newline(buf: &mut Vec<u8>) {
94    if buf.last() == Some(&b'\n') {
95        buf.pop();
96        if buf.last() == Some(&b'\r') {
97            buf.pop();
98        }
99    }
100}
101
102/// Like [`emit`], but with an [`EmitConfig`] for scalar format preservation.
103///
104/// When the config carries reprojection data (from [`reproject`]), scalar
105/// values that match their source are emitted verbatim from the original
106/// text, preserving formatting like literal strings, hex integers, etc.
107pub(crate) fn emit_with_config(
108    table: &NormalizedTable<'_>,
109    config: &EmitConfig<'_>,
110    arena: &Arena,
111    buf: &mut Vec<u8>,
112) {
113    let table = table.table();
114    let emit = Emitter {
115        arena,
116        src: config.projected_source_text.as_bytes(),
117        src_items: config.projected_source_items,
118        indent: config.indent,
119    };
120
121    if !emit.src.is_empty() {
122        let mut cursor = 0usize;
123        emit_ordered(table, None, None, &emit, buf, &mut cursor);
124        emit_gap(&emit, cursor, emit.src.len(), buf);
125        if !emit.src.ends_with(b"\n") {
126            trim_trailing_newline(buf);
127        }
128    } else {
129        emit_formatted(table, None, &emit, buf);
130    }
131}
132
133enum EmitOp<'a, 'b, 'de> {
134    Body(&'a Key<'de>, &'a Item<'de>, Option<&'b Prefix<'b, 'de>>),
135    Header(&'a Table<'de>, &'a Item<'de>, &'b Prefix<'b, 'de>),
136    AotElement(&'a Table<'de>, &'a Item<'de>, &'b Prefix<'b, 'de>),
137}
138
139fn is_reordered_aot(op: &EmitOp<'_, '_, '_>) -> bool {
140    if let EmitOp::AotElement(_, entry, _) = op {
141        entry.meta.array_reordered()
142    } else {
143        false
144    }
145}
146
147fn segment_index(sort_pos: u32, index: usize, is_body: bool) -> u64 {
148    let sub_bit = if is_body { 0 } else { 1u64 << 63 };
149    sub_bit | ((sort_pos as u64) << 32) | (index as u64)
150}
151
152/// A segment of emit output with its source-derived sort position.
153struct Segment<'a, 'b, 'de> {
154    /// Source position for sorting (or `last_projected` for unprojected items).
155    sort_pos: u32,
156    /// Source byte offset where this segment's content begins.
157    source_start: u32,
158    /// True for body entries, false for subsection entries.
159    /// Body segments are emitted before subsection segments.
160    is_body: bool,
161    /// The operation to execute during the final output pass.
162    op: EmitOp<'a, 'b, 'de>,
163}
164
165fn alloc_prefix<'b, 'de>(
166    arena: &'b Arena,
167    name: &'de str,
168    key_span: Span,
169    parent: Option<&'b Prefix<'b, 'de>>,
170) -> &'b Prefix<'b, 'de> {
171    // SAFETY:
172    // - `arena.alloc(size_of::<Prefix>())` returns memory suitably sized.
173    //   Prefix contains &str (align 8) + Span (align 4) + Option<&Prefix>
174    //   (align 8), so max field align is 8 which matches ALLOC_ALIGN.
175    // - `ptr::write` initializes the allocation with a valid Prefix.
176    // - The resulting &'b reference is valid for the arena's lifetime 'b.
177    unsafe {
178        let ptr = arena
179            .alloc(std::mem::size_of::<Prefix<'b, 'de>>())
180            .cast::<Prefix<'b, 'de>>()
181            .as_ptr();
182        std::ptr::write(
183            ptr,
184            Prefix {
185                name,
186                key_span,
187                parent,
188            },
189        );
190        &*ptr
191    }
192}
193
194fn build_segment_order(segments: &[Segment<'_, '_, '_>], ignore_source_order: bool) -> Vec<u64> {
195    let mut order = Vec::with_capacity(segments.len());
196    for (i, seg) in segments.iter().enumerate() {
197        order.push(segment_index(seg.sort_pos, i, seg.is_body));
198    }
199    if !ignore_source_order {
200        sort_index(&mut order);
201    }
202    order
203}
204
205fn emit_segment_prefix(
206    seg: &Segment<'_, '_, '_>,
207    emit: &Emitter<'_, '_>,
208    out: &mut Vec<u8>,
209    cursor: &mut usize,
210) {
211    let ss = seg.source_start;
212    let is_header = matches!(seg.op, EmitOp::Header(..) | EmitOp::AotElement(..));
213
214    if ss != u32::MAX {
215        let target = ss as usize;
216        if target >= *cursor {
217            let pre_len = out.len();
218            emit_gap(emit, *cursor, target, out);
219            if out.len() == pre_len && is_header && !out.is_empty() && out.last() != Some(&b'\n') {
220                out.push(b'\n');
221            }
222            *cursor = target;
223        } else if is_header && !is_reordered_aot(&seg.op) {
224            out.push(b'\n');
225        }
226    } else if is_header && !is_reordered_aot(&seg.op) {
227        out.push(b'\n');
228    }
229}
230
231/// Segmented path: collect format operations WITHOUT gap handling,
232/// record segments with sort keys, sort, and execute with proper gaps.
233fn emit_ordered<'a, 'b, 'de: 'b>(
234    table: &'a Table<'de>,
235    dotted_prefix: Option<&'b Prefix<'b, 'de>>,
236    section_prefix: Option<&'b Prefix<'b, 'de>>,
237    emit: &Emitter<'b, 'de>,
238    out: &mut Vec<u8>,
239    cursor: &mut usize,
240) {
241    let mut segments: Vec<Segment<'a, 'b, 'de>> = Vec::new();
242    let mut last_projected: u32 = 0;
243
244    collect_segments(
245        table,
246        dotted_prefix,
247        section_prefix,
248        emit,
249        &mut segments,
250        &mut last_projected,
251        0, // ALL
252    );
253
254    let order = build_segment_order(&segments, table.meta.ignore_source_order());
255
256    // Reassemble into output with proper gap handling.
257    for &entry in &order {
258        let seg = &segments[(entry & 0xFFFF_FFFF) as usize];
259        emit_segment_prefix(seg, emit, out, cursor);
260
261        match &seg.op {
262            EmitOp::Body(key, item, dotted) => {
263                emit_body_entry(key, item, *dotted, emit, out, cursor);
264            }
265            EmitOp::Header(sub_table, item, node) => {
266                emit_header_body(sub_table, item, node, emit, out, cursor);
267            }
268            EmitOp::AotElement(sub_table, arr_entry, node) => {
269                let pre_cursor = *cursor;
270                emit_aot_element(sub_table, arr_entry, node, emit, out, cursor);
271                // Prevent cursor from going backwards when elements are
272                // reordered — stops trailing emit_gap from re-emitting
273                // already-covered source content.
274                *cursor = (*cursor).max(pre_cursor);
275            }
276        }
277    }
278}
279
280/// Recursively collects segments, flattening implicit and dotted
281/// containers so their children can interleave in source order.
282///
283/// Segments are emitted WITHOUT gap handling — gaps are added during
284/// reassembly based on source positions.
285///
286/// `mode`: 0 = all entries, 1 = body only (skip headers/AOTs),
287///         2 = subsections only (skip body entries).
288fn collect_segments<'a, 'b, 'de: 'b>(
289    table: &'a Table<'de>,
290    dotted_prefix: Option<&'b Prefix<'b, 'de>>,
291    section_prefix: Option<&'b Prefix<'b, 'de>>,
292    emit: &Emitter<'b, 'de>,
293    segments: &mut Vec<Segment<'a, 'b, 'de>>,
294    last_projected: &mut u32,
295    mode: u8,
296) {
297    const BODY_ONLY: u8 = 1;
298    const SUBS_ONLY: u8 = 2;
299    // mode 0 = ALL (both body and subsection segments)
300
301    for entry in table.entries() {
302        let key = &entry.0;
303        let item = &entry.1;
304
305        if item.has_dotted_bit() {
306            let Some(sub_table) = item.as_table() else {
307                continue;
308            };
309            let dotted_node = alloc_prefix(emit.arena, key.name, key.span, dotted_prefix);
310            let sec_node = alloc_prefix(emit.arena, key.name, key.span, section_prefix);
311            // Anchor unprojected children near this container's source position.
312            if !key.span.is_empty() {
313                *last_projected = key.span.start;
314            }
315            collect_segments(
316                sub_table,
317                Some(dotted_node),
318                Some(sec_node),
319                emit,
320                segments,
321                last_projected,
322                mode,
323            );
324            continue;
325        }
326
327        if item.is_implicit_table() {
328            let Some(sub_table) = item.as_table() else {
329                continue;
330            };
331            // Anchor unprojected children near this container's source position.
332            if !key.span.is_empty() {
333                *last_projected = key.span.start;
334            }
335            let sec_node = alloc_prefix(emit.arena, key.name, key.span, section_prefix);
336            collect_segments(
337                sub_table,
338                None,
339                Some(sec_node),
340                emit,
341                segments,
342                last_projected,
343                mode,
344            );
345            continue;
346        }
347
348        if item.has_header_bit() {
349            if mode == BODY_ONLY {
350                continue;
351            }
352            let Some(sub_table) = item.as_table() else {
353                continue;
354            };
355            let node = alloc_prefix(emit.arena, key.name, key.span, section_prefix);
356            let sort_key_opt = projected_span(item, emit).map(|s| s.start);
357            let source_start = sort_key_opt.unwrap_or(u32::MAX);
358
359            let sp = pack_sort_pos(sort_key_opt, last_projected);
360            segments.push(Segment {
361                sort_pos: sp,
362                source_start,
363                is_body: false,
364                op: EmitOp::Header(sub_table, item, node),
365            });
366
367            collect_segments(
368                sub_table,
369                None,
370                Some(node),
371                emit,
372                segments,
373                last_projected,
374                SUBS_ONLY,
375            );
376            continue;
377        }
378
379        if item.is_aot() {
380            if mode == BODY_ONLY {
381                continue;
382            }
383            let Some(arr) = item.as_array() else {
384                continue;
385            };
386            let node = alloc_prefix(emit.arena, key.name, key.span, section_prefix);
387            // Anchor unprojected elements near this AOT's source position.
388            if !key.span.is_empty() {
389                *last_projected = key.span.start;
390            }
391            let mut prev_aot_pos: u32 = 0;
392            for arr_entry in arr {
393                let Some(sub_table) = arr_entry.as_table() else {
394                    continue;
395                };
396                let elem_sort = if arr_entry.meta.array_reordered() {
397                    None
398                } else {
399                    projected_span(arr_entry, emit).map(|s| s.start)
400                };
401                let elem_source_start = elem_sort.unwrap_or(u32::MAX);
402                let mut sp = pack_sort_pos(elem_sort, last_projected);
403                // AOT element order is semantic: ensure sort positions are
404                // monotonically non-decreasing so that content-based array
405                // matching doesn't reorder elements during source-ordered emit.
406                if sp < prev_aot_pos {
407                    sp = prev_aot_pos;
408                }
409                prev_aot_pos = sp;
410
411                segments.push(Segment {
412                    sort_pos: sp,
413                    source_start: elem_source_start,
414                    is_body: false,
415                    op: EmitOp::AotElement(sub_table, arr_entry, node),
416                });
417            }
418            continue;
419        }
420
421        // Body entry
422        if mode == SUBS_ONLY {
423            continue;
424        }
425        // Use line_start as source_start so that reassembly gap handling
426        // doesn't duplicate indentation that emit_body_entry preserves.
427        let sort_key_opt = if !key.span.is_empty() {
428            Some(key.span.start)
429        } else {
430            None
431        };
432        let ls = if let Some(pos) = sort_key_opt {
433            line_start_of(emit.src, pos as usize) as u32
434        } else {
435            u32::MAX
436        };
437        let sp = pack_sort_pos(sort_key_opt, last_projected);
438
439        segments.push(Segment {
440            sort_pos: sp,
441            source_start: ls,
442            is_body: true,
443            op: EmitOp::Body(key, item, dotted_prefix),
444        });
445    }
446}
447
448/// Emits a header line and body entries only (no subsections) into a buffer.
449/// Subsection entries (headers, AOTs) within the table are skipped so they
450/// can be collected as independent segments for interleaved sorting.
451fn emit_header_body<'a, 'b, 'de: 'b>(
452    table: &'a Table<'de>,
453    item: &'a Item<'de>,
454    prefix: &'b Prefix<'b, 'de>,
455    emit: &Emitter<'b, 'de>,
456    out: &mut Vec<u8>,
457    cursor: &mut usize,
458) {
459    if emit_projected_header_line(item, false, emit, out, cursor) {
460        emit_body_ordered(table, emit, out, cursor);
461        return;
462    }
463
464    // Fallback: formatted header
465    write_section_header(prefix, emit, out);
466    emit_body_ordered(table, emit, out, cursor);
467}
468
469/// Emits body entries from a table in source order, skipping headers and AOTs.
470/// Dotted and implicit containers are flattened via `collect_segments` with
471/// BODY_ONLY mode. Segments are sorted by source position.
472fn emit_body_ordered<'a, 'b, 'de: 'b>(
473    table: &'a Table<'de>,
474    emit: &Emitter<'b, 'de>,
475    out: &mut Vec<u8>,
476    cursor: &mut usize,
477) {
478    let mut segments: Vec<Segment<'a, 'b, 'de>> = Vec::new();
479    let mut last_projected: u32 = 0;
480
481    collect_segments(
482        table,
483        None,
484        None,
485        emit,
486        &mut segments,
487        &mut last_projected,
488        1, // BODY_ONLY
489    );
490
491    let order = build_segment_order(&segments, table.meta.ignore_source_order());
492
493    for &entry in &order {
494        let seg = &segments[(entry & 0xFFFF_FFFF) as usize];
495        let ss = seg.source_start;
496        if ss != u32::MAX {
497            let target = ss as usize;
498            if target >= *cursor {
499                emit_gap(emit, *cursor, target, out);
500                *cursor = target;
501            }
502        }
503        if let EmitOp::Body(key, item, dotted) = &seg.op {
504            emit_body_entry(key, item, *dotted, emit, out, cursor);
505        }
506    }
507}
508
509fn emit_projected_header_line(
510    item: &Item<'_>,
511    include_comment_prefix: bool,
512    emit: &Emitter<'_, '_>,
513    out: &mut Vec<u8>,
514    cursor: &mut usize,
515) -> bool {
516    let Some(src_span) = projected_span(item, emit) else {
517        return false;
518    };
519    let hdr_start = src_span.start as usize;
520    if hdr_start >= emit.src.len() || emit.src[hdr_start] != b'[' {
521        return false;
522    }
523    if include_comment_prefix {
524        let (pstart, pend) = find_comment_prefix(emit.src, hdr_start);
525        if pstart != pend {
526            out.extend_from_slice(&emit.src[pstart..pend]);
527        }
528    }
529    let hdr_line_end = line_end_of(emit.src, hdr_start);
530    let hdr_slice = &emit.src[hdr_start..hdr_line_end];
531    out.extend_from_slice(hdr_slice);
532    if !hdr_slice.ends_with(b"\n") {
533        out.push(b'\n');
534    }
535    *cursor = hdr_line_end;
536    true
537}
538
539/// Emits a single AOT element (header + body) without gap/separator handling.
540fn emit_aot_element<'a, 'b, 'de: 'b>(
541    sub_table: &'a Table<'de>,
542    entry: &'a Item<'de>,
543    prefix: &'b Prefix<'b, 'de>,
544    emit: &Emitter<'b, 'de>,
545    out: &mut Vec<u8>,
546    cursor: &mut usize,
547) {
548    if emit_projected_header_line(entry, entry.meta.array_reordered(), emit, out, cursor) {
549        emit_ordered(sub_table, None, Some(prefix), emit, out, cursor);
550        return;
551    }
552
553    // Fallback: formatted header
554    write_aot_header(prefix, emit, out);
555    emit_ordered(sub_table, None, Some(prefix), emit, out, cursor);
556}
557
558/// Returns a sort position from an optional source position.
559/// Projected entries use their position directly.
560/// Unprojected entries inherit the last projected sibling's position,
561/// keeping them adjacent during sorting (collection order breaks ties).
562fn pack_sort_pos(source_pos: Option<u32>, last_projected: &mut u32) -> u32 {
563    if let Some(pos) = source_pos {
564        *last_projected = pos;
565        pos
566    } else {
567        *last_projected
568    }
569}
570
571/// Emits a single body entry (scalar, inline array, frozen table) with
572/// forward gap scanning from the cursor.
573fn emit_body_entry(
574    key: &Key<'_>,
575    item: &Item<'_>,
576    dotted_prefix: Option<&Prefix<'_, '_>>,
577    emit: &Emitter<'_, '_>,
578    out: &mut Vec<u8>,
579    cursor: &mut usize,
580) {
581    // Try source-based emission
582    if !key.span.is_empty() && projected_span(item, emit).is_some() {
583        let key_start = key.span.start as usize;
584        let line_start = line_start_of(emit.src, key_start);
585        let ahead = line_start >= *cursor;
586        if ahead {
587            emit_gap(emit, *cursor, line_start, out);
588        }
589        // Preserve leading whitespace (indentation) before the entry.
590        // Only emit spaces/tabs at the start of the line; for dotted
591        // keys the range line_start..key_start includes the prefix
592        // (e.g. "  z." for key w in "  z.w = 2"), so we stop at the
593        // first non-whitespace byte.
594        let mut ws_len = 0;
595        for &b in &emit.src[line_start..] {
596            if b != b' ' && b != b'\t' {
597                break;
598            }
599            ws_len += 1;
600        }
601        if ws_len > 0 {
602            out.extend_from_slice(&emit.src[line_start..line_start + ws_len]);
603        }
604        if let Some(line_end) = try_emit_entry_from_source(key, item, dotted_prefix, emit, out) {
605            *cursor = if ahead {
606                line_end
607            } else {
608                (*cursor).max(line_end)
609            };
610            return;
611        }
612        // Source emit failed — fall through to formatted output.
613    }
614
615    // Fallback: formatted emission
616    write_dotted_key(dotted_prefix, key, emit, out);
617    out.extend_from_slice(b" = ");
618    format_value(item, emit, out);
619    out.push(b'\n');
620}
621
622/// Emits a table in formatted mode (no source text available).
623/// Body entries first, then subsections — matching the old behavior.
624///
625/// `section_prefix` is the path used for `[header]` lines. Body entries
626/// inside a section always have NO dotted prefix (the header establishes
627/// the context).
628fn emit_formatted(
629    table: &Table<'_>,
630    section_prefix: Option<&Prefix<'_, '_>>,
631    emit: &Emitter<'_, '_>,
632    out: &mut Vec<u8>,
633) {
634    emit_formatted_body(table, None, emit, out);
635    emit_formatted_subsections(table, section_prefix, emit, out);
636}
637
638/// Emits body entries (scalars, inline arrays, frozen tables, dotted chains)
639/// in formatted mode.
640fn emit_formatted_body(
641    table: &Table<'_>,
642    dotted_prefix: Option<&Prefix<'_, '_>>,
643    emit: &Emitter<'_, '_>,
644    out: &mut Vec<u8>,
645) {
646    for (key, item) in table {
647        if item.has_dotted_bit() {
648            let Some(sub_table) = item.as_table() else {
649                continue;
650            };
651            let node = Prefix {
652                name: key.name,
653                key_span: key.span,
654                parent: dotted_prefix,
655            };
656            emit_formatted_body(sub_table, Some(&node), emit, out);
657            continue;
658        }
659        if item.has_header_bit() || item.is_implicit_table() || item.is_aot() {
660            continue;
661        }
662
663        write_dotted_key(dotted_prefix, key, emit, out);
664        out.extend_from_slice(b" = ");
665        format_value(item, emit, out);
666        out.push(b'\n');
667    }
668}
669
670/// Emits subsections (headers, implicit tables, AOTs) in formatted mode.
671fn emit_formatted_subsections(
672    table: &Table<'_>,
673    prefix: Option<&Prefix<'_, '_>>,
674    emit: &Emitter<'_, '_>,
675    out: &mut Vec<u8>,
676) {
677    for (key, item) in table {
678        let node = Prefix {
679            name: key.name,
680            key_span: key.span,
681            parent: prefix,
682        };
683        if item.has_header_bit() {
684            let Some(sub_table) = item.as_table() else {
685                continue;
686            };
687            if !out.is_empty() {
688                out.push(b'\n');
689            }
690            write_section_header(&node, emit, out);
691            emit_formatted(sub_table, Some(&node), emit, out);
692        } else if item.is_implicit_table() || item.has_dotted_bit() {
693            let Some(sub_table) = item.as_table() else {
694                continue;
695            };
696            emit_formatted_subsections(sub_table, Some(&node), emit, out);
697        } else if item.is_aot() {
698            let Some(arr) = item.as_array() else {
699                continue;
700            };
701            for entry in arr {
702                let Some(sub_table) = entry.as_table() else {
703                    continue;
704                };
705                if !out.is_empty() {
706                    out.push(b'\n');
707                }
708                write_aot_header(&node, emit, out);
709                emit_formatted(sub_table, Some(&node), emit, out);
710            }
711        }
712    }
713}
714
715fn write_section_header(prefix: &Prefix<'_, '_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
716    out.push(b'[');
717    write_prefix_path(prefix, emit, out);
718    out.extend_from_slice(b"]\n");
719}
720
721fn write_aot_header(prefix: &Prefix<'_, '_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
722    out.extend_from_slice(b"[[");
723    write_prefix_path(prefix, emit, out);
724    out.extend_from_slice(b"]]\n");
725}
726
727fn write_prefix_path(node: &Prefix<'_, '_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
728    if let Some(parent) = node.parent {
729        write_prefix_path(parent, emit, out);
730        out.push(b'.');
731    }
732    emit_key(node.name, node.key_span, emit, out);
733}
734
735/// Writes a dotted key with optional prefix: `prefix.key`.
736fn write_dotted_key(
737    prefix: Option<&Prefix<'_, '_>>,
738    key: &Key<'_>,
739    emit: &Emitter<'_, '_>,
740    out: &mut Vec<u8>,
741) {
742    if let Some(node) = prefix {
743        write_prefix_path(node, emit, out);
744        out.push(b'.');
745    }
746    emit_key(key.name, key.span, emit, out);
747}
748
749/// Returns the byte offset of the start of the line containing `pos`.
750fn line_start_of(src: &[u8], pos: usize) -> usize {
751    let mut i = pos;
752    while i > 0 && src[i - 1] != b'\n' {
753        i -= 1;
754    }
755    i
756}
757
758/// Scans backwards from a header position to find preceding comment lines
759/// and blank lines that belong to this element. Returns the byte range
760/// `(prefix_start, header_line_start)`. Empty when there are no preceding
761/// comment/blank lines.
762fn find_comment_prefix(src: &[u8], header_pos: usize) -> (usize, usize) {
763    let header_line_start = line_start_of(src, header_pos);
764    let mut prefix_start = header_line_start;
765    let mut cursor = header_line_start;
766    while cursor > 0 {
767        let prev_line_start = line_start_of(src, cursor - 1);
768        let line = &src[prev_line_start..cursor];
769        let mut first_non_ws = None;
770        for &b in line {
771            if b != b' ' && b != b'\t' {
772                first_non_ws = Some(b);
773                break;
774            }
775        }
776        match first_non_ws {
777            Some(b'#') | Some(b'\n') | Some(b'\r') | None => {
778                prefix_start = prev_line_start;
779                cursor = prev_line_start;
780            }
781            _ => break,
782        }
783    }
784    (prefix_start, header_line_start)
785}
786
787fn contains_byte(slice: &[u8], needle: u8) -> bool {
788    for &b in slice {
789        if b == needle {
790            return true;
791        }
792    }
793    false
794}
795
796fn is_all_whitespace(slice: &[u8]) -> bool {
797    for &b in slice {
798        if b != b' ' && b != b'\t' {
799            return false;
800        }
801    }
802    true
803}
804
805/// Scans forward from `pos` past the value, any trailing whitespace and
806/// comment, returning the offset just past the `\n` (or end of source).
807fn line_end_of(src: &[u8], pos: usize) -> usize {
808    let mut i = pos;
809    while i < src.len() && src[i] != b'\n' {
810        i += 1;
811    }
812    if i < src.len() {
813        i + 1 // past the \n
814    } else {
815        i
816    }
817}
818
819/// Emits only blank lines and comment lines from `src[start..end]`.
820///
821/// Skips any line that looks like a TOML entry (key-value pair, header, etc.).
822/// This prevents leaking source entries that aren't present in dest when the
823/// cursor-based gap emission spans over removed entries.
824fn emit_gap(emit: &Emitter<'_, '_>, start: usize, end: usize, out: &mut Vec<u8>) {
825    let mut i = start;
826    while i < end {
827        let line_end = line_end_of(emit.src, i).min(end);
828        // Check the first non-whitespace byte on this line.
829        let mut first_non_ws = None;
830        for &b in &emit.src[i..line_end] {
831            if b != b' ' && b != b'\t' {
832                first_non_ws = Some(b);
833                break;
834            }
835        }
836        match first_non_ws {
837            // Comment line or blank line (only whitespace/newline) — emit it.
838            Some(b'#') | Some(b'\n') | Some(b'\r') | None => {
839                out.extend_from_slice(&emit.src[i..line_end]);
840            }
841            // Anything else is a TOML entry line — skip it.
842            _ => {}
843        }
844        i = line_end;
845    }
846}
847
848/// Checks if trailing text (after a value, before end-of-line) contains a
849/// comma outside of a comment. Commas inside `# ...` comments don't count.
850fn has_trailing_comma(trailing: &[u8]) -> bool {
851    for &b in trailing {
852        if b == b'#' {
853            return false;
854        }
855        if b == b',' {
856            return true;
857        }
858    }
859    false
860}
861
862/// Returns the projected source item's span for an item, if available.
863fn projected_span(item: &Item<'_>, emit: &Emitter<'_, '_>) -> Option<Span> {
864    let span = projected_source(item, emit)?.span_unchecked();
865    if span.is_empty() {
866        return None;
867    }
868    Some(span)
869}
870
871/// Returns the projected source item, if available.
872fn projected_source<'a>(item: &Item<'_>, emit: &Emitter<'a, '_>) -> Option<&'a Item<'a>> {
873    if emit.src.is_empty() {
874        return None;
875    }
876    item.projected(emit.src_items)
877}
878
879/// Tries to emit a body entry line from source text. Returns the line-end
880/// offset on success, or `None` if the entry can't be emitted from source.
881///
882/// Emits: key from source, `source[key_end..val_start]` (preserving ` = `
883/// whitespace), the value via `format_value` (which handles full/partial
884/// container match correctly), then trailing whitespace/comment from source.
885fn try_emit_entry_from_source(
886    key: &Key<'_>,
887    item: &Item<'_>,
888    dotted_prefix: Option<&Prefix<'_, '_>>,
889    emit: &Emitter<'_, '_>,
890    out: &mut Vec<u8>,
891) -> Option<usize> {
892    if emit.src.is_empty() || key.span.is_empty() {
893        return None;
894    }
895    let val_span = projected_span(item, emit)?;
896
897    let key_end = key.span.end as usize;
898    let val_start = val_span.start as usize;
899    let val_end = val_span.end as usize;
900    if key_end > val_start || val_end > emit.src.len() {
901        return None;
902    }
903
904    // Emit the key path (prefix + leaf key) from source spans
905    write_dotted_key(dotted_prefix, key, emit, out);
906    // Emit source from after key to start of value (preserves ` = ` whitespace)
907    out.extend_from_slice(&emit.src[key_end..val_start]);
908    // Emit value via format_value (handles full/partial container match)
909    format_value(item, emit, out);
910    // Scan forward past trailing whitespace/comment to newline
911    let line_end = line_end_of(emit.src, val_end);
912    let trailing = &emit.src[val_end..line_end];
913    out.extend_from_slice(trailing);
914    // Ensure newline-terminated output for idempotency
915    // (source entries at EOF may lack a trailing newline).
916    if !trailing.ends_with(b"\n") {
917        out.push(b'\n');
918    }
919    Some(line_end)
920}
921
922/// Tries to emit a partially-changed multiline array by preserving source
923/// lines for unchanged elements and formatting new/changed elements.
924///
925/// Handles additions (anywhere), removals, and value changes. Unchanged
926/// elements keep their original formatting, whitespace, and trailing comments.
927/// Single-line arrays fall through to `format_array`.
928/// Emits a preserved element from source, including trailing comma fixup.
929/// `from` is the byte position to start copying (usually line_start_of the element).
930/// `val_end` is the byte position past the end of the value.
931fn emit_preserved_with_comma(
932    emit: &Emitter<'_, '_>,
933    from: usize,
934    val_end: usize,
935    out: &mut Vec<u8>,
936) {
937    out.extend_from_slice(&emit.src[from..val_end]);
938    let le = line_end_of(emit.src, val_end);
939    let trailing = &emit.src[val_end..le];
940    if !has_trailing_comma(trailing) {
941        out.push(b',');
942    }
943    out.extend_from_slice(trailing);
944}
945
946fn try_emit_array_partial(
947    dest: &Array<'_>,
948    arr_span: Span,
949    emit: &Emitter<'_, '_>,
950    out: &mut Vec<u8>,
951) -> bool {
952    let arr_start = arr_span.start as usize;
953    let arr_end = arr_span.end as usize;
954    if arr_end == 0 || arr_end > emit.src.len() || emit.src[arr_end - 1] != b']' {
955        return false;
956    }
957
958    // Only handle multiline; single-line falls through to format_array
959    if !contains_byte(&emit.src[arr_start..arr_end], b'\n') {
960        return false;
961    }
962
963    let dest_slice = dest.as_slice();
964    if dest_slice.is_empty() {
965        return false;
966    }
967
968    // Detect indentation from the first projected element's source line
969    let indent = 'indent: {
970        for elem in dest_slice {
971            if let Some(sp) = projected_span(elem, emit) {
972                let elem_start = sp.start as usize;
973                let candidate = &emit.src[line_start_of(emit.src, elem_start)..elem_start];
974                // Element on same line as opening bracket: indent contains
975                // non-whitespace (e.g. key prefix). Bail out to format_array.
976                if !is_all_whitespace(candidate) {
977                    return false;
978                }
979                break 'indent candidate;
980            }
981        }
982        return false; // No projected elements → can't detect formatting
983    };
984
985    // Emit opening: `[` to end of its line
986    out.extend_from_slice(&emit.src[arr_start..line_end_of(emit.src, arr_start)]);
987
988    for elem in dest_slice {
989        if is_fully_projected(elem, emit) {
990            let val_span = projected_span(elem, emit).unwrap();
991            emit_preserved_with_comma(
992                emit,
993                line_start_of(emit.src, val_span.start as usize),
994                val_span.end as usize,
995                out,
996            );
997        } else {
998            out.extend_from_slice(indent);
999            let depth = (indent.len() / emit.indent.width()) as u32;
1000            format_value_at(elem, emit, out, depth, true);
1001            out.extend_from_slice(b",\n");
1002        }
1003    }
1004
1005    // Emit closing `]` with source indentation
1006    let bracket = arr_end - 1;
1007    out.extend_from_slice(&emit.src[line_start_of(emit.src, bracket)..arr_end]);
1008    true
1009}
1010
1011/// Tries to emit a partially-changed multiline inline table by preserving
1012/// source lines for unchanged entries and formatting new/changed entries.
1013///
1014/// Handles additions (anywhere), removals, and value changes. Unchanged
1015/// entries keep their original formatting, whitespace, and trailing comments.
1016/// Single-line inline tables fall through to `format_inline_table`.
1017fn try_emit_inline_table_partial(
1018    dest: &Table<'_>,
1019    tab_span: Span,
1020    emit: &Emitter<'_, '_>,
1021    out: &mut Vec<u8>,
1022) -> bool {
1023    let tab_start = tab_span.start as usize;
1024    let tab_end = tab_span.end as usize;
1025    if tab_end == 0 || tab_end > emit.src.len() || emit.src[tab_end - 1] != b'}' {
1026        return false;
1027    }
1028
1029    // Only handle multiline; single-line falls through to format_inline_table
1030    if !contains_byte(&emit.src[tab_start..tab_end], b'\n') {
1031        return false;
1032    }
1033
1034    let entries = dest.entries();
1035    if entries.is_empty() {
1036        return false;
1037    }
1038
1039    // Detect indentation from the first projected entry's source line
1040    let indent = 'indent: {
1041        for (key, val) in entries {
1042            if !key.span.is_empty() && projected_span(val, emit).is_some() {
1043                let k = key.span.start as usize;
1044                let candidate = &emit.src[line_start_of(emit.src, k)..k];
1045                // Entry on same line as opening brace: indent contains
1046                // non-whitespace. Bail out to format_inline_table.
1047                if !is_all_whitespace(candidate) {
1048                    return false;
1049                }
1050                break 'indent candidate;
1051            }
1052        }
1053        return false; // No projected entries → can't detect formatting
1054    };
1055
1056    // Collect all leaf entries, flattening dotted chains so that
1057    // `b.c.d = 4` and `b.f = [1, 2]` are emitted as separate lines
1058    // rather than collapsed into `b = { c.d = 4, f = [1, 2] }`.
1059    let (leaves, order) = collect_and_sort_leaves(dest, emit.arena);
1060
1061    // Emit opening: `{` to end of its line
1062    out.extend_from_slice(&emit.src[tab_start..line_end_of(emit.src, tab_start)]);
1063
1064    for &entry in &order {
1065        let leaf = &leaves[(entry & 0xFFFF_FFFF) as usize];
1066        let key = leaf.key;
1067        let val = leaf.item;
1068        if !key.span.is_empty() && is_fully_projected(val, emit) {
1069            let val_span = projected_span(val, emit).unwrap();
1070            emit_preserved_with_comma(
1071                emit,
1072                line_start_of(emit.src, key.span.start as usize),
1073                val_span.end as usize,
1074                out,
1075            );
1076        } else {
1077            out.extend_from_slice(indent);
1078            let depth = (indent.len() / emit.indent.width()) as u32;
1079            write_inline_leaf_key(leaf, emit, out);
1080            out.extend_from_slice(b" = ");
1081            format_value_at(val, emit, out, depth, true);
1082            out.extend_from_slice(b",\n");
1083        }
1084    }
1085
1086    // Emit closing `}` with source indentation
1087    let brace = tab_end - 1;
1088    out.extend_from_slice(&emit.src[line_start_of(emit.src, brace)..tab_end]);
1089    true
1090}
1091
1092/// Emits a key, using the original source text when the span is valid.
1093fn emit_key(name: &str, span: Span, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
1094    if !span.is_empty() && !emit.src.is_empty() {
1095        out.extend_from_slice(&emit.src[span.range()]);
1096    } else {
1097        format_key(name, out);
1098    }
1099}
1100
1101/// Returns the original source bytes for a projected scalar item,
1102/// or `None` if projection is unavailable or the item is unmatched.
1103fn projected_text<'a>(item: &Item<'_>, emit: &Emitter<'a, '_>) -> Option<&'a [u8]> {
1104    if emit.src.is_empty() {
1105        return None;
1106    }
1107    let src_item = item.projected(emit.src_items)?;
1108    let span = src_item.span_unchecked();
1109    if span.is_empty() {
1110        return None;
1111    }
1112    Some(&emit.src[span.range()])
1113}
1114
1115/// Checks if an item is fully projected — not just container-matched.
1116///
1117/// O(1) full-match check using the flag set during reprojection.
1118fn is_fully_projected(item: &Item<'_>, emit: &Emitter<'_, '_>) -> bool {
1119    if emit.src_items.is_empty() {
1120        return false;
1121    }
1122    item.is_reprojected_full_match()
1123}
1124
1125fn format_value(item: &Item<'_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
1126    format_value_at(item, emit, out, 0, false);
1127}
1128
1129fn format_value_at(
1130    item: &Item<'_>,
1131    emit: &Emitter<'_, '_>,
1132    out: &mut Vec<u8>,
1133    depth: u32,
1134    inline: bool,
1135) {
1136    match item.value() {
1137        Value::Array(arr) => {
1138            if let Some(src_item) = projected_source(item, emit) {
1139                if let Some(src_arr) = src_item.as_array() {
1140                    // AOT spans cover [[header]] lines, not [elem, ...] brackets.
1141                    // Skip source-based emit when source is AOT but dest is inline.
1142                    if src_arr.style() == ArrayStyle::Inline {
1143                        let span = src_item.span_unchecked();
1144                        if is_fully_projected(item, emit) {
1145                            out.extend_from_slice(&emit.src[span.range()]);
1146                            return;
1147                        }
1148                        // Partial match: try preserving unchanged elements from source
1149                        if try_emit_array_partial(arr, span, emit, out) {
1150                            return;
1151                        }
1152                    }
1153                }
1154            }
1155            if arr.is_expanded() {
1156                format_expanded_array(arr, emit, out, depth);
1157            } else {
1158                format_array(arr, emit, out);
1159            }
1160        }
1161        Value::Table(tab) => {
1162            if let Some(src_item) = projected_source(item, emit) {
1163                if let Some(src_tab) = src_item.as_table() {
1164                    // Only inline (frozen) table spans have {…} format.
1165                    // HEADER/IMPLICIT/DOTTED spans are incompatible.
1166                    if src_tab.style() == TableStyle::Inline {
1167                        let span = src_item.span_unchecked();
1168                        if is_fully_projected(item, emit) {
1169                            out.extend_from_slice(&emit.src[span.range()]);
1170                            return;
1171                        }
1172                        // Partial match: try preserving unchanged entries from source
1173                        if try_emit_inline_table_partial(tab, span, emit, out) {
1174                            return;
1175                        }
1176                    }
1177                }
1178            }
1179            format_inline_table(tab, emit, out, depth);
1180        }
1181        _ => {
1182            if let Some(text) = projected_text(item, emit) {
1183                out.extend_from_slice(text);
1184                return;
1185            }
1186            format_scalar(item, inline, out);
1187        }
1188    }
1189}
1190
1191fn format_scalar(item: &Item<'_>, inline: bool, out: &mut Vec<u8>) {
1192    match item.value() {
1193        Value::String(s) => format_string(s, inline, out),
1194        Value::Integer(i) => {
1195            let _ = write!(out, "{i}");
1196        }
1197        Value::Float(f) => format_float(*f, out),
1198        Value::Boolean(b) => out.extend_from_slice(if *b { b"true" } else { b"false" }),
1199        Value::DateTime(dt) => {
1200            let mut buf = MaybeUninit::uninit();
1201            out.extend_from_slice(dt.format(&mut buf).as_bytes());
1202        }
1203        _ => {}
1204    }
1205}
1206
1207/// Leaf entry collected from an inline table for reordering.
1208#[derive(Clone, Copy)]
1209struct InlineLeaf<'a, 'de> {
1210    key: &'a Key<'de>,
1211    item: &'a Item<'de>,
1212    /// Arena-allocated prefix chain from root to leaf's dotted parent.
1213    prefix: &'a [(&'de str, Span)],
1214    /// Source position for sorting (or inherited from last projected sibling).
1215    sort_pos: u32,
1216}
1217
1218/// Arena-allocates a new prefix slice that extends `prefix` with one element.
1219fn arena_extend_prefix<'a, 'de>(
1220    arena: &'a Arena,
1221    prefix: &[(&'de str, Span)],
1222    name: &'de str,
1223    span: Span,
1224) -> &'a [(&'de str, Span)] {
1225    let new_len = prefix.len() + 1;
1226    let byte_size = new_len * std::mem::size_of::<(&str, Span)>();
1227    let ptr = arena.alloc(byte_size);
1228    let slice_ptr = ptr.as_ptr() as *mut (&'de str, Span);
1229    // SAFETY:
1230    // - `byte_size` is `new_len * size_of::<(&str, Span)>()`, so the arena
1231    //   allocation is large enough for `new_len` elements.
1232    // - `(&str, Span)` has align <= 8, matching ALLOC_ALIGN.
1233    // - The copy writes `prefix.len()` elements from the old slice (disjoint
1234    //   from the fresh arena allocation), then one more element at the end.
1235    // - After both writes, all `new_len` elements are initialized.
1236    unsafe {
1237        std::ptr::copy_nonoverlapping(prefix.as_ptr(), slice_ptr, prefix.len());
1238        std::ptr::write(slice_ptr.add(prefix.len()), (name, span));
1239        std::slice::from_raw_parts(slice_ptr as *const (&'de str, Span), new_len)
1240    }
1241}
1242
1243/// Collects all leaf entries from an inline table, recursing through dotted chains.
1244fn collect_inline_leaves<'a, 'de>(
1245    table: &'a Table<'de>,
1246    prefix_chain: &'a [(&'de str, Span)],
1247    leaves: &mut Vec<InlineLeaf<'a, 'de>>,
1248    last_pos: &mut u32,
1249    arena: &'a Arena,
1250) {
1251    for (key, val) in table {
1252        if let Some(sub) = val.as_table() {
1253            if val.has_dotted_bit() || sub.style() == TableStyle::Dotted {
1254                let chain = arena_extend_prefix(arena, prefix_chain, key.name, key.span);
1255                if !key.span.is_empty() {
1256                    *last_pos = key.span.start;
1257                }
1258                collect_inline_leaves(sub, chain, leaves, last_pos, arena);
1259                continue;
1260            }
1261        }
1262        if !key.span.is_empty() {
1263            *last_pos = key.span.start;
1264        }
1265        leaves.push(InlineLeaf {
1266            key,
1267            item: val,
1268            prefix: prefix_chain,
1269            sort_pos: *last_pos,
1270        });
1271    }
1272}
1273
1274/// Writes the prefix chain and leaf key for an inline leaf entry.
1275fn write_inline_leaf_key(leaf: &InlineLeaf<'_, '_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
1276    let mut first = true;
1277    for &(name, span) in leaf.prefix {
1278        if !first {
1279            out.push(b'.');
1280        }
1281        first = false;
1282        emit_key(name, span, emit, out);
1283    }
1284    if !leaf.prefix.is_empty() {
1285        out.push(b'.');
1286    }
1287    emit_key(leaf.key.name, leaf.key.span, emit, out);
1288}
1289
1290/// Single sort monomorphization for all emit ordering.
1291/// Each u64 packs sort-relevant bits in the high bits and the original
1292/// array index in the low 32 bits.
1293fn sort_index(order: &mut [u64]) {
1294    order.sort_unstable();
1295}
1296
1297/// Collects inline leaves from a table, sorts by source position, returns
1298/// (leaves, sorted order indices). The arena must outlive the returned vecs.
1299fn collect_and_sort_leaves<'a, 'de>(
1300    table: &'a Table<'de>,
1301    arena: &'a Arena,
1302) -> (Vec<InlineLeaf<'a, 'de>>, Vec<u64>) {
1303    let mut leaves = Vec::new();
1304    let mut last_pos = 0u32;
1305    collect_inline_leaves(table, &[], &mut leaves, &mut last_pos, arena);
1306    let mut order: Vec<u64> = Vec::with_capacity(leaves.len());
1307    let mut i = 0u64;
1308    for leaf in &leaves {
1309        order.push(((leaf.sort_pos as u64) << 32) | i);
1310        i += 1;
1311    }
1312    sort_index(&mut order);
1313    (leaves, order)
1314}
1315
1316fn format_inline_table(tab: &Table<'_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>, depth: u32) {
1317    if tab.is_empty() {
1318        out.extend_from_slice(b"{}");
1319        return;
1320    }
1321
1322    if !emit.src_items.is_empty() && !tab.meta.ignore_source_order() {
1323        let mut needs_sort = false;
1324        for (k, v) in tab.entries() {
1325            if v.has_dotted_bit() || !k.span.is_empty() {
1326                needs_sort = true;
1327                break;
1328            }
1329            if let Some(t) = v.as_table() {
1330                if t.style() == TableStyle::Dotted {
1331                    needs_sort = true;
1332                    break;
1333                }
1334            }
1335        }
1336        if needs_sort {
1337            let (leaves, order) = collect_and_sort_leaves(tab, emit.arena);
1338            out.extend_from_slice(b"{ ");
1339            let mut first = true;
1340            for &entry in &order {
1341                let leaf = &leaves[(entry & 0xFFFF_FFFF) as usize];
1342                if !first {
1343                    out.extend_from_slice(b", ");
1344                }
1345                first = false;
1346                write_inline_leaf_key(leaf, emit, out);
1347                out.extend_from_slice(b" = ");
1348                format_value_at(leaf.item, emit, out, depth, true);
1349            }
1350            out.extend_from_slice(b" }");
1351            return;
1352        }
1353    }
1354
1355    out.extend_from_slice(b"{ ");
1356    let mut first = true;
1357    for (key, val) in tab {
1358        if let Some(sub) = val.as_table() {
1359            if val.has_dotted_bit() || sub.style() == TableStyle::Dotted {
1360                let node = Prefix {
1361                    name: key.name,
1362                    key_span: key.span,
1363                    parent: None,
1364                };
1365                format_inline_dotted_kv(sub, &node, emit, out, &mut first, depth);
1366                continue;
1367            }
1368        }
1369        if !first {
1370            out.extend_from_slice(b", ");
1371        }
1372        first = false;
1373        emit_key(key.name, key.span, emit, out);
1374        out.extend_from_slice(b" = ");
1375        format_value_at(val, emit, out, depth, true);
1376    }
1377    out.extend_from_slice(b" }");
1378}
1379
1380fn format_inline_dotted_kv(
1381    table: &Table<'_>,
1382    prefix: &Prefix<'_, '_>,
1383    emit: &Emitter<'_, '_>,
1384    out: &mut Vec<u8>,
1385    first: &mut bool,
1386    depth: u32,
1387) {
1388    for (key, val) in table {
1389        if let Some(sub) = val.as_table() {
1390            if val.has_dotted_bit() || sub.style() == TableStyle::Dotted {
1391                let node = Prefix {
1392                    name: key.name,
1393                    key_span: key.span,
1394                    parent: Some(prefix),
1395                };
1396                format_inline_dotted_kv(sub, &node, emit, out, first, depth);
1397                continue;
1398            }
1399        }
1400        if !*first {
1401            out.extend_from_slice(b", ");
1402        }
1403        *first = false;
1404        write_prefix_path(prefix, emit, out);
1405        out.push(b'.');
1406        emit_key(key.name, key.span, emit, out);
1407        out.extend_from_slice(b" = ");
1408        format_value_at(val, emit, out, depth, true);
1409    }
1410}
1411
1412fn format_expanded_array(arr: &Array<'_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>, depth: u32) {
1413    if arr.is_empty() {
1414        out.extend_from_slice(b"[]");
1415        return;
1416    }
1417    out.extend_from_slice(b"[\n");
1418    let child = depth + 1;
1419    for elem in arr {
1420        emit.indent.write(out, child);
1421        format_value_at(elem, emit, out, child, false);
1422        out.extend_from_slice(b",\n");
1423    }
1424    emit.indent.write(out, depth);
1425    out.push(b']');
1426}
1427
1428fn format_array(arr: &Array<'_>, emit: &Emitter<'_, '_>, out: &mut Vec<u8>) {
1429    if arr.is_empty() {
1430        out.extend_from_slice(b"[]");
1431        return;
1432    }
1433    out.push(b'[');
1434    let mut first = true;
1435    for elem in arr {
1436        if !first {
1437            out.extend_from_slice(b", ");
1438        }
1439        first = false;
1440        format_value_at(elem, emit, out, 0, true);
1441    }
1442    out.push(b']');
1443}
1444
1445fn format_key(name: &str, out: &mut Vec<u8>) {
1446    if is_bare_key(name) {
1447        out.extend_from_slice(name.as_bytes());
1448    } else {
1449        if can_use_literal(name) {
1450            format_literal_string(name, out);
1451        } else {
1452            format_basic_string(name, out);
1453        }
1454    }
1455}
1456
1457fn is_bare_key(name: &str) -> bool {
1458    if name.is_empty() {
1459        return false;
1460    }
1461    for b in name.bytes() {
1462        if !b.is_ascii_alphanumeric() && b != b'-' && b != b'_' {
1463            return false;
1464        }
1465    }
1466    true
1467}
1468
1469fn format_string(s: &str, inline: bool, out: &mut Vec<u8>) {
1470    if !inline && can_use_multiline_literal(s) {
1471        format_multiline_literal_string(s, out);
1472    } else if can_use_literal(s) {
1473        format_literal_string(s, out);
1474    } else {
1475        format_basic_string(s, out);
1476    }
1477}
1478
1479fn can_use_multiline_literal(s: &str) -> bool {
1480    let mut has_newline = false;
1481    let mut consecutive_quotes = 0u8;
1482    for &b in s.as_bytes() {
1483        if b == b'\'' {
1484            consecutive_quotes += 1;
1485            if consecutive_quotes == 3 {
1486                return false;
1487            }
1488        } else {
1489            consecutive_quotes = 0;
1490            match b {
1491                b'\n' => has_newline = true,
1492                b'\t' => {}
1493                b'\r' => return false,
1494                b if b < 0x20 || b == 0x7F => return false,
1495                _ => {}
1496            }
1497        }
1498    }
1499    has_newline
1500}
1501
1502fn can_use_literal(s: &str) -> bool {
1503    let mut has_escapable = false;
1504    for ch in s.chars() {
1505        match ch {
1506            '\'' => return false,
1507            '"' | '\\' => has_escapable = true,
1508            c if c < '\x20' || c == '\x7F' => return false,
1509            _ => {}
1510        }
1511    }
1512    has_escapable
1513}
1514
1515fn format_multiline_literal_string(s: &str, out: &mut Vec<u8>) {
1516    out.extend_from_slice(b"'''\n");
1517    out.extend_from_slice(s.as_bytes());
1518    out.extend_from_slice(b"'''");
1519}
1520
1521fn format_literal_string(s: &str, out: &mut Vec<u8>) {
1522    out.push(b'\'');
1523    out.extend_from_slice(s.as_bytes());
1524    out.push(b'\'');
1525}
1526
1527fn format_basic_string(s: &str, out: &mut Vec<u8>) {
1528    out.push(b'"');
1529    for ch in s.chars() {
1530        match ch {
1531            '"' => out.extend_from_slice(b"\\\""),
1532            '\\' => out.extend_from_slice(b"\\\\"),
1533            '\n' => out.extend_from_slice(b"\\n"),
1534            '\t' => out.extend_from_slice(b"\\t"),
1535            '\r' => out.extend_from_slice(b"\\r"),
1536            '\u{0008}' => out.extend_from_slice(b"\\b"),
1537            '\u{000C}' => out.extend_from_slice(b"\\f"),
1538            c if c < '\x20' || c == '\x7F' => {
1539                let val = c as u32;
1540                let hex = b"0123456789ABCDEF";
1541                out.extend_from_slice(&[
1542                    b'\\',
1543                    b'u',
1544                    b'0',
1545                    b'0',
1546                    hex[(val >> 4) as usize & 0xF],
1547                    hex[val as usize & 0xF],
1548                ]);
1549            }
1550            c => {
1551                let mut buf = [0u8; 4];
1552                out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
1553            }
1554        }
1555    }
1556    out.push(b'"');
1557}
1558
1559fn format_float(f: f64, out: &mut Vec<u8>) {
1560    if f.is_nan() {
1561        out.extend_from_slice(if f.is_sign_positive() {
1562            b"nan"
1563        } else {
1564            b"-nan"
1565        });
1566    } else if f.is_infinite() {
1567        out.extend_from_slice(if f > 0.0 { b"inf" } else { b"-inf" });
1568    } else {
1569        let mut buffer = zmij::Buffer::new();
1570        out.extend_from_slice(buffer.format(f).as_bytes());
1571    }
1572}