hyperon/metta/runner/stdlib/
debug.rs

1use hyperon_atom::*;
2use crate::metta::*;
3use crate::metta::text::Tokenizer;
4use hyperon_common::collections::{SliceDisplay, Equality, DefaultEquality};
5use hyperon_common::assert::compare_vec_no_order;
6use hyperon_atom::matcher::atoms_are_equivalent;
7use crate::metta::runner::stdlib::{grounded_op, regex, unit_result};
8use hyperon_atom::gnd::bool::*;
9use hyperon_atom::gnd::str::*;
10use hyperon_atom::gnd::GroundedFunctionAtom;
11
12use std::convert::TryInto;
13
14/// Implement trace! built-in.
15///
16/// It is equivalent to Idris or Haskell Trace, that is, it prints a
17/// message to stderr and pass a value along.
18///
19/// For instance
20/// ```metta
21/// !(trace! "Here?" 42)
22/// ```
23/// prints to stderr
24/// ```stderr
25/// Here?
26/// ```
27/// and returns
28/// ```metta
29/// [42]
30/// ```
31///
32/// Note that the first argument does not need to be a string, which
33/// makes `trace!` actually quite capable on its own.  For instance
34/// ```metta
35/// !(trace! ("Hello world!" (if True A B) 1 2 3) 42)
36/// ```
37/// prints to stderr
38/// ```stderr
39/// (Hello world! A 1 2 3)
40/// ```
41/// and returns
42/// ```metta
43/// [42]
44/// ```
45
46#[derive(Clone, Debug)]
47pub struct TraceOp {}
48
49grounded_op!(TraceOp, "trace!");
50
51impl Grounded for TraceOp {
52    fn type_(&self) -> Atom {
53        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_UNDEFINED, ATOM_TYPE_ATOM, ATOM_TYPE_UNDEFINED])
54    }
55
56    fn as_execute(&self) -> Option<&dyn CustomExecute> {
57        Some(self)
58    }
59}
60
61impl CustomExecute for TraceOp {
62    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
63        let arg_error = || ExecError::from("trace! expects two atoms as arguments");
64        let val = args.get(1).ok_or_else(arg_error)?;
65        let msg = args.get(0).ok_or_else(arg_error)?;
66        eprintln!("{}", msg);
67        Ok(vec![val.clone()])
68    }
69}
70
71#[derive(Clone, Debug)]
72pub struct PrintAlternativesOp {}
73
74grounded_op!(PrintAlternativesOp, "print-alternatives!");
75
76impl Grounded for PrintAlternativesOp {
77    fn type_(&self) -> Atom {
78        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, UNIT_TYPE])
79    }
80
81    fn as_execute(&self) -> Option<&dyn CustomExecute> {
82        Some(self)
83    }
84}
85
86impl CustomExecute for PrintAlternativesOp {
87    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
88        let arg_error = || ExecError::from("print-alternatives! expects format string as a first argument and expression as a second argument");
89        let atom = atom_to_string(args.get(0).ok_or_else(arg_error)?);
90        let args = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?;
91        let args: Vec<String> = args.children().iter()
92            .map(|atom| atom_to_string(atom))
93            .collect();
94        println!("{} {}:", args.len(), atom);
95        args.iter().for_each(|arg| println!("    {}", arg));
96        Ok(vec![UNIT_ATOM])
97    }
98}
99
100struct AlphaEquality{}
101
102impl Equality<&Atom> for AlphaEquality {
103    fn eq(a: &&Atom, b: &&Atom) -> bool {
104        atoms_are_equivalent(*a, *b)
105    }
106}
107
108fn assert_results_are_equal<'a, E: Equality<&'a Atom>>(args: &'a [Atom], cmp: E) -> Result<Vec<Atom>, ExecError> {
109    let arg_error = || ExecError::from("Pair of evaluation results with bindings is expected as an argument");
110    let actual = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
111    let expected = TryInto::<&ExpressionAtom>::try_into(args.get(1).ok_or_else(arg_error)?)?.children();
112    let assert = args.get(2).ok_or_else(arg_error)?;
113
114
115    let report = format!("\nExpected: {}\nGot: {}", SliceDisplay(expected), SliceDisplay(actual));
116
117    match compare_vec_no_order(actual.iter(), expected.iter(), cmp).as_display() {
118        None => unit_result(),
119        Some(diff) => {
120            let msg = match args.get(3) {
121                None => Atom::gnd(Str::from_string(format!("{}\n{}", report, diff))),
122                Some(m) => m.clone(),
123            };
124            Ok(vec![Atom::expr([ERROR_SYMBOL, assert.clone(), msg])])
125        },
126    }
127}
128
129#[derive(Clone, Debug)]
130pub struct AlphaEqOp {
131}
132grounded_op!(AlphaEqOp, "=alpha");
133
134impl Grounded for AlphaEqOp {
135    fn type_(&self) -> Atom {
136        Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_ATOM, ATOM_TYPE_BOOL])
137    }
138
139    fn as_execute(&self) -> Option<&dyn CustomExecute> {
140        Some(self)
141    }
142}
143
144impl CustomExecute for AlphaEqOp {
145    fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
146        log::debug!("AlphaEqOp::execute: {:?}", args);
147        let arg_error = || ExecError::from("=alpha expects two atoms as arguments: actual and expected");
148        let actual_atom = args.get(0).ok_or_else(arg_error)?;
149        let expected_atom = args.get(1).ok_or_else(arg_error)?;
150
151        Ok(vec![Atom::gnd(Bool(atoms_are_equivalent(actual_atom, expected_atom)))])
152    }
153}
154
155pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
156    let trace_op = Atom::gnd(TraceOp{});
157    tref.register_token(regex(r"trace!"), move |_| { trace_op.clone() });
158    let print_alternatives_op = Atom::gnd(PrintAlternativesOp{});
159    tref.register_token(regex(r"print-alternatives!"), move |_| { print_alternatives_op.clone() });
160    let alpha_eq_op = Atom::gnd(AlphaEqOp{});
161    tref.register_token(regex(r"=alpha"), move |_| { alpha_eq_op.clone() });
162    tref.register_function(GroundedFunctionAtom::new(
163            r"_assert-results-are-equal".into(),
164            expr!("->" "Atom" "Atom" "Atom" ("->")),
165            |args: &[Atom]| -> Result<Vec<Atom>, ExecError> { assert_results_are_equal(args, DefaultEquality{}) }, 
166            ));
167    tref.register_function(GroundedFunctionAtom::new(
168            r"_assert-results-are-alpha-equal".into(),
169            expr!("->" "Atom" "Atom" "Atom" ("->")),
170            |args: &[Atom]| -> Result<Vec<Atom>, ExecError> { assert_results_are_equal(args, AlphaEquality{}) }, 
171            ));
172    tref.register_function(GroundedFunctionAtom::new(
173        r"_assert-results-are-equal-msg".into(),
174        expr!("->" "Atom" "Atom" "Atom" "Atom" ("->")),
175        |args: &[Atom]| -> Result<Vec<Atom>, ExecError> { assert_results_are_equal(args, DefaultEquality{}) },
176    ));
177    tref.register_function(GroundedFunctionAtom::new(
178        r"_assert-results-are-alpha-equal-msg".into(),
179        expr!("->" "Atom" "Atom" "Atom" "Atom" ("->")),
180        |args: &[Atom]| -> Result<Vec<Atom>, ExecError> { assert_results_are_equal(args, AlphaEquality{}) },
181    ));
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use crate::metta::runner::{Metta, EnvBuilder, SExprParser};
188    use crate::metta::runner::run_program;
189
190    #[test]
191    fn metta_assert_equal_op() {
192        let metta = Metta::new(Some(EnvBuilder::test_env()));
193        let program = "
194            (= (foo $x) $x)
195            (= (bar $x) $x)
196        ";
197        assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![]));
198        assert_eq!(metta.run(SExprParser::new("!(assertEqual (foo A) (bar A))")), Ok(vec![
199            vec![UNIT_ATOM],
200        ]));
201        assert_eq!(metta.run(SExprParser::new("!(assertEqual (foo A) (bar B))")), Ok(vec![
202            vec![expr!("Error" ("assertEqual" ("foo" "A") ("bar" "B")) {Str::from_str("\nExpected: [B]\nGot: [A]\nMissed results: B\nExcessive results: A")})],
203        ]));
204        assert_eq!(metta.run(SExprParser::new("!(assertEqual (foo A) Empty)")), Ok(vec![
205            vec![expr!("Error" ("assertEqual" ("foo" "A") "Empty") {Str::from_str("\nExpected: []\nGot: [A]\nExcessive results: A")})]
206        ]));
207    }
208
209    #[test]
210    fn metta_assert_alpha_equal_op() {
211        let metta = Metta::new(Some(EnvBuilder::test_env()));
212        let program = "
213            (= (foo $x) $x)
214            (= (bar $x) $x)
215        ";
216        assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![]));
217        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqual (foo $x) (bar $x))")), Ok(vec![
218            vec![UNIT_ATOM],
219        ]));
220        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqual (foo A) (bar B))")), Ok(vec![
221            vec![expr!("Error" ("assertAlphaEqual" ("foo" "A") ("bar" "B")) {Str::from_str("\nExpected: [B]\nGot: [A]\nMissed results: B\nExcessive results: A")})],
222        ]));
223        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqual (foo A) Empty)")), Ok(vec![
224            vec![expr!("Error" ("assertAlphaEqual" ("foo" "A") "Empty") {Str::from_str("\nExpected: []\nGot: [A]\nExcessive results: A")})]
225        ]));
226    }
227
228    #[test]
229    fn metta_alpha_eq_op() {
230        assert_eq!(run_program(&format!("(= (foo) (R $x $y)) !(let $foo (foo) (=alpha $foo (R $x $y)))")), Ok(vec![vec![expr!({Bool(true)})]]));
231        assert_eq!(run_program(&format!("(= (foo) (R $x $y)) !(let $foo (foo) (=alpha $foo (R $x $x)))")), Ok(vec![vec![expr!({Bool(false)})]]));
232    }
233
234    #[test]
235    fn metta_assert_equal_to_result_op() {
236        let metta = Metta::new(Some(EnvBuilder::test_env()));
237        let program = "
238            (= (foo) A)
239            (= (foo) B)
240            (= (bar) C)
241            (= (baz) D)
242            (= (baz) D)
243            (= (baz) D)
244        ";
245        assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![]));
246        assert_eq!(metta.run(SExprParser::new("!(assertEqualToResult (foo) (A B))")), Ok(vec![
247            vec![UNIT_ATOM],
248        ]));
249        assert_eq!(metta.run(SExprParser::new("!(assertEqualToResult (bar) (A))")), Ok(vec![
250            vec![expr!("Error" ("assertEqualToResult" ("bar") ("A")) {Str::from_str("\nExpected: [A]\nGot: [C]\nMissed results: A\nExcessive results: C")})],
251        ]));
252        assert_eq!(metta.run(SExprParser::new("!(assertEqualToResult (baz) (D))")), Ok(vec![
253            vec![expr!("Error" ("assertEqualToResult" ("baz") ("D")) {Str::from_str("\nExpected: [D]\nGot: [D, D, D]\nExcessive results: D, D")})]
254        ]));
255    }
256
257    #[test]
258    fn metta_assert_alpha_equal_to_result_op() {
259        let metta = Metta::new(Some(EnvBuilder::test_env()));
260        let program = "
261            (= (foo) $x)
262            (= (bar) C)
263            (= (baz) D)
264            (= (baz) D)
265            (= (baz) D)
266        ";
267        assert_eq!(metta.run(SExprParser::new(program)), Ok(vec![]));
268        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult (foo) ($x))")), Ok(vec![
269            vec![UNIT_ATOM],
270        ]));
271        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult ((foo) (foo)) (($x $y)))")), Ok(vec![
272            vec![UNIT_ATOM],
273        ]));
274
275        let res = metta.run(SExprParser::new("!(assertAlphaEqualToResult ((foo) (foo)) (($x $x)))")).unwrap();
276        let res_first_atom = res.get(0).unwrap().get(0);
277        assert_eq!(res_first_atom.unwrap().iter().next().unwrap(), &sym!("Error"));
278        assert_eq!(res.get(0).unwrap().len(), 1);
279
280        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult (bar) (A))")), Ok(vec![
281            vec![expr!("Error" ("assertAlphaEqualToResult" ("bar") ("A")) {Str::from_str("\nExpected: [A]\nGot: [C]\nMissed results: A\nExcessive results: C")})],
282        ]));
283        assert_eq!(metta.run(SExprParser::new("!(assertAlphaEqualToResult (baz) (D))")), Ok(vec![
284            vec![expr!("Error" ("assertAlphaEqualToResult" ("baz") ("D")) {Str::from_str("\nExpected: [D]\nGot: [D, D, D]\nExcessive results: D, D")})]
285        ]));
286    }
287
288    #[test]
289    fn trace_op() {
290        assert_eq!(TraceOp{}.execute(&mut vec![sym!("\"Here?\""), sym!("42")]),
291                   Ok(vec![sym!("42")]));
292    }
293}