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