1use std::collections::{BTreeMap, BTreeSet};
16use std::fmt::{self, Write as _};
17use std::str::FromStr;
18
19use crate::codes::{Op, Role};
20use crate::error::{ParseError, Result};
21use crate::tokenize::{quote_label, quote_value, split_indent, strip_quotes, Tokenizer};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
27pub struct Ref(pub u32);
28
29impl Ref {
30 pub const ROOT: Ref = Ref(0);
32
33 #[must_use]
35 pub const fn is_root(self) -> bool {
36 self.0 == 0
37 }
38}
39
40impl fmt::Display for Ref {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(f, "{}", self.0)
43 }
44}
45
46impl FromStr for Ref {
47 type Err = ParseError;
48 fn from_str(s: &str) -> Result<Self> {
49 s.parse::<u32>()
50 .map(Ref)
51 .map_err(|_| ParseError::InvalidRef { raw: s.to_string() })
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct Node {
58 pub r: Ref,
59 pub role: Role,
60 pub label: String,
61 pub ops: BTreeSet<Op>,
62 pub attrs: BTreeMap<String, String>,
63 pub children: Vec<Node>,
64}
65
66impl Node {
67 #[must_use]
69 pub fn leaf(r: Ref, role: Role, label: impl Into<String>) -> Self {
70 Self {
71 r,
72 role,
73 label: label.into(),
74 ops: BTreeSet::new(),
75 attrs: BTreeMap::new(),
76 children: Vec::new(),
77 }
78 }
79}
80
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
83pub struct Tree {
84 pub roots: Vec<Node>,
85}
86
87impl Tree {
88 #[must_use]
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 #[must_use]
94 pub fn from_root(root: Node) -> Self {
95 Self { roots: vec![root] }
96 }
97
98 #[must_use]
100 pub fn iter(&self) -> TreeIter<'_> {
101 TreeIter::new(self)
102 }
103
104 #[must_use]
108 pub fn encode(&self) -> String {
109 let mut out = String::new();
110 for root in &self.roots {
111 encode_node(root, 0, &mut out);
112 }
113 out
114 }
115
116 pub fn parse(input: &str) -> Result<Self> {
119 let mut entries: Vec<(usize, NodeShell)> = Vec::new();
120 for (idx, raw_line) in input.lines().enumerate() {
121 if raw_line.trim().is_empty() {
122 continue;
123 }
124 let (depth, body) = split_indent(raw_line);
125 let shell = parse_node_line(body, idx + 1)?;
126 entries.push((depth, shell));
127 }
128 Ok(assemble(&entries))
129 }
130}
131
132#[derive(Debug)]
133struct NodeShell {
134 r: Ref,
135 role: Role,
136 label: String,
137 ops: BTreeSet<Op>,
138 attrs: BTreeMap<String, String>,
139}
140
141fn assemble(entries: &[(usize, NodeShell)]) -> Tree {
142 let mut roots: Vec<Node> = Vec::new();
143 let mut stack: Vec<(usize, Node)> = Vec::new();
144
145 for (depth, shell) in entries {
146 while let Some(&(top_depth, _)) = stack.last() {
148 if top_depth >= *depth {
149 let (_, popped) = stack.pop().expect("non-empty");
150 if let Some(parent) = stack.last_mut() {
151 parent.1.children.push(popped);
152 } else {
153 roots.push(popped);
154 }
155 } else {
156 break;
157 }
158 }
159 let node = Node {
160 r: shell.r,
161 role: shell.role,
162 label: shell.label.clone(),
163 ops: shell.ops.clone(),
164 attrs: shell.attrs.clone(),
165 children: Vec::new(),
166 };
167 stack.push((*depth, node));
168 }
169
170 while let Some((_, popped)) = stack.pop() {
171 if let Some(parent) = stack.last_mut() {
172 parent.1.children.push(popped);
173 } else {
174 roots.push(popped);
175 }
176 }
177
178 Tree { roots }
179}
180
181fn encode_node(node: &Node, depth: usize, out: &mut String) {
182 for _ in 0..depth {
183 out.push_str(" ");
184 }
185 write!(out, "{} {} {}", node.r, node.role, quote_label(&node.label))
186 .expect("write to String never fails");
187 if !node.ops.is_empty() {
188 out.push(' ');
189 let mut first = true;
190 for op in &node.ops {
191 if !first {
192 out.push(',');
193 }
194 out.push_str(op.as_str());
195 first = false;
196 }
197 }
198 for (k, v) in &node.attrs {
199 out.push(' ');
200 out.push_str(k);
201 out.push('=');
202 out.push_str("e_value(v));
203 }
204 out.push('\n');
205 for child in &node.children {
206 encode_node(child, depth + 1, out);
207 }
208}
209
210fn parse_node_line(line: &str, line_no: usize) -> Result<NodeShell> {
211 let mut tokens = Tokenizer::new(line);
212 let r_tok = tokens.next().ok_or(ParseError::MalformedLine {
213 line: line_no,
214 message: "missing ref",
215 })?;
216 let role_tok = tokens.next().ok_or(ParseError::MalformedLine {
217 line: line_no,
218 message: "missing role",
219 })?;
220 let label_tok = tokens.next().ok_or(ParseError::MalformedLine {
221 line: line_no,
222 message: "missing label",
223 })?;
224
225 let r: Ref = r_tok.parse()?;
226 let role: Role = role_tok.parse()?;
227 let label = strip_quotes(label_tok).to_string();
228
229 let mut ops = BTreeSet::new();
230 let mut attrs = BTreeMap::new();
231 for tok in tokens {
232 if let Some((k, v)) = tok.split_once('=') {
233 if k.is_empty() {
234 return Err(ParseError::InvalidAttribute { raw: tok.into() });
235 }
236 attrs.insert(k.to_string(), strip_quotes(v).to_string());
237 } else {
238 for piece in tok.split(',') {
239 if piece.is_empty() {
240 return Err(ParseError::MalformedLine {
241 line: line_no,
242 message: "empty op in cluster",
243 });
244 }
245 ops.insert(piece.parse::<Op>()?);
246 }
247 }
248 }
249
250 Ok(NodeShell {
251 r,
252 role,
253 label,
254 ops,
255 attrs,
256 })
257}
258
259impl<'a> IntoIterator for &'a Tree {
260 type Item = &'a Node;
261 type IntoIter = TreeIter<'a>;
262 fn into_iter(self) -> Self::IntoIter {
263 self.iter()
264 }
265}
266
267pub struct TreeIter<'a> {
269 stack: Vec<&'a Node>,
270}
271
272impl<'a> TreeIter<'a> {
273 fn new(tree: &'a Tree) -> Self {
274 let mut stack: Vec<&'a Node> = Vec::new();
275 for root in tree.roots.iter().rev() {
277 stack.push(root);
278 }
279 Self { stack }
280 }
281}
282
283impl<'a> Iterator for TreeIter<'a> {
284 type Item = &'a Node;
285 fn next(&mut self) -> Option<&'a Node> {
286 let node = self.stack.pop()?;
287 for child in node.children.iter().rev() {
288 self.stack.push(child);
289 }
290 Some(node)
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::codes::Op;
298
299 fn sample_tree() -> Tree {
300 let mut p = Node::leaf(Ref(3), Role::P, "This domain is for examples.");
301 p.attrs.insert("lang".into(), "en".into());
302
303 let mut lnk = Node::leaf(Ref(4), Role::Lnk, "More information");
304 lnk.ops.insert(Op::Click);
305 lnk.attrs
306 .insert("href".into(), "https://www.iana.org/".into());
307
308 let mut hd = Node::leaf(Ref(2), Role::Hd, "Example Domain");
309 hd.attrs.insert("level".into(), "1".into());
310
311 let doc = Node {
312 r: Ref(1),
313 role: Role::Doc,
314 label: "Example Domain".into(),
315 ops: BTreeSet::new(),
316 attrs: BTreeMap::new(),
317 children: vec![hd, p, lnk],
318 };
319 Tree::from_root(doc)
320 }
321
322 #[test]
323 fn encode_sample_matches_spec_shape() {
324 let encoded = sample_tree().encode();
325 let expected = "\
3261 doc \"Example Domain\"
327 2 hd \"Example Domain\" level=1
328 3 p \"This domain is for examples.\" lang=en
329 4 lnk \"More information\" click href=https://www.iana.org/
330";
331 assert_eq!(encoded, expected);
332 }
333
334 #[test]
335 fn round_trip_sample() {
336 let t = sample_tree();
337 let s = t.encode();
338 let parsed = Tree::parse(&s).expect("parses");
339 assert_eq!(parsed, t);
340 }
341
342 #[test]
343 fn empty_tree_round_trips() {
344 let empty = Tree::new();
345 assert_eq!(empty.encode(), "");
346 assert_eq!(Tree::parse("").unwrap(), empty);
347 }
348
349 #[test]
350 fn parse_handles_blank_lines() {
351 let s = "\n1 doc empty\n\n";
352 let parsed = Tree::parse(s).unwrap();
353 assert_eq!(parsed.roots.len(), 1);
354 assert_eq!(parsed.roots[0].label, "empty");
355 }
356
357 #[test]
358 fn parse_rejects_unknown_role() {
359 let err = Tree::parse("1 zzz foo").unwrap_err();
360 matches!(err, ParseError::UnknownCode(_));
361 }
362
363 #[test]
364 fn parse_rejects_bad_ref() {
365 let err = Tree::parse("xx doc foo").unwrap_err();
366 matches!(err, ParseError::InvalidRef { .. });
367 }
368
369 #[test]
370 fn ops_cluster_round_trips() {
371 let mut node = Node::leaf(Ref(1), Role::Btn, "Submit");
372 node.ops.insert(Op::Click);
373 node.ops.insert(Op::Focus);
374 node.ops.insert(Op::Hover);
375 let tree = Tree::from_root(node);
376 let s = tree.encode();
377 assert!(s.contains("click,hover,focus"));
379 let back = Tree::parse(&s).unwrap();
380 assert_eq!(back, tree);
381 }
382
383 #[test]
384 fn attribute_with_whitespace_quoted() {
385 let mut node = Node::leaf(Ref(1), Role::Tf, "");
386 node.attrs
387 .insert("placeholder".into(), "Your name here".into());
388 let s = Tree::from_root(node.clone()).encode();
389 assert!(s.contains("placeholder=\"Your name here\""));
390 let back = Tree::parse(&s).unwrap();
391 assert_eq!(back, Tree::from_root(node));
392 }
393
394 #[test]
395 fn iter_pre_order() {
396 let t = sample_tree();
397 let refs: Vec<u32> = t.iter().map(|n| n.r.0).collect();
398 assert_eq!(refs, vec![1, 2, 3, 4]);
399 }
400}