hyperon/metta/runner/stdlib/
arithmetics.rs

1use hyperon_atom::*;
2use crate::metta::*;
3use crate::metta::text::Tokenizer;
4use super::regex;
5use hyperon_atom::gnd::number::*;
6use hyperon_atom::gnd::bool::*;
7
8use std::fmt::Display;
9
10macro_rules! def_binary_number_op {
11    ($name:ident, $op:tt, $r:ident, $ret_type:ident) => {
12        #[derive(Clone, PartialEq, Debug)]
13        pub struct $name{}
14
15        impl Display for $name {
16            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17                write!(f, stringify!($op))
18            }
19        }
20
21        impl Grounded for $name {
22            fn type_(&self) -> Atom {
23                Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, $r])
24            }
25
26            fn as_execute(&self) -> Option<&dyn CustomExecute> {
27                Some(self)
28            }
29        }
30
31        impl CustomExecute for $name {
32            fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
33                let arg_error = || ExecError::IncorrectArgument;
34                let a = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?;
35                let b = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?;
36
37                let (a, b) = Number::promote(a, b);
38                let res: $ret_type = match (a, b) {
39                    (Number::Integer(a), Number::Integer(b)) => (a $op b).into(),
40                    (Number::Float(a), Number::Float(b)) => (a $op b).into(),
41                    _ => panic!("Unexpected state"),
42                };
43
44                Ok(vec![Atom::gnd(res)])
45            }
46        }
47    }
48}
49
50def_binary_number_op!(SumOp, +, ATOM_TYPE_NUMBER, Number);
51def_binary_number_op!(SubOp, -, ATOM_TYPE_NUMBER, Number);
52def_binary_number_op!(MulOp, *, ATOM_TYPE_NUMBER, Number);
53def_binary_number_op!(ModOp, %, ATOM_TYPE_NUMBER, Number);
54def_binary_number_op!(LessOp, <, ATOM_TYPE_BOOL, Bool);
55def_binary_number_op!(GreaterOp, >, ATOM_TYPE_BOOL, Bool);
56def_binary_number_op!(LessEqOp, <=, ATOM_TYPE_BOOL, Bool);
57def_binary_number_op!(GreaterEqOp, >=, ATOM_TYPE_BOOL, Bool);
58
59macro_rules! def_binary_bool_op {
60    ($name:ident, $disp:ident, $op:tt) => {
61        #[derive(Clone, PartialEq, Debug)]
62        pub struct $name{}
63
64        impl Display for $name {
65            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66                write!(f, stringify!($disp))
67            }
68        }
69
70        impl Grounded for $name {
71            fn type_(&self) -> Atom {
72                Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL, ATOM_TYPE_BOOL, ATOM_TYPE_BOOL])
73            }
74
75            fn as_execute(&self) -> Option<&dyn CustomExecute> {
76                Some(self)
77            }
78        }
79
80        impl CustomExecute for $name {
81            fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
82                let arg_error = || ExecError::IncorrectArgument;
83                let Bool(a) = args.get(0).and_then(Bool::from_atom).ok_or_else(arg_error)?;
84                let Bool(b) = args.get(1).and_then(Bool::from_atom).ok_or_else(arg_error)?;
85
86                Ok(vec![Atom::gnd(Bool(a $op b))])
87            }
88        }
89    }
90}
91
92def_binary_bool_op!(AndOp, and, &&);
93def_binary_bool_op!(OrOp, or, ||);
94
95// NOTE: xor is absent in Python intentionally for conversion testing
96def_binary_bool_op!(XorOp, xor, ^);
97
98
99#[derive(Clone, PartialEq, Debug)]
100pub struct NotOp{}
101
102impl Display for NotOp {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        write!(f, "not")
105    }
106}
107
108impl Grounded for NotOp {
109    fn type_(&self) -> Atom {
110        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_BOOL, ATOM_TYPE_BOOL])
111    }
112
113    fn as_execute(&self) -> Option<&dyn CustomExecute> {
114        Some(self)
115    }
116}
117
118impl CustomExecute for NotOp {
119    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
120        let arg_error = || ExecError::IncorrectArgument;
121        let Bool(a) = args.get(0).and_then(Bool::from_atom).ok_or_else(arg_error)?;
122
123        Ok(vec![Atom::gnd(Bool(!a))])
124    }
125}
126
127#[derive(Clone, PartialEq, Debug)]
128pub struct DivOp{}
129
130impl Display for DivOp {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        write!(f, "/")
133    }
134}
135
136impl Grounded for DivOp {
137    fn type_(&self) -> Atom {
138        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER, ATOM_TYPE_NUMBER])
139    }
140
141    fn as_execute(&self) -> Option<&dyn CustomExecute> {
142        Some(self)
143    }
144}
145
146impl CustomExecute for DivOp {
147    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
148        let arg_error = || ExecError::from("Divide expects two numbers: dividend and divisor");
149        let dividend = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?;
150        let divisor = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?;
151
152        let (dividend, divisor) = Number::promote(dividend, divisor);
153        match (dividend, divisor) {
154            (Number::Integer(_), Number::Integer(0)) => Err(ExecError::from("DivisionByZero")),
155            (Number::Integer(a), Number::Integer(b)) => Ok(vec![Atom::gnd(Number::Integer(a / b))]),
156            (Number::Float(a), Number::Float(b)) => Ok(vec![Atom::gnd(Number::Float(a / b))]),
157            _ => panic!("Unexpected state")
158        }
159    }
160}
161
162pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
163    tref.register_fallible_token(regex(r"[\-\+]?\d+"),
164        |token| { Ok(Atom::gnd(Number::from_int_str(token)?)) });
165    tref.register_fallible_token(regex(r"[\-\+]?\d+\.\d+"),
166        |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) });
167    tref.register_fallible_token(regex(r"[\-\+]?\d+(\.\d+)?[eE][\-\+]?\d+"),
168        |token| { Ok(Atom::gnd(Number::from_float_str(token)?)) });
169    tref.register_token(regex(r"True|False"),
170        |token| { Atom::gnd(Bool::from_str(token)) });
171
172    let sum_op = Atom::gnd(SumOp{});
173    tref.register_token(regex(r"\+"), move |_| { sum_op.clone() });
174    let sub_op = Atom::gnd(SubOp{});
175    tref.register_token(regex(r"\-"), move |_| { sub_op.clone() });
176    let mul_op = Atom::gnd(MulOp{});
177    tref.register_token(regex(r"\*"), move |_| { mul_op.clone() });
178    let div_op = Atom::gnd(DivOp{});
179    tref.register_token(regex(r"/"), move |_| { div_op.clone() });
180    let mod_op = Atom::gnd(ModOp{});
181    tref.register_token(regex(r"%"), move |_| { mod_op.clone() });
182    let lt_op = Atom::gnd(LessOp{});
183    tref.register_token(regex(r"<"), move |_| { lt_op.clone() });
184    let gt_op = Atom::gnd(GreaterOp{});
185    tref.register_token(regex(r">"), move |_| { gt_op.clone() });
186    let le_op = Atom::gnd(LessEqOp{});
187    tref.register_token(regex(r"<="), move |_| { le_op.clone() });
188    let ge_op = Atom::gnd(GreaterEqOp{});
189    tref.register_token(regex(r">="), move |_| { ge_op.clone() });
190    let and_op = Atom::gnd(AndOp{});
191    tref.register_token(regex(r"and"), move |_| { and_op.clone() });
192    let or_op = Atom::gnd(OrOp{});
193    tref.register_token(regex(r"or"), move |_| { or_op.clone() });
194    let not_op = Atom::gnd(NotOp{});
195    tref.register_token(regex(r"not"), move |_| { not_op.clone() });
196    // NOTE: xor is absent in Python intentionally for conversion testing
197    let xor_op = Atom::gnd(XorOp{});
198    tref.register_token(regex(r"xor"), move |_| { xor_op.clone() });
199}
200
201#[cfg(test)]
202mod tests {
203    use crate::metta::runner::run_program;
204    use super::*;
205
206    macro_rules! assert_binary_op {
207        ($name:ident, $a: expr, $b: expr, $r: expr) => {
208            assert_eq!($name{}.execute(&mut vec![Atom::gnd($a), Atom::gnd($b)]), Ok(vec![Atom::gnd($r)]));
209        }
210    }
211
212    macro_rules! assert_unary_op {
213        ($name:ident, $a: expr, $r: expr) => {
214            assert_eq!($name{}.execute(&mut vec![Atom::gnd($a)]), Ok(vec![Atom::gnd($r)]));
215        }
216    }
217
218    #[test]
219    fn div_errors() {
220        assert_eq!(run_program(&format!("!(assertEqual (/ 5 0) (Error (/ 5 0) DivisionByZero))")), Ok(vec![vec![UNIT_ATOM]]));
221        assert_eq!(run_program(&format!("!(assertEqual (let $div (/ 5.0 0.0) (isinf-math $div)) True)")), Ok(vec![vec![UNIT_ATOM]]));
222    }
223
224    #[test]
225    fn and() {
226        assert_binary_op!(AndOp, Bool(true), Bool(true), Bool(true));
227        assert_binary_op!(AndOp, Bool(true), Bool(false), Bool(false));
228        assert_binary_op!(AndOp, Bool(false), Bool(true), Bool(false));
229        assert_binary_op!(AndOp, Bool(false), Bool(false), Bool(false));
230    }
231
232    #[test]
233    fn or() {
234        assert_binary_op!(OrOp, Bool(true), Bool(true), Bool(true));
235        assert_binary_op!(OrOp, Bool(true), Bool(false), Bool(true));
236        assert_binary_op!(OrOp, Bool(false), Bool(true), Bool(true));
237        assert_binary_op!(OrOp, Bool(false), Bool(false), Bool(false));
238    }
239
240    #[test]
241    fn not() {
242        assert_unary_op!(NotOp, Bool(true), Bool(false));
243        assert_unary_op!(NotOp, Bool(false), Bool(true));
244    }
245
246    #[test]
247    fn sum_op() {
248        assert_binary_op!(SumOp, Number::Integer(40), Number::Integer(2), Number::Integer(42));
249        assert_binary_op!(SumOp, Number::Integer(40), Number::Float(2.42), Number::Float(42.42));
250        assert_binary_op!(SumOp, Number::Float(40.42), Number::Integer(2), Number::Float(42.42));
251        assert_binary_op!(SumOp, Number::Float(40.40), Number::Float(2.02), Number::Float(42.42));
252    }
253
254    #[test]
255    fn sub_op() {
256        assert_binary_op!(SubOp, Number::Integer(44), Number::Integer(2), Number::Integer(42));
257        assert_binary_op!(SubOp, Number::Integer(44), Number::Float(2.42), Number::Float(41.58));
258        assert_binary_op!(SubOp, Number::Float(44.42), Number::Integer(2), Number::Float(42.42));
259        assert_binary_op!(SubOp, Number::Float(44.5), Number::Float(2.5), Number::Float(42.0));
260    }
261
262    #[test]
263    fn mul_op() {
264        assert_binary_op!(MulOp, Number::Integer(6), Number::Integer(7), Number::Integer(42));
265        assert_binary_op!(MulOp, Number::Integer(4), Number::Float(10.5), Number::Float(42.0));
266        assert_binary_op!(MulOp, Number::Float(10.5), Number::Integer(4), Number::Float(42.0));
267        assert_binary_op!(MulOp, Number::Float(2.5), Number::Float(16.8), Number::Float(42.0));
268    }
269
270    #[test]
271    fn div_op() {
272        assert_binary_op!(DivOp, Number::Integer(84), Number::Integer(2), Number::Integer(42));
273        assert_binary_op!(DivOp, Number::Integer(441), Number::Float(10.5), Number::Float(42.0));
274        assert_binary_op!(DivOp, Number::Float(84.0), Number::Integer(2), Number::Float(42.0));
275        assert_binary_op!(DivOp, Number::Float(430.5), Number::Float(10.25), Number::Float(42.0));
276    }
277
278    #[test]
279    fn mod_op() {
280        assert_binary_op!(ModOp, Number::Integer(85), Number::Integer(43), Number::Integer(42));
281        assert_binary_op!(ModOp, Number::Integer(85), Number::Float(43.5), Number::Float(41.5));
282        assert_binary_op!(ModOp, Number::Float(85.5), Number::Integer(43), Number::Float(42.5));
283        assert_binary_op!(ModOp, Number::Float(85.5), Number::Float(43.5), Number::Float(42.0));
284    }
285}