mirror of
https://github.com/osmarks/osmarkscalculator.git
synced 2024-10-29 19:16:16 +00:00
Basic interval arithmetic capability
This commit is contained in:
parent
6e10a5f84b
commit
3b24534c84
80
Cargo.lock
generated
80
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
139
src/interval_arithmetic.rs
Normal file
139
src/interval_arithmetic.rs
Normal file
@ -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<Self> {
|
||||
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<Self> {
|
||||
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<H: Hasher>(&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<std::cmp::Ordering> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
62
src/lib.rs
62
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<Option<Value>>
|
||||
// "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<Option<Value>>
|
||||
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<F: 'static + Fn(i128, i128) -> Result<i128> + Sync + Send>(op: F) -> Box<dyn Fn(&Bindings) -> Result<Value> + Sync + Send> {
|
||||
fn wrap_binop<F: 'static + Fn(Interval, Interval) -> Result<Interval> + Sync + Send, G: 'static + Fn(i128, i128) -> Result<i128> + Sync + Send>(op: F, op_exact: G) -> Box<dyn Fn(&Bindings) -> Result<Value> + 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<F: 'static + Fn(Interval, Interval) -> Result<Interval> + Sync + Send>(op: F) -> Box<dyn Fn(&Bindings) -> Result<Value> + 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<ImperativeCtx> = 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")
|
||||
];
|
||||
|
23
src/parse.rs
23
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<Vec<Token>> {
|
||||
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<Vec<Token>> {
|
||||
|
||||
#[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::<i128>().ok() {
|
||||
Ast::Integer(i)
|
||||
} else {
|
||||
Ast::Float(n.parse::<f64>().map_err(|_| ParseError::FloatConversion)?)
|
||||
}
|
||||
},
|
||||
Token::Identifier(s) => {
|
||||
self.advance();
|
||||
@ -208,7 +212,8 @@ pub fn parse(t: Vec<Token>) -> Result<Ast> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Ast {
|
||||
Num(i128),
|
||||
Float(f64),
|
||||
Integer(i128),
|
||||
Identifier(InlinableString),
|
||||
Op(char, Box<Ast>, Box<Ast>),
|
||||
Call(Box<Ast>, Vec<Ast>)
|
||||
|
27
src/value.rs
27
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<Value>),
|
||||
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<i128> {
|
||||
pub fn assert_num(&self, ctx: &'static str) -> Result<Interval> {
|
||||
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<i128> {
|
||||
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<InlinableString> {
|
||||
match self {
|
||||
@ -163,7 +175,8 @@ impl Value {
|
||||
fn go<W: fmt::Write>(v: &Value, parent_prec: Option<usize>, 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),
|
||||
|
Loading…
Reference in New Issue
Block a user