1#[derive(Debug, Clone, PartialEq)]
3pub struct KdlDocument {
4 pub nodes: Vec<KdlNode>,
5}
6
7impl KdlDocument {
8 pub fn nodes(&self) -> &[KdlNode] {
10 &self.nodes
11 }
12}
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct KdlNode {
17 pub ty: Option<String>,
19 pub name: String,
21 pub entries: Vec<KdlEntry>,
23 pub children: Option<Vec<KdlNode>>,
25}
26
27impl KdlNode {
28 pub fn ty(&self) -> Option<&str> {
30 self.ty.as_deref()
31 }
32
33 pub fn name(&self) -> &str {
35 &self.name
36 }
37
38 pub fn entries(&self) -> &[KdlEntry] {
40 &self.entries
41 }
42
43 pub fn children(&self) -> Option<&[KdlNode]> {
45 self.children.as_deref()
46 }
47}
48
49impl KdlNode {
50 pub fn get(&self, key: &str) -> Option<&KdlValue> {
52 self.entries().iter().find_map(|entry| match entry {
53 KdlEntry::Property { key: k, value, .. } if k == key => Some(value),
54 _ => None,
55 })
56 }
57}
58
59impl KdlNode {
60 pub fn first_arg(&self) -> Option<&KdlValue> {
62 self.entries.iter().find_map(|e| match e {
63 KdlEntry::Argument { value, .. } => Some(value),
64 _ => None,
65 })
66 }
67
68 pub fn first_string_arg(&self) -> Option<&str> {
76 self.first_arg().and_then(KdlValue::as_str)
77 }
78
79 pub fn string_args(&self) -> impl Iterator<Item = &str> {
81 self.entries.iter().filter_map(|entry| match entry {
82 KdlEntry::Argument { value, .. } => value.as_str(),
83 _ => None,
84 })
85 }
86
87 pub fn arg_values(&self) -> impl Iterator<Item = &KdlValue> {
89 self.entries.iter().filter_map(|entry| match entry {
90 KdlEntry::Argument { value, .. } => Some(value),
91 _ => None,
92 })
93 }
94
95 pub fn find_child(&self, name: &str) -> Option<&KdlNode> {
104 self.children.as_deref()?.iter().find(|c| c.name == name)
105 }
106
107 pub fn find_children<'s>(&'s self, name: &'s str) -> impl Iterator<Item = &'s KdlNode> + 's {
109 self.children
110 .as_deref()
111 .into_iter()
112 .flat_map(|s| s.iter())
113 .filter(move |c| c.name == name)
114 }
115
116 pub fn string_prop(&self, key: &str) -> Option<&str> {
118 self.get(key).and_then(KdlValue::as_str)
119 }
120
121 pub fn bool_prop(&self, key: &str) -> Option<bool> {
123 self.get(key).and_then(KdlValue::as_bool)
124 }
125
126 pub fn int_prop(&self, key: &str) -> Option<i64> {
128 self.get(key).and_then(KdlValue::as_i64)
129 }
130
131 pub fn string_child_values(&self, child_name: &str) -> Vec<&str> {
133 self.children
134 .as_deref()
135 .into_iter()
136 .flat_map(|s| s.iter())
137 .filter(|c| c.name == child_name)
138 .filter_map(|c| c.first_string_arg())
139 .collect()
140 }
141}
142
143#[derive(Debug, Clone, PartialEq)]
145pub enum KdlEntry {
146 Argument {
147 ty: Option<String>,
148 value: KdlValue,
149 },
150 Property {
151 key: String,
152 ty: Option<String>,
153 value: KdlValue,
154 },
155}
156
157impl KdlEntry {
158 pub fn value(&self) -> &KdlValue {
160 match self {
161 KdlEntry::Argument { value, .. } => value,
162 KdlEntry::Property { value, .. } => value,
163 }
164 }
165
166 pub fn ty(&self) -> Option<&str> {
168 match self {
169 KdlEntry::Argument { ty, .. } | KdlEntry::Property { ty, .. } => ty.as_deref(),
170 }
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub struct KdlSpan {
177 pub start: usize,
179 pub end: usize,
181}
182
183#[derive(Debug, Clone, PartialEq)]
185pub struct SpannedKdlDocument {
186 nodes: Vec<SpannedKdlNode>,
187}
188
189impl SpannedKdlDocument {
190 pub(crate) fn new(nodes: Vec<SpannedKdlNode>) -> Self {
191 Self { nodes }
192 }
193
194 pub fn nodes(&self) -> &[SpannedKdlNode] {
196 &self.nodes
197 }
198
199 pub fn into_document(self) -> KdlDocument {
201 KdlDocument {
202 nodes: self
203 .nodes
204 .into_iter()
205 .map(SpannedKdlNode::into_node)
206 .collect(),
207 }
208 }
209}
210
211#[derive(Debug, Clone, PartialEq)]
213pub struct SpannedKdlNode {
214 span: KdlSpan,
215 ty: Option<String>,
216 name: String,
217 entries: Vec<SpannedKdlEntry>,
218 children: Option<Vec<SpannedKdlNode>>,
219}
220
221impl SpannedKdlNode {
222 pub(crate) fn new(
223 span: KdlSpan,
224 ty: Option<String>,
225 name: String,
226 entries: Vec<SpannedKdlEntry>,
227 children: Option<Vec<SpannedKdlNode>>,
228 ) -> Self {
229 Self {
230 span,
231 ty,
232 name,
233 entries,
234 children,
235 }
236 }
237
238 pub fn span(&self) -> KdlSpan {
240 self.span
241 }
242
243 pub fn ty(&self) -> Option<&str> {
245 self.ty.as_deref()
246 }
247
248 pub fn name(&self) -> &str {
250 &self.name
251 }
252
253 pub fn entries(&self) -> &[SpannedKdlEntry] {
255 &self.entries
256 }
257
258 pub fn children(&self) -> Option<&[SpannedKdlNode]> {
260 self.children.as_deref()
261 }
262
263 pub fn into_node(self) -> KdlNode {
265 KdlNode {
266 ty: self.ty,
267 name: self.name,
268 entries: self
269 .entries
270 .into_iter()
271 .map(SpannedKdlEntry::into_entry)
272 .collect(),
273 children: self
274 .children
275 .map(|nodes| nodes.into_iter().map(SpannedKdlNode::into_node).collect()),
276 }
277 }
278}
279
280#[derive(Debug, Clone, PartialEq)]
282pub struct SpannedKdlEntry {
283 span: KdlSpan,
284 entry: KdlEntry,
285}
286
287impl SpannedKdlEntry {
288 pub(crate) fn new(span: KdlSpan, entry: KdlEntry) -> Self {
289 Self { span, entry }
290 }
291
292 pub fn span(&self) -> KdlSpan {
294 self.span
295 }
296
297 pub fn entry(&self) -> &KdlEntry {
299 &self.entry
300 }
301
302 fn into_entry(self) -> KdlEntry {
303 self.entry
304 }
305}
306
307#[derive(Debug, Clone, PartialEq)]
309pub enum KdlValue {
310 String(String),
311 Number(KdlNumber),
312 Bool(bool),
313 Null,
314}
315
316impl KdlValue {
317 pub fn as_str(&self) -> Option<&str> {
319 match self {
320 KdlValue::String(s) => Some(s),
321 _ => None,
322 }
323 }
324
325 pub fn as_bool(&self) -> Option<bool> {
327 match self {
328 KdlValue::Bool(b) => Some(*b),
329 _ => None,
330 }
331 }
332
333 pub fn as_f64(&self) -> Option<f64> {
335 match self {
336 KdlValue::Number(n) => n.as_f64(),
337 _ => None,
338 }
339 }
340
341 pub fn as_i64(&self) -> Option<i64> {
343 match self {
344 KdlValue::Number(n) => n.as_i64(),
345 _ => None,
346 }
347 }
348}
349
350#[derive(Debug, Clone)]
354pub struct KdlNumber {
355 pub(crate) raw: String,
357 pub(crate) as_i64: Option<i64>,
359 pub(crate) as_f64: Option<f64>,
361}
362
363impl KdlNumber {
364 pub fn raw(&self) -> &str {
366 &self.raw
367 }
368
369 pub fn as_i64(&self) -> Option<i64> {
371 self.as_i64
372 }
373
374 pub fn as_f64(&self) -> Option<f64> {
376 self.as_f64
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
382pub struct NumberError;
383
384impl core::fmt::Display for NumberError {
385 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
386 write!(f, "KDL number raw and interpreted values do not match")
387 }
388}
389
390impl std::error::Error for NumberError {}
391
392impl PartialEq for KdlNumber {
393 fn eq(&self, other: &Self) -> bool {
394 self.raw == other.raw
395 }
396}
397
398#[derive(Debug, Clone, PartialEq)]
400pub struct KdlError {
401 pub(crate) line: usize,
403 pub(crate) col: usize,
405 pub(crate) kind: KdlErrorKind,
407}
408
409impl KdlError {
410 pub fn line(&self) -> usize {
412 self.line
413 }
414
415 pub fn col(&self) -> usize {
417 self.col
418 }
419
420 pub fn kind(&self) -> &KdlErrorKind {
422 &self.kind
423 }
424}
425
426impl core::fmt::Display for KdlError {
427 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
428 write!(f, "{}:{}: {}", self.line, self.col, self.kind)
429 }
430}
431
432#[derive(Debug, Clone, PartialEq)]
434pub enum KdlErrorKind {
435 UnexpectedChar(char),
437 UnexpectedEof,
439 InvalidEscape,
441 InvalidUnicodeEscape,
443 InvalidNumber,
445 DisallowedCodePoint(char),
447 BareKeyword,
449 UnmatchedBlockCommentEnd,
451 UnclosedBlockComment,
453 UnclosedString,
455 InconsistentIndentation,
457 InvalidSlashdash,
459}
460
461impl core::fmt::Display for KdlErrorKind {
462 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
463 match self {
464 Self::UnexpectedChar(c) => write!(f, "unexpected character: {:?}", c),
465 Self::UnexpectedEof => write!(f, "unexpected end of input"),
466 Self::InvalidEscape => write!(f, "invalid escape sequence"),
467 Self::InvalidUnicodeEscape => write!(f, "invalid unicode escape"),
468 Self::InvalidNumber => write!(f, "invalid number literal"),
469 Self::DisallowedCodePoint(c) => {
470 write!(f, "disallowed code point: U+{:04X}", *c as u32)
471 }
472 Self::BareKeyword => write!(f, "bare keyword (use #true, #false, #null, etc.)"),
473 Self::UnmatchedBlockCommentEnd => write!(f, "unmatched */"),
474 Self::UnclosedBlockComment => write!(f, "unclosed block comment"),
475 Self::UnclosedString => write!(f, "unclosed string"),
476 Self::InconsistentIndentation => write!(f, "inconsistent multiline string indentation"),
477 Self::InvalidSlashdash => write!(f, "slashdash in invalid position"),
478 }
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 fn make_number(raw: &str, i: Option<i64>, f: Option<f64>) -> KdlNumber {
487 KdlNumber {
488 raw: raw.to_string(),
489 as_i64: i,
490 as_f64: f,
491 }
492 }
493
494 #[test]
495 fn number_constructor_accepts_matching_components() {
496 assert!(KdlNumber::new("5", Some(5), Some(5.0)).is_ok());
497 assert!(KdlNumber::new("1.0", None, Some(1.0)).is_ok());
498 assert!(KdlNumber::new("#nan", None, Some(f64::NAN)).is_ok());
499 assert!(KdlNumber::new("-0", Some(0), Some(-0.0)).is_ok());
500 assert!(KdlNumber::new("-0.0", None, Some(-0.0)).is_ok());
501 }
502
503 #[test]
504 fn number_constructor_rejects_invalid_raw() {
505 for raw in ["", "0xG", "0x", "0b2", "+"] {
506 assert_eq!(KdlNumber::new(raw, None, None), Err(NumberError));
507 }
508 }
509
510 #[test]
511 fn number_constructor_rejects_mismatched_components() {
512 assert_eq!(KdlNumber::new("5", Some(5), Some(999.0)), Err(NumberError));
513 assert_eq!(KdlNumber::new("5", None, None), Err(NumberError));
514 assert_eq!(KdlNumber::new("-0", Some(0), Some(0.0)), Err(NumberError));
515 assert_eq!(KdlNumber::new("-0.0", None, Some(0.0)), Err(NumberError));
516 }
517
518 #[test]
521 fn as_str_returns_some_for_string() {
522 let v = KdlValue::String("hello".to_string());
523 assert_eq!(v.as_str(), Some("hello"));
524 }
525
526 #[test]
527 fn as_str_returns_none_for_number() {
528 let v = KdlValue::Number(make_number("42", Some(42), Some(42.0)));
529 assert_eq!(v.as_str(), None);
530 }
531
532 #[test]
535 fn as_bool_returns_some_for_bool() {
536 assert_eq!(KdlValue::Bool(true).as_bool(), Some(true));
537 assert_eq!(KdlValue::Bool(false).as_bool(), Some(false));
538 }
539
540 #[test]
541 fn as_bool_returns_none_for_string() {
542 let v = KdlValue::String("true".to_string());
543 assert_eq!(v.as_bool(), None);
544 }
545
546 #[test]
549 fn as_f64_returns_some_for_number() {
550 let v = KdlValue::Number(make_number("2.5", None, Some(2.5)));
551 assert_eq!(v.as_f64(), Some(2.5));
552 }
553
554 #[test]
555 fn as_f64_returns_none_for_bool() {
556 assert_eq!(KdlValue::Bool(true).as_f64(), None);
557 }
558
559 #[test]
562 fn as_i64_returns_some_for_integer() {
563 let v = KdlValue::Number(make_number("42", Some(42), Some(42.0)));
564 assert_eq!(v.as_i64(), Some(42));
565 }
566
567 #[test]
568 fn as_i64_returns_none_for_float_only() {
569 let v = KdlValue::Number(make_number("2.5", None, Some(2.5)));
570 assert_eq!(v.as_i64(), None);
571 }
572
573 #[test]
576 fn entry_value_for_argument() {
577 let entry = KdlEntry::Argument {
578 ty: None,
579 value: KdlValue::String("arg".to_string()),
580 };
581 assert_eq!(entry.value(), &KdlValue::String("arg".to_string()));
582 }
583
584 #[test]
585 fn entry_value_for_property() {
586 let entry = KdlEntry::Property {
587 key: "key".to_string(),
588 ty: None,
589 value: KdlValue::Bool(true),
590 };
591 assert_eq!(entry.value(), &KdlValue::Bool(true));
592 }
593
594 #[test]
597 fn node_get_returns_some_for_existing_key() {
598 let node = KdlNode {
599 ty: None,
600 name: "test".to_string(),
601 entries: vec![
602 KdlEntry::Argument {
603 ty: None,
604 value: KdlValue::String("positional".to_string()),
605 },
606 KdlEntry::Property {
607 key: "color".to_string(),
608 ty: None,
609 value: KdlValue::String("red".to_string()),
610 },
611 ],
612 children: None,
613 };
614 assert_eq!(
615 node.get("color"),
616 Some(&KdlValue::String("red".to_string()))
617 );
618 }
619
620 #[test]
621 fn node_get_returns_none_for_missing_key() {
622 let node = KdlNode {
623 ty: None,
624 name: "test".to_string(),
625 entries: vec![KdlEntry::Argument {
626 ty: None,
627 value: KdlValue::String("positional".to_string()),
628 }],
629 children: None,
630 };
631 assert_eq!(node.get("missing"), None);
632 }
633
634 fn arg(value: KdlValue) -> KdlEntry {
637 KdlEntry::Argument { ty: None, value }
638 }
639
640 fn prop(key: &str, value: KdlValue) -> KdlEntry {
641 KdlEntry::Property {
642 key: key.to_string(),
643 ty: None,
644 value,
645 }
646 }
647
648 fn typed_arg(ty: &str, value: KdlValue) -> KdlEntry {
649 KdlEntry::Argument {
650 ty: Some(ty.to_string()),
651 value,
652 }
653 }
654
655 fn typed_prop(key: &str, ty: &str, value: KdlValue) -> KdlEntry {
656 KdlEntry::Property {
657 key: key.to_string(),
658 ty: Some(ty.to_string()),
659 value,
660 }
661 }
662
663 fn node(name: &str, entries: Vec<KdlEntry>, children: Option<Vec<KdlNode>>) -> KdlNode {
664 KdlNode {
665 ty: None,
666 name: name.to_string(),
667 entries,
668 children,
669 }
670 }
671
672 #[test]
675 fn first_arg_skips_property() {
676 let n = node(
677 "n",
678 vec![
679 prop("k", KdlValue::Bool(true)),
680 arg(KdlValue::String("v".to_string())),
681 ],
682 None,
683 );
684 assert_eq!(n.first_arg(), Some(&KdlValue::String("v".to_string())));
685 }
686
687 #[test]
688 fn first_arg_returns_none_when_no_args() {
689 let n = node("n", vec![prop("k", KdlValue::Bool(true))], None);
690 assert_eq!(n.first_arg(), None);
691 }
692
693 #[test]
694 fn first_string_arg_for_string() {
695 let n = node("n", vec![arg(KdlValue::String("hi".to_string()))], None);
696 assert_eq!(n.first_string_arg(), Some("hi"));
697 }
698
699 #[test]
700 fn first_string_arg_for_non_string() {
701 let n = node(
702 "n",
703 vec![arg(KdlValue::Number(make_number("1", Some(1), Some(1.0))))],
704 None,
705 );
706 assert_eq!(n.first_string_arg(), None);
707 }
708
709 #[test]
712 fn find_child_returns_first_match() {
713 let parent = node(
714 "p",
715 vec![],
716 Some(vec![
717 node("a", vec![arg(KdlValue::String("first".to_string()))], None),
718 node("a", vec![arg(KdlValue::String("second".to_string()))], None),
719 ]),
720 );
721 assert_eq!(
722 parent.find_child("a").map(|c| c.first_string_arg()),
723 Some(Some("first")),
724 );
725 }
726
727 #[test]
728 fn find_child_returns_none_when_no_children() {
729 let n = node("n", vec![], None);
730 assert!(n.find_child("a").is_none());
731 }
732
733 #[test]
734 fn find_children_iterates_all_matches() {
735 let parent = node(
736 "p",
737 vec![],
738 Some(vec![
739 node("a", vec![arg(KdlValue::String("1".to_string()))], None),
740 node("b", vec![], None),
741 node("a", vec![arg(KdlValue::String("2".to_string()))], None),
742 ]),
743 );
744 let collected: Vec<_> = parent.find_children("a").map(|c| c.name.clone()).collect();
745 assert_eq!(collected, vec!["a".to_string(), "a".to_string()]);
746 }
747
748 #[test]
749 fn find_children_empty_for_no_match() {
750 let parent = node("p", vec![], Some(vec![node("a", vec![], None)]));
751 assert_eq!(parent.find_children("z").count(), 0);
752 }
753
754 #[test]
757 fn string_prop_for_string_value() {
758 let n = node(
759 "n",
760 vec![prop("k", KdlValue::String("hello".to_string()))],
761 None,
762 );
763 assert_eq!(n.string_prop("k"), Some("hello"));
764 }
765
766 #[test]
767 fn string_prop_for_wrong_type() {
768 let n = node(
769 "n",
770 vec![prop(
771 "k",
772 KdlValue::Number(make_number("1", Some(1), Some(1.0))),
773 )],
774 None,
775 );
776 assert_eq!(n.string_prop("k"), None);
777 }
778
779 #[test]
780 fn string_prop_missing_key() {
781 let n = node("n", vec![prop("k", KdlValue::Bool(true))], None);
782 assert_eq!(n.string_prop("absent"), None);
783 }
784
785 #[test]
786 fn bool_prop_for_bool_value() {
787 let n = node("n", vec![prop("flag", KdlValue::Bool(true))], None);
788 assert_eq!(n.bool_prop("flag"), Some(true));
789 }
790
791 #[test]
792 fn bool_prop_for_wrong_type() {
793 let n = node(
794 "n",
795 vec![prop("flag", KdlValue::String("true".to_string()))],
796 None,
797 );
798 assert_eq!(n.bool_prop("flag"), None);
799 }
800
801 #[test]
802 fn bool_prop_missing_key() {
803 let n = node("n", vec![], None);
804 assert_eq!(n.bool_prop("absent"), None);
805 }
806
807 #[test]
808 fn int_prop_for_int_value() {
809 let n = node(
810 "n",
811 vec![prop(
812 "count",
813 KdlValue::Number(make_number("42", Some(42), Some(42.0))),
814 )],
815 None,
816 );
817 assert_eq!(n.int_prop("count"), Some(42));
818 }
819
820 #[test]
821 fn int_prop_for_float_only() {
822 let n = node(
823 "n",
824 vec![prop(
825 "x",
826 KdlValue::Number(make_number("2.5", None, Some(2.5))),
827 )],
828 None,
829 );
830 assert_eq!(n.int_prop("x"), None);
831 }
832
833 #[test]
834 fn int_prop_missing_key() {
835 let n = node("n", vec![], None);
836 assert_eq!(n.int_prop("absent"), None);
837 }
838
839 #[test]
842 fn string_child_values_collects_string_args() {
843 let parent = node(
844 "p",
845 vec![],
846 Some(vec![
847 node("item", vec![arg(KdlValue::String("a".to_string()))], None),
848 node("item", vec![arg(KdlValue::String("b".to_string()))], None),
849 node("other", vec![arg(KdlValue::String("z".to_string()))], None),
850 ]),
851 );
852 assert_eq!(parent.string_child_values("item"), vec!["a", "b"]);
853 }
854
855 #[test]
856 fn string_child_values_skips_non_string() {
857 let parent = node(
858 "p",
859 vec![],
860 Some(vec![
861 node("item", vec![arg(KdlValue::String("a".to_string()))], None),
862 node(
863 "item",
864 vec![arg(KdlValue::Number(make_number("1", Some(1), Some(1.0))))],
865 None,
866 ),
867 ]),
868 );
869 assert_eq!(parent.string_child_values("item"), vec!["a"]);
870 }
871
872 #[test]
875 fn entry_ty_for_typed_argument() {
876 let e = typed_arg(
877 "u32",
878 KdlValue::Number(make_number("1", Some(1), Some(1.0))),
879 );
880 assert_eq!(e.ty(), Some("u32"));
881 }
882
883 #[test]
884 fn entry_ty_for_untyped_argument() {
885 let e = arg(KdlValue::Bool(true));
886 assert_eq!(e.ty(), None);
887 }
888
889 #[test]
890 fn entry_ty_for_typed_property() {
891 let e = typed_prop(
892 "k",
893 "i64",
894 KdlValue::Number(make_number("7", Some(7), Some(7.0))),
895 );
896 assert_eq!(e.ty(), Some("i64"));
897 }
898
899 #[test]
900 fn entry_ty_for_untyped_property() {
901 let e = prop("k", KdlValue::Bool(false));
902 assert_eq!(e.ty(), None);
903 }
904}