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)]
25pub 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 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 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 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 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 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 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)]
151pub 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)]
266pub 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)]
278pub enum PaxAccessor {
280 Tuple(usize),
281 List(PaxExpression),
282 Struct(String),
283}
284
285#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
286pub struct PaxPrefix {
288 operator: PaxOperator,
289 rhs: Box<PaxExpression>,
290}
291
292impl PaxPrefix {
293 pub fn operator_name(&self) -> &str {
295 &self.operator.name
296 }
297
298 pub fn rhs(&self) -> &PaxExpression {
300 &self.rhs
301 }
302}
303
304#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
305pub struct PaxInfix {
307 operator: PaxOperator,
308 lhs: Box<PaxExpression>,
309 rhs: Box<PaxExpression>,
310}
311
312impl PaxInfix {
313 pub fn operator_name(&self) -> &str {
315 &self.operator.name
316 }
317
318 pub fn lhs(&self) -> &PaxExpression {
320 &self.lhs
321 }
322
323 pub fn rhs(&self) -> &PaxExpression {
325 &self.rhs
326 }
327}
328
329#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
330pub struct PaxPostfix {
332 operator: PaxOperator,
333 lhs: Box<PaxExpression>,
334}
335
336impl PaxPostfix {
337 pub fn operator_name(&self) -> &str {
339 &self.operator.name
340 }
341
342 pub fn lhs(&self) -> &PaxExpression {
344 &self.lhs
345 }
346}
347
348#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
349pub 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)]
357pub struct PaxNullCoalesce {
361 lhs: Box<PaxExpression>,
362 rhs: Box<PaxExpression>,
363}
364
365impl PaxTernary {
366 pub fn condition(&self) -> &PaxExpression {
368 &self.condition
369 }
370
371 pub fn then_branch(&self) -> &PaxExpression {
373 &self.then_branch
374 }
375
376 pub fn else_branch(&self) -> &PaxExpression {
378 &self.else_branch
379 }
380}
381
382impl PaxNullCoalesce {
383 pub fn lhs(&self) -> &PaxExpression {
385 &self.lhs
386 }
387
388 pub fn rhs(&self) -> &PaxExpression {
390 &self.rhs
391 }
392}
393
394#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
395pub struct PaxOperator {
397 name: String,
398}
399
400impl PaxOperator {
401 pub fn new(name: impl Into<String>) -> Self {
403 Self { name: name.into() }
404 }
405
406 pub fn name(&self) -> &str {
408 &self.name
409 }
410}
411
412#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
413pub 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 pub fn new(name: &str) -> Self {
427 Self {
428 name: name.to_string(),
429 }
430 }
431}
432
433#[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#[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 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 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 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 Rule::expression_body => {
702 let expr = recurse_pratt_parse(symbol.into_inner(), pratt_parser)?;
703 PaxAccessor::List(expr)
704 }
705 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#[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}