dampen_core/expr/
ast.rs

1use crate::ir::span::Span;
2
3/// A parsed binding expression
4#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5pub struct BindingExpr {
6    pub expr: Expr,
7    pub span: Span,
8}
9
10/// Expression AST node
11#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
12pub enum Expr {
13    /// Field access on the local model: `{field}` or `{model.field.subfield}`
14    FieldAccess(FieldAccessExpr),
15    /// Field access on the shared context: `{shared.field}` or `{shared.field.subfield}`
16    SharedFieldAccess(SharedFieldAccessExpr),
17    MethodCall(MethodCallExpr),
18    BinaryOp(BinaryOpExpr),
19    UnaryOp(UnaryOpExpr),
20    Conditional(ConditionalExpr),
21    Literal(LiteralExpr),
22}
23
24/// Field access path
25#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
26pub struct FieldAccessExpr {
27    pub path: Vec<String>,
28}
29
30/// Shared field access path
31///
32/// Represents access to shared state fields via `{shared.field}` syntax.
33/// The path does NOT include the "shared" prefix (it's implied).
34#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
35pub struct SharedFieldAccessExpr {
36    /// The path segments after "shared." (e.g., `["theme"]` for `{shared.theme}`)
37    pub path: Vec<String>,
38}
39
40/// Method call with arguments
41#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
42pub struct MethodCallExpr {
43    pub receiver: Box<Expr>,
44    pub method: String,
45    pub args: Vec<Expr>,
46}
47
48/// Binary operation
49#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
50pub struct BinaryOpExpr {
51    pub left: Box<Expr>,
52    pub op: BinaryOp,
53    pub right: Box<Expr>,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
57pub enum BinaryOp {
58    Eq,
59    Ne,
60    Lt,
61    Le,
62    Gt,
63    Ge,
64    And,
65    Or,
66    Add,
67    Sub,
68    Mul,
69    Div,
70}
71
72/// Unary operation
73#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
74pub struct UnaryOpExpr {
75    pub op: UnaryOp,
76    pub operand: Box<Expr>,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
80pub enum UnaryOp {
81    Not,
82    Neg,
83}
84
85/// Conditional expression
86#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
87pub struct ConditionalExpr {
88    pub condition: Box<Expr>,
89    pub then_branch: Box<Expr>,
90    pub else_branch: Box<Expr>,
91}
92
93/// Literal value
94#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
95pub enum LiteralExpr {
96    String(String),
97    Integer(i64),
98    Float(f64),
99    Bool(bool),
100}
101
102// ============================================
103// Expr helper methods
104// ============================================
105
106impl Expr {
107    /// Check if this expression accesses shared state.
108    ///
109    /// Returns `true` if this expression or any of its sub-expressions
110    /// reference shared state via `{shared.field}` syntax.
111    ///
112    /// # Examples
113    ///
114    /// ```rust
115    /// use dampen_core::expr::{Expr, SharedFieldAccessExpr, FieldAccessExpr};
116    ///
117    /// // Shared field access
118    /// let shared_expr = Expr::SharedFieldAccess(SharedFieldAccessExpr {
119    ///     path: vec!["theme".to_string()],
120    /// });
121    /// assert!(shared_expr.uses_shared());
122    ///
123    /// // Regular field access
124    /// let regular_expr = Expr::FieldAccess(FieldAccessExpr {
125    ///     path: vec!["count".to_string()],
126    /// });
127    /// assert!(!regular_expr.uses_shared());
128    /// ```
129    pub fn uses_shared(&self) -> bool {
130        match self {
131            Expr::SharedFieldAccess(_) => true,
132            Expr::FieldAccess(_) => false,
133            Expr::Literal(_) => false,
134            Expr::MethodCall(m) => {
135                m.receiver.uses_shared() || m.args.iter().any(|a| a.uses_shared())
136            }
137            Expr::BinaryOp(b) => b.left.uses_shared() || b.right.uses_shared(),
138            Expr::UnaryOp(u) => u.operand.uses_shared(),
139            Expr::Conditional(c) => {
140                c.condition.uses_shared()
141                    || c.then_branch.uses_shared()
142                    || c.else_branch.uses_shared()
143            }
144        }
145    }
146
147    /// Check if this expression accesses the local model.
148    ///
149    /// Returns `true` if this expression or any of its sub-expressions
150    /// reference local model fields via `{field}` syntax.
151    pub fn uses_model(&self) -> bool {
152        match self {
153            Expr::FieldAccess(_) => true,
154            Expr::SharedFieldAccess(_) => false,
155            Expr::Literal(_) => false,
156            Expr::MethodCall(m) => m.receiver.uses_model() || m.args.iter().any(|a| a.uses_model()),
157            Expr::BinaryOp(b) => b.left.uses_model() || b.right.uses_model(),
158            Expr::UnaryOp(u) => u.operand.uses_model(),
159            Expr::Conditional(c) => {
160                c.condition.uses_model() || c.then_branch.uses_model() || c.else_branch.uses_model()
161            }
162        }
163    }
164}
165
166impl BindingExpr {
167    /// Check if this binding expression accesses shared state.
168    ///
169    /// Convenience method that delegates to `Expr::uses_shared()`.
170    pub fn uses_shared(&self) -> bool {
171        self.expr.uses_shared()
172    }
173
174    /// Check if this binding expression accesses the local model.
175    ///
176    /// Convenience method that delegates to `Expr::uses_model()`.
177    pub fn uses_model(&self) -> bool {
178        self.expr.uses_model()
179    }
180}