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#[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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35pub enum Indent {
36 Spaces(u8),
38 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#[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
102pub(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
144struct Segment<'a, 'b, 'de> {
146 sort_pos: u32,
148 source_start: u32,
150 is_body: bool,
153 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 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
234fn 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, );
256
257 let order = build_segment_order(&segments, table.meta.ignore_source_order());
258
259 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 *cursor = (*cursor).max(pre_cursor);
278 }
279 }
280 }
281}
282
283fn 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 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 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 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 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 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 if mode == SUBS_ONLY {
427 continue;
428 }
429 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
452fn 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 write_section_header(prefix, emit, out);
470 emit_body_ordered(table, emit, out, cursor);
471}
472
473fn 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, );
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
545fn 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 write_aot_header(prefix, emit, out);
561 emit_ordered(sub_table, None, Some(prefix), emit, out, cursor);
562}
563
564fn 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
577fn 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 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 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 }
620
621 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
628fn 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
644fn 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
676fn 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
741fn 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
787fn 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
796fn 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
843fn 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 } else {
853 i
854 }
855}
856
857fn 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
897fn 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
911fn 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
920fn 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
928fn 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 write_dotted_key(dotted_prefix, key, emit, out);
955 out.extend_from_slice(&emit.src[key_end..val_start]);
957 format_value(item, emit, out);
959 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 if !trailing.ends_with(b"\n") {
966 out.push(b'\n');
967 }
968 Some(line_end)
969}
970
971fn 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 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 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 if !is_all_whitespace(candidate) {
1026 return false;
1027 }
1028 break 'indent candidate;
1029 }
1030 }
1031 return false; };
1033
1034 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 let bracket = arr_end - 1;
1056 out.extend_from_slice(&emit.src[line_start_of(emit.src, bracket)..arr_end]);
1057 true
1058}
1059
1060fn 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 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 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 if !is_all_whitespace(candidate) {
1097 return false;
1098 }
1099 break 'indent candidate;
1100 }
1101 }
1102 return false; };
1104
1105 let (leaves, order) = collect_and_sort_leaves(dest, emit.arena);
1109
1110 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 let brace = tab_end - 1;
1137 out.extend_from_slice(&emit.src[line_start_of(emit.src, brace)..tab_end]);
1138 true
1139}
1140
1141fn 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
1150fn 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
1164fn 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 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 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 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 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#[derive(Clone, Copy)]
1258struct InlineLeaf<'a, 'de> {
1259 key: &'a Key<'de>,
1260 item: &'a Item<'de>,
1261 prefix: &'a [(&'de str, Span)],
1263 sort_pos: u32,
1265}
1266
1267fn 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 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
1292fn 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
1323fn 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
1339fn sort_index(order: &mut [u64]) {
1343 order.sort_unstable();
1344}
1345
1346fn 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}