Skip to main content

blockworx/schema/
kdl.rs

1//! A small hand-rolled KDL (v2) parser: source text → a node tree with byte
2//! spans, so the document walker ([`super::decode`]) can attach precise source
3//! locations to its diagnostics. This intentionally covers only the subset the
4//! legacy document format and the tutorial level files use (see the grammar
5//! note on [`parse`]).
6//!
7//! Quarantined (D14 — see [`super`]) and deleted in Phase 7 with the level
8//! format that is its last real consumer.
9
10/// A byte range into the source text (`start..end`), used to build `miette`
11/// source spans.
12pub type Span = std::ops::Range<usize>;
13
14/// A scalar value: a string (quoted or raw), a signed integer, a float, or a
15/// boolean. The document format never uses `null`.
16#[derive(Debug, Clone, PartialEq)]
17pub enum Value {
18    String(String),
19    Int(i64),
20    Float(f64),
21    Bool(bool),
22}
23
24/// A value together with its span (an argument or a property's value).
25#[derive(Debug, Clone, PartialEq)]
26pub struct Entry {
27    pub value: Value,
28    pub span: Span,
29}
30
31/// A `key=value` property.
32#[derive(Debug, Clone, PartialEq)]
33pub struct Prop {
34    pub key: String,
35    pub key_span: Span,
36    pub value: Entry,
37}
38
39/// A KDL node: `name arg* (key=value)* ('{' child* '}')?`.
40#[derive(Debug, Clone, PartialEq)]
41pub struct Node {
42    pub name: String,
43    pub name_span: Span,
44    pub args: Vec<Entry>,
45    pub props: Vec<Prop>,
46    pub children: Vec<Node>,
47    /// The full extent of the node (name through the end of its children block).
48    pub span: Span,
49}
50
51/// A syntax error located at a byte span in the source.
52#[derive(Debug, Clone)]
53pub struct SyntaxError {
54    pub message: String,
55    pub span: Span,
56}
57
58/// Parse a KDL document into its top-level nodes.
59///
60/// Grammar (the subset the document format uses): a document is a sequence of
61/// nodes separated by newlines or `;`. A node is a bare-identifier name followed
62/// by scalar arguments and `key=value` properties (keys are bare identifiers,
63/// values are scalars), optionally followed by a `{ … }` block of child nodes.
64/// Strings are `"…"` (with `\n \t \r \" \\` and `\u{…}` escapes, plus the
65/// KDL v2 whitespace escape: `\` folds away the whitespace after it) or raw
66/// `r#"…"#` (any number of `#`). Numbers are signed integers or floats. `//`
67/// line comments and `/* … */` block comments are skipped. Errors carry a span.
68pub fn parse(src: &str) -> Result<Vec<Node>, SyntaxError> {
69    imp::parse(src)
70}
71
72impl Node {
73    /// The `idx`-th positional argument, if present.
74    pub fn arg(&self, idx: usize) -> Option<&Entry> {
75        self.args.get(idx)
76    }
77    /// The value of property `key`, if present.
78    pub fn prop(&self, key: &str) -> Option<&Prop> {
79        self.props.iter().find(|p| p.key == key)
80    }
81    /// The single child node named `name`, if present.
82    pub fn child(&self, name: &str) -> Option<&Node> {
83        self.children.iter().find(|c| c.name == name)
84    }
85}
86
87impl Value {
88    pub fn as_str(&self) -> Option<&str> {
89        match self {
90            Value::String(s) => Some(s),
91            _ => None,
92        }
93    }
94}
95
96mod imp;