Skip to main content

haystack_core/xeto/
ast.rs

1// Xeto AST nodes -- the output of parsing a `.xeto` file.
2
3use std::collections::HashMap;
4
5use crate::kinds::Kind;
6
7/// A slot (child tag) definition within a spec.
8#[derive(Debug, Clone)]
9pub struct SlotDef {
10    /// Slot name.
11    pub name: String,
12    /// Type reference (e.g. `"Str"`, `"Number"`, `"Ref"`).
13    pub type_ref: Option<String>,
14    /// Metadata tags from angle-bracket meta section.
15    pub meta: HashMap<String, Kind>,
16    /// Default value.
17    pub default: Option<Kind>,
18    /// True if this slot is a marker (no type, no value).
19    pub is_marker: bool,
20    /// True if this slot is a query (type is `Query<...>`).
21    pub is_query: bool,
22    /// True if this slot has the `?` suffix (optional).
23    pub is_maybe: bool,
24    /// True if prefixed with `*` (global slot).
25    pub is_global: bool,
26    /// For query slots: the `of` parameter type.
27    pub query_of: Option<String>,
28    /// For query slots: the `via` parameter path.
29    pub query_via: Option<String>,
30    /// For query slots: the `inverse` parameter path.
31    pub query_inverse: Option<String>,
32    /// Nested child slots.
33    pub children: Vec<SlotDef>,
34    /// Doc comment text (collected from `//` comments before this slot).
35    pub doc: String,
36}
37
38impl SlotDef {
39    /// Create a new empty slot definition with the given name.
40    pub fn new(name: impl Into<String>) -> Self {
41        Self {
42            name: name.into(),
43            type_ref: None,
44            meta: HashMap::new(),
45            default: None,
46            is_marker: false,
47            is_query: false,
48            is_maybe: false,
49            is_global: false,
50            query_of: None,
51            query_via: None,
52            query_inverse: None,
53            children: Vec::new(),
54            doc: String::new(),
55        }
56    }
57}
58
59/// A top-level spec (type) definition.
60#[derive(Debug, Clone)]
61pub struct SpecDef {
62    /// Spec name.
63    pub name: String,
64    /// Base type reference (after the `:`).
65    pub base: Option<String>,
66    /// Metadata tags from angle-bracket meta section.
67    pub meta: HashMap<String, Kind>,
68    /// Child slot definitions within the `{ }` body.
69    pub slots: Vec<SlotDef>,
70    /// Doc comment text.
71    pub doc: String,
72    /// Default value.
73    pub default: Option<Kind>,
74}
75
76impl SpecDef {
77    /// Create a new empty spec definition with the given name.
78    pub fn new(name: impl Into<String>) -> Self {
79        Self {
80            name: name.into(),
81            base: None,
82            meta: HashMap::new(),
83            slots: Vec::new(),
84            doc: String::new(),
85            default: None,
86        }
87    }
88}
89
90/// Library pragma at the top of a `.xeto` file.
91#[derive(Debug, Clone)]
92pub struct LibPragma {
93    /// Library name.
94    pub name: String,
95    /// Library version string.
96    pub version: String,
97    /// Doc comment text.
98    pub doc: String,
99    /// Dependent library names.
100    pub depends: Vec<String>,
101    /// Additional metadata tags.
102    pub meta: HashMap<String, Kind>,
103}
104
105/// Parsed representation of a `.xeto` file.
106#[derive(Debug, Clone)]
107pub struct XetoFile {
108    /// Optional library pragma.
109    pub pragma: Option<LibPragma>,
110    /// Top-level spec definitions.
111    pub specs: Vec<SpecDef>,
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn slot_def_new() {
120        let slot = SlotDef::new("discharge");
121        assert_eq!(slot.name, "discharge");
122        assert!(slot.type_ref.is_none());
123        assert!(!slot.is_marker);
124        assert!(!slot.is_query);
125        assert!(!slot.is_maybe);
126        assert!(!slot.is_global);
127        assert!(slot.children.is_empty());
128    }
129
130    #[test]
131    fn spec_def_new() {
132        let spec = SpecDef::new("Ahu");
133        assert_eq!(spec.name, "Ahu");
134        assert!(spec.base.is_none());
135        assert!(spec.slots.is_empty());
136    }
137
138    #[test]
139    fn xeto_file_empty() {
140        let file = XetoFile {
141            pragma: None,
142            specs: vec![],
143        };
144        assert!(file.pragma.is_none());
145        assert!(file.specs.is_empty());
146    }
147}