Skip to content

Commit 1167c9d

Browse files
authored
Merge branch 'main' into artur-das-test
2 parents 56997ea + b5ec252 commit 1167c9d

File tree

3 files changed

+50
-74
lines changed

3 files changed

+50
-74
lines changed

lib/src/metta/runner/stdlib/core.rs

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -253,65 +253,6 @@ impl CustomExecute for CaptureOp {
253253
}
254254
}
255255

256-
#[derive(Clone, Debug)]
257-
pub struct CaseOp {
258-
space: DynSpace,
259-
settings: PragmaSettings,
260-
}
261-
262-
grounded_op!(CaseOp, "case");
263-
264-
impl CaseOp {
265-
pub fn new(space: DynSpace, settings: PragmaSettings) -> Self {
266-
Self{ space, settings }
267-
}
268-
}
269-
270-
impl Grounded for CaseOp {
271-
fn type_(&self) -> Atom {
272-
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_ATOM, ATOM_TYPE_EXPRESSION, ATOM_TYPE_UNDEFINED])
273-
}
274-
275-
fn as_execute(&self) -> Option<&dyn CustomExecute> {
276-
Some(self)
277-
}
278-
}
279-
280-
impl CustomExecute for CaseOp {
281-
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
282-
let arg_error = || ExecError::from("case expects two arguments: atom and expression of cases");
283-
let cases = args.get(1).ok_or_else(arg_error)?;
284-
let atom = args.get(0).ok_or_else(arg_error)?;
285-
log::debug!("CaseOp::execute: atom: {}, cases: {:?}", atom, cases);
286-
287-
let switch = |interpreted: Atom| -> Atom {
288-
Atom::expr([sym!("switch"), interpreted, cases.clone()])
289-
};
290-
291-
// Interpreting argument inside CaseOp is required because otherwise `Empty` result
292-
// calculated inside interpreter cuts the whole branch of the interpretation. Also we
293-
// cannot use `unify` in a unit test because after interpreting `(chain... (chain
294-
// (metta (unify ...) Atom <space>)) ...)` `chain` executes `unify` and also gets
295-
// `Empty` even if we have `Atom` as a resulting type. It can be solved by different ways.
296-
// One way is to invent new type `EmptyType` (type of the `Empty` atom) and use this type
297-
// in a function to allow `Empty` atoms as an input. `EmptyType` type should not be
298-
// casted to the `%Undefined%` thus one cannot pass `Empty` to the function which accepts
299-
// `%Undefined%`. Another way is to introduce "call" level. Thus if function called
300-
// returned the result to the `chain` it should stop reducing it and insert it into the
301-
// last argument.
302-
let results = interpret(self.space.clone(), atom, self.settings.clone());
303-
log::debug!("CaseOp::execute: atom results: {:?}", results);
304-
let results = match results {
305-
Ok(results) if results.is_empty() =>
306-
vec![switch(EMPTY_SYMBOL)],
307-
Ok(results) =>
308-
results.into_iter().map(|atom| switch(atom)).collect(),
309-
Err(err) => vec![Atom::expr([ERROR_SYMBOL, atom.clone(), Atom::sym(err)])],
310-
};
311-
Ok(results)
312-
}
313-
}
314-
315256
fn collapse_add_next_atom_from_collapse_bind_result(args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
316257
let arg0_error = || ExecError::from("Expression is expected as a first argument");
317258
let list = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg0_error)?).map_err(|_| arg0_error())?;
@@ -347,8 +288,6 @@ pub(super) fn register_context_independent_tokens(tref: &mut Tokenizer) {
347288
}
348289

