aimx/expressions/
retry.rs

1//! Retry rule-level expression.
2//!
3//! Parsed from `Retry(count, condition) -> target_rule` in a rule body.
4//! When `condition` evaluates truthy, decrements the numeric value of
5//! `target_rule` (if present, else uses `count`) and, if still > 0, yields
6//! `Value::Branch(target_rule)` for the workflow engine; otherwise yields
7//! `Value::Empty`.
8//! Propagates existing error values from `condition` unchanged.
9
10use crate::{
11    aim::{ContextLike, WriterLike, Writer},
12    expressions::{Coalesce, ExpressionLike, Reference, parse_coalesce, parse_arc_identifier},
13    literals::{Literal, parse_unsigned},
14    values::Value,
15};
16use anyhow::Result;
17use nom::{
18    IResult, Parser,
19    bytes::complete::tag,
20    character::complete::{char, multispace0},
21    sequence::{delimited, preceded},
22};
23use std::{
24    fmt,
25    sync::Arc,
26};
27
28#[derive(Debug, Clone, PartialEq)]
29pub struct Retry {
30    count: u32,
31    condition: Coalesce,
32    target: Arc<str>,
33}
34
35impl Retry {
36    pub fn new(count: u32, condition: Coalesce, target: Arc<str>) -> Self {
37        Self {
38            count,
39            condition,
40            target,
41        }
42    }
43
44    pub fn count(&self) -> u32 {
45        self.count
46    }   
47
48    pub fn condition(&self) -> &dyn ExpressionLike {
49        &self.condition
50    }
51
52    pub fn target(&self) -> Arc<str> {
53        self.target.clone()
54    }
55
56    pub fn print(&self, writer: &mut Writer) {
57        writer.write_unsigned(self.count);
58        writer.write_str(", ");
59        self.condition.print(writer);
60        writer.write_str(" -> ");
61        writer.write_str(&self.target);
62    }
63}
64
65pub fn parse_retry(input: &str) -> IResult<&str, Retry> {
66    let (input, (count, condition, target)) = delimited(
67        multispace0,
68        // Inner content: count, condition -> target
69        (
70            parse_unsigned,
71            preceded(
72                (multispace0, char(','), multispace0), 
73                parse_coalesce),
74            preceded(
75                (multispace0, tag("->"), multispace0),
76                parse_arc_identifier
77            ),
78        ),
79        multispace0,
80    ).parse(input)?;
81    
82    Ok((input, Retry::new(count, condition, target)))
83}
84
85impl ExpressionLike for Retry {
86    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
87        let value = self.condition.evaluate(context)?;
88        if value.is_error() {
89            return Ok(value);
90        }
91        match &*value.to_bool() {
92            Value::Literal(Literal::Bool(true)) => {
93                let reference = Reference::new(self.target.clone());
94                let value = context.get_value(&reference)?;
95                let count = if value.is_number() {
96                    value.to_number().unwrap_or(self.count as f64) as u32
97                } else {
98                    self.count
99                };
100                if count > 0 {
101                    let new_value = Arc::new(Value::Literal(Literal::Number((count - 1) as f64)));
102                    let _ = context.set_value(&self.target, new_value);
103                    return Ok(Arc::new(Value::Branch(self.target.clone())));
104                }
105            }
106            _ => {}
107        }
108        Ok(Value::empty())
109    }
110
111    /// Return the formula-string representation (round-trippable by the parser).
112    fn to_formula(&self) -> String {
113        let mut writer = Writer::formulizer();
114        self.print(&mut writer);
115        writer.finish()
116    }
117}
118
119impl WriterLike for Retry {
120    fn write(&self, writer: &mut Writer) {
121        self.print(writer);
122    }
123}
124
125impl fmt::Display for Retry {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "{}", self.to_stringized())
128    }
129}