From: Mark Thom Date: Sat, 2 Dec 2017 18:10:25 +0000 (-0700) Subject: add support for rational numbers, division. X-Git-Tag: v0.8.110~666 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=9a7419c507462627ff1d247b01c07e0bb78c0a4c;p=scryer-prolog.git add support for rational numbers, division. --- diff --git a/Cargo.lock b/Cargo.lock index 6717f6f2..bcb30e0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "rusty-wam" -version = "0.7.2" +version = "0.7.3" dependencies = [ "lazy_static 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 092db674..6a8c39d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusty-wam" -version = "0.7.2" +version = "0.7.3" authors = ["Mark Thom"] [dependencies] diff --git a/README.md b/README.md index 26bdbd60..fe91e241 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Extend rusty-wam to include the following, among other features: * ISO Prolog compliant throw/catch (_done_). * Built-in and user-defined operators of all fixities, with custom associativity and precedence (_done_). -* Bignum and floating point arithmetic (_in progress_). +* Bignum, rational number and floating point arithmetic (_in progress_). * Built-in control operators (`,`, `;`, `->`, etc.). * Built-in predicates for list processing and top-level declarative control (`setup_call_control/3`, `call_with_inference_limit/3`, @@ -47,7 +47,7 @@ ideally, very fast) [Shen](http://shenlanguage.org) implementation. The following predicates are built-in to rusty-wam. * Arithmetic support: - * is/2 works for (+)/2, (-)/{1,2}, (*)/2, (//)/2, (div)/2. + * is/2 works for (+)/2, (-)/{1,2}, (*)/2, (//)/2, (div)/2, (/)/2, (rdiv)/2. * atomic/1 * call/N (1 <= N <= 63) * catch/3 diff --git a/src/main.rs b/src/main.rs index 64bd07b4..c174a592 100644 --- a/src/main.rs +++ b/src/main.rs @@ -757,17 +757,32 @@ mod tests { assert_eq!(submit(&mut wam, "?- 7 is 3 * 2."), false); assert_eq!(submit(&mut wam, "?- 7 is 3.5 * 2."), false); assert_eq!(submit(&mut wam, "?- 7.0 is 3.5 * 2."), true); + assert_eq!(submit(&mut wam, "?- 7.0 is 14 / 2."), true); + assert_eq!(submit(&mut wam, "?- 4.666 is 14.0 / 3."), false); + assert_eq!(submit(&mut wam, "?- 4.0 is 8.0 / 2."), true); submit(&mut wam, "f(X) :- X is 5 // 0."); assert_eq!(submit(&mut wam, "?- catch(f(X), evaluation_error(E), true), E = zero_divisor."), true); + + submit(&mut wam, "f(X) :- X is (5 rdiv 1) / 0."); + assert_eq!(submit(&mut wam, "?- catch(f(X), evaluation_error(E), true), E = zero_divisor."), + true); + + submit(&mut wam, "f(X) :- X is 5.0 / 0."); + + assert_eq!(submit(&mut wam, "?- catch(f(X), evaluation_error(E), true), E = zero_divisor."), + true); + assert_eq!(submit(&mut wam, "?- X is ((3 + 4) // 2) + 2 - 1 // 1, Y is 2+2, Z is X+Y."), true); assert_eq!(submit(&mut wam, "?- X is ((3 + 4) // 2) + 2 - 1 // 1, Y is 2+2, Z = 8, Y is 4."), true); + + assert_eq!(submit(&mut wam, "?- X is (3 rdiv 4) / 2, Y is 3 rdiv 8, X = Y."), true); } } diff --git a/src/prolog/arithmetic.rs b/src/prolog/arithmetic.rs index 51341230..5978b6bb 100644 --- a/src/prolog/arithmetic.rs +++ b/src/prolog/arithmetic.rs @@ -97,12 +97,14 @@ impl<'a> ArithmeticEvaluator<'a> { -> Result { match name.as_str() { - "+" => Ok(ArithmeticInstruction::Add(a1, a2, t)), - "-" => Ok(ArithmeticInstruction::Sub(a1, a2, t)), - "//" => Ok(ArithmeticInstruction::IDiv(a1, a2, t)), - "div" => Ok(ArithmeticInstruction::IDiv(a1, a2, t)), - "*" => Ok(ArithmeticInstruction::Mul(a1, a2, t)), - _ => Err(ArithmeticError::InvalidOp) + "+" => Ok(ArithmeticInstruction::Add(a1, a2, t)), + "-" => Ok(ArithmeticInstruction::Sub(a1, a2, t)), + "/" => Ok(ArithmeticInstruction::Div(a1, a2, t)), + "//" => Ok(ArithmeticInstruction::IDiv(a1, a2, t)), + "div" => Ok(ArithmeticInstruction::IDiv(a1, a2, t)), + "rdiv" => Ok(ArithmeticInstruction::RDiv(a1, a2, t)), + "*" => Ok(ArithmeticInstruction::Mul(a1, a2, t)), + _ => Err(ArithmeticError::InvalidOp) } } diff --git a/src/prolog/ast.rs b/src/prolog/ast.rs index 0f7c993e..b58f6b2f 100644 --- a/src/prolog/ast.rs +++ b/src/prolog/ast.rs @@ -1,5 +1,6 @@ use prolog::num::bigint::BigInt; -use prolog::num::ToPrimitive; +use prolog::num::{Float, ToPrimitive, Zero}; +use prolog::num::rational::Ratio; use prolog::ordered_float::*; @@ -9,7 +10,7 @@ use std::collections::{HashMap, VecDeque}; use std::fmt; use std::io::Error as IOError; use std::num::{ParseFloatError}; -use std::ops::{Add, AddAssign, Sub, Mul, Neg}; +use std::ops::{Add, AddAssign, Div, Sub, Mul, Neg}; use std::str::Utf8Error; use std::vec::Vec; @@ -268,6 +269,7 @@ pub enum Constant { Atom(Atom), Float(OrderedFloat), Integer(BigInt), + Rational(Ratio), String(String), Usize(usize), EmptyList @@ -284,6 +286,8 @@ impl fmt::Display for Constant { write!(f, "{}", fl), &Constant::Integer(ref i) => write!(f, "{}", i), + &Constant::Rational(ref r) => + write!(f, "{}", r), &Constant::String(ref s) => write!(f, "{}", s), &Constant::Usize(integer) => @@ -295,6 +299,7 @@ impl fmt::Display for Constant { impl From for Constant { fn from(n: Number) -> Self { match n { + Number::Rational(r) => Constant::Rational(r), Number::Integer(n) => Constant::Integer(n), Number::Float(f) => Constant::Float(f) } @@ -309,10 +314,10 @@ pub enum Term { Var(Cell, Var) } -pub enum InlinedQueryTerm { +pub enum InlinedQueryTerm { IsAtomic(Vec>), IsVar(Vec>) -} +} impl InlinedQueryTerm { pub fn arity(&self) -> usize { @@ -434,24 +439,98 @@ impl IndexedChoiceInstruction { #[derive(Clone)] pub enum Number { Float(OrderedFloat), - Integer(BigInt) + Integer(BigInt), + Rational(Ratio) +} + +impl Number { + pub fn is_zero(&self) -> bool { + match self { + &Number::Float(fl) => fl.into_inner().is_zero(), + &Number::Integer(ref bi) => bi.is_zero(), + &Number::Rational(ref r) => r.is_zero() + } + } +} + +enum NumberPair { + Float(OrderedFloat, OrderedFloat), + Integer(BigInt, BigInt), + Rational(Ratio, Ratio) +} + +impl NumberPair { + fn flip(self) -> NumberPair { + match self { + NumberPair::Float(f1, f2) => NumberPair::Float(f2, f1), + NumberPair::Integer(n1, n2) => NumberPair::Integer(n2, n1), + NumberPair::Rational(r1, r2) => NumberPair::Rational(r2, r1) + } + } + + fn integer_float_pair(n1: BigInt, n2: OrderedFloat) -> NumberPair { + match n1.to_f64() { + Some(f1) => NumberPair::Float(OrderedFloat(f1), n2), + None => if let Some(r) = Ratio::from_float(n2.into_inner()) { + NumberPair::Rational(Ratio::from_integer(n1), r) + } else if n2.into_inner().is_sign_positive() { + NumberPair::Float(OrderedFloat(f64::infinity()), + OrderedFloat(f64::infinity())) + } else { + NumberPair::Float(OrderedFloat(f64::neg_infinity()), + OrderedFloat(f64::neg_infinity())) + } + } + } + + fn float_rational_pair(n1: OrderedFloat, n2: Ratio) -> NumberPair { + if let Some(r) = Ratio::from_float(n1.into_inner()) { + NumberPair::Rational(r, n2) + } else if n1.into_inner().is_sign_positive() { + NumberPair::Float(OrderedFloat(f64::infinity()), + OrderedFloat(f64::infinity())) + } else { + NumberPair::Float(OrderedFloat(f64::neg_infinity()), + OrderedFloat(f64::neg_infinity())) + } + } + + fn from(n1: Number, n2: Number) -> NumberPair + { + match (n1, n2) { + (Number::Integer(n1), Number::Integer(n2)) => + NumberPair::Integer(n1, n2), + (Number::Float(n1), Number::Float(n2)) => + NumberPair::Float(n1, n2), + (Number::Rational(n1), Number::Rational(n2)) => + NumberPair::Rational(n1, n2), + (Number::Integer(n1), Number::Float(n2)) => + Self::integer_float_pair(n1, n2), + (Number::Float(n1), Number::Integer(n2)) => + Self::integer_float_pair(n2, n1).flip(), + (Number::Float(n1), Number::Rational(n2)) => + Self::float_rational_pair(n1, n2), + (Number::Rational(n1), Number::Float(n2)) => + Self::float_rational_pair(n2, n1).flip(), + (Number::Rational(n1), Number::Integer(n2)) => + NumberPair::Rational(n1, Ratio::from_integer(n2)), + (Number::Integer(n1), Number::Rational(n2)) => + NumberPair::Rational(Ratio::from_integer(n1), n2) + } + } } impl Add for Number { type Output = Number; fn add(self, rhs: Number) -> Self::Output { - match (self, rhs) { - (Number::Integer(n1), Number::Integer(n2)) => + match NumberPair::from(self, rhs) { + NumberPair::Float(f1, f2) => + Number::Float(OrderedFloat(f1.into_inner() + f2.into_inner())), + NumberPair::Integer(n1, n2) => Number::Integer(n1 + n2), - (Number::Float(n1), Number::Float(n2)) => - Number::Float(OrderedFloat(n1.into_inner() + n2.into_inner())), - (Number::Integer(n1), Number::Float(n2)) - | (Number::Float(n2), Number::Integer(n1)) => - match n1.to_f64() { - Some(n1) => Number::Float(OrderedFloat(n1 + n2.into_inner())), - None => Number::Integer(n1) - } + NumberPair::Rational(r1, r2) => + Number::Rational(r1 + r2) } } } @@ -460,17 +539,13 @@ impl Sub for Number { type Output = Number; fn sub(self, rhs: Number) -> Self::Output { - match (self, rhs) { - (Number::Integer(n1), Number::Integer(n2)) => + match NumberPair::from(self, rhs) { + NumberPair::Float(f1, f2) => + Number::Float(OrderedFloat(f1.into_inner() - f2.into_inner())), + NumberPair::Integer(n1, n2) => Number::Integer(n1 - n2), - (Number::Float(n1), Number::Float(n2)) => - Number::Float(OrderedFloat(n1.into_inner() - n2.into_inner())), - (Number::Integer(n1), Number::Float(n2)) - | (Number::Float(n2), Number::Integer(n1)) => - match n1.to_f64() { - Some(n1) => Number::Float(OrderedFloat(n1 - n2.into_inner())), - None => Number::Integer(n1) - } + NumberPair::Rational(r1, r2) => + Number::Rational(r1 - r2) } } } @@ -479,41 +554,46 @@ impl Mul for Number { type Output = Number; fn mul(self, rhs: Number) -> Self::Output { - match (self, rhs) { - (Number::Integer(n1), Number::Integer(n2)) => + match NumberPair::from(self, rhs) { + NumberPair::Float(f1, f2) => + Number::Float(OrderedFloat(f1.into_inner() * f2.into_inner())), + NumberPair::Integer(n1, n2) => Number::Integer(n1 * n2), - (Number::Float(n1), Number::Float(n2)) => - Number::Float(OrderedFloat(n1.into_inner() * n2.into_inner())), - (Number::Integer(n1), Number::Float(n2)) - | (Number::Float(n2), Number::Integer(n1)) => - match n1.to_f64() { - Some(n1) => Number::Float(OrderedFloat(n1 * n2.into_inner())), - None => Number::Integer(n1) - } - } + NumberPair::Rational(r1, r2) => + Number::Rational(r1 * r2) + } } } - -/*TODO: reserved for proper division. + impl Div for Number { type Output = Number; fn div(self, rhs: Number) -> Self::Output { - match (self, rhs) { - (Number::Integer(n1), Number::Integer(n2)) => - Number::Integer(n1 / n2), - (Number::Float(n1), Number::Float(n2)) => - Number::Float(OrderedFloat(n1.into_inner() / n2.into_inner())), - (Number::Integer(n1), Number::Float(n2)) - | (Number::Float(n2), Number::Integer(n1)) => + match NumberPair::from(self, rhs) { + NumberPair::Float(f1, f2) => + Number::Float(OrderedFloat(f1.into_inner() / f2.into_inner())), + NumberPair::Integer(n1, n2) => match n1.to_f64() { - Some(n1) => Number::Float(OrderedFloat(n1 / n2.into_inner())), - None => Number::Integer(n1) - } + Some(f1) => if let Some(f2) = n2.to_f64() { + Number::Float(OrderedFloat(f1 / f2)) + } else { + let r1 = Ratio::from_integer(n1); + let r2 = Ratio::from_integer(n2); + + Number::Rational(r1 / r2) + }, + None => { + let r1 = Ratio::from_integer(n1); + let r2 = Ratio::from_integer(n2); + + Number::Rational(r1 / r2) + }, + }, + NumberPair::Rational(r1, r2) => + Number::Rational(r1 / r2) } } } -*/ impl Neg for Number { type Output = Number; @@ -521,7 +601,8 @@ impl Neg for Number { fn neg(self) -> Self::Output { match self { Number::Integer(n) => Number::Integer(-n), - Number::Float(f) => Number::Float(OrderedFloat(-1.0 * f.into_inner())) + Number::Float(f) => Number::Float(OrderedFloat(-1.0 * f.into_inner())), + Number::Rational(r) => Number::Rational(- r) } } } @@ -549,6 +630,8 @@ pub enum ArithmeticInstruction { Sub(ArithmeticTerm, ArithmeticTerm, usize), Mul(ArithmeticTerm, ArithmeticTerm, usize), IDiv(ArithmeticTerm, ArithmeticTerm, usize), + RDiv(ArithmeticTerm, ArithmeticTerm, usize), + Div(ArithmeticTerm, ArithmeticTerm, usize), Neg(ArithmeticTerm, usize) } @@ -584,7 +667,7 @@ pub enum ControlInstruction { IsExecute(RegType), Proceed, ThrowCall, - ThrowExecute, + ThrowExecute, } impl ControlInstruction { @@ -601,7 +684,7 @@ impl ControlInstruction { &ControlInstruction::Goto(_, _) => true, &ControlInstruction::Proceed => true, &ControlInstruction::IsCall(_) => true, - &ControlInstruction::IsExecute(_) => true, + &ControlInstruction::IsExecute(_) => true, _ => false } } diff --git a/src/prolog/builtins.rs b/src/prolog/builtins.rs index d547b364..d50bab03 100644 --- a/src/prolog/builtins.rs +++ b/src/prolog/builtins.rs @@ -128,9 +128,11 @@ pub fn build_code_dir() -> (Code, CodeDir, OpDir) op_dir.insert((String::from("+"), Fixity::In), (YFX, 500)); op_dir.insert((String::from("-"), Fixity::In), (YFX, 500)); op_dir.insert((String::from("//"), Fixity::In), (YFX, 400)); + op_dir.insert((String::from("/"), Fixity::In), (YFX, 400)); op_dir.insert((String::from("div"), Fixity::In), (YFX, 400)); op_dir.insert((String::from("*"), Fixity::In), (YFX, 400)); op_dir.insert((String::from("-"), Fixity::Pre), (FY, 200)); + op_dir.insert((String::from("rdiv"), Fixity::In), (YFX, 400)); // there are 63 registers in the VM, so call/N is defined for all 0 <= N <= 62 // (an extra register is needed for the predicate name) diff --git a/src/prolog/io.rs b/src/prolog/io.rs index 38f46bfe..5b274f5f 100644 --- a/src/prolog/io.rs +++ b/src/prolog/io.rs @@ -212,8 +212,12 @@ impl fmt::Display for ArithmeticInstruction { write!(f, "sub {}, {}, @{}", a1, a2, t), &ArithmeticInstruction::Mul(ref a1, ref a2, ref t) => write!(f, "mul {}, {}, @{}", a1, a2, t), + &ArithmeticInstruction::Div(ref a1, ref a2, ref t) => + write!(f, "div {}, {}, @{}", a1, a2, t), &ArithmeticInstruction::IDiv(ref a1, ref a2, ref t) => write!(f, "idiv {}, {}, @{}", a1, a2, t), + &ArithmeticInstruction::RDiv(ref a1, ref a2, ref t) => + write!(f, "rdiv {}, {}, @{}", a1, a2, t), &ArithmeticInstruction::Neg(ref a, ref t) => write!(f, "neg {}, @{}", a, t) } diff --git a/src/prolog/machine.rs b/src/prolog/machine.rs index 41d58e9d..c876076e 100644 --- a/src/prolog/machine.rs +++ b/src/prolog/machine.rs @@ -6,6 +6,7 @@ use prolog::heapview::*; use prolog::and_stack::*; use prolog::num::Zero; use prolog::num::bigint::BigInt; +use prolog::num::rational::Ratio; use prolog::or_stack::*; use prolog::ordered_float::OrderedFloat; use prolog::fixtures::*; @@ -610,7 +611,7 @@ macro_rules! try_or_fail { match $e { Ok(val) => val, Err(msg) => { - $s.throw_exception(string!(msg)); + $s.throw_exception(msg); return; } } @@ -841,7 +842,23 @@ impl MachineState { }; } - fn get_number(&self, at: &ArithmeticTerm) -> Result { + fn get_rational(&self, at: &ArithmeticTerm) -> Result, Vec> { + let n = self.get_number(at)?; + + match n { + Number::Rational(r) => Ok(r), + Number::Float(fl) => + if let Some(r) = Ratio::from_float(fl.into_inner()) { + Ok(r) + } else { + Err(functor!("instantiation_error", 1, [atom!("(is)/2")])) + }, + Number::Integer(bi) => + Ok(Ratio::from_integer(bi)) + } + } + + fn get_number(&self, at: &ArithmeticTerm) -> Result> { match at { &ArithmeticTerm::Reg(r) => { let addr = self[r].clone(); @@ -852,8 +869,10 @@ impl MachineState { Ok(Number::Integer(bi)), Addr::Con(Constant::Float(fl)) => Ok(Number::Float(fl)), + Addr::Con(Constant::Rational(r)) => + Ok(Number::Rational(r)), _ => - Err("is/2: variable not instantiated to number.") + Err(functor!("instantiation_error", 1, [atom!("(is)/2")])) } }, &ArithmeticTerm::Interm(i) => Ok(self.interms[i-1].clone()), @@ -885,6 +904,20 @@ impl MachineState { self.interms[t - 1] = n1 * n2; self.p += 1; }, + &ArithmeticInstruction::RDiv(ref a1, ref a2, t) => { + let r1 = try_or_fail!(self, self.get_rational(a1)); + let r2 = try_or_fail!(self, self.get_rational(a2)); + + if r2 == Ratio::zero() { + self.throw_exception(functor!("evaluation_error", + 1, + [atom!("zero_divisor")])); + return; + } + + self.interms[t - 1] = Number::Rational(r1 / r2); + self.p += 1; + }, &ArithmeticInstruction::IDiv(ref a1, ref a2, t) => { let n1 = try_or_fail!(self, self.get_number(a1)); let n2 = try_or_fail!(self, self.get_number(a2)); @@ -914,6 +947,20 @@ impl MachineState { self.interms[t - 1] = - n1; self.p += 1; + }, + &ArithmeticInstruction::Div(ref a1, ref a2, t) => { + let n1 = try_or_fail!(self, self.get_number(a1)); + let n2 = try_or_fail!(self, self.get_number(a2)); + + if n2.is_zero() { + self.throw_exception(functor!("evaluation_error", + 1, + [atom!("zero_divisor")])); + return; + } + + self.interms[t - 1] = n1 / n2; + self.p += 1; } }; } diff --git a/src/prolog/macros.rs b/src/prolog/macros.rs index 75ee4eb6..8aff2437 100644 --- a/src/prolog/macros.rs +++ b/src/prolog/macros.rs @@ -28,12 +28,6 @@ macro_rules! arith { ) } -macro_rules! string { - ($str:expr) => { - vec![HeapCellValue::Con(Constant::String(String::from($str)))] - } -} - macro_rules! functor { ($name:expr, $len:expr, [$($args:expr),*]) => {{ if $len > 0 {