Skip to main content

microcad_syntax/ast/
expression.rs

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