diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 04f4b49473..698338ccd6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,23 +18,51 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - dmg: [no_dmg] + include: + - os: ubuntu-latest + shell: bash + dmg: no_dmg + - os: macos-latest + shell: bash + dmg: no_dmg + - os: windows-latest + shell: msys2 + dmg: no_dmg + + defaults: + run: + shell: ${{ matrix.shell}} {0} #include: # - os: macos-latest # dmg: dmg runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 + # This is needed because the GMP crate does not support MSVC - name: Install SWI Prolog on Linux if: ${{ matrix.os == 'ubuntu-latest' }} run: | sudo apt-add-repository ppa:swi-prolog/stable sudo apt-get update sudo apt install swi-prolog-nox - - name: Install LLVM and SWI Prolog on Windows + + - name: Install msys2 for Windows if: ${{ matrix.os == 'windows-latest' }} - run: choco install llvm swi-prolog + uses: msys2/setup-msys2@v2 + with: + install: >- + diffutils + m4 + make + gmp + gmp-devel + mingw-w64-x86_64-gcc + mingw-w64-x86_64-rust + mingw-w64-x86_64-swi-prolog + mingw-w64-clang-x86_64-toolchain + mingw-w64-x86_64-gmp + mingw-w64-x86_64-pkg-config + - name: Install SWI Prolog on MacOS from brew if: ${{ matrix.os == 'macos-latest' && matrix.dmg == 'no_dmg' }} run: brew install swi-prolog @@ -44,27 +72,31 @@ jobs: - name: Print information with swipl-info on posix if: ${{ matrix.os != 'windows-latest' && matrix.dmg == 'no_dmg' }} run: cargo run --bin print_swipl_info + - name: Install cargo-swipl + run: cargo install --path ./cargo-swipl - name: Print information with swipl-info on windows if: ${{ matrix.os == 'windows-latest' }} - env: - SWIPL: C:\Program Files\swipl\bin\swipl.exe - run: cargo run --bin print_swipl_info + env: + SWIPL: /mingw64/lib/swipl/bin/amd64-windows/swipl.exe + run: cargo swipl run --bin print_swipl_info - name: Print information with swipl-info on osx with dmg if: ${{ matrix.dmg == 'dmg' }} env: SWIPL: /Applications/SWI-Prolog.app/Contents/MacOS/swipl run: cargo run --bin print_swipl_info - - name: Install cargo-swipl - run: cargo install --path ./cargo-swipl - name: Run tests on posix if: ${{ matrix.os != 'windows-latest' && matrix.dmg == 'no_dmg' }} run: cargo swipl test --all-features + - name: Run tests on windows if: ${{ matrix.os == 'windows-latest' }} - env: - SWIPL: C:\Program Files\swipl\bin\swipl.exe - LIBCLANG_PATH: C:\Program Files\LLVM\bin\ - run: cargo swipl test --all-features + env: + C_INCLUDE_PATH: /usr/include + SWIPL: /mingw64/lib/swipl/bin/amd64-windows/swipl.exe + run: | + export C_INCLUDE_PATH=/usr/include/ + cargo swipl test --all-features --target=x86_64-pc-windows-gnu + - name: Run tests on osx with dmg if: ${{ matrix.dmg == 'dmg' }} env: diff --git a/swipl-fli/Cargo.toml b/swipl-fli/Cargo.toml index a080e483b8..df4d277f14 100644 --- a/swipl-fli/Cargo.toml +++ b/swipl-fli/Cargo.toml @@ -13,3 +13,6 @@ documentation = "https://terminusdb-labs.github.io/swipl-rs/swipl_fli/" [build-dependencies] bindgen = "0.59.0" swipl-info = {path = "../swipl-info", version = "0.3.2"} + +[features] +gmp = [] \ No newline at end of file diff --git a/swipl-fli/build.rs b/swipl-fli/build.rs index 3265919a84..329e871dc9 100644 --- a/swipl-fli/build.rs +++ b/swipl-fli/build.rs @@ -10,7 +10,12 @@ fn main() { println!("cargo:rerun-if-changed=c/wrapper.h"); println!("cargo:rerun-if-env-changed=SWIPL"); - let bindings = bindgen::Builder::default() + let mut bindings = bindgen::Builder::default(); + if cfg!(feature = "gmp") { + bindings = bindings.header_contents("include_gmp", "#include \n"); + } + + let bindings = bindings .header("c/wrapper.h") .clang_arg(format!("-I{}", info.header_dir)) .parse_callbacks(Box::new(bindgen::CargoCallbacks)) diff --git a/swipl/Cargo.toml b/swipl/Cargo.toml index 9bef286429..7de4ce2a99 100644 --- a/swipl/Cargo.toml +++ b/swipl/Cargo.toml @@ -17,6 +17,11 @@ lazy_static = "1.4.0" thiserror = "1.0" serde = {version="1.0", optional=true} convert_case = "0.6" +rug = {version = "1.17", default-features = false, optional=true, features=["integer","rational"]} +gmp-mpfr-sys = {version = "1.4", default-features = false, optional=true, features=["use-system-libs"]} [dev-dependencies] -serde = {version="1.0", features=["derive"]} \ No newline at end of file +serde = {version="1.0", features=["derive"]} + +[features] +rug = ["dep:rug", "dep:gmp-mpfr-sys", "swipl-fli/gmp"] \ No newline at end of file diff --git a/swipl/src/context.rs b/swipl/src/context.rs index 583830b006..d211f6598a 100644 --- a/swipl/src/context.rs +++ b/swipl/src/context.rs @@ -455,7 +455,7 @@ enum FrameState { /// let term2 = frame.new_term_ref(); /// /// frame.close(); -/// assert_eq!(42_u64, term.get()?); +/// assert_eq!(42_u64, term.get::()?); /// // the following would result in a compile error: /// // term2.unify(42_u64)?; /// @@ -482,7 +482,7 @@ enum FrameState { /// term.unify(43_u64)?; /// /// frame.close(); -/// assert_eq!(43_u64, term.get()?); +/// assert_eq!(43_u64, term.get::()?); /// /// Ok(()) /// } @@ -1207,7 +1207,7 @@ mod tests { let next = query.next_solution()?; assert!(!next); - assert_eq!(42_u64, term1.get()?); + assert_eq!(42_u64, term1.get::()?); let next = query.next_solution(); assert!(next.is_err()); @@ -1239,7 +1239,7 @@ mod tests { let next = query.next_solution()?; assert!(!next); - assert_eq!(42_u64, term1.get().unwrap()); + assert_eq!(42_u64, term1.get::().unwrap()); } // after leaving the block, we have discarded @@ -1272,7 +1272,7 @@ mod tests { let next = query.next_solution()?; assert!(!next); - assert_eq!(42_u64, term1.get()?); + assert_eq!(42_u64, term1.get::()?); query.discard(); } @@ -1306,12 +1306,12 @@ mod tests { let next = query.next_solution()?; assert!(!next); - assert_eq!(42_u64, term1.get()?); + assert_eq!(42_u64, term1.get::()?); query.cut(); } // a cut query leaves data intact - assert_eq!(42_u64, term1.get()?); + assert_eq!(42_u64, term1.get::()?); Ok(()) } diff --git a/swipl/src/dict.rs b/swipl/src/dict.rs index 703f515bc1..585adcb3c7 100644 --- a/swipl/src/dict.rs +++ b/swipl/src/dict.rs @@ -656,7 +656,7 @@ mod tests { dict.unify(&builder).unwrap(); dict.get_dict_tag_term(&tag2).unwrap(); - assert_eq!(42_u64, tag2.get().unwrap()); + assert_eq!(42_u64, tag2.get::().unwrap()); tag.unify(&tag2).unwrap(); } @@ -673,7 +673,7 @@ mod tests { dict.unify(&builder).unwrap(); - assert_eq!(42_u64, dict.get_dict_key("foo").unwrap()); + assert_eq!(42_u64, dict.get_dict_key::<_, u64>("foo").unwrap()); let hello_str: String = dict.get_dict_key("bar").unwrap(); assert_eq!("hello", hello_str); diff --git a/swipl/src/functor.rs b/swipl/src/functor.rs index 17ed1f8874..f6f8924d9c 100644 --- a/swipl/src/functor.rs +++ b/swipl/src/functor.rs @@ -272,12 +272,12 @@ mod tests { assert!(term.unify(f).is_ok()); assert!(term.get_arg::(1).unwrap_err().is_failure()); assert!(term.unify_arg(1, 42_u64).is_ok()); - assert_eq!(42_u64, term.get_arg(1).unwrap()); + assert_eq!(42_u64, term.get_arg::(1).unwrap()); assert!(term.unify_arg(1, 42_u64).is_ok()); assert!(!term.unify_arg(1, 43_u64).is_ok()); assert!(term.unify_arg(2, 24_u64).is_ok()); - assert_eq!(24_u64, term.get_arg(2).unwrap()); + assert_eq!(24_u64, term.get_arg::(2).unwrap()); assert!(!term.unify_arg(3, 24_u64).is_ok()); assert!(term.get_arg::(3).unwrap_err().is_failure()); diff --git a/swipl/src/result.rs b/swipl/src/result.rs index 8d13f0d858..411b900d61 100644 --- a/swipl/src/result.rs +++ b/swipl/src/result.rs @@ -16,7 +16,7 @@ use crate::context::{Context, QueryableContextType}; /// This is either a failure or an exception. In case of an exception, /// whowever returned the exception was also supposed to raise an /// exception on the context. -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq, Eq)] pub enum PrologError { #[error("prolog function failed")] Failure, diff --git a/swipl/src/term/bignum.rs b/swipl/src/term/bignum.rs new file mode 100644 index 0000000000..75969f3ae9 --- /dev/null +++ b/swipl/src/term/bignum.rs @@ -0,0 +1,163 @@ +//! Support for rug Integer and Rational using SWI-Prolog's GMP support. + +use crate::fli; +use crate::term::*; +use crate::{term_getable, unifiable}; +use rug::{Integer, Rational}; + +term_getable! { + (Integer, "bigint", term) => { + let mut i = Integer::new(); + let ptr = i.as_raw_mut(); + + let result = unsafe { + fli::PL_get_mpz(term.term_ptr(), std::mem::transmute(ptr)) + }; + if result != 0 { + Some(i) + } + else { + None + } + } +} + +unifiable! { + (self:&Integer, term) => { + let ptr = self.as_raw(); + + let result = unsafe { + fli::PL_unify_mpz(term.term_ptr(), std::mem::transmute(ptr)) + }; + + result != 0 + } +} + +term_getable! { + (Rational, "bigrat", term) => { + eprintln!("hi"); + let mut r = Rational::new(); + let ptr = r.as_raw_mut(); + + let result = unsafe { + fli::PL_get_mpq(term.term_ptr(), std::mem::transmute(ptr)) + }; + if result != 0 { + Some(r) + } + else { + None + } + } +} + +unifiable! { + (self:&Rational, term) => { + let ptr = self.as_raw(); + + let result = unsafe { + fli::PL_unify_mpq(term.term_ptr(), std::mem::transmute(ptr)) + }; + + result != 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::context::*; + use std::str::FromStr; + #[test] + fn get_bigint() { + let engine = Engine::new(); + let activation = engine.activate(); + let context: Context<_> = activation.into(); + + let big_int_str = "1234123412341234123412341234123412341234"; + let term = context.term_from_string(big_int_str).unwrap(); + + let expected = Integer::from_str(big_int_str).unwrap(); + let i: Integer = term.get().unwrap(); + assert_eq!(expected, i); + } + + #[test] + fn unify_bigint() { + let engine = Engine::new(); + let activation = engine.activate(); + let context: Context<_> = activation.into(); + + let big_int_str = "1234123412341234123412341234123412341234"; + let i = Integer::from_str(big_int_str).unwrap(); + + let term = context.new_term_ref(); + term.unify(&i).unwrap(); + + let s = context.string_from_term(&term).unwrap(); + + assert_eq!(big_int_str, s); + } + + #[test] + fn get_bigrat() { + let engine = Engine::new(); + let activation = engine.activate(); + let context: Context<_> = activation.into(); + + // we construct a big rational in swipl using the 'r' notation. + // We use a prime denominator to ensure no unexpected normalizing happens. + let big_numerator = "1234123412341234123412341234123412341234"; + let big_prime_str = "19131612631094571991039"; + let big_rat_prolog_string = format!("{big_numerator}r{big_prime_str}"); + let big_rat_div_string = format!("{big_numerator}/{big_prime_str}"); + let term = context.term_from_string(&big_rat_prolog_string).unwrap(); + + let expected = Rational::from_str(&big_rat_div_string).unwrap(); + let i: Rational = term.get().unwrap(); + assert_eq!(expected, i); + } + + #[test] + fn unify_bigrat() { + let engine = Engine::new(); + let activation = engine.activate(); + let context: Context<_> = activation.into(); + + let big_numerator = "1234123412341234123412341234123412341234"; + let big_prime_str = "19131612631094571991039"; + let big_rat_prolog_string = format!("{big_numerator}r{big_prime_str}"); + let big_rat_div_string = format!("{big_numerator}/{big_prime_str}"); + let i = Rational::from_str(&big_rat_div_string).unwrap(); + + let term = context.new_term_ref(); + term.unify(&i).unwrap(); + + let s = context.string_from_term(&term).unwrap(); + + assert_eq!(big_rat_prolog_string, s); + } + + #[test] + fn unify_unequal_bigrat_fails() { + let engine = Engine::new(); + let activation = engine.activate(); + let context: Context<_> = activation.into(); + + let big_numerator = "1234123412341234123412341234123412341234"; + let big_prime_str = "19131612631094571991039"; + let big_rat_prolog_string = format!("{big_numerator}r{big_prime_str}"); + let big_rat_div_string = format!("{big_numerator}/{big_prime_str}"); + let i = Rational::from_str(&big_rat_div_string).unwrap(); + let i2: Rational = i.clone() + 1; + + let term = context.new_term_ref(); + term.unify(&i).unwrap(); + assert!(term.unify(&i2).unwrap_err().is_failure()); + + let s = context.string_from_term(&term).unwrap(); + + assert_eq!(big_rat_prolog_string, s); + } +} diff --git a/swipl/src/term/bignum/de.rs b/swipl/src/term/bignum/de.rs new file mode 100644 index 0000000000..a0219675db --- /dev/null +++ b/swipl/src/term/bignum/de.rs @@ -0,0 +1,47 @@ +impl<'de> Deserialize<'de> for BigInt { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + deserializer.deserialize_str(BigIntDeserializationVisitor) + } +} + +struct BigIntDeserializationVisitor; + +impl<'de> Visitor<'de> for BigIntDeserializationVisitor { + type Value = BigInt; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "bigint") + } + + fn visit_str(self, v: &str) -> Result { + Integer::from_str(v) + .map(|i| BigInt(i)) + .map_err(|_e| E::custom(format!("{v} did not parse to a bigint"))) + } +} + +impl<'de> Deserialize<'de> for BigRat { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + deserializer.deserialize_str(BigRatDeserializationVisitor) + } +} + +struct BigRatDeserializationVisitor; + +impl<'de> Visitor<'de> for BigRatDeserializationVisitor { + type Value = BigRat; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "bigrat") + } + + fn visit_str(self, v: &str) -> Result { + Rational::from_str(v) + .map(|i| BigRat(i)) + .map_err(|_e| E::custom(format!("{v} did not parse to a bigrat"))) + } +} diff --git a/swipl/src/term/bignum/ser.rs b/swipl/src/term/bignum/ser.rs new file mode 100644 index 0000000000..ab71601bf1 --- /dev/null +++ b/swipl/src/term/bignum/ser.rs @@ -0,0 +1,18 @@ +impl Serialize for BigInt { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + let s = self.0.to_string(); + serializer.serialize_str(&s) + } +} + +impl Serialize for BigRat { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + let s = self.0.to_string(); + serializer.serialize_str(&s) + } +} + diff --git a/swipl/src/term/de.rs b/swipl/src/term/de.rs index b6745218e3..651b58fd7c 100644 --- a/swipl/src/term/de.rs +++ b/swipl/src/term/de.rs @@ -7,8 +7,8 @@ use crate::text::*; use crate::{atom, functor}; use serde::de::{self, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, VariantAccess, Visitor}; use serde::Deserialize; -use std::fmt::{self, Display}; use std::cell::Cell; +use std::fmt::{self, Display}; /// Deserialize a term into a rust value using serde. pub fn from_term<'a, C: QueryableContextType, T>( @@ -587,15 +587,13 @@ impl<'de, C: QueryableContextType> de::Deserializer<'de> for Deserializer<'de, C Some(atom) => { if cfg!(target_pointer_width = "32") { visitor.visit_u32(atom.atom_ptr() as u32) - } - else { + } else { visitor.visit_u64(atom.atom_ptr() as u64) } - }, - None => Err(Error::ValueNotOfExpectedType("atom")) + } + None => Err(Error::ValueNotOfExpectedType("atom")), } - } - else { + } else { self.deserialize_string(visitor) } } else { @@ -968,15 +966,13 @@ impl<'de> de::Deserializer<'de> for KeyDeserializer { Key::Atom(atom) => { if cfg!(target_pointer_width = "32") { visitor.visit_u32(atom.atom_ptr() as u32) - } - else { + } else { visitor.visit_u64(atom.atom_ptr() as u64) } - }, - _ => Err(Error::ValueNotOfExpectedType("atom")) + } + _ => Err(Error::ValueNotOfExpectedType("atom")), } - } - else { + } else { self.deserialize_string(visitor) } } else { @@ -1267,7 +1263,6 @@ impl DeserializingAtomState { da.set(true) }); - Self } diff --git a/swipl/src/term/mod.rs b/swipl/src/term/mod.rs index 360089542e..7d1d9c8a25 100644 --- a/swipl/src/term/mod.rs +++ b/swipl/src/term/mod.rs @@ -54,6 +54,9 @@ pub use de::Deserializer; #[cfg(feature = "serde")] pub use ser::{Serializer, SerializerConfiguration}; +#[cfg(feature = "rug")] +mod bignum; + /// A term reference. #[derive(Clone)] pub struct Term<'a> { diff --git a/swipl/src/term/ser.rs b/swipl/src/term/ser.rs index 14409366bc..f85dd4fdb4 100644 --- a/swipl/src/term/ser.rs +++ b/swipl/src/term/ser.rs @@ -93,7 +93,6 @@ impl SerializerConfiguration { self.default_tag = Some(tag.as_atom()); } - /// Set the default tag to use for dictionaries. /// /// This is used when serializing maps. By default, this tag will @@ -397,7 +396,6 @@ impl SerializingSwiplTermState { sst.set(true) }); - Self } @@ -412,7 +410,6 @@ impl Drop for SerializingSwiplTermState { } } - impl ser::Serialize for Atom { fn serialize(&self, serializer: S) -> Result where @@ -420,8 +417,7 @@ impl ser::Serialize for Atom { { if SerializingSwiplTermState::is_serializing_swipl_term() { serializer.serialize_newtype_struct(ATOM_STRUCT_NAME, &self.atom_ptr()) - } - else { + } else { serializer.serialize_newtype_struct(ATOM_STRUCT_NAME, &self.name()) } }