349290
pub(super) fn register_context_dependent_tokens(tref: &mut Tokenizer, space: &DynSpace, metta: &Metta) {
350-
let case_op = Atom::gnd(CaseOp::new(space.clone(), metta.settings().clone()));
351-
tref.register_token(regex(r"case"), move |_| { case_op.clone() });
352291
let capture_op = Atom::gnd(CaptureOp::new(space.clone(), metta.settings().clone()));
353292
tref.register_token(regex(r"capture"), move |_| { capture_op.clone() });
354293
let pragma_op = Atom::gnd(PragmaOp::new(metta.settings().clone()));
@@ -409,6 +348,18 @@ mod tests {
409348
assert_eq!(result, Ok(vec![vec![expr!("nok")]]));
410349
}
411350

351+
#[test]
352+
fn metta_case_error() {
353+
let program = "
354+
(= (err) (Error (err) \"Test error\"))
355+
!(case (err) (
356+
((Error $a \"Test error\") ())
357+
($_ (Error $_ \"Error is expected\"))
358+
))
359+
";
360+
assert_eq!(run_program(program), Ok(vec![vec![UNIT_ATOM]]));
361+
}
362+
412363
#[test]
413364
fn test_pragma_interpreter_bare_minimal() {
414365
let program = "
@@ -503,8 +454,8 @@ mod tests {
503454
let nested = run_program("!(sealed ($c) (quote (= ($a $x $c) ($b))))");
504455
let simple_replace = run_program("!(sealed ($z $x) (quote (= ($y $z))))");
505456

506-
assert!(hyperon_atom::matcher::atoms_are_equivalent(&nested.unwrap()[0][0], &expr!("quote" ("=" (a b c) (z)))));
507-
assert!(hyperon_atom::matcher::atoms_are_equivalent(&simple_replace.unwrap()[0][0], &expr!("quote" ("=" (y z)))));
457+
assert!(atoms_are_equivalent(&nested.unwrap()[0][0], &expr!("quote" ("=" (a b c) (z)))));
458+
assert!(atoms_are_equivalent(&simple_replace.unwrap()[0][0], &expr!("quote" ("=" (y z)))));
508459
}
509460

510461
#[test]

lib/src/metta/runner/stdlib/stdlib.metta

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@
310310
(return $then) ))))))
311311

312312
; Difference between `switch` and `case` is a way how they interpret `Empty`
313-
; result. `CaseOp` interprets first argument inside itself and then manually
313+
; result. `case` interprets first argument inside itself and then manually
314314
; checks whether result is empty. `switch` is interpreted in a context of
315315
; main interpreter. Minimal interpreter correctly passes `Empty` as an
316316
; argument to the `switch` but when `switch` is called from MeTTa interpreter
@@ -325,6 +325,10 @@
325325
(@return "Result which corresponds to the pattern which is matched with the passed atom first"))
326326
(: switch (-> %Undefined% Expression %Undefined%))
327327
(= (switch $atom $cases)
328+
(id (switch-minimal $atom $cases)))
329+
330+
(: switch-minimal (-> Atom Expression Atom))
331+
(= (switch-minimal $atom $cases)
328332
(function (chain (decons-atom $cases) $list
329333
(chain (eval (switch-internal $atom $list)) $res
330334
(chain (eval (if-equal $res NotReducible Empty $res)) $x (return $x)) ))))
@@ -335,10 +339,11 @@
335339
(@param "Atom (it will be evaluated)")
336340
(@param "Deconsed tuple of pairs mapping condition patterns to results")))
337341
(@return "Result of evaluating of Atom bound to met condition"))
342+
(: switch-internal (-> Atom Expression Atom))
338343
(= (switch-internal $atom (($pattern $template) $tail))
339344
(function (unify $atom $pattern
340345
(return $template)
341-
(chain (eval (switch $atom $tail)) $ret (return $ret)) )))
346+
(chain (eval (switch-minimal $atom $tail)) $ret (return $ret)) )))
342347

343348
; TODO: Type is used here, but there is no definition for the -> type
344349
; constructor for instance, thus in practice it matches because -> has
@@ -351,15 +356,14 @@
351356
(@return "True if type is a function type, False - otherwise"))
352357
(: is-function (-> Type Bool))
353358
(= (is-function $type)
354-
(function (chain (eval (get-metatype $type)) $meta
355-
(eval (switch ($type $meta) (
356-
(($type Expression)
357-
(chain (decons-atom $type) $pair
358-
(unify ($head $_tail) $pair
359-
(unify $head -> (return True) (return False))
360-
(return (Error (is-function $type) "is-function non-empty expression as an argument")) )))
361-
(($type $meta) (return False))
362-
))))))
359+
(let $mtype (get-metatype $type)
360+
(unify $mtype Expression
361+
(let $size (size-atom $type)
362+
(unify $size 0
363+
False
364+
(let ($h $t) (decons-atom $type)
365+
(unify $h -> True False))))
366+
False)))
363367

364368
(@doc type-cast
365369
(@desc "Casts atom passed as a first argument to the type passed as a second argument using space as a context")
@@ -735,6 +739,7 @@
735739
(@return "Expression containing full documentation on function"))
736740
(: @doc-formal (-> DocItem DocKindFunction DocType DocDescription DocParameters DocReturn DocFormal))
737741
(: @doc-formal (-> DocItem DocKindAtom DocType DocDescription DocFormal))
742+
(: @doc-formal (-> DocItem DocKindFunction DocType DocDescription DocFormal))
738743

739744
(@doc @item
740745
(@desc "Used for documentation purposes. Converts atom/function's name to DocItem")
@@ -1167,6 +1172,12 @@
11671172
(@param "Atom (it will be evaluated)")
11681173
(@param "Tuple of pairs mapping condition patterns to results")))
11691174
(@return "Result of evaluating of Atom bound to met condition"))
1175+
(: case (-> Atom Expression %Undefined%))
1176+
(= (case $atom $cases)
1177+
(let $c (collapse $atom)
1178+
(if (== (noeval $c) ())
1179+
(id (switch-minimal Empty $cases))
1180+
(chain (eval (superpose $c)) $e (id (switch-minimal $e $cases))) )))
11701181

11711182
(@doc capture
11721183
(@desc "Wraps an atom and capture the current space")

lib/tests/test_stdlib.metta

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@
2222
(case (unify (B C) (C B) ok Empty) ((ok ok) (Empty nok)))
2323
(nok))
2424

25+
; case/switch/switch-minimal check if result is being reduced or not
26+
27+
!(assertEqual
28+
(case 5 ((4 False) (5 (+ 1 2))))
29+
3)
30+
31+
!(assertEqual
32+
(switch 5 ((4 False) (5 (+ 1 2))))
33+
3)
34+
35+
!(assertEqual
36+
(switch-minimal 5 ((4 False) (5 (+ 1 2))))
37+
(noeval (+ 1 2)))
38+
2539
; assertIncludes
2640
!(assertEqualToResult
2741
(assertIncludes (superpose (41 42 43)) (42))

0 commit comments

Comments
 (0)