microcad-lang 0.5.0

µcad language
Documentation
// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

use microcad_lang_base::{PushDiag, SrcReferrer};

use crate::{
    eval::*,
    lower::ir,
    model::*,
    symbol::{Symbol, SymbolDef},
};

impl Eval for ir::RangeFirst {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
        let value: Value = self.0.eval(context)?;
        Ok(match value {
            Value::Integer(_) => value,
            value => {
                context.error(
                    self,
                    EvalError::ExpectedType {
                        expected: Type::Integer,
                        found: value.ty(),
                    },
                )?;

                Value::None
            }
        })
    }
}

impl Eval for ir::RangeLast {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
        let value: Value = self.0.eval(context)?;
        Ok(match value {
            Value::Integer(_) => value,
            value => {
                context.error(
                    self,
                    EvalError::ExpectedType {
                        expected: Type::Integer,
                        found: value.ty(),
                    },
                )?;

                Value::None
            }
        })
    }
}

impl Eval for ir::RangeExpression {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
        Ok(
            match (self.first.eval(context)?, self.last.eval(context)?) {
                (Value::Integer(first), Value::Integer(last)) => {
                    if first > last {
                        context.error(self, EvalError::BadRange(first, last))?;
                    }

                    Value::Array(Array::from_values(
                        (first..last + 1).map(Value::Integer).collect(),
                        Type::Integer,
                    ))
                }
                (_, _) => Value::None,
            },
        )
    }
}

impl Eval for ir::ArrayExpression {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
        match &self.inner {
            ir::ArrayExpressionInner::Range(range_expression) => range_expression.eval(context),
            ir::ArrayExpressionInner::List(expressions) => {
                let value_list = ValueList::new(
                    expressions
                        .iter()
                        .map(|expr| expr.eval(context))
                        .collect::<Result<_, _>>()?,
                );

                match value_list.types().common_type() {
                    Some(common_type) => {
                        match Value::Array(Array::from_values(value_list, common_type)) * self.unit
                        {
                            Ok(value) => Ok(value),
                            Err(err) => {
                                context.error(self, err)?;
                                Ok(Value::None)
                            }
                        }
                    }
                    None => {
                        context.error(
                            self,
                            EvalError::ArrayElementsDifferentTypes(value_list.types()),
                        )?;
                        Ok(Value::None)
                    }
                }
            }
        }
    }
}

impl Eval<Option<Symbol>> for ir::QualifiedName {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Symbol>> {
        match context.lookup(self, LookupTarget::AnyButMethod) {
            Ok(symbol) => Ok(Some(symbol.clone())),
            Err(error) => {
                context.error(self, error)?;
                Ok(None)
            }
        }
    }
}

impl Eval for ir::QualifiedName {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
        use crate::lower::Identifiable;

        context
            .lookup(self, LookupTarget::AnyButMethod)?
            .with_def(|def| match def {
                SymbolDef::Root => unreachable!("<ROOT> cannot be looked up"),
                SymbolDef::Value(.., value) => Ok(value.clone()),
                SymbolDef::Assignment(a) => a.eval(context),
                SymbolDef::SourceFile(_) => Ok(Value::None),
                SymbolDef::Builtin(crate::builtin::Builtin::Constant(c)) => Ok(c.value.clone()),
                SymbolDef::Module(ns) => {
                    context.error(self, EvalError::UnexpectedNested("mod", ns.id()))?;
                    Ok(Value::None)
                }
                SymbolDef::Workbench(w) => {
                    context.error(self, EvalError::UnexpectedNested(w.kind.as_str(), w.id()))?;
                    Ok(Value::None)
                }
                SymbolDef::Function(f) => {
                    context.error(self, EvalError::UnexpectedNested("function", f.id()))?;
                    Ok(Value::None)
                }
                SymbolDef::Builtin(bm) => {
                    context.error(self, EvalError::UnexpectedNested("builtin", bm.id()))?;
                    Ok(Value::None)
                }
                SymbolDef::Alias(_, id, _) => {
                    // Alias should have been resolved within previous lookup()
                    unreachable!(
                        "Unexpected alias {id} in value expression at {}",
                        self.src_ref()
                    )
                }
                SymbolDef::UseAll(_, name) => {
                    unreachable!("Unexpected use {name} in value expression")
                }
                #[cfg(test)]
                SymbolDef::Tester(..) => {
                    unreachable!()
                }
            })
    }
}

