From b8af470373401304abc328f9bbe25191bad47486 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 14 Dec 2024 02:22:49 -0300 Subject: [PATCH 1/2] Add Serialize impl for Term --- Cargo.lock | 4 + Cargo.toml | 3 +- src/machine/lib_machine/mod.rs | 122 +++++++++++++++++++++++++++++++ src/machine/lib_machine/tests.rs | 84 +++++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 623d5cf78..923b407df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,7 @@ dependencies = [ "dashu-int", "num-modular", "num-order", + "num-traits", "rand", "rustversion", "static_assertions", @@ -646,6 +647,7 @@ dependencies = [ "dashu-base", "num-modular", "num-order", + "num-traits", "rand", "rustversion", "static_assertions", @@ -678,6 +680,7 @@ dependencies = [ "dashu-int", "num-modular", "num-order", + "num-traits", "rand", "rustversion", ] @@ -2579,6 +2582,7 @@ dependencies = [ "maplit", "native-tls", "num-order", + "num-traits", "ordered-float", "phf 0.11.2", "pprof", diff --git a/Cargo.toml b/Cargo.toml index a299e6900..dd79cfb75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ bytes = "1" chrono = "0.4.38" cpu-time = "1.0.0" crrl = "0.9.0" -dashu = { version = "0.4.2", features = ["rand"] } +dashu = { version = "0.4.2", features = ["rand", "num-traits"] } derive_more = "0.99.18" dirs-next = "2.0.0" divrem = "1.0.0" @@ -71,6 +71,7 @@ ryu = "1.0.18" sha3 = "0.10.8" smallvec = "1.13.2" static_assertions = "1.1.0" +num-traits = "0.2.19" scraper = { version = "0.19.1", default-features = false, features = [ "errors", diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 24f22fe78..a3f8d4ac0 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -12,6 +12,10 @@ use crate::parser::ast::{Var, VarPtr}; use crate::parser::parser::{Parser, Tokens}; use crate::read::{write_term_to_heap, TermWriteResult}; +use ::num_traits::cast::ToPrimitive; +use ::serde::ser::{SerializeMap, SerializeSeq}; +use ::serde::{Serialize, Serializer}; +use dashu::integer::{IBig, UBig}; use dashu::{Integer, Rational}; use indexmap::IndexMap; @@ -73,6 +77,124 @@ pub enum Term { Var(String), } +struct RationalInner { + numerator: IBig, + denominator: UBig, +} + +impl Serialize for RationalInner { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(2))?; + match self.numerator.to_i64() { + Some(small) => map.serialize_entry("numerator", &small)?, + None => map.serialize_entry("numerator", &self.numerator.to_string())?, + } + match self.denominator.to_u64() { + Some(small) => map.serialize_entry("denominator", &small)?, + None => map.serialize_entry("denominator", &self.denominator.to_string())?, + } + map.end() + } +} + +impl Serialize for Term { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Term::Integer(i) => match i.to_i64() { + Some(small) => serializer.serialize_i64(small), + None => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("integer", &i.to_string())?; + map.end() + } + }, + Term::Rational(r) => { + let (numerator, denominator) = r.clone().into_parts(); + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry( + "rational", + &RationalInner { + numerator, + denominator, + }, + )?; + map.end() + } + Term::Float(f) => serializer.serialize_f64(*f), + Term::Atom(a) => match a.as_str() { + "true" => serializer.serialize_bool(true), + "false" => serializer.serialize_bool(false), + "[]" => { + let seq = serializer.serialize_seq(Some(0))?; + seq.end() + } + _ => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("atom", a)?; + map.end() + } + }, + Term::String(s) => serializer.serialize_str(s), + Term::List(l) => l.serialize(serializer), + Term::Compound(f, args) => { + if f == "," && args.len() == 2 { + // Conjunction syntax sugar + let mut conj = vec![args[0].clone()]; + let mut curr_val = args[1].clone(); + loop { + if let Term::Compound(f, args) = &curr_val { + if f == "," && args.len() == 2 { + conj.push(args[0].clone()); + curr_val = args[1].clone(); + continue; + } + } + conj.push(curr_val); + break; + } + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("conjunction", &conj)?; + map.end() + } else if f == ";" && args.len() == 2 { + // Disjunction syntax sugar + let mut disj = vec![args[0].clone()]; + let mut curr_val = args[1].clone(); + loop { + if let Term::Compound(f, args) = &curr_val { + if f == ";" && args.len() == 2 { + disj.push(args[0].clone()); + curr_val = args[1].clone(); + continue; + } + } + disj.push(curr_val); + break; + } + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("disjunction", &disj)?; + map.end() + } else { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("functor", f)?; + map.serialize_entry("args", args)?; + map.end() + } + } + Term::Var(v) => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("variable", v)?; + map.end() + } + } + } +} + impl Term { /// Creates an integer term. pub fn integer(value: impl Into) -> Self { diff --git a/src/machine/lib_machine/tests.rs b/src/machine/lib_machine/tests.rs index 5b89d67da..538fee578 100644 --- a/src/machine/lib_machine/tests.rs +++ b/src/machine/lib_machine/tests.rs @@ -1,3 +1,7 @@ +use dashu::integer::{IBig, UBig}; +use dashu::rational::RBig; +use serde_json::json; + use super::*; use crate::MachineBuilder; @@ -608,3 +612,83 @@ fn errors_and_exceptions() { [Ok(LeafAnswer::Exception(Term::atom("a")))] ); } + +#[test] +#[cfg_attr(miri, ignore)] +fn term_json_serialize() { + let ibig = IBig::from(10).pow(100); + let ubig = UBig::from(7u32).pow(100); + let prolog_value = Term::compound( + "a", + [ + Term::atom("asdf"), + Term::atom("true"), + Term::atom("false"), + Term::string("fdsa"), + Term::list([Term::integer(1), Term::float(2.43)]), + Term::integer(ibig.clone()), + Term::rational(RBig::from_parts(1.into(), 7u32.into())), + Term::rational(RBig::from_parts(ibig.clone(), 7u32.into())), + Term::rational(RBig::from_parts(1.into(), ubig.clone())), + Term::rational(RBig::from_parts(ibig.clone(), ubig.clone())), + Term::variable("X"), + ], + ); + + let json_value = json!({ + "functor": "a", + "args": [ + { "atom": "asdf" }, + true, + false, + "fdsa", + [1, 2.43], + { "integer": ibig.to_string() }, + { "rational": { "numerator": 1, "denominator": 7} }, + { "rational": { "numerator": ibig.to_string(), "denominator": 7} }, + { "rational": { "numerator": 1, "denominator": ubig.to_string()} }, + { "rational": { "numerator": ibig.to_string(), "denominator": ubig.to_string()} }, + { "variable": "X" }, + ], + }); + + assert_eq!(json_value, serde_json::to_value(prolog_value).unwrap()); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn term_json_serialize_conjuntions() { + let prolog_value = Term::list([ + Term::conjunction([Term::integer(1), Term::string("asdf"), Term::atom("fdsa")]), + Term::compound( + ",", + [Term::integer(1), Term::string("asdf"), Term::atom("fdsa")], + ), + ]); + + let json_value = json!([ + { "conjunction": [1, "asdf", { "atom": "fdsa" }] }, + { "functor": ",", "args": [1, "asdf", { "atom": "fdsa" }] }, + ]); + + assert_eq!(json_value, serde_json::to_value(prolog_value).unwrap()); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn term_json_serialize_disjunctions() { + let prolog_value = Term::list([ + Term::disjunction([Term::integer(1), Term::string("asdf"), Term::atom("fdsa")]), + Term::compound( + ";", + [Term::integer(1), Term::string("asdf"), Term::atom("fdsa")], + ), + ]); + + let json_value = json!([ + { "disjunction": [1, "asdf", { "atom": "fdsa" }] }, + { "functor": ";", "args": [1, "asdf", { "atom": "fdsa" }] }, + ]); + + assert_eq!(json_value, serde_json::to_value(prolog_value).unwrap()); +} From 45de3a14a4c48f28104715fca0a872fc609515a3 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 14 Dec 2024 03:21:28 -0300 Subject: [PATCH 2/2] Add Serialize impl for LeafAnswer --- src/machine/lib_machine/mod.rs | 22 ++++++++++++++++++++++ src/machine/lib_machine/tests.rs | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index a3f8d4ac0..2394af029 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -44,6 +44,28 @@ pub enum LeafAnswer { }, } +impl Serialize for LeafAnswer { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + LeafAnswer::True => serializer.serialize_bool(true), + LeafAnswer::False => serializer.serialize_bool(false), + LeafAnswer::Exception(e) => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("exception", &e)?; + map.end() + } + LeafAnswer::LeafAnswer { bindings } => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("bindings", &bindings)?; + map.end() + } + } + } +} + impl LeafAnswer { /// Creates a leaf answer with no residual goals. pub fn from_bindings>(bindings: impl IntoIterator) -> Self { diff --git a/src/machine/lib_machine/tests.rs b/src/machine/lib_machine/tests.rs index 538fee578..9e876b926 100644 --- a/src/machine/lib_machine/tests.rs +++ b/src/machine/lib_machine/tests.rs @@ -692,3 +692,26 @@ fn term_json_serialize_disjunctions() { assert_eq!(json_value, serde_json::to_value(prolog_value).unwrap()); } + +#[test] +#[cfg_attr(miri, ignore)] +fn leaf_answer_json_serialize() { + let leaf_answers = [ + LeafAnswer::True, + LeafAnswer::False, + LeafAnswer::Exception(Term::atom("a")), + LeafAnswer::from_bindings([("X", Term::atom("a")), ("Y", Term::string("b"))]), + ]; + + let json_value = json!([ + true, + false, + { "exception": { "atom": "a" } }, + { "bindings": { + "X": { "atom": "a" }, + "Y": "b", + }}, + ]); + + assert_eq!(json_value, serde_json::to_value(leaf_answers).unwrap()); +}