Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve primitive conversion code #801

Merged
merged 4 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion lib/src/atom/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,35 @@ pub trait Serializer {
}

/// Serialization error code
#[derive(Debug)]
pub enum Error {
/// Serialization of the type is not supported by serializer.
NotSupported,
}

/// Serializer which converts serialized atom into native Rust type T.
pub trait ConvertingSerializer<T>: Serializer {
fn as_mut(&mut self) -> &mut dyn Serializer;
fn into_type(self) -> Option<T>;

/// Converts atom into Rust value using `Self::default`
fn convert(atom: &super::Atom) -> Option<T>
where
T: 'static + Clone,
Self: Default {
std::convert::TryInto::<&dyn super::GroundedAtom>::try_into(atom)
.ok()
.map(|gnd| {
gnd.as_any_ref()
.downcast_ref::<T>()
.cloned()
.or_else(|| {
let mut serializer = Self::default();
gnd.serialize(&mut serializer).expect("ConvertingSerializer is not expected returning error");
serializer.into_type()
})
})
.flatten()
}
}

/// Serialization result type
Expand Down
141 changes: 55 additions & 86 deletions lib/src/metta/runner/arithmetics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::*;
use crate::metta::*;
use crate::atom::serial;
use crate::atom::serial::ConvertingSerializer;

use std::fmt::Display;

Expand Down Expand Up @@ -70,6 +71,10 @@ impl Number {
(a.cast(res_type), b.cast(res_type))
}

pub fn from_atom(atom: &Atom) -> Option<Self> {
NumberSerializer::convert(atom)
}

fn get_type(&self) -> NumberType {
match self {
Number::Integer(_) => NumberType::Integer,
Expand Down Expand Up @@ -124,6 +129,28 @@ impl Grounded for Number {
}
}

#[derive(Default)]
struct NumberSerializer {
value: Option<Number>,
}

impl serial::Serializer for NumberSerializer {
fn serialize_i64(&mut self, v: i64) -> serial::Result {
self.value = Some(Number::Integer(v));
Ok(())
}
fn serialize_f64(&mut self, v: f64) -> serial::Result {
self.value = Some(Number::Float(v));
Ok(())
}
}

impl serial::ConvertingSerializer<Number> for NumberSerializer {
fn into_type(self) -> Option<Number> {
self.value
}
}

#[derive(Clone, PartialEq, Debug)]
pub struct Bool(pub bool);

Expand All @@ -135,6 +162,10 @@ impl Bool {
_ => panic!("Could not parse Bool value: {}", b),
}
}

pub fn from_atom(atom: &Atom) -> Option<Self> {
BoolSerializer::convert(atom)
}
}

impl Into<Bool> for bool {
Expand Down Expand Up @@ -172,6 +203,25 @@ impl CustomMatch for Bool {
}
}

#[derive(Default)]
struct BoolSerializer {
value: Option<Bool>,
}

impl serial::Serializer for BoolSerializer {
fn serialize_bool(&mut self, v: bool) -> serial::Result {
self.value = Some(Bool(v));
Ok(())
}
}

impl serial::ConvertingSerializer<Bool> for BoolSerializer {
fn into_type(self) -> Option<Bool> {
self.value
}
}


