Skip to main content

gram_codec/
ast.rs

1//! Abstract Syntax Tree (AST) types for gram notation
2//!
3//! The AST provides a language-agnostic, JSON-serializable representation
4//! of the `Pattern<Subject>` structure that gram notation describes.
5//!
6//! # Design Philosophy
7//!
8//! The AST mirrors the `Pattern<Subject>` structure exactly:
9//! - No graph-specific concepts (no "nodes", "edges", "relationships")
10//! - Path notation is already desugared by the parser
11//! - Just patterns and subjects - clean and conceptual
12//!
13//! # Usage
14//!
15//! ```rust
16//! use gram_codec::{parse_to_ast, AstPattern};
17//!
18//! let ast = parse_to_ast("(alice:Person {name: \"Alice\"})")?;
19//! println!("Identity: {}", ast.subject.identity);
20//! println!("Labels: {:?}", ast.subject.labels);
21//! # Ok::<(), Box<dyn std::error::Error>>(())
22//! ```
23//!
24//! # JSON Serialization
25//!
26//! The AST is designed to be JSON-serializable for cross-language use:
27//!
28//! ```rust
29//! use gram_codec::parse_to_ast;
30//! use serde_json;
31//!
32//! let ast = parse_to_ast("(alice:Person)")?;
33//! let json = serde_json::to_string(&ast)?;
34//! # Ok::<(), Box<dyn std::error::Error>>(())
35//! ```
36
37use serde::{Deserialize, Serialize};
38use std::collections::HashMap;
39
40/// Abstract Syntax Tree representation of a Pattern
41///
42/// This is the minimal, language-agnostic output format from pattern-rs.
43/// It mirrors the `Pattern<Subject>` structure exactly.
44///
45/// # Structure
46///
47/// - `subject`: The value of this pattern (identity, labels, properties)
48/// - `elements`: Child patterns (recursive structure)
49///
50/// # Examples
51///
52/// Simple node:
53/// ```json
54/// {
55///   "subject": {
56///     "identity": "alice",
57///     "labels": ["Person"],
58///     "properties": {"name": "Alice"}
59///   },
60///   "elements": []
61/// }
62/// ```
63///
64/// Subject pattern with elements:
65/// ```json
66/// {
67///   "subject": {
68///     "identity": "team",
69///     "labels": ["Team"],
70///     "properties": {}
71///   },
72///   "elements": [
73///     {"subject": {"identity": "alice", ...}, "elements": []},
74///     {"subject": {"identity": "bob", ...}, "elements": []}
75///   ]
76/// }
77/// ```
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct AstPattern {
81    /// The subject (value) of this pattern
82    pub subject: AstSubject,
83
84    /// Child patterns (elements)
85    pub elements: Vec<AstPattern>,
86}
87
88/// Subject data - identity, labels, and properties
89///
90/// The subject provides "information about the elements" in a pattern.
91///
92/// # Examples
93///
94/// Node with identity and label:
95/// ```json
96/// {
97///   "identity": "alice",
98///   "labels": ["Person"],
99///   "properties": {}
100/// }
101/// ```
102///
103/// Anonymous node with properties:
104/// ```json
105/// {
106///   "identity": "",
107///   "labels": [],
108///   "properties": {"name": "Alice", "age": 30}
109/// }
110/// ```
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct AstSubject {
114    /// Identity (empty string if not specified)
115    pub identity: String,
116
117    /// Labels (type tags)
118    pub labels: Vec<String>,
119
120    /// Properties (arbitrary JSON values)
121    ///
122    /// Values use mixed serialization:
123    /// - Simple types: native JSON (string, number, boolean, array, object)
124    /// - Complex types: tagged objects (Symbol, Integer, Decimal, Range, etc.)
125    pub properties: HashMap<String, serde_json::Value>,
126}
127
128impl AstPattern {
129    /// Create an empty pattern
130    ///
131    /// Useful for representing empty gram files or as a placeholder.
132    pub fn empty() -> Self {
133        AstPattern {
134            subject: AstSubject {
135                identity: String::new(),
136                labels: Vec::new(),
137                properties: HashMap::new(),
138            },
139            elements: Vec::new(),
140        }
141    }
142}
143
144// Conversion from Pattern<Subject> to AST
145use pattern_core::{Pattern, Subject, Value};
146
147impl AstPattern {
148    /// Convert from native `Pattern<Subject>` to AST
149    ///
150    /// This is the core conversion function that transforms the Rust
151    /// Pattern structure into a JSON-serializable AST.
152    ///
153    /// # Examples
154    ///
155    /// ```rust
156    /// use gram_codec::AstPattern;
157    /// use pattern_core::{Pattern, Subject, Symbol};
158    /// use std::collections::{HashSet, HashMap};
159    ///
160    /// let subject = Subject {
161    ///     identity: Symbol("alice".to_string()),
162    ///     labels: HashSet::new(),
163    ///     properties: HashMap::new(),
164    /// };
165    /// let pattern = Pattern::point(subject);
166    /// let ast = AstPattern::from_pattern(&pattern);
167    /// assert_eq!(ast.subject.identity, "alice");
168    /// ```
169    pub fn from_pattern(pattern: &Pattern<Subject>) -> Self {
170        let subject = pattern.value();
171
172        AstPattern {
173            subject: AstSubject {
174                identity: subject.identity.0.clone(),
175                labels: subject.labels.iter().cloned().collect(),
176                properties: subject
177                    .properties
178                    .iter()
179                    .map(|(k, v)| (k.clone(), value_to_json(v)))
180                    .collect(),
181            },
182            elements: pattern
183                .elements()
184                .iter()
185                .map(AstPattern::from_pattern)
186                .collect(),
187        }
188    }
189}
190
191/// Convert a Value to JSON using canonical format (aligned with gram-hs)
192///
193/// # Strategy
194///
195/// **Native JSON** (simple, common types):
196/// - VInteger, VDecimal, VBoolean, VString, VArray, VMap
197///
198/// **Tagged objects** (complex, need disambiguation):
199/// - VSymbol, VRange, VMeasurement, VTaggedString
200///
201/// This matches the gram-hs canonical format:
202/// - Numbers (integer/decimal) use native JSON
203/// - Complex types use lowercase type discriminators
204fn value_to_json(value: &Value) -> serde_json::Value {
205    match value {
206        // Native JSON types (simple, common)
207        Value::VInteger(i) => serde_json::Value::Number((*i).into()),
208
209        Value::VDecimal(d) => {
210            // Convert f64 to serde_json::Number
211            serde_json::Number::from_f64(*d)
212                .map(serde_json::Value::Number)
213                .unwrap_or_else(|| serde_json::Value::Null)
214        }
215
216        Value::VBoolean(b) => serde_json::Value::Bool(*b),
217
218        Value::VString(s) => serde_json::Value::String(s.clone()),
219
220        Value::VArray(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
221
222        Value::VMap(map) => serde_json::Value::Object(
223            map.iter()
224                .map(|(k, v)| (k.clone(), value_to_json(v)))
225                .collect(),
226        ),
227
228        // Tagged types (need disambiguation or structure)
229        // Use lowercase type discriminators to match gram-hs canonical format
230        Value::VSymbol(sym) => serde_json::json!({
231            "type": "symbol",
232            "value": sym.clone()
233        }),
234
235        Value::VRange(range) => serde_json::json!({
236            "type": "range",
237            "lower": range.lower,
238            "upper": range.upper
239        }),
240
241        Value::VMeasurement { unit, value } => serde_json::json!({
242            "type": "measurement",
243            "unit": unit,
244            "value": value
245        }),
246
247        Value::VTaggedString { tag, content } => serde_json::json!({
248            "type": "tagged",
249            "tag": tag,
250            "content": content
251        }),
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use pattern_core::{Pattern, Subject, Symbol};
259    use std::collections::{HashMap, HashSet};
260
261    #[test]
262    fn test_empty_pattern() {
263        let pattern = AstPattern::empty();
264        assert_eq!(pattern.subject.identity, "");
265        assert_eq!(pattern.subject.labels.len(), 0);
266        assert_eq!(pattern.subject.properties.len(), 0);
267        assert_eq!(pattern.elements.len(), 0);
268    }
269
270    #[test]
271    fn test_json_serialization() {
272        let pattern = AstPattern {
273            subject: AstSubject {
274                identity: "alice".to_string(),
275                labels: vec!["Person".to_string()],
276                properties: {
277                    let mut props = HashMap::new();
278                    props.insert("name".to_string(), serde_json::json!("Alice"));
279                    props
280                },
281            },
282            elements: vec![],
283        };
284
285        // Serialize to JSON
286        let json = serde_json::to_string(&pattern).unwrap();
287        assert!(json.contains("alice"));
288        assert!(json.contains("Person"));
289
290        // Deserialize back
291        let deserialized: AstPattern = serde_json::from_str(&json).unwrap();
292        assert_eq!(deserialized, pattern);
293    }
294
295    #[test]
296    fn test_nested_patterns() {
297        let child1 = AstPattern {
298            subject: AstSubject {
299                identity: "child1".to_string(),
300                labels: vec![],
301                properties: HashMap::new(),
302            },
303            elements: vec![],
304        };
305
306        let child2 = AstPattern {
307            subject: AstSubject {
308                identity: "child2".to_string(),
309                labels: vec![],
310                properties: HashMap::new(),
311            },
312            elements: vec![],
313        };
314
315        let parent = AstPattern {
316            subject: AstSubject {
317                identity: "parent".to_string(),
318                labels: vec![],
319                properties: HashMap::new(),
320            },
321            elements: vec![child1, child2],
322        };
323
324        assert_eq!(parent.elements.len(), 2);
325        assert_eq!(parent.elements[0].subject.identity, "child1");
326        assert_eq!(parent.elements[1].subject.identity, "child2");
327
328        // Serialize and check it works
329        let json = serde_json::to_string(&parent).unwrap();
330        let deserialized: AstPattern = serde_json::from_str(&json).unwrap();
331        assert_eq!(deserialized.elements.len(), 2);
332    }
333
334    #[test]
335    fn test_from_pattern_simple() {
336        let subject = Subject {
337            identity: Symbol("alice".to_string()),
338            labels: {
339                let mut labels = HashSet::new();
340                labels.insert("Person".to_string());
341                labels
342            },
343            properties: HashMap::new(),
344        };
345        let pattern = Pattern::point(subject);
346
347        let ast = AstPattern::from_pattern(&pattern);
348
349        assert_eq!(ast.subject.identity, "alice");
350        assert_eq!(ast.subject.labels, vec!["Person"]);
351        assert_eq!(ast.elements.len(), 0);
352    }
353
354    #[test]
355    fn test_from_pattern_with_properties() {
356        let subject = Subject {
357            identity: Symbol("alice".to_string()),
358            labels: HashSet::new(),
359            properties: {
360                let mut props = HashMap::new();
361                props.insert("name".to_string(), Value::VString("Alice".to_string()));
362                props.insert("age".to_string(), Value::VInteger(30));
363                props
364            },
365        };
366        let pattern = Pattern::point(subject);
367
368        let ast = AstPattern::from_pattern(&pattern);
369
370        assert_eq!(ast.subject.identity, "alice");
371        assert_eq!(ast.subject.properties.len(), 2);
372
373        // Check native JSON for string
374        assert_eq!(ast.subject.properties.get("name").unwrap(), "Alice");
375
376        // Check native JSON for integer (canonical format)
377        let age_value = ast.subject.properties.get("age").unwrap();
378        assert_eq!(age_value, 30); // Native JSON number, not tagged
379    }
380
381    #[test]
382    fn test_value_serialization_simple_types() {
383        // Integer (native JSON)
384        let v = value_to_json(&Value::VInteger(42));
385        assert_eq!(v, serde_json::json!(42));
386        assert!(v.is_number());
387
388        // Decimal (native JSON)
389        let v = value_to_json(&Value::VDecimal(3.14));
390        assert_eq!(v, serde_json::json!(3.14));
391        assert!(v.is_number());
392
393        // Boolean
394        let v = value_to_json(&Value::VBoolean(true));
395        assert_eq!(v, serde_json::Value::Bool(true));
396
397        // String
398        let v = value_to_json(&Value::VString("hello".to_string()));
399        assert_eq!(v, serde_json::Value::String("hello".to_string()));
400
401        // Array (with native JSON integers)
402        let v = value_to_json(&Value::VArray(vec![Value::VInteger(1), Value::VInteger(2)]));
403        assert!(v.is_array());
404        assert_eq!(v.as_array().unwrap().len(), 2);
405        assert_eq!(v.as_array().unwrap()[0], serde_json::json!(1));
406        assert_eq!(v.as_array().unwrap()[1], serde_json::json!(2));
407    }
408
409    #[test]
410    fn test_value_serialization_tagged_types() {
411        // Symbol (lowercase type discriminator)
412        let v = value_to_json(&Value::VSymbol("user123".to_string()));
413        assert_eq!(v["type"], "symbol");
414        assert_eq!(v["value"], "user123");
415
416        // Range (lowercase type discriminator)
417        let v = value_to_json(&Value::VRange(pattern_core::RangeValue {
418            lower: Some(1.0),
419            upper: Some(10.0),
420        }));
421        assert_eq!(v["type"], "range");
422        assert_eq!(v["lower"], 1.0);
423        assert_eq!(v["upper"], 10.0);
424
425        // Measurement (lowercase type discriminator)
426        let v = value_to_json(&Value::VMeasurement {
427            unit: "cm".to_string(),
428            value: 168.0,
429        });
430        assert_eq!(v["type"], "measurement");
431        assert_eq!(v["value"], 168.0);
432        assert_eq!(v["unit"], "cm");
433
434        // Tagged string (lowercase type discriminator)
435        let v = value_to_json(&Value::VTaggedString {
436            tag: "date".to_string(),
437            content: "2024-01-09".to_string(),
438        });
439        assert_eq!(v["type"], "tagged");
440        assert_eq!(v["tag"], "date");
441        assert_eq!(v["content"], "2024-01-09");
442    }
443
444    #[test]
445    fn test_value_serialization_map() {
446        let mut map = HashMap::new();
447        map.insert("key1".to_string(), Value::VString("value1".to_string()));
448        map.insert("key2".to_string(), Value::VInteger(42));
449
450        let v = value_to_json(&Value::VMap(map));
451
452        assert!(v.is_object());
453        assert_eq!(v["key1"], "value1");
454        // Integer is now native JSON, not tagged
455        assert_eq!(v["key2"], 42);
456    }
457}