1mod error;
21mod query;
22
23pub use error::TomlToEureError;
24pub use query::{TomlToEureDocument, TomlToEureSource};
25
26use eure_document::document::constructor::{DocumentConstructor, Scope};
27use eure_document::identifier::Identifier;
28use eure_document::path::PathSegment;
29use eure_document::source::{
30 ArrayElementSource, BindSource, BindingSource, Comment, EureSource, SectionBody,
31 SourceDocument, SourceKey, SourcePathSegment, Trivia,
32};
33use eure_document::text::{Language, Text};
34use eure_document::value::ObjectKey;
35use eure_document::value::PrimitiveValue;
36use num_bigint::BigInt;
37use toml_parser::decoder::Encoding;
38use toml_parser::decoder::ScalarKind;
39use toml_parser::parser::EventReceiver;
40use toml_parser::{ErrorSink, ParseError, Source, Span};
41
42pub fn to_source_document(toml_str: &str) -> Result<SourceDocument, TomlToEureError> {
48 let source = Source::new(toml_str);
49 let tokens: Vec<_> = source.lex().collect();
50
51 let mut converter = TomlParserConverter::new(source);
52 let mut errors = ErrorCollector::new();
53
54 toml_parser::parser::parse_document(&tokens, &mut converter, &mut errors);
55
56 if let Some(err) = errors.first_error() {
57 return Err(err);
58 }
59
60 converter.finish()
61}
62
63struct ErrorCollector {
65 errors: Vec<TomlToEureError>,
66}
67
68impl ErrorCollector {
69 fn new() -> Self {
70 Self { errors: Vec::new() }
71 }
72
73 fn first_error(&self) -> Option<TomlToEureError> {
74 self.errors.first().cloned()
75 }
76}
77
78impl ErrorSink for ErrorCollector {
79 fn report_error(&mut self, error: ParseError) {
80 self.errors.push(TomlToEureError::ParseError {
81 message: format!("{:?}", error),
82 });
83 }
84}
85
86#[derive(Debug, Clone)]
88enum ValueContext {
89 Root,
91 StdTable {
93 trivia_before: Vec<Trivia>,
95 path: Vec<SourcePathSegment>,
97 bindings: Vec<BindingSource>,
99 scope: Scope,
101 },
102 ArrayTable {
104 trivia_before: Vec<Trivia>,
106 path: Vec<SourcePathSegment>,
108 bindings: Vec<BindingSource>,
110 scope: Scope,
112 },
113 InlineTable {
115 scope: Scope,
117 binding_path: Vec<SourcePathSegment>,
119 },
120 Array {
122 scope: Scope,
124 element_index: usize,
126 binding_path: Vec<SourcePathSegment>,
128 element_sources: Vec<ArrayElementSource>,
130 element_pending_trivia: Vec<Trivia>,
132 is_multiline: bool,
134 last_element_span_end: Option<usize>,
136 },
137}
138
139struct TomlParserConverter<'a> {
141 source: Source<'a>,
143 constructor: DocumentConstructor,
145 sources: Vec<EureSource>,
147
148 context_stack: Vec<ValueContext>,
150
151 current_keys: Vec<(String, Option<Encoding>)>,
153 parsing_key: bool,
155
156 pending_trivia: Vec<Trivia>,
158 saw_newline: bool,
160
161 multiline_arrays: std::collections::HashSet<eure_document::document::NodeId>,
163}
164
165impl<'a> TomlParserConverter<'a> {
166 fn new(source: Source<'a>) -> Self {
167 let sources = vec![EureSource::default()];
169 Self {
170 source,
171 constructor: DocumentConstructor::new(),
172 sources,
173 context_stack: vec![ValueContext::Root],
174 current_keys: Vec::new(),
175 parsing_key: false,
176 pending_trivia: Vec::new(),
177 saw_newline: false,
178 multiline_arrays: std::collections::HashSet::new(),
179 }
180 }
181
182 fn finish(mut self) -> Result<SourceDocument, TomlToEureError> {
183 self.close_current_section();
185 if !self.pending_trivia.is_empty() {
187 self.sources[0].trailing_trivia = std::mem::take(&mut self.pending_trivia);
188 }
189 let mut source_doc = SourceDocument::new(self.constructor.finish(), self.sources);
190 source_doc.multiline_arrays = self.multiline_arrays;
191 Ok(source_doc)
192 }
193
194 fn current_context(&self) -> &ValueContext {
195 self.context_stack.last().unwrap()
196 }
197
198 fn current_context_mut(&mut self) -> &mut ValueContext {
199 self.context_stack.last_mut().unwrap()
200 }
201
202 fn close_current_section(&mut self) {
204 if let Some(context) = self.context_stack.pop() {
205 match context {
206 ValueContext::StdTable {
207 trivia_before,
208 path,
209 bindings,
210 scope,
211 } => {
212 self.constructor.end_scope(scope).expect("scope mismatch");
213
214 self.sources[0]
216 .sections
217 .push(eure_document::source::SectionSource {
218 trivia_before,
219 path,
220 body: SectionBody::Items {
221 value: None,
222 bindings,
223 },
224 trailing_comment: None,
225 });
226 }
227 ValueContext::ArrayTable {
228 trivia_before,
229 path,
230 bindings,
231 scope,
232 } => {
233 self.constructor.end_scope(scope).expect("scope mismatch");
234
235 self.sources[0]
237 .sections
238 .push(eure_document::source::SectionSource {
239 trivia_before,
240 path,
241 body: SectionBody::Items {
242 value: None,
243 bindings,
244 },
245 trailing_comment: None,
246 });
247 }
248 ValueContext::Root => {
249 self.context_stack.push(ValueContext::Root);
251 }
252 _ => {}
253 }
254 }
255 }
256
257 fn decode_key(&self, span: Span, encoding: Option<Encoding>) -> String {
259 let raw = self.source.get(span).expect("valid span");
260 let raw = toml_parser::Raw::new_unchecked(raw.as_str(), encoding, span);
261 let mut output = String::new();
262 let mut errors = ErrorCollector::new();
263 raw.decode_key(&mut output, &mut errors);
264 output
265 }
266
267 fn decode_scalar(&self, span: Span, encoding: Option<Encoding>) -> (ScalarKind, String) {
269 let raw = self.source.get(span).expect("valid span");
270 let raw = toml_parser::Raw::new_unchecked(raw.as_str(), encoding, span);
271 let mut output = String::new();
272 let mut errors = ErrorCollector::new();
273 let kind = raw.decode_scalar(&mut output, &mut errors);
274 (kind, output)
275 }
276
277 fn parse_key(&self, key: &str) -> (SourceKey, PathSegment) {
279 match key.parse::<Identifier>() {
280 Ok(id) => (SourceKey::Ident(id.clone()), PathSegment::Ident(id)),
281 Err(_) => (
282 SourceKey::quoted(key.to_string()),
283 PathSegment::Value(ObjectKey::String(key.to_string())),
284 ),
285 }
286 }
287
288 fn source_path_segment(&self, key: SourceKey) -> SourcePathSegment {
290 SourcePathSegment { key, array: None }
291 }
292
293 fn has_newline_between(&self, start: usize, end: usize) -> bool {
295 if let Some(raw) = self.source.get(Span::new_unchecked(start, end)) {
296 raw.as_str().contains('\n')
297 } else {
298 true
300 }
301 }
302
303 fn bind_value(&mut self, value: PrimitiveValue) {
305 self.constructor
306 .bind_primitive(value)
307 .expect("binding should succeed");
308 }
309
310 fn add_binding(&mut self, path: Vec<SourcePathSegment>, node: eure_document::document::NodeId) {
312 match self.current_context() {
314 ValueContext::InlineTable { .. } | ValueContext::Array { .. } => {
315 return;
317 }
318 _ => {}
319 }
320
321 let trivia_before = std::mem::take(&mut self.pending_trivia);
323 let binding = BindingSource {
324 trivia_before,
325 path,
326 bind: BindSource::Value(node),
327 trailing_comment: None,
328 };
329
330 match self.current_context_mut() {
331 ValueContext::Root => {
332 self.sources[0].bindings.push(binding);
333 }
334 ValueContext::StdTable { bindings, .. } | ValueContext::ArrayTable { bindings, .. } => {
335 bindings.push(binding);
336 }
337 ValueContext::InlineTable { .. } | ValueContext::Array { .. } => {
338 }
340 }
341 }
342
343 fn add_array_binding(
345 &mut self,
346 path: Vec<SourcePathSegment>,
347 node: eure_document::document::NodeId,
348 elements: Vec<ArrayElementSource>,
349 ) {
350 match self.current_context() {
352 ValueContext::InlineTable { .. } | ValueContext::Array { .. } => {
353 return;
355 }
356 _ => {}
357 }
358
359 let trivia_before = std::mem::take(&mut self.pending_trivia);
361 let binding = BindingSource {
362 trivia_before,
363 path,
364 bind: BindSource::Array { node, elements },
365 trailing_comment: None,
366 };
367
368 match self.current_context_mut() {
369 ValueContext::Root => {
370 self.sources[0].bindings.push(binding);
371 }
372 ValueContext::StdTable { bindings, .. } | ValueContext::ArrayTable { bindings, .. } => {
373 bindings.push(binding);
374 }
375 ValueContext::InlineTable { .. } | ValueContext::Array { .. } => {
376 }
378 }
379 }
380
381 fn scalar_to_primitive(
383 &self,
384 kind: ScalarKind,
385 value: &str,
386 encoding: Option<Encoding>,
387 ) -> PrimitiveValue {
388 match kind {
389 ScalarKind::String => {
390 let is_multiline = matches!(
392 encoding,
393 Some(Encoding::MlBasicString) | Some(Encoding::MlLiteralString)
394 );
395
396 if is_multiline {
397 use eure_document::text::SyntaxHint;
400
401 let mut content = value.to_string();
402 if !content.ends_with('\n') {
403 content.push('\n');
404 }
405
406 let syntax_hint = if content.contains("``````") {
408 SyntaxHint::Block6
411 } else if content.contains("`````") {
412 SyntaxHint::Block6
413 } else if content.contains("````") {
414 SyntaxHint::Block5
415 } else if content.contains("```") {
416 SyntaxHint::Block4
417 } else {
418 SyntaxHint::Block3
419 };
420
421 PrimitiveValue::Text(Text {
422 content,
423 language: Language::Implicit,
424 syntax_hint: Some(syntax_hint),
425 })
426 } else {
427 let text = Text::plaintext(value.to_string());
429 PrimitiveValue::Text(text)
430 }
431 }
432 ScalarKind::Boolean(b) => PrimitiveValue::Bool(b),
433 ScalarKind::Integer(_radix) => {
434 let clean: String = value.chars().filter(|c| *c != '_').collect();
436 let parsed = if clean.starts_with("0x") || clean.starts_with("0X") {
437 i64::from_str_radix(&clean[2..], 16)
438 } else if clean.starts_with("0o") || clean.starts_with("0O") {
439 i64::from_str_radix(&clean[2..], 8)
440 } else if clean.starts_with("0b") || clean.starts_with("0B") {
441 i64::from_str_radix(&clean[2..], 2)
442 } else {
443 clean.parse::<i64>()
444 };
445 match parsed {
446 Ok(n) => PrimitiveValue::Integer(BigInt::from(n)),
447 Err(_) => {
448 let n = clean.parse::<BigInt>().unwrap_or_else(|e| {
450 panic!("TOML parser validated integer '{clean}' failed to parse: {e}")
451 });
452 PrimitiveValue::Integer(n)
453 }
454 }
455 }
456 ScalarKind::Float => {
457 let clean: String = value.chars().filter(|c| *c != '_').collect();
458 if clean == "inf" || clean == "+inf" {
459 PrimitiveValue::F64(f64::INFINITY)
460 } else if clean == "-inf" {
461 PrimitiveValue::F64(f64::NEG_INFINITY)
462 } else if clean == "nan" || clean == "+nan" || clean == "-nan" {
463 PrimitiveValue::F64(f64::NAN)
464 } else {
465 let f = clean.parse::<f64>().unwrap_or_else(|e| {
466 panic!("TOML parser validated float '{clean}' failed to parse: {e}")
467 });
468 PrimitiveValue::F64(f)
469 }
470 }
471 ScalarKind::DateTime => {
472 let lang = if value.contains('T') || value.contains(' ') {
474 "datetime"
476 } else if value.contains(':') {
477 "time"
479 } else {
480 "date"
482 };
483 PrimitiveValue::Text(Text::new(value.to_string(), Language::Other(lang.into())))
484 }
485 }
486 }
487}
488
489impl<'a> EventReceiver for TomlParserConverter<'a> {
490 fn std_table_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
491 self.saw_newline = false;
493
494 self.close_current_section();
496
497 self.current_keys.clear();
499 self.parsing_key = true;
500 }
501
502 fn std_table_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
503 let trivia_before = std::mem::take(&mut self.pending_trivia);
505
506 let keys: Vec<_> = self.current_keys.drain(..).collect();
508
509 let path: Vec<SourcePathSegment> = keys
511 .iter()
512 .map(|(key, _)| {
513 let (source_key, _) = self.parse_key(key);
514 self.source_path_segment(source_key)
515 })
516 .collect();
517
518 let scope = self.constructor.begin_scope();
520
521 for seg in &path {
523 let path_seg = match &seg.key {
524 SourceKey::Ident(id) => PathSegment::Ident(id.clone()),
525 SourceKey::String(s, _) => PathSegment::Value(ObjectKey::String(s.clone())),
526 _ => continue,
527 };
528 self.constructor
529 .navigate(path_seg)
530 .expect("navigation should succeed");
531 }
532
533 if self.constructor.current_node().content.is_hole() {
535 self.constructor
536 .bind_empty_map()
537 .expect("binding should succeed");
538 }
539
540 self.context_stack.push(ValueContext::StdTable {
541 trivia_before,
542 path,
543 bindings: Vec::new(),
544 scope,
545 });
546
547 self.parsing_key = false;
548 }
549
550 fn array_table_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
551 self.saw_newline = false;
553
554 self.close_current_section();
556
557 self.current_keys.clear();
559 self.parsing_key = true;
560 }
561
562 fn array_table_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
563 let trivia_before = std::mem::take(&mut self.pending_trivia);
565
566 let keys: Vec<_> = self.current_keys.drain(..).collect();
568 let mut path: Vec<SourcePathSegment> = Vec::new();
569
570 for (i, (key, _)) in keys.iter().enumerate() {
571 let (source_key, _) = self.parse_key(key);
572 let mut seg = self.source_path_segment(source_key);
573 if i == keys.len() - 1 {
575 seg = seg.with_array_push();
576 }
577 path.push(seg);
578 }
579
580 let scope = self.constructor.begin_scope();
582
583 for (i, (key, _)) in keys.iter().enumerate() {
584 let (_, path_seg) = self.parse_key(key);
585 self.constructor
586 .navigate(path_seg)
587 .expect("navigation should succeed");
588
589 if i == keys.len() - 1 {
590 if self.constructor.current_node().content.is_hole() {
592 self.constructor
593 .bind_empty_array()
594 .expect("binding should succeed");
595 }
596 self.constructor
597 .navigate(PathSegment::ArrayIndex(None))
598 .expect("array navigation should succeed");
599 }
600 }
601
602 if self.constructor.current_node().content.is_hole() {
604 self.constructor
605 .bind_empty_map()
606 .expect("binding should succeed");
607 }
608
609 self.context_stack.push(ValueContext::ArrayTable {
610 trivia_before,
611 path,
612 bindings: Vec::new(),
613 scope,
614 });
615
616 self.parsing_key = false;
617 }
618
619 fn inline_table_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) -> bool {
620 let scope = self.constructor.begin_scope();
621
622 let binding_path: Vec<SourcePathSegment> = self
624 .current_keys
625 .iter()
626 .map(|(key, _)| {
627 let (source_key, _) = self.parse_key(key);
628 self.source_path_segment(source_key)
629 })
630 .collect();
631
632 for (key, _) in &self.current_keys {
634 let (_, path_seg) = self.parse_key(key);
635 self.constructor
636 .navigate(path_seg)
637 .expect("navigation should succeed");
638 }
639
640 if let Some(ValueContext::Array {
642 element_index,
643 element_pending_trivia,
644 element_sources,
645 ..
646 }) = self.context_stack.last_mut()
647 {
648 self.constructor
649 .navigate(PathSegment::ArrayIndex(None))
650 .expect("array navigation should succeed");
651
652 let trivia = std::mem::take(element_pending_trivia);
654 let idx = *element_index;
655 element_sources.push(ArrayElementSource {
656 trivia_before: trivia,
657 index: idx,
658 trailing_comment: None,
659 });
660
661 *element_index += 1;
662 self.saw_newline = false;
664 }
665
666 self.constructor
667 .bind_empty_map()
668 .expect("binding should succeed");
669 self.context_stack.push(ValueContext::InlineTable {
670 scope,
671 binding_path,
672 });
673 self.current_keys.clear();
674 true
675 }
676
677 fn inline_table_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
678 if let Some(ValueContext::InlineTable {
679 scope,
680 binding_path,
681 }) = self.context_stack.pop()
682 {
683 let node_id = self.constructor.current_node_id();
684 self.constructor.end_scope(scope).expect("scope mismatch");
685
686 if !binding_path.is_empty() {
688 self.add_binding(binding_path, node_id);
689 }
690 }
691 }
692
693 fn array_open(&mut self, _span: Span, _error: &mut dyn ErrorSink) -> bool {
694 let scope = self.constructor.begin_scope();
695
696 let binding_path: Vec<SourcePathSegment> = self
698 .current_keys
699 .iter()
700 .map(|(key, _)| {
701 let (source_key, _) = self.parse_key(key);
702 self.source_path_segment(source_key)
703 })
704 .collect();
705
706 for (key, _) in &self.current_keys {
708 let (_, path_seg) = self.parse_key(key);
709 self.constructor
710 .navigate(path_seg)
711 .expect("navigation should succeed");
712 }
713
714 if let Some(ValueContext::Array {
717 element_index,
718 element_pending_trivia,
719 element_sources,
720 ..
721 }) = self.context_stack.last_mut()
722 {
723 self.constructor
724 .navigate(PathSegment::ArrayIndex(None))
725 .expect("array navigation should succeed");
726 let trivia = std::mem::take(element_pending_trivia);
727 let idx = *element_index;
728 *element_index += 1;
729 element_sources.push(ArrayElementSource {
731 trivia_before: trivia,
732 index: idx,
733 trailing_comment: None,
734 });
735 self.saw_newline = false;
737 }
738
739 self.constructor
740 .bind_empty_array()
741 .expect("binding should succeed");
742 self.context_stack.push(ValueContext::Array {
743 scope,
744 element_index: 0,
745 binding_path,
746 element_sources: Vec::new(),
747 element_pending_trivia: Vec::new(),
748 is_multiline: false,
749 last_element_span_end: None,
750 });
751 self.current_keys.clear();
752 true
753 }
754
755 fn array_close(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
756 self.saw_newline = false;
758
759 if let Some(ValueContext::Array {
760 scope,
761 binding_path,
762 element_sources,
763 is_multiline,
764 ..
765 }) = self.context_stack.pop()
766 {
767 let node_id = self.constructor.current_node_id();
768 self.constructor.end_scope(scope).expect("scope mismatch");
769
770 if is_multiline {
772 self.multiline_arrays.insert(node_id);
773 }
774
775 if !binding_path.is_empty() {
777 let has_element_trivia = element_sources
779 .iter()
780 .any(|e| !e.trivia_before.is_empty() || e.trailing_comment.is_some());
781
782 if is_multiline || has_element_trivia {
783 self.add_array_binding(binding_path, node_id, element_sources);
784 } else {
785 self.add_binding(binding_path, node_id);
786 }
787 }
788 }
789 }
790
791 fn simple_key(&mut self, span: Span, kind: Option<Encoding>, _error: &mut dyn ErrorSink) {
792 let key = self.decode_key(span, kind);
793 self.current_keys.push((key, kind));
794 }
795
796 fn key_sep(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
797 }
799
800 fn key_val_sep(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
801 self.saw_newline = false;
803
804 self.parsing_key = false;
806 }
807
808 fn scalar(&mut self, span: Span, kind: Option<Encoding>, _error: &mut dyn ErrorSink) {
809 let (scalar_kind, value) = self.decode_scalar(span, kind);
810 let primitive = self.scalar_to_primitive(scalar_kind, &value, kind);
811
812 let path: Vec<SourcePathSegment> = self
814 .current_keys
815 .iter()
816 .map(|(key, _)| {
817 let (source_key, _) = self.parse_key(key);
818 self.source_path_segment(source_key)
819 })
820 .collect();
821
822 let scope = self.constructor.begin_scope();
824 for (key, _) in &self.current_keys {
825 let (_, path_seg) = self.parse_key(key);
826 self.constructor
827 .navigate(path_seg)
828 .expect("navigation should succeed");
829 }
830
831 if let Some(ValueContext::Array {
833 element_index,
834 element_pending_trivia,
835 element_sources,
836 ..
837 }) = self.context_stack.last_mut()
838 {
839 self.constructor
841 .navigate(PathSegment::ArrayIndex(None))
842 .expect("array navigation should succeed");
843
844 let trivia = std::mem::take(element_pending_trivia);
846 let idx = *element_index;
847 element_sources.push(ArrayElementSource {
848 trivia_before: trivia,
849 index: idx,
850 trailing_comment: None,
851 });
852
853 *element_index += 1;
854 self.saw_newline = false;
856 }
857
858 if let Some(ValueContext::Array {
860 last_element_span_end,
861 ..
862 }) = self.context_stack.last_mut()
863 {
864 *last_element_span_end = Some(span.end());
865 }
866
867 let node_id = self.constructor.current_node_id();
868 self.bind_value(primitive);
869 self.constructor.end_scope(scope).expect("scope mismatch");
870
871 if !path.is_empty() {
873 self.add_binding(path, node_id);
874 self.current_keys.clear();
875 }
876 }
877
878 fn value_sep(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
879 if matches!(self.current_context(), ValueContext::InlineTable { .. }) {
881 self.current_keys.clear();
882 }
883 }
884
885 fn comment(&mut self, span: Span, _error: &mut dyn ErrorSink) {
886 if let Some(raw) = self.source.get(span) {
888 let text = raw.as_str();
889 let content = text
891 .strip_prefix('#')
892 .map(|s| s.trim_start().to_string())
893 .unwrap_or_else(|| text.to_string());
894 let comment = Comment::Line(content);
895
896 let is_trailing_comment = if let Some(ValueContext::Array {
898 last_element_span_end: Some(elem_end),
899 element_sources,
900 ..
901 }) = self.context_stack.last()
902 {
903 !element_sources.is_empty() && !self.has_newline_between(*elem_end, span.start())
904 } else {
905 false
906 };
907
908 if is_trailing_comment
909 && let Some(ValueContext::Array {
911 element_sources,
912 last_element_span_end,
913 ..
914 }) = self.context_stack.last_mut()
915 && let Some(last_elem) = element_sources.last_mut()
916 {
917 last_elem.trailing_comment = Some(comment);
918 *last_element_span_end = None; self.saw_newline = false;
920 return;
921 }
922
923 if let Some(ValueContext::Array {
925 element_pending_trivia,
926 ..
927 }) = self.context_stack.last_mut()
928 {
929 element_pending_trivia.push(Trivia::Comment(comment));
930 } else {
931 self.pending_trivia.push(Trivia::Comment(comment));
932 }
933 }
934 self.saw_newline = false;
935 }
936
937 fn whitespace(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
938 }
940
941 fn newline(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
942 if self.saw_newline {
944 let trivia = Trivia::BlankLine;
945
946 if let Some(ValueContext::Array {
948 element_pending_trivia,
949 ..
950 }) = self.context_stack.last_mut()
951 {
952 element_pending_trivia.push(trivia);
953 } else {
954 self.pending_trivia.push(trivia);
955 }
956 }
957
958 if let Some(ValueContext::Array { is_multiline, .. }) = self.context_stack.last_mut() {
960 *is_multiline = true;
961 }
962
963 self.saw_newline = true;
964 }
965
966 fn error(&mut self, _span: Span, _error: &mut dyn ErrorSink) {
967 }
969}
970
971pub use eure_fmt::{build_source_doc, format_source_document};
973
974#[cfg(test)]
975mod tests {
976 use super::*;
977
978 #[test]
979 fn test_simple_key_value() {
980 let toml = r#"key = "value""#;
981 let result = to_source_document(toml);
982 assert!(result.is_ok());
983
984 let source = result.expect("conversion should succeed");
985 assert_eq!(source.root_source().bindings.len(), 1);
986 }
987
988 #[test]
989 fn test_section() {
990 let toml = r#"
991[server]
992host = "localhost"
993port = 8080
994"#;
995 let result = to_source_document(toml);
996 assert!(result.is_ok());
997
998 let source = result.expect("conversion should succeed");
999 assert_eq!(source.root_source().sections.len(), 1);
1001 }
1002
1003 #[test]
1004 fn test_array_of_tables() {
1005 let toml = r#"
1006[[items]]
1007name = "first"
1008
1009[[items]]
1010name = "second"
1011"#;
1012 let result = to_source_document(toml);
1013 assert!(result.is_ok());
1014
1015 let source = result.expect("conversion should succeed");
1016 assert_eq!(source.root_source().sections.len(), 2);
1018 }
1019
1020 #[test]
1021 fn test_interleaved_sections() {
1022 let toml = r#"
1024[[example]]
1025name = "first"
1026
1027[metadata.first]
1028description = "First example"
1029
1030[[example]]
1031name = "second"
1032
1033[metadata.second]
1034description = "Second example"
1035"#;
1036 let result = to_source_document(toml);
1037 assert!(result.is_ok());
1038
1039 let source = result.expect("conversion should succeed");
1040 assert_eq!(source.root_source().sections.len(), 4);
1042 }
1043
1044 #[test]
1045 fn test_quoted_string_key() {
1046 let toml = r#""invalid key with spaces" = "value""#;
1048 let result = to_source_document(toml);
1049 assert!(result.is_ok());
1050
1051 let source_doc = result.unwrap();
1053 let formatted = format_source_document(&source_doc);
1054 assert!(
1055 formatted.contains(r#""invalid key with spaces""#),
1056 "Expected quoted key in output: {}",
1057 formatted
1058 );
1059 }
1060
1061 #[test]
1062 fn test_numeric_key() {
1063 let toml = r#"[features]
10652d = ["value"]"#;
1066 let result = to_source_document(toml);
1067 assert!(result.is_ok());
1068
1069 let source_doc = result.unwrap();
1070 let formatted = format_source_document(&source_doc);
1071 assert!(
1072 formatted.contains(r#""2d""#),
1073 "Expected quoted key in output: {}",
1074 formatted
1075 );
1076 }
1077}