Skip to main content

okane_core/syntax/
expr.rs

1//! Defines value expression representation used in Ledger format.
2//! Note this is purely lexicographical and not always valid expression.
3
4use core::fmt;
5use std::borrow::Cow;
6
7use bounded_static::ToStatic;
8use pretty_decimal::PrettyDecimal;
9
10/// Amount, which is a single unit of value with a commodity.
11#[derive(Debug, PartialEq, Eq, Clone, ToStatic)]
12pub struct Amount<'i> {
13    pub value: PrettyDecimal,
14    pub commodity: Cow<'i, str>,
15}
16
17/// Defines value expression.
18/// Value expression is a valid expression when used in amount.
19/// It can be either amount literal or expression wrapped in `()`.
20#[derive(Debug, PartialEq, Eq, Clone, ToStatic)]
21pub enum ValueExpr<'i> {
22    Paren(Expr<'i>),
23    Amount(Amount<'i>),
24}
25
26impl<'i> From<Amount<'i>> for ValueExpr<'i> {
27    fn from(v: Amount<'i>) -> Self {
28        ValueExpr::Amount(v)
29    }
30}
31
32/// Generic expression.
33#[derive(Debug, PartialEq, Eq, Clone, ToStatic)]
34pub enum Expr<'i> {
35    Unary(UnaryOpExpr<'i>),
36    Binary(BinaryOpExpr<'i>),
37    Value(Box<ValueExpr<'i>>),
38}
39
40/// Represents unary operator.
41#[derive(Debug, PartialEq, Eq, Clone, Copy, ToStatic)]
42pub enum UnaryOp {
43    /// `-x`
44    Negate,
45}
46
47impl fmt::Display for UnaryOp {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let op = match self {
50            UnaryOp::Negate => "-",
51        };
52        write!(f, "{}", op)
53    }
54}
55
56/// Unary operator expression.
57#[derive(Debug, PartialEq, Eq, Clone, ToStatic)]
58pub struct UnaryOpExpr<'i> {
59    pub op: UnaryOp,
60    pub expr: Box<Expr<'i>>,
61}
62
63/// Binary operator.
64#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::EnumIter, ToStatic)]
65pub enum BinaryOp {
66    /// `+`
67    Add,
68    /// `-`
69    Sub,
70    /// `*`
71    Mul,
72    /// `/`
73    Div,
74}
75
76impl fmt::Display for BinaryOp {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        let op = match self {
79            BinaryOp::Add => "+",
80            BinaryOp::Sub => "-",
81            BinaryOp::Mul => "*",
82            BinaryOp::Div => "/",
83        };
84        write!(f, "{}", op)
85    }
86}
87
88/// Represents binary operator expression.
89#[derive(Debug, PartialEq, Eq, Clone, ToStatic)]
90pub struct BinaryOpExpr<'i> {
91    pub op: BinaryOp,
92    pub lhs: Box<Expr<'i>>,
93    pub rhs: Box<Expr<'i>>,
94}
95
96/// Visitor over the value expression AST.
97///
98/// Every `visit_*` method is responsible for recursing into its own children, usually by
99/// calling [`ExprVisitor::visit_expr`]. That leaves the visitor free to run code before,
100/// between and after the children, which a plain fold over already-computed child values
101/// could not express.
102///
103/// [`ExprVisitor::visit_value_expr`] and [`ExprVisitor::visit_expr`] only dispatch on the node
104/// kind, so they come with default implementations. Implementors normally provide just
105/// [`ExprVisitor::visit_amount`], [`ExprVisitor::visit_unary`] and [`ExprVisitor::visit_binary`].
106pub(crate) trait ExprVisitor<'i> {
107    /// Value produced for every expression node.
108    type Output;
109
110    /// Error aborting the traversal.
111    type Error;
112
113    /// Visits an amount literal, the only leaf of the tree.
114    fn visit_amount(&mut self, amount: &Amount<'i>) -> Result<Self::Output, Self::Error>;
115
116    /// Visits a unary operator expression, recursing into `expr.expr`.
117    fn visit_unary(&mut self, expr: &UnaryOpExpr<'i>) -> Result<Self::Output, Self::Error>;
118
119    /// Visits a binary operator expression, recursing into `expr.lhs` and `expr.rhs`.
120    fn visit_binary(&mut self, expr: &BinaryOpExpr<'i>) -> Result<Self::Output, Self::Error>;
121
122    /// Visits a value expression, dispatching to [`Self::visit_expr`] or [`Self::visit_amount`].
123    #[inline]
124    fn visit_value_expr(&mut self, expr: &ValueExpr<'i>) -> Result<Self::Output, Self::Error> {
125        match expr {
126            ValueExpr::Paren(x) => self.visit_expr(x),
127            ValueExpr::Amount(x) => self.visit_amount(x),
128        }
129    }
130
131    /// Visits a generic expression, dispatching on the node kind.
132    #[inline]
133    fn visit_expr(&mut self, expr: &Expr<'i>) -> Result<Self::Output, Self::Error> {
134        match expr {
135            Expr::Unary(e) => self.visit_unary(e),
136            Expr::Binary(e) => self.visit_binary(e),
137            Expr::Value(e) => self.visit_value_expr(e),
138        }
139    }
140}
141
142/// Node of the expression AST an [`ExprVisitor`] can be applied to.
143pub(crate) trait Visitable<'i> {
144    /// Invokes the `visitor` method corresponding to `Self`.
145    fn accept<V: ExprVisitor<'i> + ?Sized>(&self, visitor: &mut V) -> Result<V::Output, V::Error>;
146}
147
148impl<'i> Visitable<'i> for Amount<'i> {
149    #[inline]
150    fn accept<V: ExprVisitor<'i> + ?Sized>(&self, visitor: &mut V) -> Result<V::Output, V::Error> {
151        visitor.visit_amount(self)
152    }
153}
154
155impl<'i> Visitable<'i> for ValueExpr<'i> {
156    #[inline]
157    fn accept<V: ExprVisitor<'i> + ?Sized>(&self, visitor: &mut V) -> Result<V::Output, V::Error> {
158        visitor.visit_value_expr(self)
159    }
160}
161
162impl<'i> Visitable<'i> for Expr<'i> {
163    #[inline]
164    fn accept<V: ExprVisitor<'i> + ?Sized>(&self, visitor: &mut V) -> Result<V::Output, V::Error> {
165        visitor.visit_expr(self)
166    }
167}
168
169impl<'i> Visitable<'i> for UnaryOpExpr<'i> {
170    #[inline]
171    fn accept<V: ExprVisitor<'i> + ?Sized>(&self, visitor: &mut V) -> Result<V::Output, V::Error> {
172        visitor.visit_unary(self)
173    }
174}
175
176impl<'i> Visitable<'i> for BinaryOpExpr<'i> {
177    #[inline]
178    fn accept<V: ExprVisitor<'i> + ?Sized>(&self, visitor: &mut V) -> Result<V::Output, V::Error> {
179        visitor.visit_binary(self)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    use std::convert::Infallible;
188
189    use pretty_assertions::assert_eq;
190
191    /// [`ExprVisitor`] recording every node it reaches, relying on the default dispatch.
192    #[derive(Default)]
193    struct TraceVisitor(Vec<String>);
194
195    impl<'i> ExprVisitor<'i> for TraceVisitor {
196        type Output = ();
197        type Error = Infallible;
198
199        fn visit_amount(&mut self, amount: &Amount<'i>) -> Result<(), Infallible> {
200            self.0.push(format!("amount({})", amount.commodity));
201            Ok(())
202        }
203
204        fn visit_unary(&mut self, expr: &UnaryOpExpr<'i>) -> Result<(), Infallible> {
205            self.0.push(format!("unary({})", expr.op));
206            self.visit_expr(&expr.expr)
207        }
208
209        fn visit_binary(&mut self, expr: &BinaryOpExpr<'i>) -> Result<(), Infallible> {
210            self.0.push(format!("binary({})", expr.op));
211            self.visit_expr(&expr.lhs)?;
212            self.visit_expr(&expr.rhs)
213        }
214    }
215
216    #[test]
217    fn visitor_reaches_all_nodes_in_pre_order() {
218        let input: ValueExpr = "(1 USD * (-2 + 3 EUR))".try_into().expect("must parse");
219        let mut visitor = TraceVisitor::default();
220        input.accept(&mut visitor).expect("must not fail");
221        assert_eq!(
222            vec![
223                "binary(*)",
224                "amount(USD)",
225                "binary(+)",
226                "unary(-)",
227                "amount()",
228                "amount(EUR)",
229            ],
230            visitor.0
231        );
232    }
233
234    #[test]
235    fn visitor_aborts_on_error() {
236        /// [`ExprVisitor`] failing on the first amount without a commodity.
237        struct FailingVisitor(usize);
238
239        impl<'i> ExprVisitor<'i> for FailingVisitor {
240            type Output = ();
241            type Error = ();
242
243            fn visit_amount(&mut self, amount: &Amount<'i>) -> Result<(), ()> {
244                self.0 += 1;
245                if amount.commodity.is_empty() {
246                    return Err(());
247                }
248                Ok(())
249            }
250
251            fn visit_unary(&mut self, expr: &UnaryOpExpr<'i>) -> Result<(), ()> {
252                self.visit_expr(&expr.expr)
253            }
254
255            fn visit_binary(&mut self, expr: &BinaryOpExpr<'i>) -> Result<(), ()> {
256                self.visit_expr(&expr.lhs)?;
257                self.visit_expr(&expr.rhs)
258            }
259        }
260
261        let input: ValueExpr = "(1 USD + 2 + 3 EUR)".try_into().expect("must parse");
262        let mut visitor = FailingVisitor(0);
263        assert_eq!(Err(()), input.accept(&mut visitor));
264        // Stopped right at the bare `2`, without reaching `3 EUR`.
265        assert_eq!(2, visitor.0);
266    }
267}