From 3b24534c8456c282b0b9f35e89964539a57bad60 Mon Sep 17 00:00:00 2001 From: osmarks Date: Sun, 4 Aug 2024 15:46:06 +0100 Subject: [PATCH] Basic interval arithmetic capability --- Cargo.lock | 80 +++++++++++---------- Cargo.toml | 1 + src/interval_arithmetic.rs | 139 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 62 ++++++++++------- src/parse.rs | 23 +++--- src/value.rs | 27 +++++-- 6 files changed, 254 insertions(+), 78 deletions(-) create mode 100644 src/interval_arithmetic.rs diff --git a/Cargo.lock b/Cargo.lock index ad1ea11..4867299 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,15 +4,15 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.44" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cfg-if" @@ -28,39 +28,36 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "either" -version = "1.6.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "inlinable_string" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "libc" -version = "0.2.137" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "log" -version = "0.4.17" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memory_units" @@ -70,9 +67,9 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "once_cell" -version = "1.16.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "osmarkscalculator" @@ -81,33 +78,40 @@ dependencies = [ "anyhow", "inlinable_string", "itertools", + "seahash", "wasm-bindgen", "wee_alloc", ] [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] -name = "syn" -version = "1.0.103" +name = "seahash" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -116,15 +120,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -132,9 +136,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -147,9 +151,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -157,9 +161,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -170,9 +174,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wee_alloc" diff --git a/Cargo.toml b/Cargo.toml index a2a9cae..de7dac6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ inlinable_string = "0.1" itertools = "0.10" wasm-bindgen = "0.2.63" wee_alloc = "0.4.5" +seahash = "4" [profile.release] opt-level = "s" diff --git a/src/interval_arithmetic.rs b/src/interval_arithmetic.rs new file mode 100644 index 0000000..5b3a52b --- /dev/null +++ b/src/interval_arithmetic.rs @@ -0,0 +1,139 @@ +use std::{fmt::Display, hash::{Hash, Hasher}, ops::{Add, Div, Mul, Sub, Neg}}; +use anyhow::{Result, anyhow}; + +// This is not strictly right because 1.0 + 1.0 etc can return exact results and don't need to expand the interval. +// Unfortunately, I don't actually know how to fix that. + +#[derive(Debug, Clone, Copy)] +pub struct Interval(pub f64, pub f64); + +impl Add for Interval { + type Output = Interval; + + fn add(self, rhs: Interval) -> Interval { + Interval((self.0 + rhs.0).next_down(), (self.1 + rhs.1).next_up()) + } +} + +impl Sub for Interval { + type Output = Interval; + + fn sub(self, rhs: Interval) -> Interval { + Interval((self.0 - rhs.1).next_down(), (self.0 - rhs.1).next_up()) + } +} + +impl Mul for Interval { + type Output = Interval; + + fn mul(self, rhs: Self) -> Interval { + if self.0 >= 0.0 && rhs.0 >= 0.0 && self.1 >= 0.0 && rhs.1 >= 0.0 { + // fast path + Interval((self.0 * rhs.0).next_down(), (self.1 * rhs.1).next_up()) + } else { + let raw_min = (self.0 * rhs.0).min(self.0 * rhs.1).min(self.1 * rhs.0).min(self.1 * rhs.1); + let raw_max = (self.0 * rhs.0).max(self.0 * rhs.1).max(self.1 * rhs.0).max(self.1 * rhs.1); + Interval(raw_min.next_down(), raw_max.next_up()) + } + } +} + +impl Div for Interval { + type Output = Interval; + + fn div(self, rhs: Self) -> Interval { + self * rhs.recip() + } +} + +impl Neg for Interval { + type Output = Interval; + + fn neg(self) -> Interval { + Interval(-self.1, -self.0) + } +} + +impl Interval { + pub fn recip(&self) -> Self { + let a = 1.0 / self.0; + let b = 1.0 / self.1; + Interval(b.next_down(), a.next_up()) + } + + #[inline] + fn integrity_check(&self) { + debug_assert!(!self.0.is_nan() && !self.1.is_nan()); + debug_assert!(self.0 <= self.1); + } + + pub fn from_float(x: f64) -> Self { + Interval(x.next_down(), x.next_up()) + } + + fn ln(&self) -> Result { + if self.0 <= 0.0 || self.1 <= 0.0 { + Err(anyhow!("ln of negative number")) + } else { + Ok(Interval(self.0.ln(), self.1.ln())) + } + } + + fn exp(&self) -> Self { + Interval(self.0.exp(), self.1.exp()) + } + + pub fn pow(&self, rhs: Self) -> Result { + Ok((rhs * self.ln()?).exp()) + } + + pub fn round_to_int(&self) -> usize { + let a = self.0.round(); + let b = self.1.round(); + if a == b { + a as usize + } else { + panic!("interval not a single integer") + } + } +} + +impl PartialEq for Interval { + fn eq(&self, other: &Self) -> bool { + self.integrity_check(); + other.integrity_check(); + self.0 == other.0 && self.1 == other.1 + } +} + +impl Eq for Interval {} + +impl Hash for Interval { + fn hash(&self, state: &mut H) { + self.integrity_check(); + self.0.to_bits().hash(state); + self.1.to_bits().hash(state); + } +} + +impl Display for Interval { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Interval[{}, {}]", self.0, self.1) + } +} + +impl PartialOrd for Interval { + fn partial_cmp(&self, other: &Self) -> Option { + self.integrity_check(); + other.integrity_check(); + if self.0 < other.0 && self.1 < other.1 { + Some(std::cmp::Ordering::Less) + } else if self.0 > other.0 && self.1 > other.1 { + Some(std::cmp::Ordering::Greater) + } else if self.0 == other.0 && self.1 == other.1 { + Some(std::cmp::Ordering::Less) + } else { + None + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6dfbc2a..77e0cb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,12 @@ +#![feature(float_next_up_down)] + use anyhow::{Result, Context, bail}; use inlinable_string::InlinableString; -use std::collections::HashMap; +use interval_arithmetic::Interval; +use std::{collections::HashMap, convert::TryInto}; use std::borrow::Cow; -use std::convert::TryInto; use std::sync::Arc; +#[cfg(target_family="wasm")] use wasm_bindgen::prelude::*; #[global_allocator] @@ -13,6 +16,7 @@ mod parse; mod value; mod util; mod env; +mod interval_arithmetic; use value::Value; use env::{Rule, Ruleset, Env, Bindings, RuleResult, Operation}; @@ -36,7 +40,7 @@ fn match_and_bind(expr: &Value, rule: &Rule, env: &Env) -> Result> // "Num" predicate matches successfully if something is a number Value::Identifier(i) if i.as_ref() == "Num" => { match val { - Value::Num(_) => (), + Value::Num(_) | Value::ExactNum(_) => (), _ => success = false } }, @@ -190,7 +194,7 @@ fn match_and_bind(expr: &Value, rule: &Rule, env: &Env) -> Result> fn canonical_sort(v: &mut Value, env: &Env) -> Result<()> { match v { Value::Call(head, args) => if env.get_op(head).commutative { - args.sort(); + args.sort_by(|a, b| a.get_hash().cmp(&b.get_hash())); }, _ => () } @@ -215,7 +219,7 @@ fn flatten_tree(v: &mut Value, env: &Env) -> Result<()> { } match move_pos { Some(pos) => { - let removed = std::mem::replace(&mut args[pos], Value::Num(0)); + let removed = std::mem::replace(&mut args[pos], Value::Identifier(InlinableString::from(""))); // We know that removed will be a Call (because its head wasn't None earlier). Unfortunately, rustc does not know this. match removed { Value::Call(_, removed_child_args) => args.splice(pos..=pos, removed_child_args.into_iter()), @@ -285,11 +289,22 @@ fn run_rewrite(v: &mut Value, env: &Env) -> Result<()> { // Utility function for defining intrinsic functions for binary operators. // Converts a function which does the actual operation to a function from bindings to a value. -fn wrap_binop Result + Sync + Send>(op: F) -> Box Result + Sync + Send> { +fn wrap_binop Result + Sync + Send, G: 'static + Fn(i128, i128) -> Result + Sync + Send>(op: F, op_exact: G) -> Box Result + Sync + Send> { Box::new(move |bindings: &Bindings| { - let a = bindings.get(&InlinableString::from("a")).context("binop missing first argument")?.assert_num("binop first argument")?; - let b = bindings.get(&InlinableString::from("b")).context("binop missing second argument")?.assert_num("binop second argument")?; - op(a, b).map(Value::Num) + let a = bindings.get(&InlinableString::from("a")).context("binop missing first argument")?; + let b = bindings.get(&InlinableString::from("b")).context("binop missing second argument")?; + match (a, b) { + (Value::ExactNum(a), Value::ExactNum(b)) => op_exact(*a, *b).map(Value::ExactNum), + _ => op(a.assert_num("binop first argument")?, b.assert_num("binop second argument")?).map(Value::Num) + } + }) +} + +fn wrap_binop_no_exact Result + Sync + Send>(op: F) -> Box Result + Sync + Send> { + Box::new(move |bindings: &Bindings| { + let a = bindings.get(&InlinableString::from("a")).context("binop missing first argument")?; + let b = bindings.get(&InlinableString::from("b")).context("binop missing second argument")?; + op(a.assert_num("binop first argument")?, b.assert_num("binop second argument")?).map(Value::Num) }) } @@ -305,14 +320,11 @@ fn make_initial_env() -> Env { ops.insert(InlinableString::from("#"), Operation { commutative: false, associative: true }); let ops = Arc::new(ops); let mut intrinsics = HashMap::new(); - intrinsics.insert(0, wrap_binop(|a, b| a.checked_add(b).context("integer overflow"))); - intrinsics.insert(1, wrap_binop(|a, b| a.checked_sub(b).context("integer overflow"))); - intrinsics.insert(2, wrap_binop(|a, b| a.checked_mul(b).context("integer overflow"))); - intrinsics.insert(3, wrap_binop(|a, b| a.checked_div(b).context("division by zero"))); - intrinsics.insert(4, wrap_binop(|a, b| { - // The "pow" function takes a usize (machine-sized unsigned integer) and an i128 may not fit into this, so an extra conversion is needed - Ok(a.pow(b.try_into()?)) - })); + intrinsics.insert(0, wrap_binop(|a, b| Ok(a + b), |a, b| Ok(a + b))); + intrinsics.insert(1, wrap_binop(|a, b| Ok(a - b), |a, b| Ok(a - b))); + intrinsics.insert(2, wrap_binop(|a, b| Ok(a * b), |a, b| Ok(a * b))); + intrinsics.insert(3, wrap_binop_no_exact(|a, b| Ok(a / b))); + intrinsics.insert(4, wrap_binop(|a, b| a.pow(b), |a, b| a.checked_pow(b.try_into()?).context("integer overflow"))); intrinsics.insert(5, Box::new(|bindings| { // Substitute a single, given binding var=value into a target expression let var = bindings.get(&InlinableString::from("var")).unwrap(); @@ -323,7 +335,7 @@ fn make_initial_env() -> Env { new_bindings.insert(name, value.clone()); Ok(target.subst(&new_bindings)) })); - intrinsics.insert(6, wrap_binop(|a, b| a.checked_rem(b).context("division by zero"))); + //intrinsics.insert(6, wrap_binop(|a, b| a.checked_rem(b).context("division by zero"))); let intrinsics = Arc::new(intrinsics); Env { ruleset: vec![], @@ -341,7 +353,6 @@ a#Num * b#Num = Intrinsic[2] a#Num / b#Num = Intrinsic[3] a#Num ^ b#Num = Intrinsic[4] Subst[var=value, target] = Intrinsic[5] -Mod[a#Num, b#Num] = Intrinsic[6] PushRuleset[builtins] "; @@ -440,7 +451,7 @@ impl ImperativeCtx { // Insert a rule into the current ruleset; handles switching out the result for a relevant intrinsic use, generating possible reorderings, and inserting into the lookup map. fn insert_rule(&mut self, condition: &Value, result_val: Value) -> Result<()> { let result = match result_val { - Value::Call(head, args) if head == "Intrinsic" => RuleResult::Intrinsic(args[0].assert_num("Intrinsic ID")? as usize), + Value::Call(head, args) if head == "Intrinsic" => RuleResult::Intrinsic(args[0].assert_num("Intrinsic ID")?.round_to_int()), _ => RuleResult::Exp(result_val) }; for rearrangement in condition.pattern_reorderings(&self.base_env).into_iter() { @@ -477,7 +488,7 @@ impl ImperativeCtx { }, // Rebinding numbers can only bring confusion, so it is not allowed. // They also do not have a head, and so cannot be inserted into the ruleset anyway. - Value::Num(_) => bail!("You cannot rebind numbers") + Value::Num(_) | Value::ExactNum(_) => bail!("You cannot rebind numbers") } }, // SetStage[] calls set the stage the current ruleset will be applied at @@ -557,14 +568,17 @@ impl ImperativeCtx { } } +#[cfg(target_family="wasm")] static mut JS_CONTEXT: Option = None; +#[cfg(target_family="wasm")] #[wasm_bindgen] pub fn init_context() { unsafe { JS_CONTEXT = Some(ImperativeCtx::init()); } } +#[cfg(target_family="wasm")] unsafe fn load_defaults_internal() -> Result<()> { let ctx = (&mut JS_CONTEXT).as_mut().unwrap(); ctx.eval_program(BUILTINS)?; @@ -575,12 +589,14 @@ unsafe fn load_defaults_internal() -> Result<()> { ctx.eval_program(DIFFERENTIATION_DEFINITION)?; Ok(()) } +#[cfg(target_family="wasm")] #[wasm_bindgen] pub fn load_defaults() { unsafe { load_defaults_internal().unwrap(); } } +#[cfg(target_family="wasm")] #[wasm_bindgen] pub fn run_program(program: &str) -> String { unsafe { @@ -592,6 +608,7 @@ pub fn run_program(program: &str) -> String { } } } +#[cfg(target_family="wasm")] #[wasm_bindgen] pub fn deinit_context() { unsafe { @@ -628,9 +645,6 @@ mod test { ("a = 7 b = Negate[4] a + b", "3"), - ("IsEven[x] = 0 - IsEven[x#Eq[Mod[x, 2], 0]] = 1 - IsEven[3] - IsEven[4]", "Negate[1]"), ("(a+b+c)^2", "2*a*b+2*a*c+2*b*c+a^2+b^2+c^2"), ("(x+2)^7", "128+2*x^6+12*x^5+12*x^6+16*x^3+16*x^5+24*x^4+24*x^5+32*x^2+32*x^3+32*x^5+128*x^2+256*x^4+448*x+512*x^2+512*x^3+x^7") ]; diff --git a/src/parse.rs b/src/parse.rs index 0b089a8..66e2484 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,3 @@ -use std::str::FromStr; use anyhow::{Result, anyhow, Context}; use std::fmt; use inlinable_string::{InlinableString, StringExt}; @@ -22,12 +21,12 @@ pub fn lex(input: &str) -> Result> { for (index, char) in input.chars().enumerate() { state = match (char, state) { // if digit seen, switch into number state (and commit existing one if relevant) - ('0'..='9', LexState::None) => LexState::Number(char_to_string(char)), - ('0'..='9', LexState::Identifier(s)) => { + ('0'..='9' | '.', LexState::None) => LexState::Number(char_to_string(char)), + ('0'..='9' | '.', LexState::Identifier(s)) => { toks.push(Token::Identifier(s)); LexState::Number(char_to_string(char)) }, - ('0'..='9', LexState::Number(mut n)) => { + ('0'..='9' | '.', LexState::Number(mut n)) => { n.push(char); LexState::Number(n) }, @@ -79,13 +78,15 @@ pub fn lex(input: &str) -> Result> { #[derive(Debug)] pub enum ParseError { - Invalid(Token) + Invalid(Token), + FloatConversion } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ParseError::Invalid(tok) => write!(f, "invalid token {:?}", tok) + ParseError::Invalid(tok) => write!(f, "invalid token {:?}", tok), + ParseError::FloatConversion => write!(f, "invalid float") } } } @@ -148,9 +149,12 @@ impl Parser { res }, Token::Number(n) => { - let x = i128::from_str(&n).unwrap(); self.advance(); - Ast::Num(x) + if let Some(i) = n.parse::().ok() { + Ast::Integer(i) + } else { + Ast::Float(n.parse::().map_err(|_| ParseError::FloatConversion)?) + } }, Token::Identifier(s) => { self.advance(); @@ -208,7 +212,8 @@ pub fn parse(t: Vec) -> Result { #[derive(Debug)] pub enum Ast { - Num(i128), + Float(f64), + Integer(i128), Identifier(InlinableString), Op(char, Box, Box), Call(Box, Vec) diff --git a/src/value.rs b/src/value.rs index 4a5d012..ebc5336 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,6 @@ use inlinable_string::{InlinableString, StringExt}; use std::hash::{Hash, Hasher}; -use std::collections::{hash_map::DefaultHasher, HashSet, HashMap}; +use std::collections::{HashSet, HashMap}; use std::fmt::{self, Write}; use itertools::Itertools; use anyhow::{Result, anyhow}; @@ -8,10 +8,12 @@ use anyhow::{Result, anyhow}; use crate::parse::{Ast, precedence}; use crate::env::{Env, Bindings}; use crate::util::char_to_string; +use crate::interval_arithmetic::Interval; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Hash)] pub enum Value { - Num(i128), + Num(Interval), + ExactNum(i128), Call(InlinableString, Vec), Identifier(InlinableString), } @@ -27,7 +29,8 @@ impl Value { _ => unimplemented!() }, args.into_iter().map(Value::from_ast).collect()) }, - Ast::Num(n) => Value::Num(n), + Ast::Integer(n) => Value::ExactNum(n), + Ast::Float(i) => Value::Num(Interval::from_float(i)), Ast::Identifier(i) => Value::Identifier(i) } } @@ -35,7 +38,7 @@ impl Value { // Gets the hash of a Value pub fn get_hash(&self) -> u64 { // according to https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html, all instances created here are guaranteed to be the same - let mut hasher = DefaultHasher::new(); + let mut hasher = seahash::SeaHasher::new(); self.hash(&mut hasher); hasher.finish() } @@ -54,6 +57,7 @@ impl Value { fn go(v: &Value, bindings: &mut Bindings, counter: &mut usize) -> Value { match v { Value::Num(_) => v.clone(), + Value::ExactNum(_) => v.clone(), Value::Identifier(name) => { match bindings.get(name) { Some(id) => id.clone(), @@ -144,13 +148,21 @@ impl Value { } // Ensure that a value is a number, returning an error otherwise. - pub fn assert_num(&self, ctx: &'static str) -> Result { + pub fn assert_num(&self, ctx: &'static str) -> Result { match self { Value::Num(n) => Ok(*n), + Value::ExactNum(n) => Ok(Interval::from_float(*n as f64)), _ => Err(anyhow!("expected number, got {:?}", self).context(ctx)) } } + pub fn assert_exact_num(&self, ctx: &'static str) -> Result { + match self { + Value::ExactNum(n) => Ok(*n), + _ => Err(anyhow!("expected exact number, got {:?}", self).context(ctx)) + } + } + // The same but for identfiers. pub fn assert_ident(&self, ctx: &'static str) -> Result { match self { @@ -163,7 +175,8 @@ impl Value { fn go(v: &Value, parent_prec: Option, env: &Env, f: &mut W) -> fmt::Result { match v { // As unary - isn't parsed, negative numbers are written with Negate instead. - Value::Num(x) => if *x >= 0 { + Value::Num(x) => write!(f, "{}", x), + Value::ExactNum(x) => if *x >= 0 { write!(f, "{}", x) } else { write!(f, "Negate[{}]", -x) }, Value::Identifier(i) => write!(f, "{}", i),