aimx/expressions/
collection.rs

1//! Parsing and evaluation for AIMX collection expressions.
2//!
3//! A collection literal has the form `{ "key" = expr; ... }` and is parsed
4//! into a `Collection` that evaluates to `Value::Collection`, storing keys
5//! mapped to unevaluated `Expression`s. Entries are evaluated lazily when
6//! accessed by the evaluator (e.g. via postfix indexing).
7
8use crate::{
9    aim::{ContextLike, WriterLike, Writer},
10    expressions::{Expression, ExpressionLike, parse_expression},
11    literals::parse_text,
12    values::Value,
13};
14use anyhow::Result;
15use nom::{
16    IResult, Parser,
17    character::complete::{char, multispace0},
18    multi::{separated_list1}, sequence::{delimited, separated_pair},
19};
20use std::{collections::HashMap, fmt, sync::Arc};
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct Collection {
24    // Immutable map of keys to expression thunks.
25    // Wrapped in Arc so Value::Collection clones are cheap (O(1)).
26    pool: Arc<HashMap<Arc<str>, Arc<Expression>>>,
27}
28
29impl Collection {
30    pub fn new(pool: Arc<HashMap<Arc<str>, Arc<Expression>>>) -> Self {
31        Collection { pool }
32    }
33
34    pub fn to_value(&self) -> Arc<Value> {
35        Arc::new(Value::Collection(self.pool.clone()))
36    }
37
38    pub fn print(&self, writer: &mut Writer) {
39        writer.write_char('{');
40        let mut first = true;
41        for (key, expr) in self.pool.iter() {
42            if !first {
43                writer.write_str("; ");
44            }
45            first = false;
46            // Keys are parsed as text literals, so write them quoted in formula/string form
47            writer.write_char('"');
48            writer.write_str(key);
49            writer.write_str("\" = ");
50            expr.print(writer);
51        }
52        writer.write_char('}');
53    }
54}
55
56impl ExpressionLike for Collection {
57    fn evaluate(&self, _context: &mut dyn ContextLike) -> Result<Arc<Value>> {
58        // Lazy: just wrap the expression map as a Value::Collection
59        Ok(self.to_value())
60    }
61
62    /// Return the formula-string representation (round-trippable by the parser).
63    fn to_formula(&self) -> String {
64        let mut writer = Writer::formulizer();
65        self.print(&mut writer);
66        writer.finish()
67    }
68}
69
70impl WriterLike for Collection {
71    fn write(&self, writer: &mut Writer) {
72        self.print(writer);
73    }
74}
75
76impl fmt::Display for Collection {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{}", self.to_stringized())
79    }
80}
81
82fn parse_tuple(input: &str) -> IResult<&str, (Arc<str>, Expression)> {
83    delimited(
84        multispace0,
85        separated_pair(
86            parse_text,
87            delimited(multispace0, char('='), multispace0),
88            parse_expression,
89        ),
90        multispace0
91    ).parse(input)
92}
93
94pub fn parse_collection(input: &str) -> IResult<&str, Collection> {
95    let (input, tuples) = delimited(
96        char('{'),
97        // Parse tuples separated by semicolons with optional trailing semicolon
98        separated_list1(
99            delimited(multispace0, char(';'), multispace0),
100            parse_tuple,
101        ),
102        char('}'),
103    ).parse(input)?;
104
105    // Build HashMap in a more functional way
106    let pool = tuples.into_iter()
107        .map(|(key, expr)| (key, Arc::new(expr)))
108        .collect();
109    
110    Ok((input, Collection::new(Arc::new(pool))))
111}