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