Skip to content

Commit

Permalink
Merge pull request #801 from vsbogd/primitive-conversion
Browse files Browse the repository at this point in the history
Improve primitive conversion code
  • Loading branch information
vsbogd authored Nov 15, 2024
2 parents 988b716 + 7cb77e1 commit 0596870
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 96 deletions.
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
}
}

0 comments on commit 0596870

Please sign in to comment.