macro_rules! def_binary_number_op {
($name:ident, $op:tt, $r:ident, $ret_type:ident) => {
#[derive(Clone, PartialEq, Debug)]
Expand All @@ -196,8 +246,8 @@ macro_rules! def_binary_number_op {
impl CustomExecute for $name {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from(concat!(stringify!($op), " expects two number arguments"));
let a = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?;
let b = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?;
let a = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?;
let b = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?;

let (a, b) = Number::promote(a, b);
let res: $ret_type = match (a, b) {
Expand Down Expand Up @@ -246,8 +296,8 @@ macro_rules! def_binary_bool_op {
impl CustomExecute for $name {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from(concat!(stringify!($disp), " expects two boolean arguments"));
let Bool(a) = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_bool().ok_or_else(arg_error)?;
let Bool(b) = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_bool().ok_or_else(arg_error)?;
let Bool(a) = args.get(0).and_then(Bool::from_atom).ok_or_else(arg_error)?;
let Bool(b) = args.get(1).and_then(Bool::from_atom).ok_or_else(arg_error)?;

Ok(vec![Atom::gnd(Bool(a $op b))])
}
Expand Down Expand Up @@ -309,93 +359,12 @@ impl Grounded for NotOp {
impl CustomExecute for NotOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("not expects one boolean arguments");
let &Bool(a) = args.get(0).ok_or_else(arg_error)?.as_gnd::<Bool>().ok_or_else(arg_error)?;
let &Bool(a) = args.get(0).and_then(Atom::as_gnd).ok_or_else(arg_error)?;

Ok(vec![Atom::gnd(Bool(!a))])
}
}

#[derive(Default)]
struct BoolSerializer {
value: Option<Bool>,
}

impl serial::Serializer for BoolSerializer {
fn serialize_bool(&mut self, v: bool) -> serial::Result {
self.value = Some(Bool(v));
Ok(())
}
}

#[derive(Default)]
struct NumberSerializer {
value: Option<Number>,
}

impl serial::Serializer for NumberSerializer {
fn serialize_i64(&mut self, v: i64) -> serial::Result {
self.value = Some(Number::Integer(v));
Ok(())
}
fn serialize_f64(&mut self, v: f64) -> serial::Result {
self.value = Some(Number::Float(v));
Ok(())
}
}

pub struct AsPrimitive<'a> {
atom: &'a super::Atom
}

impl<'a> AsPrimitive<'a> {
pub fn from_atom(atom: &'a super::Atom) -> Self {
Self{ atom }
}

fn as_gnd(&self) -> Option<&dyn super::GroundedAtom> {
std::convert::TryInto::<&dyn super::GroundedAtom>::try_into(self.atom).ok()
}

fn as_type<T: 'static + Clone, S: serial::ConvertingSerializer<T>>(&self, mut serializer: S) -> Option<T> {
self.as_gnd()
.map(|gnd| {
gnd.as_any_ref()
.downcast_ref::<T>()
.cloned()
.or_else(|| {
let _ = gnd.serialize(serializer.as_mut());
serializer.into_type()
})
}).flatten()
}

pub fn as_bool(self) -> Option<Bool> {
self.as_type(BoolSerializer::default())
}

pub fn as_number(self) -> Option<Number> {
self.as_type(NumberSerializer::default())
}
}

impl serial::ConvertingSerializer<Bool> for BoolSerializer {
fn as_mut(&mut self) -> &mut dyn serial::Serializer {
self
}
fn into_type(self) -> Option<Bool> {
self.value
}
}

impl serial::ConvertingSerializer<Number> for NumberSerializer {
fn as_mut(&mut self) -> &mut dyn serial::Serializer {
self
}
fn into_type(self) -> Option<Number> {
self.value
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
18 changes: 9 additions & 9 deletions lib/src/metta/runner/stdlib_minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,7 +1126,7 @@ impl CustomExecute for MaxAtomOp {
Err(ExecError::from("Empty expression"))
} else {
children.into_iter().fold(Ok(f64::NEG_INFINITY), |res, x| {
match (res, AsPrimitive::from_atom(x).as_number()) {
match (res, Number::from_atom(x)) {
(res @ Err(_), _) => res,
(_, None) => Err(ExecError::from("Only numbers are allowed in expression")),
(Ok(max), Some(x)) => Ok(f64::max(max, x.into())),
Expand Down Expand Up @@ -1159,7 +1159,7 @@ impl CustomExecute for MinAtomOp {
Err(ExecError::from("Empty expression"))
} else {
children.into_iter().fold(Ok(f64::INFINITY), |res, x| {
match (res, AsPrimitive::from_atom(x).as_number()) {
match (res, Number::from_atom(x)) {
(res @ Err(_), _) => res,
(_, None) => Err(ExecError::from("Only numbers are allowed in expression")),
(Ok(min), Some(x)) => Ok(f64::min(min, x.into())),
Expand Down Expand Up @@ -1212,7 +1212,7 @@ impl CustomExecute for IndexAtomOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("index-atom expects two arguments: expression and atom");
let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
let index = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?;
let index = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?;
match children.get(Into::<i64>::into(index) as usize) {
Some(atom) => Ok(vec![atom.clone()]),
None => Err(ExecError::from("Index is out of bounds")),
Expand Down Expand Up @@ -1309,8 +1309,8 @@ impl Grounded for RandomIntOp {
impl CustomExecute for RandomIntOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("random-int expects two arguments: number (start) and number (end)");
let start: i64 = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into();
let end: i64 = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into();
let start: i64 = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
let end: i64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
let range = start..end;
if range.is_empty() {
return Err(ExecError::from("Range is empty"));
Expand Down Expand Up @@ -1338,8 +1338,8 @@ impl Grounded for RandomFloatOp {
impl CustomExecute for RandomFloatOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("random-float expects two arguments: number (start) and number (end)");
let start: f64 = AsPrimitive::from_atom(args.get(0).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into();
let end: f64 = AsPrimitive::from_atom(args.get(1).ok_or_else(arg_error)?).as_number().ok_or_else(arg_error)?.into();
let start: f64 = args.get(0).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
let end: f64 = args.get(1).and_then(Number::from_atom).ok_or_else(arg_error)?.into();
let range = start..end;
if range.is_empty() {
return Err(ExecError::from("Range is empty"));
Expand Down Expand Up @@ -3080,14 +3080,14 @@ mod tests {
fn random_op() {
let res = RandomIntOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
let range = 0..5;
let res_i64: i64 = AsPrimitive::from_atom(res.unwrap().get(0).unwrap()).as_number().unwrap().into();
let res_i64: i64 = res.unwrap().get(0).and_then(Number::from_atom).unwrap().into();
assert!(range.contains(&res_i64));
let res = RandomIntOp{}.execute(&mut vec![expr!({Number::Integer(2)}), expr!({Number::Integer(-2)})]);
assert_eq!(res, Err(ExecError::from("Range is empty")));

let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(5)})]);
let range = 0.0..5.0;
let res_f64: f64 = AsPrimitive::from_atom(res.unwrap().get(0).unwrap()).as_number().unwrap().into();
let res_f64: f64 = res.unwrap().get(0).and_then(Number::from_atom).unwrap().into();
assert!(range.contains(&res_f64));
let res = RandomFloatOp{}.execute(&mut vec![expr!({Number::Integer(0)}), expr!({Number::Integer(0)})]);
assert_eq!(res, Err(ExecError::from("Range is empty")));
Expand Down
28 changes: 28 additions & 0 deletions lib/src/metta/runner/string.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
use crate::*;
use crate::common::collections::ImmutableString;
use crate::serial;
use crate::atom::serial::ConvertingSerializer;

/// String type
pub const ATOM_TYPE_STRING : Atom = sym!("String");

/// Grounded Rust string representation
#[derive(Clone, PartialEq, Debug)]
pub struct Str(ImmutableString);

impl Str {
/// Construct new instance from string literal
pub fn from_str(s: &'static str) -> Self {
Str(ImmutableString::Literal(s))
}
/// Construct new instance from owned string
pub fn from_string(s: String) -> Self {
Str(ImmutableString::Allocated(s))
}
/// Return reference to string slice
pub fn as_str(&self) -> &str {
self.0.as_str()
}
/// Try to convert an atom into `Str` instance
pub fn from_atom(atom: &Atom) -> Option<Self> {
StrSerializer::convert(atom)
}
}

impl AsRef<str> for Str {
Expand Down Expand Up @@ -56,3 +66,21 @@ pub fn strip_quotes(src: &str) -> &str {
}
src
}

#[derive(Default)]
struct StrSerializer {
value: Option<Str>,
}

impl serial::Serializer for StrSerializer {
fn serialize_str(&mut self, v: &str) -> serial::Result {
self.value = Some(Str::from_string(v.into()));
Ok(())
}
}

impl serial::ConvertingSerializer<Str> for StrSerializer {
fn into_type(self) -> Option<Str> {
self.value
}
}
Loading