pax-language 0.39.1

Pax language parser
Documentation
use std::rc::Rc;

use pax_runtime_api::{
    functions::call_function, CoercionRules, Duration, Functions, Numeric, PaxValue, Percent,
    Rotation, Size,
};

use super::{
    property_resolution::IdentifierResolver, PaxAccessor, PaxExpression, PaxIdentifier, PaxInfix,
    PaxNullCoalesce, PaxPostfix, PaxPrefix, PaxPrimary, PaxTernary, PaxUnit,
};

/// Trait for expression types that can be computed to a value
pub trait Computable {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String>;
}

impl Computable for PaxExpression {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        match self {
            PaxExpression::Primary(p) => p.compute(idr),
            PaxExpression::Prefix(p) => p.compute(idr),
            PaxExpression::Infix(p) => p.compute(idr),
            PaxExpression::Postfix(p) => p.compute(idr),
            PaxExpression::Ternary(p) => p.compute(idr),
            PaxExpression::NullCoalesce(p) => p.compute(idr),
        }
    }
}

impl Computable for PaxPrimary {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        match self {
            PaxPrimary::Literal(v) => Ok(v.clone()),
            PaxPrimary::Identifier(i, accessors) => idr
                .resolve(i.name.clone())?
                .read_pax_value_ref(|mut value| {
                    for accessor in accessors {
                        match accessor {
                            PaxAccessor::Tuple(index) => {
                                if let PaxValue::Vec(v) = value {
                                    value = v.into_iter().nth(*index).ok_or_else(|| {
                                        format!(
                                            "paxel interpreter: tuple index out of bounds: {:?}",
                                            index
                                        )
                                    })?;
                                } else {
                                    return Err(
                                        "Tuple access must be performed on a tuple".to_string()
                                    );
                                }
                            }
                            PaxAccessor::List(index) => {
                                if let PaxValue::Vec(v) = value {
                                    let index = Numeric::try_coerce(index.compute(idr.clone())?)?
                                        .to_int()
                                        as usize;
                                    value = v.into_iter().nth(index).ok_or_else(|| {
                                        format!(
                                            "paxel interpreter: list index out of bounds: {:?}",
                                            index
                                        )
                                    })?;
                                } else {
                                    return Err(
                                        "List access must be performed on a list".to_string()
                                    );
                                }
                            }
                            PaxAccessor::Struct(field) => {
                                if field == "color" {
                                    match value {
                                        PaxValue::Enum(layer) if layer.0 == "Stroke" && layer.1 == "__layer" => return Err("Stroke.color was renamed to paint".into()),
                                        PaxValue::Vec(layers) if layers.iter().any(|layer| matches!(layer, PaxValue::Enum(layer) if layer.0 == "Stroke" && layer.1 == "__layer")) => return Err("stroke is now a stack; select an entry (stroke[0].paint). Stroke.color was renamed to paint".into()),
                                        _ => {}
                                    }
                                }
                                if let PaxValue::Enum(contents) = value {
                                    if (contents.0 == "Fill" || contents.0 == "Stroke")
                                        && contents.1 == "__layer"
                                        && contents.2.len() == 1
                                    {
                                        value = &contents.2[0];
                                    }
                                }
                                if let PaxValue::Object(obj) = value {
                                    value = obj
                                        .into_iter()
                                        .find_map(|(n, v)| (n == field).then_some(v))
                                        .ok_or(format!("Field not found: {}", field))?;
                                } else {
                                    return Err(
                                        "Struct access must be performed on an object".to_string()
                                    );
                                }
                            }
                        }
                    }
                    Ok(value.clone())
                }),
            PaxPrimary::Object(o) => Ok(PaxValue::Object(
                o.into_iter()
                    .map(|(k, v)| Result::<_, String>::Ok((k.clone(), v.compute(idr.clone())?)))
                    .collect::<Result<_, _>>()?,
            )),
            PaxPrimary::Range(start, end) => {
                let start = start.compute(idr.clone())?;
                let end = end.compute(idr)?;
                Ok(PaxValue::Range(Box::new(start), Box::new(end)))
            }
            PaxPrimary::Tuple(t) => {
                let tuple = t
                    .iter()
                    .map(|e| e.compute(idr.clone()))
                    .collect::<Result<Vec<PaxValue>, String>>()?;
                Ok(PaxValue::Vec(tuple))
            }
            PaxPrimary::List(l) => {
                let list = l
                    .iter()
                    .map(|e| e.compute(idr.clone()))
                    .collect::<Result<Vec<PaxValue>, String>>()?;
                Ok(PaxValue::Vec(list))
            }
            PaxPrimary::Grouped(expr, unit) => {
                let expr_val = expr.compute(idr.clone())?;
                if let Some(unit) = unit {
                    match Numeric::try_coerce(expr_val) {
                        Ok(n) => match unit {
                            PaxUnit::Percent => Ok(PaxValue::Percent(Percent(n))),
                            PaxUnit::Pixels => Ok(PaxValue::Size(Size::Pixels(n))),
                            PaxUnit::Radians => Ok(PaxValue::Rotation(Rotation::Radians(n))),
                            PaxUnit::Degrees => Ok(PaxValue::Rotation(Rotation::Degrees(n))),
                            PaxUnit::Milliseconds => {
                                Ok(PaxValue::Duration(Duration::Milliseconds(n)))
                            }
                            PaxUnit::Seconds => Ok(PaxValue::Duration(Duration::Seconds(n))),
                            PaxUnit::Frames => Ok(PaxValue::Duration(Duration::Frames(n))),
                        },
                        Err(e) => Err(format!(
                            "A grouped expression with a unit must be of type numeric: {e:?}"
                        )),
                    }
                } else {
                    Ok(expr_val)
                }
            }
            PaxPrimary::FunctionOrEnum(scope, name_or_variant, args) => {
                let args = args
                    .iter()
                    .map(|a| a.compute(idr.clone()))
                    .collect::<Result<Vec<PaxValue>, String>>()?;

                if Functions::has_function(scope, name_or_variant) {
                    return call_function(scope.clone(), name_or_variant.clone(), args);
                }

                Ok(PaxValue::Enum(Box::new((
                    scope.clone(),
                    name_or_variant.clone(),
                    args,
                ))))
            }
        }
    }
}

