blots_core/
ast.rs

1use crate::functions::BuiltInFunction;
2use crate::values::LambdaArg;
3use serde::{Deserialize, Serialize};
4
5/// Represents a source code location for error reporting
6#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
7pub struct Span {
8    pub start_byte: usize,
9    pub end_byte: usize,
10    pub start_line: usize,
11    pub start_col: usize,
12}
13
14impl Span {
15    pub fn new(start_byte: usize, end_byte: usize, start_line: usize, start_col: usize) -> Self {
16        Self {
17            start_byte,
18            end_byte,
19            start_line,
20            start_col,
21        }
22    }
23
24    /// Create a dummy span for cases where location info is unavailable
25    pub fn dummy() -> Self {
26        Self {
27            start_byte: 0,
28            end_byte: 0,
29            start_line: 1,
30            start_col: 1,
31        }
32    }
33}
34
35/// Wrapper type that attaches source location information to any AST node
36///
37/// Note: PartialEq is manually implemented to compare only the node content,
38/// ignoring spans. This ensures structural equality for AST nodes regardless
39/// of their source location.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Spanned<T> {
42    pub node: T,
43    pub span: Span,
44}
45
46impl<T: PartialEq> PartialEq for Spanned<T> {
47    fn eq(&self, other: &Self) -> bool {
48        // Only compare the node content, ignore the span
49        self.node == other.node
50    }
51}
52
53impl<T> Spanned<T> {
54    pub fn new(node: T, span: Span) -> Self {
55        Self { node, span }
56    }
57
58    pub fn dummy(node: T) -> Self {
59        Self {
60            node,
61            span: Span::dummy(),
62        }
63    }
64}
65
66/// Expression with source location information
67pub type SpannedExpr = Spanned<Expr>;
68
69/// Wrapper that attaches leading/trailing comments to any AST node.
70/// Used for preserving comments during formatting while keeping evaluation clean.
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct Commented<T> {
73    /// Comments appearing on lines before this node
74    pub leading: Vec<String>,
75    /// The wrapped node
76    pub node: T,
77    /// Optional inline comment at end of line
78    pub trailing: Option<String>,
79}
80
81impl<T> Commented<T> {
82    /// Create a new Commented wrapper with no comments
83    pub fn new(node: T) -> Self {
84        Self {
85            leading: vec![],
86            node,
87            trailing: None,
88        }
89    }
90
91    /// Create a Commented wrapper with comments
92    pub fn with_comments(leading: Vec<String>, node: T, trailing: Option<String>) -> Self {
93        Self {
94            leading,
95            node,
96            trailing,
97        }
98    }
99
100    /// Check if this node has any comments attached
101    pub fn has_comments(&self) -> bool {
102        !self.leading.is_empty() || self.trailing.is_some()
103    }
104}
105
106impl<T> From<T> for Commented<T> {
107    fn from(node: T) -> Self {
108        Self::new(node)
109    }
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub enum Expr {
114    // Literals
115    Number(f64),
116    String(String),
117    Bool(bool),
118    Null,
119
120    // Variables and functions
121    Identifier(String),
122    InputReference(String),   // Shorthand for inputs.field (e.g., #field)
123    BuiltIn(BuiltInFunction), // Built-in function
124
125    // Collections (elements wrapped in Commented for formatting)
126    List(Vec<Commented<SpannedExpr>>),
127    Record(Vec<Commented<RecordEntry>>),
128
129    // Lambda
130    Lambda {
131        args: Vec<LambdaArg>,
132        body: Box<SpannedExpr>,
133    },
134
135    // Control flow
136    Conditional {
137        condition: Box<SpannedExpr>,
138        then_expr: Box<SpannedExpr>,
139        else_expr: Box<SpannedExpr>,
140    },
141
142    DoBlock {
143        statements: Vec<Commented<SpannedExpr>>,
144        return_expr: Box<Commented<SpannedExpr>>,
145    },
146
147    // Operations
148    Assignment {
149        ident: String,
150        value: Box<SpannedExpr>,
151    },
152
153    Output {
154        expr: Box<SpannedExpr>,
155    },
156
157    Call {
158        func: Box<SpannedExpr>,
159        args: Vec<SpannedExpr>,
160    },
161
162    Access {
163        expr: Box<SpannedExpr>,
164        index: Box<SpannedExpr>,
165    },
166
167    DotAccess {
168        expr: Box<SpannedExpr>,
169        field: String,
170    },
171
172    BinaryOp {
173        op: BinaryOp,
174        left: Box<SpannedExpr>,
175        right: Box<SpannedExpr>,
176    },
177
178    UnaryOp {
179        op: UnaryOp,
180        expr: Box<SpannedExpr>,
181    },
182
183    PostfixOp {
184        op: PostfixOp,
185        expr: Box<SpannedExpr>,
186    },
187
188    // Special
189    Spread(Box<SpannedExpr>),
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct RecordEntry {
194    pub key: RecordKey,
195    pub value: SpannedExpr,
196}
197
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199pub enum RecordKey {
200    Static(String),
201    Dynamic(Box<SpannedExpr>),
202    Shorthand(String),
203    Spread(Box<SpannedExpr>),
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207pub enum BinaryOp {
208    // Arithmetic
209    Add,
210    Subtract,
211    Multiply,
212    Divide,
213    Modulo,
214    Power,
215
216    // Comparison (with broadcasting)
217    Equal,
218    NotEqual,
219    Less,
220    LessEq,
221    Greater,
222    GreaterEq,
223
224    // Comparison (without broadcasting)
225    DotEqual,
226    DotNotEqual,
227    DotLess,
228    DotLessEq,
229    DotGreater,
230    DotGreaterEq,
231
232    // Logical
233    And,
234    NaturalAnd,
235    Or,
236    NaturalOr,
237
238    // Special
239    Via,
240    Into,
241    Where,
242    Coalesce,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246pub enum UnaryOp {
247    Negate,
248    Not,
249    Invert,
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
253pub enum PostfixOp {
254    Factorial,
255}