Skip to main content

pax_language/interpreter/
mod.rs

1#[cfg(feature = "parser")]
2use computable::Computable;
3use pax_runtime_api::PaxValue;
4#[cfg(feature = "parser")]
5use pest::{
6    iterators::{Pair, Pairs},
7    pratt_parser::PrattParser,
8};
9#[cfg(feature = "parser")]
10use property_resolution::IdentifierResolver;
11use serde::{Deserialize, Serialize};
12use std::fmt::Display;
13#[cfg(feature = "parser")]
14use std::rc::Rc;
15
16#[cfg(feature = "parser")]
17use crate::{deserializer::from_pax_ast, get_pax_pratt_parser, parse_pax_pairs, Rule};
18
19pub(crate) mod computable;
20pub mod property_resolution;
21#[cfg(test)]
22mod tests;
23
24#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
25/// PAXEL expression AST node.
26pub enum PaxExpression {
27    Primary(Box<PaxPrimary>),
28    Prefix(Box<PaxPrefix>),
29    Infix(Box<PaxInfix>),
30    Postfix(Box<PaxPostfix>),
31    Ternary(Box<PaxTernary>),
32    NullCoalesce(Box<PaxNullCoalesce>),
33}
34
35impl Default for PaxExpression {
36    fn default() -> Self {
37        Self::Primary(Box::new(PaxPrimary::default()))
38    }
39}
40
41impl PaxExpression {
42    /// Construct a prefix expression.
43    pub fn prefix(operator: impl Into<String>, rhs: PaxExpression) -> Self {
44        Self::Prefix(Box::new(PaxPrefix {
45            operator: PaxOperator::new(operator),
46            rhs: Box::new(rhs),
47        }))
48    }
49
50    /// Construct an infix expression.
51    pub fn infix(lhs: PaxExpression, operator: impl Into<String>, rhs: PaxExpression) -> Self {
52        Self::Infix(Box::new(PaxInfix {
53            operator: PaxOperator::new(operator),
54            lhs: Box::new(lhs),
55            rhs: Box::new(rhs),
56        }))
57    }
58
59    /// Construct a postfix expression.
60    pub fn postfix(lhs: PaxExpression, operator: impl Into<String>) -> Self {
61        Self::Postfix(Box::new(PaxPostfix {
62            operator: PaxOperator::new(operator),
63            lhs: Box::new(lhs),
64        }))
65    }
66
67    /// Construct a ternary expression.
68    pub fn ternary(
69        condition: PaxExpression,
70        then_branch: PaxExpression,
71        else_branch: PaxExpression,
72    ) -> Self {
73        Self::Ternary(Box::new(PaxTernary {
74            condition: Box::new(condition),
75            then_branch: Box::new(then_branch),
76            else_branch: Box::new(else_branch),
77        }))
78    }
79
80    /// Construct a null-coalescing expression.
81    pub fn null_coalesce(lhs: PaxExpression, rhs: PaxExpression) -> Self {
82        Self::NullCoalesce(Box::new(PaxNullCoalesce {
83            lhs: Box::new(lhs),
84            rhs: Box::new(rhs),
85        }))
86    }
87}
88
89impl PaxExpression {
90    // Mirror parsing.rs (including the outer grammar) when displaying ASTs
91    // constructed without explicit Grouped nodes, e.g. by cartridge generation.
92    fn precedence(&self) -> u8 {
93        match self {
94            Self::Ternary(_) => 1,
95            Self::NullCoalesce(_) => 2,
96            Self::Infix(i) => match i.operator.name.as_str() {
97                "||" => 3,
98                "&&" => 4,
99                "==" | "!=" | "<" | "<=" | ">" | ">=" => 5,
100                "+" | "-" => 6,
101                "*" | "/" | "%%" => 7,
102                "^" => 8,
103                _ => 0,
104            },
105            Self::Prefix(p) if p.operator.name == "-" => 9,
106            Self::Prefix(_) => 10,
107            Self::Postfix(_) | Self::Primary(_) => 11,
108        }
109    }
110
111    fn fmt_operand(&self, f: &mut std::fmt::Formatter<'_>, minimum: u8) -> std::fmt::Result {
112        if self.precedence() < minimum {
113            write!(f, "({self})")
114        } else {
115            write!(f, "{self}")
116        }
117    }
118}
119
120impl Display for PaxExpression {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        match self {
123            PaxExpression::Primary(p) => write!(f, "{}", p),
124            PaxExpression::Prefix(p) => {
125                write!(f, "{}", p.operator.name)?;
126                p.rhs.fmt_operand(f, self.precedence())
127            }
128            PaxExpression::Infix(i) => {
129                let right_associative = i.operator.name == "^";
130                i.lhs
131                    .fmt_operand(f, self.precedence() + u8::from(right_associative))?;
132                write!(f, " {} ", i.operator.name)?;
133                i.rhs
134                    .fmt_operand(f, self.precedence() + u8::from(!right_associative))
135            }
136            PaxExpression::Postfix(p) => write!(f, "{}{}", p.lhs, p.operator.name),
137            PaxExpression::Ternary(t) => {
138                t.condition.fmt_operand(f, self.precedence() + 1)?;
139                write!(f, " ? {} : {}", t.then_branch, t.else_branch)
140            }
141            PaxExpression::NullCoalesce(n) => {
142                n.lhs.fmt_operand(f, self.precedence() + 1)?;
143                write!(f, " ?? ")?;
144                n.rhs.fmt_operand(f, self.precedence())
145            }
146        }
147    }
148}
149
150#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
151/// Primary expression forms: literals, symbols, object/list/tuple literals, calls, and ranges.
152pub enum PaxPrimary {
153    Literal(PaxValue),
154    Grouped(Box<PaxExpression>, Option<PaxUnit>),
155    Identifier(PaxIdentifier, Vec<PaxAccessor>),
156    Object(Vec<(String, PaxExpression)>),
157    FunctionOrEnum(String, String, Vec<PaxExpression>),
158    Range(PaxExpression, PaxExpression),
159    Tuple(Vec<PaxExpression>),
160    List(Vec<PaxExpression>),
161}
162
163impl Display for PaxPrimary {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            PaxPrimary::Literal(l) => write!(f, "{}", l),
167            PaxPrimary::Grouped(e, u) => {
168                if let Some(u) = u {
169                    write!(
170                        f,
171                        "({}){}",
172                        e,
173                        match u {
174                            PaxUnit::Percent => "%",
175                            PaxUnit::Pixels => "px",
176                            PaxUnit::Radians => "rad",
177                            PaxUnit::Degrees => "deg",
178                            PaxUnit::Milliseconds => "ms",
179                            PaxUnit::Seconds => "s",
180                            PaxUnit::Frames => "f",
181                        }
182                    )
183                } else {
184                    write!(f, "({})", e)
185                }
186            }
187            PaxPrimary::Identifier(i, a) => {
188                write!(f, "{}", i.name)?;
189                for accessor in a {
190                    match accessor {
191                        PaxAccessor::Tuple(i) => write!(f, ".{}", i)?,
192                        PaxAccessor::List(e) => write!(f, "[{}]", e)?,
193                        PaxAccessor::Struct(s) => write!(f, ".{}", s)?,
194                    }
195                }
196                Ok(())
197            }
198            PaxPrimary::Object(o) => {
199                write!(f, "{{")?;
200                let mut o = o.iter().collect::<Vec<_>>();
201                o.sort_by(|a, b| a.0.cmp(&b.0));
202                for (i, (key, val)) in o.iter().enumerate() {
203                    write!(f, "{}: {}", key, val)?;
204                    if i != o.len() - 1 {
205                        write!(f, ", ")?;
206                    }
207                }
208                write!(f, "}}")?;
209                Ok(())
210            }
211            PaxPrimary::FunctionOrEnum(name, e, a) => {
212                if (name == "Fill" || name == "Stroke") && e == "__layer" && a.len() == 1 {
213                    return write!(f, "{} {}", name, a[0]);
214                }
215                if name == "Color" {
216                    write!(f, "{}", e)?;
217                } else {
218                    write!(f, "{}::{}", name, e)?;
219                }
220                if !a.is_empty() {
221                    write!(f, "(")?;
222                    for (i, arg) in a.iter().enumerate() {
223                        write!(f, "{}", arg)?;
224                        if i != a.len() - 1 {
225                            write!(f, ", ")?;
226                        }
227                    }
228                    write!(f, ")")?;
229                }
230                Ok(())
231            }
232            PaxPrimary::Range(s, e) => write!(f, "{}..{}", s, e),
233            PaxPrimary::Tuple(t) => {
234                write!(f, "(")?;
235                for (i, e) in t.iter().enumerate() {
236                    write!(f, "{}", e)?;
237                    if i != t.len() - 1 {
238                        write!(f, ", ")?;
239                    }
240                }
241                write!(f, ")")?;
242                Ok(())
243            }
244            PaxPrimary::List(l) => {
245                write!(f, "[")?;
246                for (i, e) in l.iter().enumerate() {
247                    write!(f, "{}", e)?;
248                    if i != l.len() - 1 {
249                        write!(f, ", ")?;
250                    }
251                }
252                write!(f, "]")?;
253                Ok(())
254            }
255        }
256    }
257}
258
259impl Default for PaxPrimary {
260    fn default() -> Self {
261        Self::Literal(PaxValue::default())
262    }
263}
264
265#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
266/// Unit suffix attached to a grouped numeric expression.
267pub enum PaxUnit {
268    Percent,
269    Pixels,
270    Radians,
271    Degrees,
272    Milliseconds,
273    Seconds,
274    Frames,
275}
276
277#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
278/// Access path applied after an identifier, such as `.field`, `.0`, or `[index]`.
279pub enum PaxAccessor {
280    Tuple(usize),
281    List(PaxExpression),
282    Struct(String),
283}
284
285#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
286/// Prefix operation such as numeric negation or boolean not.
287pub struct PaxPrefix {
288    operator: PaxOperator,
289    rhs: Box<PaxExpression>,
290}
291
292impl PaxPrefix {
293    /// Name of the prefix operator.
294    pub fn operator_name(&self) -> &str {
295        &self.operator.name
296    }
297
298    /// Right-hand expression.
299    pub fn rhs(&self) -> &PaxExpression {
300        &self.rhs
301    }
302}
303
304#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
305/// Binary infix operation with left and right expression operands.
306pub struct PaxInfix {
307    operator: PaxOperator,
308    lhs: Box<PaxExpression>,
309    rhs: Box<PaxExpression>,
310}
311
312impl PaxInfix {
313    /// Name of the infix operator.
314    pub fn operator_name(&self) -> &str {
315        &self.operator.name
316    }
317
318    /// Left-hand expression.
319    pub fn lhs(&self) -> &PaxExpression {
320        &self.lhs
321    }
322
323    /// Right-hand expression.
324    pub fn rhs(&self) -> &PaxExpression {
325        &self.rhs
326    }
327}
328
329#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
330/// Postfix operation node.
331pub struct PaxPostfix {
332    operator: PaxOperator,
333    lhs: Box<PaxExpression>,
334}
335
336impl PaxPostfix {
337    /// Name of the postfix operator.
338    pub fn operator_name(&self) -> &str {
339        &self.operator.name
340    }
341
342    /// Left-hand expression.
343    pub fn lhs(&self) -> &PaxExpression {
344        &self.lhs
345    }
346}
347
348#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
349/// Conditional expression with a boolean condition and selected true/false branch.
350pub struct PaxTernary {
351    condition: Box<PaxExpression>,
352    then_branch: Box<PaxExpression>,
353    else_branch: Box<PaxExpression>,
354}
355
356#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
357/// Short-circuiting fallback expression. `Some(value) ?? fallback` evaluates to
358/// `value`, `None ?? fallback` evaluates the fallback, and non-option left
359/// operands pass through unchanged.
360pub struct PaxNullCoalesce {
361    lhs: Box<PaxExpression>,
362    rhs: Box<PaxExpression>,
363}
364
365impl PaxTernary {
366    /// Condition expression.
367    pub fn condition(&self) -> &PaxExpression {
368        &self.condition
369    }
370
371    /// Expression evaluated when the condition is true.
372    pub fn then_branch(&self) -> &PaxExpression {
373        &self.then_branch
374    }
375
376    /// Expression evaluated when the condition is false.
377    pub fn else_branch(&self) -> &PaxExpression {
378        &self.else_branch
379    }
380}
381
382impl PaxNullCoalesce {
383    /// Left-hand expression.
384    pub fn lhs(&self) -> &PaxExpression {
385        &self.lhs
386    }
387
388    /// Right-hand fallback expression.
389    pub fn rhs(&self) -> &PaxExpression {
390        &self.rhs
391    }
392}
393
394#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
395/// Parsed operator token, stored by display name.
396pub struct PaxOperator {
397    name: String,
398}
399
400impl PaxOperator {
401    /// Construct an operator from its source spelling.
402    pub fn new(name: impl Into<String>) -> Self {
403        Self { name: name.into() }
404    }
405
406    /// Source spelling for this operator.
407    pub fn name(&self) -> &str {
408        &self.name
409    }
410}
411
412#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
413/// Symbol reference in a PAXEL expression.
414pub struct PaxIdentifier {
415    pub name: String,
416}
417
418impl Display for PaxIdentifier {
419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420        write!(f, "{}", self.name)
421    }
422}
423
424impl PaxIdentifier {
425    /// Construct an identifier from its source spelling.
426    pub fn new(name: &str) -> Self {
427        Self {
428            name: name.to_string(),
429        }
430    }
431}
432
433/// Parse a pax expression into a computable AST
434#[cfg(feature = "parser")]
435pub fn parse_pax_expression(expr: &str) -> Result<PaxExpression, String> {
436    let parsed_expr = parse_pax_pairs(Rule::expression_body, expr)
437        .map_err(|e| format!("Failed to parse expression: {}", e))?;
438    let pratt_parser = get_pax_pratt_parser();
439    recurse_pratt_parse(parsed_expr, &pratt_parser)
440}
441
442/// Parse an already-produced pest pair into a PAXEL expression AST.
443#[cfg(feature = "parser")]
444pub fn parse_pax_expression_from_pair(expr: Pair<Rule>) -> Result<PaxExpression, String> {
445    let pratt_parser = get_pax_pratt_parser();
446    recurse_pratt_parse(Pairs::single(expr), &pratt_parser)
447}
448
449#[cfg(feature = "parser")]
450fn recurse_pratt_parse(
451    expr: Pairs<Rule>,
452    pratt_parser: &PrattParser<Rule>,
453) -> Result<PaxExpression, String> {
454    pratt_parser
455        .map_primary(move |primary| match primary.as_rule() {
456            Rule::xo_literal => {
457                let inner = primary.into_inner().next().unwrap();
458                match inner.as_rule() {
459                    Rule::literal_value => {
460                        let pax_value = from_pax_ast(inner)
461                            .map_err(|e| format!("Failed to parse literal value: {}", e))?;
462                        let value = PaxPrimary::Literal(pax_value);
463                        let exp = PaxExpression::Primary(Box::new(value));
464                        Ok(exp)
465                    }
466                    Rule::literal_tuple_access => {
467                        let mut inner = inner.into_inner();
468                        let ident = inner.next().unwrap().as_str().trim().to_string();
469                        let index = inner.next().unwrap().as_str().parse::<usize>().unwrap();
470                        let value = PaxPrimary::Identifier(
471                            PaxIdentifier { name: ident },
472                            vec![PaxAccessor::Tuple(index)],
473                        );
474                        let exp = PaxExpression::Primary(Box::new(value));
475                        Ok(exp)
476                    }
477                    Rule::literal_list_access => {
478                        let mut inner = inner.into_inner();
479                        let ident = inner.next().unwrap().as_str().trim().to_string();
480                        let index = inner.next().unwrap().as_str();
481                        let index = parse_pax_expression(index)?;
482                        let value = PaxPrimary::Identifier(
483                            PaxIdentifier { name: ident },
484                            vec![PaxAccessor::List(index)],
485                        );
486                        let exp = PaxExpression::Primary(Box::new(value));
487                        Ok(exp)
488                    }
489                    _ => {
490                        return Err(format!("Unexpected rule: {:?}", inner.as_rule()));
491                    }
492                }
493            }
494            Rule::expression_body | Rule::expression_binary => {
495                recurse_pratt_parse(primary.into_inner(), pratt_parser)
496            }
497            Rule::expression_coalesce => {
498                let mut inner = primary.into_inner();
499                let lhs = recurse_pratt_parse(Pairs::single(inner.next().unwrap()), pratt_parser)?;
500                if inner.next().is_some() {
501                    let rhs =
502                        recurse_pratt_parse(Pairs::single(inner.next().unwrap()), pratt_parser)?;
503                    Ok(PaxExpression::NullCoalesce(Box::new(PaxNullCoalesce {
504                        lhs: Box::new(lhs),
505                        rhs: Box::new(rhs),
506                    })))
507                } else {
508                    Ok(lhs)
509                }
510            }
511            Rule::expression_ternary => {
512                let mut inner = primary.into_inner();
513                let condition =
514                    recurse_pratt_parse(Pairs::single(inner.next().unwrap()), pratt_parser)?;
515                if inner.next().is_some() {
516                    let then_branch =
517                        recurse_pratt_parse(Pairs::single(inner.next().unwrap()), pratt_parser)?;
518                    inner.next();
519                    let else_branch =
520                        recurse_pratt_parse(Pairs::single(inner.next().unwrap()), pratt_parser)?;
521                    Ok(PaxExpression::Ternary(Box::new(PaxTernary {
522                        condition: Box::new(condition),
523                        then_branch: Box::new(then_branch),
524                        else_branch: Box::new(else_branch),
525                    })))
526                } else {
527                    Ok(condition)
528                }
529            }
530            Rule::expression_grouped => {
531                let mut inner = primary.clone().into_inner();
532                let expr = inner.next().unwrap();
533                let expr_val = recurse_pratt_parse(expr.into_inner(), pratt_parser)?;
534                let ret: Result<PaxExpression, String> = if let Some(unit) = inner.next() {
535                    let unit = unit.as_str().trim().trim_start_matches(')');
536                    match unit {
537                        "%" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
538                            Box::new(expr_val),
539                            Some(PaxUnit::Percent),
540                        )))),
541                        "px" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
542                            Box::new(expr_val),
543                            Some(PaxUnit::Pixels),
544                        )))),
545                        "rad" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
546                            Box::new(expr_val),
547                            Some(PaxUnit::Radians),
548                        )))),
549                        "deg" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
550                            Box::new(expr_val),
551                            Some(PaxUnit::Degrees),
552                        )))),
553                        "ms" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
554                            Box::new(expr_val),
555                            Some(PaxUnit::Milliseconds),
556                        )))),
557                        "s" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
558                            Box::new(expr_val),
559                            Some(PaxUnit::Seconds),
560                        )))),
561                        "f" => Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
562                            Box::new(expr_val),
563                            Some(PaxUnit::Frames),
564                        )))),
565                        _ => Err(format!("Unsupported unit: {}", unit)),
566                    }
567                } else {
568                    Ok(PaxExpression::Primary(Box::new(PaxPrimary::Grouped(
569                        Box::new(expr_val),
570                        None,
571                    ))))
572                };
573                ret
574            }
575            Rule::xo_enum_or_function_call => {
576                let mut inner = primary.into_inner();
577                while inner.len() > 3 {
578                    inner.next();
579                }
580                let scope = inner.next().unwrap().as_str().trim().to_string();
581                let function_name = inner.next().unwrap().as_str().trim().to_string();
582
583                let args = if let Some(args) = inner.next() {
584                    args.into_inner()
585                        .map(|a| recurse_pratt_parse(a.into_inner(), pratt_parser))
586                        .collect::<Result<Vec<PaxExpression>, String>>()?
587                } else {
588                    vec![]
589                };
590                let exp = PaxExpression::Primary(Box::new(PaxPrimary::FunctionOrEnum(
591                    scope,
592                    function_name,
593                    args,
594                )));
595                Ok(exp)
596            }
597            Rule::xo_color_space_func => {
598                let func = primary.as_str().trim().split("(").next().unwrap();
599                let inner = primary.into_inner();
600                let args = inner
601                    .map(|a| recurse_pratt_parse(a.into_inner(), pratt_parser))
602                    .collect::<Result<Vec<PaxExpression>, String>>()?;
603                let exp = PaxExpression::Primary(Box::new(PaxPrimary::FunctionOrEnum(
604                    "Color".to_string(),
605                    func.to_string(),
606                    args,
607                )));
608                Ok(exp)
609            }
610            Rule::xo_object => {
611                let mut inner = primary.into_inner();
612                let type_name = if inner
613                    .peek()
614                    .is_some_and(|v| v.as_rule() == Rule::identifier)
615                {
616                    Some(inner.next().unwrap().as_str().to_string())
617                } else {
618                    None
619                };
620                let mut obj = vec![];
621                for pair in inner {
622                    let mut pair = pair.into_inner();
623                    // settings_key = { identifier ~ (":" | "=") }
624                    let key = pair
625                        .next()
626                        .unwrap()
627                        .as_str()
628                        .trim()
629                        .trim_end_matches(':')
630                        .trim_end_matches('=')
631                        .to_string();
632                    let value = pair.next().unwrap();
633                    let value = recurse_pratt_parse(value.into_inner(), pratt_parser)?;
634                    obj.push((key, value));
635                }
636                let value = if let Some(name) =
637                    type_name.filter(|name| name == "Fill" || name == "Stroke")
638                {
639                    PaxPrimary::FunctionOrEnum(
640                        name,
641                        "__layer".into(),
642                        vec![PaxExpression::Primary(Box::new(PaxPrimary::Object(obj)))],
643                    )
644                } else {
645                    PaxPrimary::Object(obj)
646                };
647                let exp = PaxExpression::Primary(Box::new(value));
648                Ok(exp)
649            }
650            Rule::xo_range => {
651                let mut inner = primary.into_inner();
652                let start_rule = Pairs::single(inner.next().unwrap());
653                let start = recurse_pratt_parse(start_rule, pratt_parser)?;
654                // xo_range_exclusive
655                inner.next();
656                let end_rule = Pairs::single(inner.next().unwrap());
657                let end = recurse_pratt_parse(end_rule, pratt_parser)?;
658                let value = PaxPrimary::Range(start, end);
659                let exp = PaxExpression::Primary(Box::new(value));
660                Ok(exp)
661            }
662            Rule::xo_tuple => {
663                let inner = primary.into_inner();
664                let tuple = inner
665                    .map(|e| recurse_pratt_parse(e.into_inner(), pratt_parser))
666                    .collect::<Result<Vec<PaxExpression>, String>>()?;
667                let value = PaxPrimary::Tuple(tuple);
668                let exp = PaxExpression::Primary(Box::new(value));
669                Ok(exp)
670            }
671            Rule::xo_list => {
672                let inner = primary.into_inner();
673                let list = inner
674                    .map(|e| recurse_pratt_parse(e.into_inner(), pratt_parser))
675                    .collect::<Result<Vec<PaxExpression>, String>>()?;
676                let value = PaxPrimary::List(list);
677                let exp = PaxExpression::Primary(Box::new(value));
678                Ok(exp)
679            }
680            Rule::xo_symbol => {
681                let builtins_prefix = if primary.as_str().starts_with("$") {
682                    "$"
683                } else {
684                    ""
685                }
686                .to_string();
687
688                let mut symbols = primary.into_inner();
689
690                // skip self or this
691                let peek = symbols.peek().unwrap();
692                if peek.as_str().trim() == "self" || peek.as_str().trim() == "this" {
693                    symbols.next();
694                }
695
696                let identifier = builtins_prefix + symbols.next().unwrap().as_str().trim();
697                let mut accessors = vec![];
698                for symbol in symbols {
699                    let accessor = match symbol.as_rule() {
700                        // list access
701                        Rule::expression_body => {
702                            let expr = recurse_pratt_parse(symbol.into_inner(), pratt_parser)?;
703                            PaxAccessor::List(expr)
704                        }
705                        // field access .field
706                        Rule::identifier => {
707                            let field = symbol.as_str().trim().to_string();
708                            PaxAccessor::Struct(field)
709                        }
710                        _ => {
711                            return Err(format!("Unexpected rule: {:?}", symbol.as_rule()));
712                        }
713                    };
714                    accessors.push(accessor);
715                }
716                let value = PaxPrimary::Identifier(PaxIdentifier { name: identifier }, accessors);
717                let exp = PaxExpression::Primary(Box::new(value));
718                Ok(exp)
719            }
720            _ => {
721                return Err(format!(
722                    "Unexpected rule: {:?}, str: {} ",
723                    primary.as_rule(),
724                    primary.as_str()
725                ));
726            }
727        })
728        .map_prefix(|op, rhs| match op.as_rule() {
729            _ => {
730                let a = PaxOperator {
731                    name: op.as_str().trim().to_string(),
732                };
733                let r = rhs?;
734                let exp = PaxExpression::Prefix(Box::new(PaxPrefix {
735                    operator: a,
736                    rhs: Box::new(r),
737                }));
738                Ok(exp)
739            }
740        })
741        .map_postfix(|lhs, op| match op.as_rule() {
742            _ => {
743                let a = PaxOperator {
744                    name: op.as_str().trim().to_string(),
745                };
746                let l = lhs?;
747                let exp = PaxExpression::Postfix(Box::new(PaxPostfix {
748                    operator: a,
749                    lhs: Box::new(l),
750                }));
751                Ok(exp)
752            }
753        })
754        .map_infix(|lhs, op, rhs| match op.as_rule() {
755            _ => {
756                let a = PaxOperator {
757                    name: op.as_str().trim().to_string(),
758                };
759                let l = lhs?;
760                let r = rhs?;
761                let exp = PaxExpression::Infix(Box::new(PaxInfix {
762                    operator: a,
763                    lhs: Box::new(l),
764                    rhs: Box::new(r),
765                }));
766                Ok(exp)
767            }
768        })
769        .parse(expr)
770}
771
772/// Compute a pax expression to a PaxValue
773#[cfg(feature = "parser")]
774pub fn compute_paxel(expr: &str, idr: Rc<dyn IdentifierResolver>) -> Result<PaxValue, String> {
775    let expr = parse_pax_expression(expr)?;
776    expr.compute(idr)
777}