impl ir::Expression {
    /// Evaluate an expression together with an attribute list.
    ///
    /// The attribute list will be also evaluated and the resulting attributes
    /// will be assigned to the resulting value.
    pub fn eval_with_attribute_list(
        &self,
        attribute_list: &ir::AttributeList,
        context: &mut EvalContext,
    ) -> EvalResult<Value> {
        let value = self.eval(context)?;
        match value {
            Value::Model(model) => {
                let attributes = attribute_list.eval(context)?;
                model.borrow_mut().attributes = attributes.clone();
                Ok(Value::Model(model))
            }
            Value::None => Ok(Value::None),
            _ => {
                if !attribute_list.is_empty() {
                    context.error(
                        attribute_list,
                        AttributeError::CannotAssignAttribute(self.to_string()),
                    )?;
                }
                Ok(value)
            }
        }
    }
}

impl Eval for ir::Expression {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Value> {
        log::trace!("Evaluating expression:\n{self}");
        let result = match self {
            Self::Literal(literal) => literal.eval(context),
            Self::FormatString(format_string) => format_string.eval(context),
            Self::ArrayExpression(array_expression) => array_expression.eval(context),
            Self::TupleExpression(tuple_expression) => tuple_expression.eval(context),
            Self::BinaryOp {
                lhs,
                op,
                rhs,
                src_ref: _,
            } => {
                let lhs: Value = lhs.eval(context)?;
                let rhs: Value = rhs.eval(context)?;
                if lhs.is_invalid() || rhs.is_invalid() {
                    return Ok(Value::None);
                }

                match Value::binary_op(lhs, rhs, op.as_str()) {
                    Err(err) => {
                        context.error(self, err)?;
                        Ok(Value::None)
                    }
                    Ok(value) => Ok(value),
                }
            }
            Self::UnaryOp {
                op,
                rhs,
                src_ref: _,
            } => {
                let value: Value = rhs.eval(context)?;
                value
                    .unary_op(op.as_str())
                    .map_err(|err| Box::new(err.into()))
            }
            Self::ArrayElementAccess(lhs, rhs, _) => {
                let lhs = lhs.eval(context)?;
                let rhs = rhs.eval(context)?;

                match (lhs, rhs) {
                    (Value::Array(list), Value::Integer(index)) => {
                        let index = index as usize;
                        if index < list.len() {
                            match list.get(index) {
                                Some(value) => Ok(value.clone()),
                                None => Err(EvalError::ListIndexOutOfBounds {
                                    index,
                                    len: list.len(),
                                }
                                .into()),
                            }
                        } else {
                            context.error(
                                self,
                                EvalError::ListIndexOutOfBounds {
                                    index,
                                    len: list.len(),
                                },
                            )?;
                            Ok(Value::None)
                        }
                    }
                    _ => unimplemented!(),
                }
            }
            Self::MethodCall(lhs, method_call, _) => method_call.eval(context, lhs),
            Self::Call(call) => call.eval(context),
            Self::Body(body) => {
                if let Some(model) = body.eval(context)? {
                    Ok(model.into())
                } else {
                    Ok(Value::None)
                }
            }
            Self::If(if_) => {
                if let Some(model) = if_.eval(context)? {
                    Ok(model.into())
                } else {
                    Ok(Value::None)
                }
            }
            Self::QualifiedName(qualified_name) => qualified_name.eval(context),
            Self::Marker(marker) => {
                let model: Option<Model> = marker.eval(context)?;
                Ok(model.map(Value::Model).unwrap_or_default())
            }
            // Access a property `x` of an expression `circle.x`
            Self::PropertyAccess(lhs, id, src_ref) => {
                let value: Value = lhs.eval(context)?;
                match value {
                    Value::Tuple(tuple) => match tuple.by_id(id) {
                        Some(value) => return Ok(value.clone()),
                        None => context.error(src_ref, EvalError::PropertyNotFound(id.clone()))?,
                    },
                    Value::Model(model) => match model.borrow().get_property(id) {
                        Some(prop) => return Ok(prop.clone()),
                        None => context.error(src_ref, EvalError::PropertyNotFound(id.clone()))?,
                    },
                    _ => {}
                }

                Ok(Value::None)
            }
            Self::AttributeAccess(lhs, identifier, src_ref) => {
                let value: Value = lhs.eval(context)?;
                let value = value.get_attribute_value(identifier);
                if value == Value::None {
                    context.error(src_ref, AttributeError::NotFound(identifier.clone()))?;
                }
                Ok(value)
            }
            expr => todo!("{expr:?}"),
        };
        match result {
            Ok(value) => {
                log::trace!("Evaluated expression:\n{self:?}\n--- into ---\n{value:?}");
                Ok(value)
            }
            Err(err) => {
                context.error(self, err)?;
                Ok(Value::None)
            }
        }
    }
}

impl Eval<Option<Model>> for ir::Expression {
    fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Model>> {
        Ok(match self.eval(context)? {
            Value::Model(model) => Some(model),
            _ => None,
        })
    }
}