Skip to main content

pax_language/interpreter/
computable.rs

1use std::rc::Rc;
2
3use pax_runtime_api::{
4    functions::call_function, CoercionRules, Duration, Functions, Numeric, PaxValue, Percent,
5    Rotation, Size,
6};
7
8use super::{
9    property_resolution::IdentifierResolver, PaxAccessor, PaxExpression, PaxIdentifier, PaxInfix,
10    PaxNullCoalesce, PaxPostfix, PaxPrefix, PaxPrimary, PaxTernary, PaxUnit,
11};
12
13/// Trait for expression types that can be computed to a value
14pub trait Computable {
15    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String>;
16}
17
18impl Computable for PaxExpression {
19    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
20        match self {
21            PaxExpression::Primary(p) => p.compute(idr),
22            PaxExpression::Prefix(p) => p.compute(idr),
23            PaxExpression::Infix(p) => p.compute(idr),
24            PaxExpression::Postfix(p) => p.compute(idr),
25            PaxExpression::Ternary(p) => p.compute(idr),
26            PaxExpression::NullCoalesce(p) => p.compute(idr),
27        }
28    }
29}
30
31impl Computable for PaxPrimary {
32    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
33        match self {
34            PaxPrimary::Literal(v) => Ok(v.clone()),
35            PaxPrimary::Identifier(i, accessors) => idr
36                .resolve(i.name.clone())?
37                .read_pax_value_ref(|mut value| {
38                    for accessor in accessors {
39                        match accessor {
40                            PaxAccessor::Tuple(index) => {
41                                if let PaxValue::Vec(v) = value {
42                                    value = v.into_iter().nth(*index).ok_or_else(|| {
43                                        format!(
44                                            "paxel interpreter: tuple index out of bounds: {:?}",
45                                            index
46                                        )
47                                    })?;
48                                } else {
49                                    return Err(
50                                        "Tuple access must be performed on a tuple".to_string()
51                                    );
52                                }
53                            }
54                            PaxAccessor::List(index) => {
55                                if let PaxValue::Vec(v) = value {
56                                    let index = Numeric::try_coerce(index.compute(idr.clone())?)?
57                                        .to_int()
58                                        as usize;
59                                    value = v.into_iter().nth(index).ok_or_else(|| {
60                                        format!(
61                                            "paxel interpreter: list index out of bounds: {:?}",
62                                            index
63                                        )
64                                    })?;
65                                } else {
66                                    return Err(
67                                        "List access must be performed on a list".to_string()
68                                    );
69                                }
70                            }
71                            PaxAccessor::Struct(field) => {
72                                if field == "color" {
73                                    match value {
74                                        PaxValue::Enum(layer) if layer.0 == "Stroke" && layer.1 == "__layer" => return Err("Stroke.color was renamed to paint".into()),
75                                        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()),
76                                        _ => {}
77                                    }
78                                }
79                                if let PaxValue::Enum(contents) = value {
80                                    if (contents.0 == "Fill" || contents.0 == "Stroke")
81                                        && contents.1 == "__layer"
82                                        && contents.2.len() == 1
83                                    {
84                                        value = &contents.2[0];
85                                    }
86                                }
87                                if let PaxValue::Object(obj) = value {
88                                    value = obj
89                                        .into_iter()
90                                        .find_map(|(n, v)| (n == field).then_some(v))
91                                        .ok_or(format!("Field not found: {}", field))?;
92                                } else {
93                                    return Err(
94                                        "Struct access must be performed on an object".to_string()
95                                    );
96                                }
97                            }
98                        }
99                    }
100                    Ok(value.clone())
101                }),
102            PaxPrimary::Object(o) => Ok(PaxValue::Object(
103                o.into_iter()
104                    .map(|(k, v)| Result::<_, String>::Ok((k.clone(), v.compute(idr.clone())?)))
105                    .collect::<Result<_, _>>()?,
106            )),
107            PaxPrimary::Range(start, end) => {
108                let start = start.compute(idr.clone())?;
109                let end = end.compute(idr)?;
110                Ok(PaxValue::Range(Box::new(start), Box::new(end)))
111            }
112            PaxPrimary::Tuple(t) => {
113                let tuple = t
114                    .iter()
115                    .map(|e| e.compute(idr.clone()))
116                    .collect::<Result<Vec<PaxValue>, String>>()?;
117                Ok(PaxValue::Vec(tuple))
118            }
119            PaxPrimary::List(l) => {
120                let list = l
121                    .iter()
122                    .map(|e| e.compute(idr.clone()))
123                    .collect::<Result<Vec<PaxValue>, String>>()?;
124                Ok(PaxValue::Vec(list))
125            }
126            PaxPrimary::Grouped(expr, unit) => {
127                let expr_val = expr.compute(idr.clone())?;
128                if let Some(unit) = unit {
129                    match Numeric::try_coerce(expr_val) {
130                        Ok(n) => match unit {
131                            PaxUnit::Percent => Ok(PaxValue::Percent(Percent(n))),
132                            PaxUnit::Pixels => Ok(PaxValue::Size(Size::Pixels(n))),
133                            PaxUnit::Radians => Ok(PaxValue::Rotation(Rotation::Radians(n))),
134                            PaxUnit::Degrees => Ok(PaxValue::Rotation(Rotation::Degrees(n))),
135                            PaxUnit::Milliseconds => {
136                                Ok(PaxValue::Duration(Duration::Milliseconds(n)))
137                            }
138                            PaxUnit::Seconds => Ok(PaxValue::Duration(Duration::Seconds(n))),
139                            PaxUnit::Frames => Ok(PaxValue::Duration(Duration::Frames(n))),
140                        },
141                        Err(e) => Err(format!(
142                            "A grouped expression with a unit must be of type numeric: {e:?}"
143                        )),
144                    }
145                } else {
146                    Ok(expr_val)
147                }
148            }
149            PaxPrimary::FunctionOrEnum(scope, name_or_variant, args) => {
150                let args = args
151                    .iter()
152                    .map(|a| a.compute(idr.clone()))
153                    .collect::<Result<Vec<PaxValue>, String>>()?;
154
155                if Functions::has_function(scope, name_or_variant) {
156                    return call_function(scope.clone(), name_or_variant.clone(), args);
157                }
158
159                Ok(PaxValue::Enum(Box::new((
160                    scope.clone(),
161                    name_or_variant.clone(),
162                    args,
163                ))))
164            }
165        }
166    }
167}
168
169impl Computable for PaxPrefix {
170    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
171        let rhs = self.rhs.compute(idr)?;
172        let operator = &self.operator.name;
173        call_function("Math".to_string(), operator.to_string(), vec![rhs])
174    }
175}
176
177impl Computable for PaxPostfix {
178    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
179        let lhs = self.lhs.compute(idr)?;
180        let operator = &self.operator.name;
181        call_function("Math".to_string(), operator.to_string(), vec![lhs])
182    }
183}
184
185impl Computable for PaxInfix {
186    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
187        let lhs = self.lhs.compute(idr.clone())?;
188        let operator = &self.operator.name;
189        // Branch selection belongs to the AST evaluator: function arguments
190        // have already been evaluated by the time they reach Math dispatch.
191        match (operator.as_str(), &lhs) {
192            ("&&", PaxValue::Bool(false)) | ("||", PaxValue::Bool(true)) => return Ok(lhs),
193            _ => {}
194        }
195        let rhs = self.rhs.compute(idr)?;
196        call_function("Math".to_string(), operator.to_string(), vec![lhs, rhs])
197    }
198}
199
200impl Computable for PaxTernary {
201    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
202        match self.condition.compute(idr.clone())? {
203            PaxValue::Bool(true) => self.then_branch.compute(idr),
204            PaxValue::Bool(false) => self.else_branch.compute(idr),
205            value => Err(format!(
206                "Ternary condition must compute to a boolean, got: {:?}",
207                value
208            )),
209        }
210    }
211}
212
213impl Computable for PaxNullCoalesce {
214    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
215        match self.lhs.compute(idr.clone())? {
216            PaxValue::Option(opt) => match *opt {
217                Some(value) => Ok(value),
218                None => self.rhs.compute(idr),
219            },
220            value => Ok(value),
221        }
222    }
223}
224
225impl Computable for PaxIdentifier {
226    fn compute(&self, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
227        Ok(idr.resolve(self.name.clone())?.get_as_pax_value())
228    }
229}