Skip to main content

microcad_lang_parse/ast/
expression.rs

1// Copyright © 2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use microcad_lang_base::Spanned;
5
6use crate::ast;
7use crate::ast::Span;
8use std::num::ParseIntError;
9
10/// The type of the operator for binary operations
11#[derive(Debug, PartialEq, Clone)]
12#[allow(missing_docs)]
13pub enum BinaryOperator {
14    Add,
15    Subtract,
16    Multiply,
17    Divide,
18    Union,
19    Intersect,
20    PowerXor,
21    GreaterThan,
22    LessThan,
23    GreaterEqual,
24    LessEqual,
25    Equal,
26    Near,
27    NotEqual,
28    And,
29    Or,
30    Xor,
31}
32
33impl BinaryOperator {
34    /// Get the symbolic representation for the operator
35    pub fn as_str(&self) -> &'static str {
36        match self {
37            Self::Add => "+",
38            Self::Subtract => "-",
39            Self::Multiply => "*",
40            Self::Divide => "/",
41            Self::Union => "|",
42            Self::Intersect => "&",
43            Self::PowerXor => "^",
44            Self::GreaterThan => ">",
45            Self::LessThan => "<",
46            Self::GreaterEqual => "≥",
47            Self::LessEqual => "≤",
48            Self::Equal => "==",
49            Self::Near => "~",
50            Self::NotEqual => "!=",
51            Self::And => "&",
52            Self::Or => "|",
53            Self::Xor => "^",
54        }
55    }
56}
57
58/// The type of the operator for unary operations
59#[derive(Debug, PartialEq, Clone)]
60#[allow(missing_docs)]
61pub enum UnaryOperator {
62    Minus,
63    Plus,
64    Not,
65}
66
67impl UnaryOperator {
68    /// Get the symbolic representation for the operator
69    pub fn as_str(&self) -> &'static str {
70        match self {
71            Self::Minus => "-",
72            Self::Plus => "+",
73            Self::Not => "!",
74        }
75    }
76}
77
78/// Any expression.
79#[derive(Debug, PartialEq)]
80pub enum Expression {
81    /// A literal: `42mm`
82    Literal(ast::Literal),
83    /// Something in `()` brackets: `(42mm)`
84    Bracketed(Box<Expression>, Span),
85    /// A tuple: `(a = 1, b = 23)`
86    Tuple(TupleExpression),
87    /// A range expression: `[1..4]`
88    ArrayRange(ArrayRangeExpression),
89    /// A list expression: `[1, 2, 3]`
90    ArrayList(ArrayListExpression),
91    /// A format string: `"We have {n} items"`
92    String(FormatString),
93    /// A qualified name: `foo::bar::baz`
94    QualifiedName(QualifiedName),
95    /// A marker expression: `@input`
96    Marker(ast::Identifier),
97    /// A binary operation: `1 + 3`
98    BinaryOperation(BinaryOperation),
99    /// A unary operation: `-2`
100    UnaryOperation(UnaryOperation),
101    /// A body expression containing statements: `{ ... }`
102    Body(ast::Body),
103    /// A call: `call::me(1, 2, 3)`
104    Call(Call),
105    /// Accessing an element: `.foo`, `.rotate()`, `#attr`, `[1]`
106    ElementAccess(ElementAccess),
107    /// An if expression: `if a == b { ... } else { ... }`
108    If(If),
109    /// Any occurred during parsing
110    Error(Span),
111}
112
113impl Expression {
114    /// Get the source span for the identifier
115    pub fn span(&self) -> Span {
116        match self {
117            Expression::Literal(ex) => ex.span.clone(),
118            Expression::Bracketed(_, span) => span.clone(),
119            Expression::Tuple(ex) => ex.span.clone(),
120            Expression::ArrayRange(ex) => ex.span.clone(),
121            Expression::ArrayList(ex) => ex.span.clone(),
122            Expression::String(ex) => ex.span.clone(),
123            Expression::QualifiedName(ex) => ex.span.clone(),
124            Expression::Marker(ex) => ex.span.clone(),
125            Expression::BinaryOperation(ex) => ex.span.clone(),
126            Expression::UnaryOperation(ex) => ex.span.clone(),
127            Expression::Body(ex) => ex.span.clone(),
128            Expression::Call(ex) => ex.span.clone(),
129            Expression::ElementAccess(ex) => ex.span.clone(),
130            Expression::If(ex) => ex.span.clone(),
131            Expression::Error(span) => span.clone(),
132        }
133    }
134
135    /// Can this expression also be used as a statement, without extra semicolon
136    pub fn is_also_statement(&self) -> bool {
137        matches!(self, Expression::Body(_) | Expression::If(_))
138    }
139}
140
141/// A string containing a format expression
142#[derive(Debug, PartialEq)]
143#[allow(missing_docs)]
144pub struct FormatString {
145    pub span: Span,
146    pub extras: ast::ItemExtras,
147    pub parts: Vec<StringPart>,
148}
149
150/// A part of a [`FormatString`]
151#[derive(Debug, PartialEq)]
152#[allow(missing_docs)]
153pub enum StringPart {
154    Char(StringCharacter),
155    Content(ast::StringLiteral),
156    Expression(StringExpression),
157}
158
159/// A single character that is part of a [`FormatString`]
160#[derive(Debug, PartialEq)]
161#[allow(missing_docs)]
162pub struct StringCharacter {
163    pub span: Span,
164    pub character: char,
165}
166
167/// A format expression that is part of a [`FormatString`]
168#[derive(Debug, PartialEq)]
169#[allow(missing_docs)]
170pub struct StringExpression {
171    pub span: Span,
172    pub extras: ast::ItemExtras,
173    pub expr: Box<Expression>,
174    pub specification: Box<StringFormatSpecification>,
175}
176
177/// The format specification for a [`StringExpression`], specifying the width and precision for number formatting
178///
179/// All parts of the specification are optional
180#[derive(Debug, PartialEq)]
181#[allow(missing_docs)]
182pub struct StringFormatSpecification {
183    pub span: Span,
184    pub precision: Option<Result<u32, (ParseIntError, Span)>>,
185    pub width: Option<Result<u32, (ParseIntError, Span)>>,
186}
187
188impl StringFormatSpecification {
189    /// Check if an part of the specification is specified
190    pub fn is_some(&self) -> bool {
191        self.precision.is_some() || self.width.is_some()
192    }
193}
194
195/// An item that is part of a tuple expression
196#[derive(Debug, PartialEq)]
197#[allow(missing_docs)]
198pub struct TupleItem {
199    pub span: Span,
200    pub extras: ast::ItemExtras,
201    pub id: Option<ast::Identifier>,
202    pub expr: Expression,
203}
204
205impl ast::Dummy for TupleItem {
206    fn dummy(span: Span) -> Self {
207        Self {
208            span: span.clone(),
209            extras: ast::ItemExtras::default(),
210            id: None,
211            expr: Expression::Error(span),
212        }
213    }
214}
215
216/// A tuple expression, a fixed size set of items that don't need to be the same type
217#[derive(Debug, PartialEq)]
218#[allow(missing_docs)]
219pub struct TupleExpression {
220    pub span: Span,
221    pub extras: ast::ItemExtras,
222    pub values: Vec<TupleItem>,
223}
224
225/// An array range, containing all values from the start value (inclusive) till then end value (exclusive)
226#[derive(Debug, PartialEq)]
227#[allow(missing_docs)]
228pub struct ArrayRangeExpression {
229    pub span: Span,
230    pub extras: ast::ItemExtras,
231    pub start: Box<ArrayItem>,
232    pub end: Box<ArrayItem>,
233    pub unit: Option<ast::Unit>,
234}
235
236/// An array specified as a list of items
237#[derive(Debug, PartialEq)]
238#[allow(missing_docs)]
239pub struct ArrayListExpression {
240    pub span: Span,
241    pub extras: ast::ItemExtras,
242    pub items: Vec<ArrayItem>,
243    pub unit: Option<ast::Unit>,
244}
245
246/// An item that can be part of an array expression
247#[derive(Debug, PartialEq)]
248#[allow(missing_docs)]
249pub struct ArrayItem {
250    pub span: Span,
251    pub extras: ast::ItemExtras,
252    pub expr: Expression,
253}
254
255/// A qualified name, containing one or more [`Identifier`]s separated by `::`
256#[derive(Debug, PartialEq)]
257#[allow(missing_docs)]
258pub struct QualifiedName {
259    pub span: Span,
260    pub extras: ast::ItemExtras,
261    pub parts: Vec<ast::Identifier>,
262}
263
264/// A binary operation
265#[derive(Debug, PartialEq)]
266#[allow(missing_docs)]
267pub struct BinaryOperation {
268    pub span: Span,
269    pub lhs: Box<Expression>,
270    pub op: Spanned<BinaryOperator>,
271    pub rhs: Box<Expression>,
272}
273
274/// A unary operation
275#[derive(Debug, PartialEq)]
276#[allow(missing_docs)]
277pub struct UnaryOperation {
278    pub span: Span,
279    pub extras: ast::ItemExtras,
280    pub op: Spanned<UnaryOperator>,
281    pub rhs: Box<Expression>,
282}
283
284/// A function call
285#[derive(Debug, PartialEq)]
286#[allow(missing_docs)]
287pub struct Call {
288    pub span: Span,
289    pub extras: ast::ItemExtras,
290    pub name: QualifiedName,
291    pub arguments: ArgumentList,
292}
293
294/// An expression that access an element from another expression.
295///
296/// Either accessing an array or tuple item, accessing an attribute of a value or a method call.
297#[derive(Debug, PartialEq)]
298#[allow(missing_docs)]
299pub struct ElementAccess {
300    pub span: Span,
301    pub expr: Box<Expression>,
302    pub element_chain: Vec<Element>,
303}
304
305/// The possible element access types
306#[derive(Debug, PartialEq)]
307#[allow(missing_docs)]
308pub enum ElementInner {
309    Attribute(ast::Identifier),
310    Tuple(ast::Identifier),
311    Method(Call),
312    ArrayElement(Box<Expression>),
313}
314
315#[derive(Debug, PartialEq)]
316#[allow(missing_docs)]
317pub struct Element {
318    pub span: Span,
319    pub extras: ast::ItemExtras,
320    pub inner: ElementInner,
321}
322
323#[derive(Debug, PartialEq)]
324#[allow(missing_docs)]
325pub struct Body {
326    pub span: Span,
327    pub statements: ast::StatementList,
328}
329
330/// An if expression, can be used as either a statement or expression
331#[derive(Debug, PartialEq)]
332#[allow(missing_docs)]
333pub struct If {
334    pub span: Span,
335    pub if_span: Span,
336    pub extras: ast::ItemExtras,
337    pub condition: Box<Expression>,
338    pub body: Body,
339    pub next_if_span: Option<Span>,
340    pub next_if: Option<Box<If>>,
341    pub else_span: Option<Span>,
342    pub else_body: Option<Body>,
343}
344
345/// A list of arguments to a function call
346#[derive(Debug, PartialEq)]
347#[allow(missing_docs)]
348pub struct ArgumentList {
349    pub span: Span,
350    pub extras: ast::ItemExtras,
351    pub arguments: Vec<Argument>,
352}
353
354impl ast::Dummy for ArgumentList {
355    fn dummy(span: Span) -> Self {
356        Self {
357            span,
358            extras: ast::ItemExtras::default(),
359            arguments: Vec::new(),
360        }
361    }
362}
363
364/// A function argument that is part of an [`ArgumentList`]
365#[derive(Debug, PartialEq)]
366#[allow(missing_docs)]
367pub enum Argument {
368    Unnamed(UnnamedArgument),
369    Named(NamedArgument),
370}
371
372impl Argument {
373    /// The name of the argument, if specified
374    pub fn name(&self) -> Option<&ast::Identifier> {
375        match self {
376            Argument::Unnamed(_) => None,
377            Argument::Named(arg) => Some(&arg.id),
378        }
379    }
380
381    /// The value of the argument
382    pub fn value(&self) -> &Expression {
383        match self {
384            Argument::Unnamed(arg) => &arg.expr,
385            Argument::Named(arg) => &arg.expr,
386        }
387    }
388
389    /// The span of the argument
390    pub fn span(&self) -> &Span {
391        match self {
392            Argument::Unnamed(arg) => &arg.span,
393            Argument::Named(arg) => &arg.span,
394        }
395    }
396}
397
398/// An argument without specified name
399#[derive(Debug, PartialEq)]
400#[allow(missing_docs)]
401pub struct UnnamedArgument {
402    pub span: Span,
403    pub extras: ast::ItemExtras,
404    pub expr: Expression,
405}
406
407/// An argument with a specified name
408#[derive(Debug, PartialEq)]
409#[allow(missing_docs)]
410pub struct NamedArgument {
411    pub span: Span,
412    pub extras: ast::ItemExtras,
413    pub id: ast::Identifier,
414    pub expr: Expression,
415}