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;