impl Computable for PaxPrefix {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        let rhs = self.rhs.compute(idr)?;
        let operator = &self.operator.name;
        call_function("Math".to_string(), operator.to_string(), vec![rhs])
    }
}

impl Computable for PaxPostfix {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        let lhs = self.lhs.compute(idr)?;
        let operator = &self.operator.name;
        call_function("Math".to_string(), operator.to_string(), vec![lhs])
    }
}

impl Computable for PaxInfix {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        let lhs = self.lhs.compute(idr.clone())?;
        let operator = &self.operator.name;
        // Branch selection belongs to the AST evaluator: function arguments
        // have already been evaluated by the time they reach Math dispatch.
        match (operator.as_str(), &lhs) {
            ("&&", PaxValue::Bool(false)) | ("||", PaxValue::Bool(true)) => return Ok(lhs),
            _ => {}
        }
        let rhs = self.rhs.compute(idr)?;
        call_function("Math".to_string(), operator.to_string(), vec![lhs, rhs])
    }
}

impl Computable for PaxTernary {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        match self.condition.compute(idr.clone())? {
            PaxValue::Bool(true) => self.then_branch.compute(idr),
            PaxValue::Bool(false) => self.else_branch.compute(idr),
            value => Err(format!(
                "Ternary condition must compute to a boolean, got: {:?}",
                value
            )),
        }
    }
}

impl Computable for PaxNullCoalesce {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        match self.lhs.compute(idr.clone())? {
            PaxValue::Option(opt) => match *opt {
                Some(value) => Ok(value),
                None => self.rhs.compute(idr),
            },
            value => Ok(value),
        }
    }
}

impl Computable for PaxIdentifier {
    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
        Ok(idr.resolve(self.name.clone())?.get_as_pax_value())
    }
}