Skip to main content

microcad_lang/parse/
expression.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{parse::*, parser::*, syntax::*};
5use microcad_lang_base::Refer;
6use microcad_syntax::ast;
7use microcad_syntax::ast::{Element, LiteralKind};
8
9impl FromAst for RangeFirst {
10    type AstNode = ast::ArrayItem;
11
12    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
13        if matches!(
14            node.expression,
15            ast::Expression::Literal(
16                ast::Literal {
17                    literal: ast::LiteralKind::Float(_)
18                        | ast::LiteralKind::String(_)
19                        | ast::LiteralKind::Quantity(_)
20                        | ast::LiteralKind::Bool(_),
21                    ..
22                },
23                ..
24            )
25        ) {
26            return Err(ParseError::InvalidRangeType {
27                src_ref: context.src_ref(&node.expression.span()),
28            });
29        }
30        Ok(RangeFirst(Box::new(Expression::from_ast(
31            &node.expression,
32            context,
33        )?)))
34    }
35}
36
37impl FromAst for RangeLast {
38    type AstNode = ast::ArrayItem;
39
40    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
41        if matches!(
42            node.expression,
43            ast::Expression::Literal(
44                ast::Literal {
45                    literal: ast::LiteralKind::Float(_)
46                        | ast::LiteralKind::String(_)
47                        | ast::LiteralKind::Quantity(_)
48                        | ast::LiteralKind::Bool(_),
49                    ..
50                },
51                ..
52            )
53        ) {
54            return Err(ParseError::InvalidRangeType {
55                src_ref: context.src_ref(&node.expression.span()),
56            });
57        }
58        Ok(RangeLast(Box::new(Expression::from_ast(
59            &node.expression,
60            context,
61        )?)))
62    }
63}
64
65impl FromAst for RangeExpression {
66    type AstNode = ast::ArrayRangeExpression;
67
68    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
69        Ok(RangeExpression {
70            first: RangeFirst::from_ast(&node.start, context)?,
71            last: RangeLast::from_ast(&node.end, context)?,
72            src_ref: context.src_ref(&node.span),
73        })
74    }
75}
76
77impl FromAst for ListExpression {
78    type AstNode = ast::ArrayListExpression;
79
80    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
81        node.items
82            .iter()
83            .map(|item| Expression::from_ast(&item.expression, context))
84            .collect::<Result<ListExpression, _>>()
85    }
86}
87
88impl FromAst for Marker {
89    type AstNode = ast::Identifier;
90
91    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
92        Ok(Marker {
93            id: Identifier::from_ast(node, context)?,
94            src_ref: context.src_ref(&node.span),
95        })
96    }
97}
98
99impl FromAst for Expression {
100    type AstNode = ast::Expression;
101
102    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
103        Ok(match node {
104            ast::Expression::Call(expr) => Expression::Call(Call::from_ast(expr, context)?),
105            ast::Expression::Literal(ast::Literal {
106                literal: LiteralKind::String(s),
107                span,
108                ..
109            }) => Expression::FormatString(FormatString(Refer::new(
110                vec![FormatStringInner::String(Refer::new(
111                    s.content.clone(),
112                    context.src_ref(&s.span),
113                ))],
114                context.src_ref(span),
115            ))),
116            ast::Expression::Literal(expr) => {
117                Expression::Literal(Literal::from_ast(expr, context)?)
118            }
119            ast::Expression::String(s) => {
120                Expression::FormatString(FormatString::from_ast(s, context)?)
121            }
122            ast::Expression::Tuple(t) => {
123                Expression::TupleExpression(TupleExpression::from_ast(t, context)?)
124            }
125            ast::Expression::ArrayRange(a) => Expression::ArrayExpression(ArrayExpression {
126                inner: ArrayExpressionInner::Range(RangeExpression::from_ast(a, context)?),
127                unit: a
128                    .ty
129                    .as_ref()
130                    .map(|ty| Unit::from_ast(ty, context))
131                    .transpose()?
132                    .unwrap_or_default(),
133                src_ref: context.src_ref(&a.span),
134            }),
135            ast::Expression::ArrayList(a) => Expression::ArrayExpression(ArrayExpression {
136                inner: ArrayExpressionInner::List(ListExpression::from_ast(a, context)?),
137                unit: a
138                    .ty
139                    .as_ref()
140                    .map(|ty| Unit::from_ast(ty, context))
141                    .transpose()?
142                    .unwrap_or_default(),
143                src_ref: context.src_ref(&a.span),
144            }),
145            ast::Expression::QualifiedName(n) => {
146                Expression::QualifiedName(QualifiedName::from_ast(n, context)?)
147            }
148            ast::Expression::Marker(m) => Expression::Marker(Marker::from_ast(m, context)?),
149            ast::Expression::BinaryOperation(binop) => Expression::BinaryOp {
150                lhs: Box::new(Expression::from_ast(&binop.lhs, context)?),
151                rhs: Box::new(Expression::from_ast(&binop.rhs, context)?),
152                op: Refer::new(
153                    binop.operation.operation.as_str().into(),
154                    context.src_ref(&binop.operation.span),
155                ),
156                src_ref: context.src_ref(&binop.span),
157            },
158            ast::Expression::UnaryOperation(unop) => Expression::UnaryOp {
159                rhs: Box::new(Expression::from_ast(&unop.rhs, context)?),
160                op: Refer::new(
161                    unop.operation.operation.as_str().into(),
162                    context.src_ref(&unop.operation.span),
163                ),
164                src_ref: context.src_ref(&unop.span),
165            },
166            ast::Expression::Block(b) => Expression::Body(Body::from_ast(b, context)?),
167            ast::Expression::ElementAccess(access) => match &access.element {
168                Element::Attribute(a) => Expression::AttributeAccess(
169                    Box::new(Expression::from_ast(&access.value, context)?),
170                    Identifier::from_ast(a, context)?,
171                    context.src_ref(&access.span),
172                ),
173                Element::Tuple(t) => Expression::PropertyAccess(
174                    Box::new(Expression::from_ast(&access.value, context)?),
175                    Identifier::from_ast(t, context)?,
176                    context.src_ref(&access.span),
177                ),
178                Element::Method(m) => Expression::MethodCall(
179                    Box::new(Expression::from_ast(&access.value, context)?),
180                    MethodCall::from_ast(m, context)?,
181                    context.src_ref(&access.span),
182                ),
183                Element::ArrayElement(e) => Expression::ArrayElementAccess(
184                    Box::new(Expression::from_ast(&access.value, context)?),
185                    Box::new(Expression::from_ast(e, context)?),
186                    context.src_ref(&access.span),
187                ),
188            },
189            ast::Expression::If(i) => Expression::If(Box::new(IfStatement::from_ast(i, context)?)),
190            ast::Expression::Error(span) => {
191                return Err(ParseError::InvalidExpression {
192                    src_ref: context.src_ref(span),
193                });
194            }
195        })
196    }
197}
198
199impl FromAst for TupleExpression {
200    type AstNode = ast::TupleExpression;
201
202    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
203        let mut args = ArgumentList::default();
204        for value in &node.values {
205            args.value
206                .try_push(Argument {
207                    id: value
208                        .name
209                        .as_ref()
210                        .map(|name| Identifier::from_ast(name, context))
211                        .transpose()?,
212                    expression: Expression::from_ast(&value.value, context)?,
213                    src_ref: context.src_ref(&value.span),
214                })
215                .map_err(|(previous, id)| ParseError::DuplicateArgument { previous, id })?;
216        }
217
218        Ok(TupleExpression {
219            args,
220            src_ref: context.src_ref(&node.span),
221        })
222    }
223}
224
225/// Create TupleExpression from µcad code
226#[cfg(test)]
227#[macro_export]
228macro_rules! tuple_expression {
229    ($code:literal) => {{
230        use microcad_syntax::ast;
231        use $crate::parser::FromAst;
232        let context = $crate::parser::ParseContext::new($code);
233        let ast = $crate::parse::build_ast($code, &context).unwrap();
234        let statement = ast
235            .statements
236            .statements
237            .first()
238            .or(ast.statements.tail.as_deref())
239            .expect("empty source");
240        let tuple_expression = match statement {
241            ast::Statement::Expression(ast::ExpressionStatement {
242                expression: ast::Expression::Tuple(tuple),
243                ..
244            }) => tuple,
245            _ => panic!("non tuple source"),
246        };
247        TupleExpression::from_ast(&tuple_expression, &context).unwrap()
248    }};
249}