Skip to main content

interscript_rs/
tree.rs

1//! Resolved parse tree: the parslet-shaped value tree the parsanol
2//! portable engine produces for the ISC grammar, mirrored into plain
3//! Rust types (`TNode`). Mirrors `parsanol::portable::parslet_transform`
4//! output consumed by `Interscript::Isc::DocumentBuilder` on the Ruby
5//! side.
6
7use parsanol::portable::ast::AstNode;
8use parsanol::portable::AstArena;
9
10#[derive(Debug, Clone)]
11pub enum TNode {
12    Str(String),
13    Array(Vec<TNode>),
14    /// Ordered key/value pairs (a parslet capture hash).
15    Hash(Vec<(String, TNode)>),
16    Nil,
17}
18
19/// Ordered-pair lookup on a capture-hash slice.
20pub fn pget<'a>(pairs: &'a [(String, TNode)], key: &str) -> Option<&'a TNode> {
21    pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
22}
23
24impl TNode {
25    pub fn get(&self, key: &str) -> Option<&TNode> {
26        match self {
27            TNode::Hash(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
28            _ => None,
29        }
30    }
31
32    /// The ordered pairs when this node is a capture hash.
33    pub fn ruby_array_hash(&self) -> Option<&Vec<(String, TNode)>> {
34        match self {
35            TNode::Hash(pairs) => Some(pairs),
36            _ => None,
37        }
38    }
39
40    pub fn has_key(&self, key: &str) -> bool {
41        matches!(self, TNode::Hash(pairs) if pairs.iter().any(|(k, _)| k == key))
42    }
43
44    /// First key of a hash node (dispatch on single-key captures).
45    pub fn single_key(&self) -> Option<&str> {
46        match self {
47            TNode::Hash(pairs) if pairs.len() == 1 => Some(pairs[0].0.as_str()),
48            _ => None,
49        }
50    }
51
52    pub fn as_str(&self) -> Option<&str> {
53        match self {
54            TNode::Str(s) => Some(s),
55            _ => None,
56        }
57    }
58
59    pub fn as_array(&self) -> Option<&[TNode]> {
60        match self {
61            TNode::Array(items) => Some(items),
62            _ => None,
63        }
64    }
65
66    /// Ruby `Array(value)`: nil -> [], scalar -> [v], array -> itself.
67    pub fn ruby_array(&self) -> &[TNode] {
68        match self {
69            TNode::Array(items) => items,
70            TNode::Nil => &[],
71            other => std::slice::from_ref(other),
72        }
73    }
74}
75
76/// Resolve the portable engine's arena-backed AST into owned `TNode`s.
77pub fn resolve(node: &AstNode, arena: &AstArena, input: &str) -> TNode {
78    match node {
79        AstNode::Nil => TNode::Nil,
80        AstNode::InputRef { offset, length } => {
81            let start = *offset as usize;
82            let end = start + *length as usize;
83            TNode::Str(input.get(start..end).unwrap_or_default().to_string())
84        }
85        AstNode::StringRef { pool_index } => {
86            TNode::Str(arena.get_string(*pool_index as usize).to_string())
87        }
88        AstNode::Array { pool_index, length } => TNode::Array(
89            arena
90                .get_array(*pool_index as usize, *length as usize)
91                .iter()
92                .map(|child| resolve(child, arena, input))
93                .collect(),
94        ),
95        AstNode::Hash { pool_index, length } => {
96            let pairs = arena.get_hash_items(*pool_index as usize, *length as usize);
97            TNode::Hash(
98                pairs
99                    .into_iter()
100                    .map(|(k, v)| (k, resolve(&v, arena, input)))
101                    .collect(),
102            )
103        }
104        other => TNode::Str(format!("{other:?}")),
105    }
106}
107
108/// Collect leaf text under a node, concatenating string leaves in
109/// order (used for `run` capture values and raw repetitions).
110pub fn leaf_text(node: &TNode) -> String {
111    match node {
112        TNode::Str(s) => s.clone(),
113        TNode::Array(items) => items.iter().map(leaf_text).collect(),
114        TNode::Hash(pairs) => pairs.iter().map(|(_, v)| leaf_text(v)).collect(),
115        TNode::Nil => String::new(),
116    }
117}