diff --git a/lib/src/metta/runner/stdlib.metta b/lib/src/metta/runner/stdlib.metta index 341d6edee..c68b8022b 100644 --- a/lib/src/metta/runner/stdlib.metta +++ b/lib/src/metta/runner/stdlib.metta @@ -415,6 +415,24 @@ (@param "Tail of an expression"))) (@return "New expression consists of two input arguments")) +(@doc min-atom + (@desc "Returns atom with min value in the expression (first argument). Only numbers allowed") + (@params ( + (@param "Expression which contains atoms of Number type"))) + (@return "Min value in the expression. Error if expression contains non-numeric value or is empty")) + +(@doc max-atom + (@desc "Returns atom with max value in the expression (first argument). Only numbers allowed") + (@params ( + (@param "Expression which contains atoms of Number type"))) + (@return "Max value in the expression. Error if expression contains non-numeric value or is empty")) + +(@doc size-atom + (@desc "Returns size of an expression (first argument)") + (@params ( + (@param "Expression"))) + (@return "Size of an expression")) + (@doc index-atom (@desc "Returns atom from an expression (first argument) using index (second argument) or error if index is out of bounds") (@params ( diff --git a/lib/src/metta/runner/stdlib.rs b/lib/src/metta/runner/stdlib.rs index 31ed06af1..89bceebe2 100644 --- a/lib/src/metta/runner/stdlib.rs +++ b/lib/src/metta/runner/stdlib.rs @@ -1161,6 +1161,96 @@ impl CustomExecute for IntersectionAtomOp { } } +#[derive(Clone, Debug)] +pub struct MaxAtomOp {} + +grounded_op!(MaxAtomOp, "max-atom"); + +impl Grounded for MaxAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for MaxAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("max-atom expects one argument: expression"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + if children.is_empty() { + Err(ExecError::from("Empty expression")) + } else { + children.into_iter().fold(Ok(f64::NEG_INFINITY), |res, x| { + match (res, AsPrimitive::from_atom(x).as_number()) { + (res @ Err(_), _) => res, + (_, None) => Err(ExecError::from("Only numbers are allowed in expression")), + (Ok(max), Some(x)) => Ok(f64::max(max, x.into())), + } + }) + }.map(|max| vec![Atom::gnd(Number::Float(max))]) + } +} + +#[derive(Clone, Debug)] +pub struct MinAtomOp {} + +grounded_op!(MinAtomOp, "min-atom"); + +impl Grounded for MinAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for MinAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("min-atom expects one argument: expression"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + if children.is_empty() { + Err(ExecError::from("Empty expression")) + } else { + children.into_iter().fold(Ok(f64::INFINITY), |res, x| { + match (res, AsPrimitive::from_atom(x).as_number()) { + (res @ Err(_), _) => res, + (_, None) => Err(ExecError::from("Only numbers are allowed in expression")), + (Ok(min), Some(x)) => Ok(f64::min(min, x.into())), + } + }) + }.map(|min| vec![Atom::gnd(Number::Float(min))]) + } +} + +#[derive(Clone, Debug)] +pub struct SizeAtomOp {} + +grounded_op!(SizeAtomOp, "size-atom"); + +impl Grounded for SizeAtomOp { + fn type_(&self) -> Atom { + Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER]) + } + + fn as_execute(&self) -> Option<&dyn CustomExecute> { + Some(self) + } +} + +impl CustomExecute for SizeAtomOp { + fn execute(&self, args: &[Atom]) -> Result, ExecError> { + let arg_error = || ExecError::from("size-atom expects one argument: expression"); + let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children(); + let size = children.len(); + Ok(vec![Atom::gnd(Number::Integer(size as i64))]) + } +} + #[derive(Clone, Debug)] pub struct IndexAtomOp {} @@ -1829,6 +1919,12 @@ mod non_minimal_only_stdlib { tref.register_token(regex(r"cdr-atom"), move |_| { cdr_atom_op.clone() }); let cons_atom_op = Atom::gnd(ConsAtomOp{}); tref.register_token(regex(r"cons-atom"), move |_| { cons_atom_op.clone() }); + let max_atom_op = Atom::gnd(MaxAtomOp{}); + tref.register_token(regex(r"max-atom"), move |_| { max_atom_op.clone() }); + let min_atom_op = Atom::gnd(MinAtomOp{}); + tref.register_token(regex(r"min-atom"), move |_| { min_atom_op.clone() }); + let size_atom_op = Atom::gnd(SizeAtomOp{}); + tref.register_token(regex(r"size-atom"), move |_| { size_atom_op.clone() }); let index_atom_op = Atom::gnd(IndexAtomOp{}); tref.register_token(regex(r"index-atom"), move |_| { index_atom_op.clone() }); let random_int_op = Atom::gnd(RandomIntOp{}); @@ -2122,6 +2218,34 @@ mod tests { assert_eq!(res, vec![expr!(("A" "F") ("B" "C") "D")]); } + #[test] + fn size_atom_op() { + let res = SizeAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(5)})]); + let res = SizeAtomOp{}.execute(&mut vec![expr!()]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(0)})]); + } + + #[test] + fn min_atom_op() { + let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Integer(4)})]); + let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]); + assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression"))); + let res = MinAtomOp{}.execute(&mut vec![expr!()]); + assert_eq!(res, Err(ExecError::from("Empty expression"))); + } + + #[test] + fn max_atom_op() { + let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned"); + assert_eq!(res, vec![expr!({Number::Float(5.5)})]); + let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]); + assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression"))); + let res = MaxAtomOp{}.execute(&mut vec![expr!()]); + assert_eq!(res, Err(ExecError::from("Empty expression"))); + } + #[test] fn index_atom_op() { let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(2)})]).expect("No result returned"); diff --git a/lib/src/metta/runner/stdlib_minimal.metta b/lib/src/metta/runner/stdlib_minimal.metta index 68e2f56ca..d73540549 100644 --- a/lib/src/metta/runner/stdlib_minimal.metta +++ b/lib/src/metta/runner/stdlib_minimal.metta @@ -72,6 +72,24 @@ (@return "Deconsed expression")) (: decons-atom (-> Expression Expression)) +(@doc min-atom + (@desc "Returns atom with min value in the expression (first argument). Only numbers allowed") + (@params ( + (@param "Expression which contains atoms of Number type"))) + (@return "Min value in the expression. Error if expression contains non-numeric value or is empty")) + +(@doc max-atom + (@desc "Returns atom with max value in the expression (first argument). Only numbers allowed") + (@params ( + (@param "Expression which contains atoms of Number type"))) + (@return "Max value in the expression. Error if expression contains non-numeric value or is empty")) + +(@doc size-atom + (@desc "Returns size of an expression (first argument)") + (@params ( + (@param "Expression"))) + (@return "Size of an expression")) + (@doc index-atom (@desc "Returns atom from an expression (first argument) using index (second argument) or error if index is out of bounds") (@params ( diff --git a/lib/src/metta/runner/stdlib_minimal.rs b/lib/src/metta/runner/stdlib_minimal.rs index 92a8cb704..043bebbcb 100644 --- a/lib/src/metta/runner/stdlib_minimal.rs +++ b/lib/src/metta/runner/stdlib_minimal.rs @@ -432,6 +432,12 @@ pub fn register_common_tokens(tref: &mut Tokenizer, _tokenizer: Shared