kdlite/
dom.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! document tree structures, start at [`Document`]
3
4use std::borrow::Cow;
5use std::cell::Cell;
6use std::collections::HashSet;
7use std::convert::Infallible;
8use std::fmt;
9use std::num::FpCategory;
10use std::ops::{Index, IndexMut};
11
12use crate::stream::{Error, Event, Parser};
13use crate::{cow_static, IdentDisplay};
14
15fn maybe_debug<T: fmt::Debug>(value: Option<&T>) -> &dyn fmt::Debug {
16  match value {
17    Some(value) => value,
18    None => &None::<Infallible>,
19  }
20}
21
22/// A `document` or `nodes` element, a container of [`Node`]
23#[derive(Default, Clone, PartialEq, Eq, Hash)]
24pub struct Document<'text> {
25  /// The nodes in this document, in order
26  pub nodes: Vec<Node<'text>>,
27}
28
29impl<'text> Document<'text> {
30  /// Create a document with no children
31  pub fn new() -> Self {
32    Self::default()
33  }
34  /// Convert into an owned value
35  pub fn into_owned(self) -> Document<'static> {
36    Document {
37      nodes: self.nodes.into_iter().map(Node::into_owned).collect(),
38    }
39  }
40  /// Iterator over every node with a particular name
41  pub fn get<'a, 'b>(&'a self, name: &'b str) -> impl Iterator<Item = &'a Node<'text>> + 'b
42  where
43    'text: 'a,
44    'a: 'b,
45  {
46    self.nodes.iter().filter(move |node| node.name() == name)
47  }
48  /// Mutable iterator over every node with a particular name
49  pub fn get_mut<'a, 'b>(&'a mut self, name: &'b str) -> impl Iterator<Item = &'a mut Node<'text>> + 'b
50  where
51    'text: 'a,
52    'a: 'b,
53  {
54    self.nodes.iter_mut().filter(move |node| node.name() == name)
55  }
56  pub fn parse(text: &'text str) -> Result<Self, Error> {
57    Ok(Parser::new(text).collect::<Result<Vec<_>, _>>()?.into_iter().collect())
58  }
59}
60
61impl fmt::Debug for Document<'_> {
62  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63    f.write_str("Document ")?;
64    f.debug_list().entries(&self.nodes).finish()
65  }
66}
67impl fmt::Display for Document<'_> {
68  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69    let mut iter = self.nodes.iter();
70    if let Some(first) = iter.next() {
71      write!(f, "{first}")?;
72      for node in iter {
73        write!(f, "\n{node}")?;
74      }
75    }
76    Ok(())
77  }
78}
79/// Currently panic's if the iterator is invalid, oh well
80impl<'text> FromIterator<Event<'text>> for Document<'text> {
81  fn from_iter<T: IntoIterator<Item = Event<'text>>>(iter: T) -> Self {
82    let mut stack = vec![Document::new()];
83    for event in iter {
84      match event {
85        Event::Node { r#type, name } => {
86          let mut node = Node::new(name);
87          node.set_type_hint(r#type);
88          stack.last_mut().unwrap().nodes.push(node);
89        }
90        Event::Entry { r#type, key, value } => {
91          let mut entry = Entry::new_value(value);
92          entry.set_key(key);
93          entry.set_type_hint(r#type);
94          stack.last_mut().unwrap().nodes.last_mut().unwrap().entries.push(entry);
95        }
96        Event::Begin => stack.push(Document::new()),
97        Event::End => {
98          let children = stack.pop().unwrap();
99          stack.last_mut().unwrap().nodes.last_mut().unwrap().children = Some(children);
100        }
101      }
102    }
103    let document = stack.pop().unwrap();
104    assert!(stack.is_empty(), "invalid iterator stream");
105    document
106  }
107}
108
109/// A `node` element
110#[derive(Clone, PartialEq, Eq, Hash)]
111pub struct Node<'text> {
112  r#type: Option<Cow<'text, str>>,
113  name: Cow<'text, str>,
114  /// The node's entries in order
115  pub entries: Vec<Entry<'text>>,
116  /// The node's child document
117  pub children: Option<Document<'text>>,
118}
119
120impl<'text> Node<'text> {
121  /// Create a new node with a name
122  pub fn new(name: impl Into<Cow<'text, str>>) -> Self {
123    Self {
124      r#type: None,
125      name: name.into(),
126      entries: Vec::new(),
127      children: None,
128    }
129  }
130  /// Convert into an owned value
131  pub fn into_owned(self) -> Node<'static> {
132    Node {
133      r#type: self.r#type.map(cow_static),
134      name: cow_static(self.name),
135      entries: self.entries.into_iter().map(Entry::into_owned).collect(),
136      children: self.children.map(Document::into_owned),
137    }
138  }
139  /// Get the node's name
140  pub fn name(&self) -> &str {
141    &self.name
142  }
143  /// Set the node's name
144  pub fn set_name(&mut self, name: impl Into<Cow<'text, str>>) {
145    self.name = name.into();
146  }
147  /// Get the node's type hint
148  pub fn type_hint(&self) -> Option<&str> {
149    self.r#type.as_deref()
150  }
151  /// Set the node's type hint
152  pub fn set_type_hint(&mut self, r#type: Option<impl Into<Cow<'text, str>>>) {
153    self.r#type = r#type.map(Into::into);
154  }
155  /// Get a specific entry
156  pub fn entry<'key>(&self, key: impl Into<EntryKey<'key>>) -> Option<&Entry<'text>> {
157    key.into().seek(self.entries.iter(), |ent| ent.key.as_deref())
158  }
159  /// Mutably get a specific entry
160  pub fn entry_mut<'key>(&mut self, key: impl Into<EntryKey<'key>>) -> Option<&mut Entry<'text>> {
161    key.into().seek(self.entries.iter_mut(), |ent| ent.key.as_deref())
162  }
163  /// Normalize node to kdl spec:
164  /// - Empty children block gets removed
165  /// - Normalize child nodes
166  /// - Duplicate properties are removed
167  pub fn normalize(&mut self) {
168    if let Some(children) = &mut self.children {
169      if children.nodes.is_empty() {
170        self.children = None;
171      } else {
172        for node in &mut children.nodes {
173          node.normalize();
174        }
175      }
176    }
177    // TODO: this is simply an unlikely string-pointer
178    // consider a real way to get a fake/random string pointer
179    let marker = &"\0temp"[5..];
180    // two-pass approach to remove duplicate props
181    let mut seen = HashSet::new();
182    for entry in self.entries.iter_mut().rev() {
183      if let Some(key) = &mut entry.key {
184        if seen.contains(key) {
185          *key = Cow::Borrowed(marker);
186        } else {
187          seen.insert(&*key);
188        }
189      }
190    }
191    self.entries.retain(|ent| {
192      !ent
193        .key
194        .as_ref()
195        .is_some_and(|key| std::ptr::eq(key.as_ptr(), marker.as_ptr()) && key.is_empty())
196    });
197  }
198}
199
200impl fmt::Debug for Node<'_> {
201  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202    f.debug_struct("Node")
203      .field("type", maybe_debug(self.type_hint().as_ref()))
204      .field("name", &self.name)
205      .field("props", &self.entries)
206      .field("children", maybe_debug(self.children.as_ref()))
207      .finish()
208  }
209}
210impl fmt::Display for Node<'_> {
211  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212    if let Some(r#type) = &self.r#type {
213      write!(f, "({})", IdentDisplay(r#type))?;
214    }
215    fmt::Display::fmt(&IdentDisplay(&self.name), f)?;
216    for entry in &self.entries {
217      write!(f, " {entry}")?;
218    }
219    if let Some(children) = &self.children {
220      // make rust fmt do indents for me
221      struct Children<'this>(&'this Document<'this>, Cell<bool>);
222      impl fmt::Debug for Children<'_> {
223        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224          fmt::Display::fmt(self.0, f)?;
225          // really stupid hack to have debug_set not print the trailing comma
226          // (while not ignoring real errors!)
227          self.1.set(true);
228          Err(fmt::Error)
229        }
230      }
231      struct Block<'this>(&'this Document<'this>);
232      impl fmt::Debug for Block<'_> {
233        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234          let children = Children(self.0, Cell::new(false));
235          let result = f.debug_set().entry(&children).finish();
236          if children.1.get() {
237            Ok(())
238          } else {
239            result
240          }
241        }
242      }
243      f.write_str(" ")?;
244      write!(f, "{:#?}\n}}", Block(children))?;
245    }
246    Ok(())
247  }
248}
249impl<'key, 'text, T: Into<EntryKey<'key>>> Index<T> for Node<'text> {
250  type Output = Entry<'text>;
251  fn index(&self, index: T) -> &Self::Output {
252    let key = index.into();
253    self
254      .entry(key)
255      .unwrap_or_else(|| panic!("Key {key:?} does not exist in node"))
256  }
257}
258impl<'key, 'text, T: Into<EntryKey<'key>>> IndexMut<T> for Node<'text> {
259  fn index_mut(&mut self, index: T) -> &mut Self::Output {
260    let key = index.into();
261    self
262      .entry_mut(key)
263      .unwrap_or_else(|| panic!("Key {key:?} does not exist in node"))
264  }
265}
266
267/// A `prop` or `value` element
268#[derive(Clone, PartialEq, Eq, Hash)]
269pub struct Entry<'text> {
270  key: Option<Cow<'text, str>>,
271  r#type: Option<Cow<'text, str>>,
272  /// The value of this property
273  pub value: Value<'text>,
274}
275
276impl<'text> Entry<'text> {
277  /// Create an entry that represents a plain value
278  pub fn new_value(value: Value<'text>) -> Self {
279    Self {
280      key: None,
281      r#type: None,
282      value,
283    }
284  }
285  /// Create an entry that represents a named property
286  pub fn new_prop(name: impl Into<Cow<'text, str>>, value: Value<'text>) -> Self {
287    Self {
288      key: Some(name.into()),
289      r#type: None,
290      value,
291    }
292  }
293  /// Convert into an owned value
294  pub fn into_owned(self) -> Entry<'static> {
295    Entry {
296      key: self.key.map(cow_static),
297      r#type: self.r#type.map(cow_static),
298      value: self.value.into_owned(),
299    }
300  }
301  /// Get the property's key, if it has one
302  pub fn key(&self) -> Option<&str> {
303    self.key.as_deref()
304  }
305  /// Get the property's key, if it has one
306  pub fn set_key(&mut self, key: Option<impl Into<Cow<'text, str>>>) {
307    self.key = key.map(Into::into);
308  }
309  /// Get the property's type hint
310  pub fn type_hint(&self) -> Option<&str> {
311    self.r#type.as_deref()
312  }
313  /// Set the node's type hint
314  pub fn set_type_hint(&mut self, r#type: Option<impl Into<Cow<'text, str>>>) {
315    self.r#type = r#type.map(Into::into);
316  }
317}
318
319impl fmt::Debug for Entry<'_> {
320  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
321    f.debug_struct("Property")
322      .field("key", &self.key)
323      .field("type", maybe_debug(self.type_hint().as_ref()))
324      .field("value", &self.value)
325      .finish()
326  }
327}
328impl fmt::Display for Entry<'_> {
329  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330    if let Some(key) = &self.key {
331      write!(f, "{}=", IdentDisplay(key))?;
332    }
333    if let Some(r#type) = &self.r#type {
334      write!(f, "({})", IdentDisplay(r#type))?;
335    }
336    fmt::Display::fmt(&self.value, f)
337  }
338}
339impl<'text, K: Into<Cow<'text, str>>, V: Into<Value<'text>>> From<(K, V)> for Entry<'text> {
340  fn from((name, value): (K, V)) -> Self {
341    Self::new_prop(name.into(), value.into())
342  }
343}
344impl<'text, V: Into<Value<'text>>> From<V> for Entry<'text> {
345  fn from(value: V) -> Self {
346    Self::new_value(value.into())
347  }
348}
349
350/// A numeric or textual key to index an [`Entry`] in a [`Node`]
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
352pub enum EntryKey<'text> {
353  Pos(usize),
354  Name(&'text str),
355}
356impl EntryKey<'_> {
357  fn seek<T>(self, mut iter: impl DoubleEndedIterator<Item = T>, name: impl Fn(&T) -> Option<&str>) -> Option<T> {
358    match self {
359      EntryKey::Pos(key) => iter.filter(|ent| name(ent).is_none()).nth(key),
360      // right-most property overrides value
361      EntryKey::Name(key) => iter.rfind(|ent| name(ent) == Some(key)),
362    }
363  }
364}
365impl From<usize> for EntryKey<'_> {
366  fn from(value: usize) -> Self {
367    Self::Pos(value)
368  }
369}
370impl<'text> From<&'text str> for EntryKey<'text> {
371  fn from(value: &'text str) -> Self {
372    Self::Name(value)
373  }
374}
375
376fn norm_float(v: f64) -> u64 {
377  match v.classify() {
378    FpCategory::Nan => u64::MAX,
379    FpCategory::Zero => 0,
380    FpCategory::Infinite | FpCategory::Subnormal | FpCategory::Normal => v.to_bits(),
381  }
382}
383
384/// The value of an [`Entry`]
385#[derive(Clone)]
386pub enum Value<'text> {
387  /// A textual value
388  String(Cow<'text, str>),
389  /// An integer value
390  Integer(i128),
391  /// A floating-point number value
392  Float(f64),
393  /// A boolean value
394  Bool(bool),
395  /// The `#null` value
396  Null,
397}
398
399impl Value<'_> {
400  /// Convert into an owned value
401  pub fn into_owned(self) -> Value<'static> {
402    match self {
403      Self::String(value) => Value::String(cow_static(value)),
404      Self::Integer(value) => Value::Integer(value),
405      Self::Float(value) => Value::Float(value),
406      Self::Bool(value) => Value::Bool(value),
407      Self::Null => Value::Null,
408    }
409  }
410  // TODO: maybe some helper methods?
411}
412
413impl fmt::Debug for Value<'_> {
414  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
415    match self {
416      Self::String(value) => fmt::Debug::fmt(&**value, f),
417      Self::Integer(value) => fmt::Debug::fmt(value, f),
418      Self::Float(value) => fmt::Debug::fmt(value, f),
419      Self::Bool(true) => f.write_str("#true"),
420      Self::Bool(false) => f.write_str("#false"),
421      Self::Null => f.write_str("#null"),
422    }
423  }
424}
425impl fmt::Display for Value<'_> {
426  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
427    match self {
428      Value::String(value) => fmt::Display::fmt(&IdentDisplay(value), f),
429      Value::Integer(value) => fmt::Display::fmt(value, f),
430      Value::Float(value) => match value.classify() {
431        FpCategory::Nan => f.write_str("#nan"),
432        FpCategory::Infinite => f.write_str(if value.is_sign_negative() { "#-inf" } else { "#inf" }),
433        FpCategory::Zero | FpCategory::Subnormal | FpCategory::Normal => {
434          // use debug fmt to ensure that floats get re-parsed as floats
435          fmt::Debug::fmt(&value, f)
436        }
437      },
438      Value::Bool(true) => f.write_str("#true"),
439      Value::Bool(false) => f.write_str("#false"),
440      Value::Null => f.write_str("#null"),
441    }
442  }
443}
444impl PartialEq for Value<'_> {
445  fn eq(&self, other: &Self) -> bool {
446    match (self, other) {
447      (Self::String(l), Self::String(r)) => l == r,
448      (Self::Integer(l), Self::Integer(r)) => l == r,
449      (Self::Float(l), Self::Float(r)) => norm_float(*l) == norm_float(*r),
450      (Self::Bool(l), Self::Bool(r)) => l == r,
451      (Self::Null, Self::Null) => true,
452      _ => false,
453    }
454  }
455}
456impl Eq for Value<'_> {}
457impl std::hash::Hash for Value<'_> {
458  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
459    match self {
460      Value::String(value) => {
461        state.write_u8(0);
462        value.hash(state);
463      }
464      Value::Integer(value) => {
465        state.write_u8(1);
466        value.hash(state);
467      }
468      Value::Float(value) => {
469        state.write_u8(2);
470        norm_float(*value).hash(state);
471      }
472      Value::Bool(value) => {
473        state.write_u8(3);
474        value.hash(state);
475      }
476      Value::Null => {
477        state.write_u8(4);
478      }
479    }
480  }
481}
482impl<'text> From<&'text str> for Value<'text> {
483  fn from(value: &'text str) -> Self {
484    Self::String(Cow::Borrowed(value))
485  }
486}
487impl<'text> From<String> for Value<'text> {
488  fn from(value: String) -> Self {
489    Self::String(Cow::Owned(value))
490  }
491}
492impl<'text> From<f64> for Value<'text> {
493  fn from(value: f64) -> Self {
494    Self::Float(value)
495  }
496}
497impl<'text> From<i128> for Value<'text> {
498  fn from(value: i128) -> Self {
499    Self::Integer(value)
500  }
501}
502impl<'text> From<bool> for Value<'text> {
503  fn from(value: bool) -> Self {
504    Self::Bool(value)
505  }
506}
507impl<'text> From<()> for Value<'text> {
508  fn from((): ()) -> Self {
509    Self::Null
510  }
511}
512impl<'text, T: Into<Value<'text>>> From<Option<T>> for Value<'text> {
513  fn from(value: Option<T>) -> Self {
514    match value {
515      Some(v) => v.into(),
516      _ => Self::Null,
517    }
518  }
519}