From 75d620038c4756d8e3e2b10533bec7886845cf7e Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi Date: Tue, 28 Nov 2023 16:23:09 -0800 Subject: [PATCH] Formalize concept of a Number Number is implemented using rust_decimal::Decimal which uses a 96 bit mantissa. TODO: a) Support u64, i64 variants b) Determine desired semantics for floating-point c) Determine desired big integer length d) Explore other big int/big float crates Signed-off-by: Anand Krishnamoorthi --- Cargo.toml | 5 +- src/builtins/aggregates.rs | 47 +-- src/builtins/arrays.rs | 20 +- src/builtins/bitwise.rs | 83 ++-- src/builtins/conversions.rs | 4 +- src/builtins/mod.rs | 1 + src/builtins/numbers.rs | 80 ++-- src/builtins/semver.rs | 2 +- src/builtins/strings.rs | 132 ++++--- src/builtins/time.rs | 2 +- src/builtins/units.rs | 196 ++++++---- src/builtins/utils.rs | 7 +- src/interpreter.rs | 28 +- src/lib.rs | 2 + src/number.rs | 366 ++++++++++++++++++ src/value.rs | 283 +++++++++----- .../cases/builtins/numbers/div.yaml | 29 +- .../cases/builtins/numbers/mod.yaml | 21 +- .../cases/builtins/units/parse_bytes.yaml | 4 +- tests/opa.passing | 3 + tests/value/mod.rs | 56 +-- 21 files changed, 948 insertions(+), 423 deletions(-) create mode 100644 src/number.rs diff --git a/Cargo.toml b/Cargo.toml index 5f30aadb..1f7593cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,8 @@ edition = "2021" [dependencies] anyhow = "1.0.66" semver = "1.0.20" -ordered-float = "3.4.0" serde = {version = "1.0.150", features = ["derive", "rc"] } -serde_json = "1.0.89" +serde_json = {version = "1.0.89", features = ["arbitrary_precision"] } serde_yaml = "0.9.16" log = "0.4.17" env_logger="0.10.0" @@ -18,6 +17,8 @@ lazy_static = "1.4.0" rand = "0.8.5" data-encoding = "2.4.0" regex = "1.10.2" +num = "0.4.1" +rust_decimal = { version = "1.33.1", features = ["serde-with-arbitrary-precision", "serde_json", "maths"] } [dev-dependencies] clap = { version = "4.4.7", features = ["derive"] } diff --git a/src/builtins/aggregates.rs b/src/builtins/aggregates.rs index dbd8a553..f7e40253 100644 --- a/src/builtins/aggregates.rs +++ b/src/builtins/aggregates.rs @@ -5,7 +5,8 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric}; use crate::lexer::Span; -use crate::value::{Float, Value}; +use crate::number::Number; +use crate::value::Value; use std::collections::HashMap; @@ -23,18 +24,18 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { fn count(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "count", params, args, 1)?; - Ok(Value::from_float(match &args[0] { - Value::Array(a) => a.len() as Float, - Value::Set(a) => a.len() as Float, - Value::Object(a) => a.len() as Float, - Value::String(a) => a.encode_utf16().count() as Float, + Ok(Value::from(Number::from(match &args[0] { + Value::Array(a) => a.len(), + Value::Set(a) => a.len(), + Value::Object(a) => a.len(), + Value::String(a) => a.encode_utf16().count(), a => { let span = params[0].span(); bail!(span.error( format!("`count` requires array/object/set/string argument. Got `{a}`.").as_str() )) } - })) + }))) } fn max(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -70,26 +71,26 @@ fn min(span: &Span, params: &[Ref], args: &[Value]) -> Result { fn product(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "product", params, args, 1)?; - let mut v = 1 as Float; - Ok(match &args[0] { + let mut v = Number::from(1_u64); + Ok(Value::from(match &args[0] { Value::Array(a) => { for e in a.iter() { - v *= ensure_numeric("product", ¶ms[0], e)?; + v.mul_assign(&ensure_numeric("product", ¶ms[0], e)?)?; } - Value::from_float(v) + v } Value::Set(a) => { for e in a.iter() { - v *= ensure_numeric("product", ¶ms[0], e)?; + v.mul_assign(&ensure_numeric("product", ¶ms[0], e)?)?; } - Value::from_float(v) + v } a => { let span = params[0].span(); bail!(span.error(format!("`product` requires array/set argument. Got `{a}`.").as_str())) } - }) + })) } fn sort(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -98,10 +99,10 @@ fn sort(span: &Span, params: &[Ref], args: &[Value]) -> Result { Value::Array(a) => { let mut ac = (**a).clone(); ac.sort(); - Value::from_array(ac) + Value::from(ac) } // Sorting a set produces array. - Value::Set(a) => Value::from_array(a.iter().cloned().collect()), + Value::Set(a) => Value::from(a.iter().cloned().collect::>()), a => { let span = params[0].span(); bail!(span.error(format!("`sort` requires array/set argument. Got `{a}`.").as_str())) @@ -112,24 +113,24 @@ fn sort(span: &Span, params: &[Ref], args: &[Value]) -> Result { fn sum(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "sum", params, args, 1)?; - let mut v = 0 as Float; - Ok(match &args[0] { + let mut v = Number::from(0_u64); + Ok(Value::from(match &args[0] { Value::Array(a) => { for e in a.iter() { - v += ensure_numeric("sum", ¶ms[0], e)?; + v.add_assign(&ensure_numeric("sum", ¶ms[0], e)?)?; } - Value::from_float(v) + v } Value::Set(a) => { for e in a.iter() { - v += ensure_numeric("sum", ¶ms[0], e)?; + v.add_assign(&ensure_numeric("sum", ¶ms[0], e)?)?; } - Value::from_float(v) + v } a => { let span = params[0].span(); bail!(span.error(format!("`sum` requires array/set argument. Got `{a}`.").as_str())) } - }) + })) } diff --git a/src/builtins/arrays.rs b/src/builtins/arrays.rs index 39a694d1..fe5c04d0 100644 --- a/src/builtins/arrays.rs +++ b/src/builtins/arrays.rs @@ -45,15 +45,21 @@ fn slice(span: &Span, params: &[Ref], args: &[Value]) -> Result { let start = ensure_numeric(name, ¶ms[1], &args[1].clone())?; let stop = ensure_numeric(name, ¶ms[2], &args[2].clone())?; - if start != start.floor() || stop != stop.floor() { + if !start.is_integer() || !stop.is_integer() { return Ok(Value::Undefined); } - // TODO: usize conversion checks. - let start = start as usize; - let stop = match stop as usize { - s if s > array.len() => array.len(), - s => s, + let start = match start.as_i64() { + Some(n) if n < 0 => 0, + Some(n) => n as usize, + _ => return Ok(Value::Undefined), + }; + + let stop = match stop.as_i64() { + Some(n) if n < 0 => 0, + Some(n) if n as usize > array.len() => array.len(), + Some(n) => n as usize, + _ => return Ok(Value::Undefined), }; if start >= stop { @@ -61,5 +67,5 @@ fn slice(span: &Span, params: &[Ref], args: &[Value]) -> Result { } let slice = &array[start..stop]; - Ok(Value::from_array(slice.to_vec())) + Ok(Value::from(slice.to_vec())) } diff --git a/src/builtins/bitwise.rs b/src/builtins/bitwise.rs index e1b3e055..82e035ed 100644 --- a/src/builtins/bitwise.rs +++ b/src/builtins/bitwise.rs @@ -6,7 +6,7 @@ use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric}; use crate::lexer::Span; -use crate::value::{Float, Value}; +use crate::value::Value; use std::collections::HashMap; @@ -28,14 +28,10 @@ fn and(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v1 = ensure_numeric(name, ¶ms[0], &args[0])?; let v2 = ensure_numeric(name, ¶ms[1], &args[1])?; - if v1 != v1.floor() || v2 != v2.floor() { - return Ok(Value::Undefined); - } - - // TODO: precision - let v1 = v1 as i64; - let v2 = v2 as i64; - Ok(Value::from_float((v1 & v2) as Float)) + Ok(match v1.and(&v2) { + Some(v) => Value::from(v), + _ => Value::Undefined, + }) } fn lsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -45,19 +41,10 @@ fn lsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v1 = ensure_numeric(name, ¶ms[0], &args[0])?; let v2 = ensure_numeric(name, ¶ms[1], &args[1])?; - if v1 != v1.floor() || v2 != v2.floor() { - return Ok(Value::Undefined); - } - - // TODO: precision - let v1 = v1 as i64; - let v2 = v2 as i64; - - if v2 <= 0 { - return Ok(Value::Undefined); - } - - Ok(Value::from_float((v1 << v2) as Float)) + Ok(match v1.lsh(&v2) { + Some(v) => Value::from(v), + _ => Value::Undefined, + }) } fn negate(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -66,13 +53,10 @@ fn negate(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v = ensure_numeric(name, ¶ms[0], &args[0])?; - if v != v.floor() { - return Ok(Value::Undefined); - } - - // TODO: precision - let v = v as i64; - Ok(Value::from_float((!v) as Float)) + Ok(match v.neg() { + Some(v) => Value::from(v), + _ => Value::Undefined, + }) } fn or(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -82,14 +66,10 @@ fn or(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v1 = ensure_numeric(name, ¶ms[0], &args[0])?; let v2 = ensure_numeric(name, ¶ms[1], &args[1])?; - if v1 != v1.floor() || v2 != v2.floor() { - return Ok(Value::Undefined); - } - - // TODO: precision - let v1 = v1 as i64; - let v2 = v2 as i64; - Ok(Value::from_float((v1 | v2) as Float)) + Ok(match v1.or(&v2) { + Some(v) => Value::from(v), + _ => Value::Undefined, + }) } fn rsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -99,19 +79,10 @@ fn rsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v1 = ensure_numeric(name, ¶ms[0], &args[0])?; let v2 = ensure_numeric(name, ¶ms[1], &args[1])?; - if v1 != v1.floor() || v2 != v2.floor() { - return Ok(Value::Undefined); - } - - // TODO: precision - let v1 = v1 as i64; - let v2 = v2 as i64; - - if v2 < 0 { - return Ok(Value::Undefined); - } - - Ok(Value::from_float((v1 >> v2) as Float)) + Ok(match v1.rsh(&v2) { + Some(v) => Value::from(v), + _ => Value::Undefined, + }) } fn xor(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -121,12 +92,8 @@ fn xor(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v1 = ensure_numeric(name, ¶ms[0], &args[0])?; let v2 = ensure_numeric(name, ¶ms[1], &args[1])?; - if v1 != v1.floor() || v2 != v2.floor() { - return Ok(Value::Undefined); - } - - // TODO: precision - let v1 = v1 as i64; - let v2 = v2 as i64; - Ok(Value::from_float((v1 ^ v2) as Float)) + Ok(match v1.xor(&v2) { + Some(v) => Value::from(v), + _ => Value::Undefined, + }) } diff --git a/src/builtins/conversions.rs b/src/builtins/conversions.rs index 6855df95..c7d4631e 100644 --- a/src/builtins/conversions.rs +++ b/src/builtins/conversions.rs @@ -21,8 +21,8 @@ fn to_number(span: &Span, params: &[Ref], args: &[Value]) -> Result let span = params[0].span(); Ok(match &args[0] { - Value::Bool(true) => Value::from_float(1.0), - Value::Bool(false) => Value::from_float(0.0), + Value::Bool(true) => Value::from(1u64), + Value::Bool(false) => Value::from(0u64), Value::Number(_) => args[0].clone(), // Eventhough the doc says that strings are converted using strconv.Atoi golang method, // in practice strings seems to be read as json numbers. This means that floating point diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 121af013..afd21335 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -8,6 +8,7 @@ pub mod comparison; mod conversions; mod debugging; pub mod deprecated; + mod encoding; pub mod numbers; mod objects; diff --git a/src/builtins/numbers.rs b/src/builtins/numbers.rs index ad199de0..7238b2d5 100644 --- a/src/builtins/numbers.rs +++ b/src/builtins/numbers.rs @@ -5,11 +5,12 @@ use crate::ast::{ArithOp, Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string}; use crate::lexer::Span; -use crate::value::{Float, Value}; +use crate::number::Number; +use crate::value::Value; use std::collections::HashMap; -use anyhow::Result; +use anyhow::{bail, Result}; use rand::{thread_rng, Rng}; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { @@ -22,6 +23,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { } pub fn arithmetic_operation( + span: &Span, op: &ArithOp, expr1: &Expr, expr2: &Expr, @@ -32,36 +34,37 @@ pub fn arithmetic_operation( let v1 = ensure_numeric(op_name.as_str(), expr1, &v1)?; let v2 = ensure_numeric(op_name.as_str(), expr2, &v2)?; - Ok(Value::from_float(match op { - ArithOp::Add => v1 + v2, - ArithOp::Sub => v1 - v2, - ArithOp::Mul => v1 * v2, - ArithOp::Div if v2 == 0.0 => return Ok(Value::Undefined), - ArithOp::Div => v1 / v2, - ArithOp::Mod if v2 == 0.0 => return Ok(Value::Undefined), - ArithOp::Mod if v1.floor() != v1 => return Ok(Value::Undefined), - ArithOp::Mod if v2.floor() != v2 => return Ok(Value::Undefined), - ArithOp::Mod => v1 % v2, + Ok(Value::from(match op { + ArithOp::Add => v1.add(&v2)?, + ArithOp::Sub => v1.sub(&v2)?, + ArithOp::Mul => v1.mul(&v2)?, + ArithOp::Div if v2 == Number::from(0u64) => bail!(span.error("divide by zero")), + ArithOp::Div => v1.divide(&v2)?, + ArithOp::Mod if v2 == Number::from(0u64) => bail!(span.error("modulo by zero")), + ArithOp::Mod if !v1.is_integer() || !v2.is_integer() => { + bail!(span.error("modulo on floating-point number")) + } + ArithOp::Mod => v1.modulo(&v2)?, })) } fn abs(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "abs", params, args, 1)?; - Ok(Value::from_float( + Ok(Value::from( ensure_numeric("abs", ¶ms[0], &args[0])?.abs(), )) } fn ceil(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "ceil", params, args, 1)?; - Ok(Value::from_float( + Ok(Value::from( ensure_numeric("ceil", ¶ms[0], &args[0])?.ceil(), )) } fn floor(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "floor", params, args, 1)?; - Ok(Value::from_float( + Ok(Value::from( ensure_numeric("floor", ¶ms[0], &args[0])?.floor(), )) } @@ -71,27 +74,31 @@ fn range(span: &Span, params: &[Ref], args: &[Value]) -> Result { let v1 = ensure_numeric("numbers.range", ¶ms[0], &args[0].clone())?; let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1].clone())?; - if v1 != v1.floor() || v2 != v2.floor() { - // TODO: OPA returns undefined here. - // Can we emit a warning? - return Ok(Value::Undefined); - } - let incr = if v2 >= v1 { 1 } else { -1 } as Float; + let (incr, num_elements) = match (v1.as_i64(), v2.as_i64()) { + (Some(v1), Some(v2)) if v2 > v1 => (1, v2 + 1 - v1), + (Some(v1), Some(v2)) => (-1, v1 + 1 - v2), + _ => { + // TODO: OPA returns undefined here. + // Can we emit a warning? + return Ok(Value::Undefined); + } + }; - let mut values = Vec::with_capacity((v2 - v1).abs() as usize + 1); + let mut values = Vec::with_capacity(num_elements as usize); let mut v = v1; + let incr = Number::from(incr as i64); while v != v2 { - values.push(Value::from_float(v)); - v += incr; + values.push(Value::from(v.clone())); + v.add_assign(&incr)?; } - values.push(Value::from_float(v)); + values.push(Value::from(v)); Ok(Value::from_array(values)) } fn round(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, "round", params, args, 1)?; - Ok(Value::from_float( + Ok(Value::from( ensure_numeric("round", ¶ms[0], &args[0])?.round(), )) } @@ -101,16 +108,15 @@ fn intn(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, fcn, params, args, 2)?; let _ = ensure_string(fcn, ¶ms[0], &args[0])?; let n = ensure_numeric(fcn, ¶ms[0], &args[1])?; - if n != n.floor() || n < 0 as Float { - return Ok(Value::Undefined); - } - - if n == 0.0 { - return Ok(Value::from_float(0 as Float)); - } - // TODO: bounds checking; arbitrary precision - let mut rng = thread_rng(); - let v = rng.gen_range(0..n as u64); - Ok(Value::from_float(v as f64)) + Ok(match n.as_u64() { + Some(0) => Value::from(0u64), + Some(n) => { + // TODO: bounds checking; arbitrary precision + let mut rng = thread_rng(); + let v = rng.gen_range(0..n); + Value::from(v) + } + _ => Value::Undefined, + }) } diff --git a/src/builtins/semver.rs b/src/builtins/semver.rs index 72937e5c..7748587d 100644 --- a/src/builtins/semver.rs +++ b/src/builtins/semver.rs @@ -32,7 +32,7 @@ fn compare(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ordering::Equal => 0, Ordering::Greater => 1, }; - Ok(Value::from_float(result as f64)) + Ok(Value::from(result as i64)) } fn is_valid(span: &Span, params: &[Ref], args: &[Value]) -> Result { diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs index 8a4ff4b8..53b3d989 100644 --- a/src/builtins/strings.rs +++ b/src/builtins/strings.rs @@ -8,7 +8,8 @@ use crate::builtins::utils::{ ensure_string_collection, }; use crate::lexer::Span; -use crate::value::{Float, Number, Value}; +use crate::number::Number; +use crate::value::Value; use std::collections::HashMap; @@ -65,24 +66,31 @@ fn endswith(span: &Span, params: &[Ref], args: &[Value]) -> Result Ok(Value::Bool(s1.ends_with(s2.as_ref()))) } +fn format_number(n: &Number, base: u64) -> String { + match base { + 2 => n.format_bin(), + 8 => n.format_octal(), + 10 => n.format_decimal(), + 16 => n.format_hex(), + _ => "".to_owned(), + } +} + fn format_int(span: &Span, params: &[Ref], args: &[Value]) -> Result { - let name = "endswith"; + let name = "format_int"; ensure_args_count(span, name, params, args, 2)?; let mut n = ensure_numeric(name, ¶ms[0], &args[0])?; let mut sign = ""; - if n < 0.0 { + if n < Number::from(0u64) { n = n.abs(); sign = "-"; } - let n = n.floor() as u64; + let n = n.floor(); Ok(Value::String( (sign.to_owned() - + &match ensure_numeric(name, ¶ms[1], &args[1])? as u64 { - 2 => format!("{:b}", n), - 8 => format!("{:o}", n), - 10 => format!("{}", n), - 16 => format!("{:x}", n), + + &match ensure_numeric(name, ¶ms[1], &args[1])?.as_u64() { + Some(b) => format_number(&n, b), _ => return Ok(Value::Undefined), }) .into(), @@ -94,10 +102,10 @@ fn indexof(span: &Span, params: &[Ref], args: &[Value]) -> Result { ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; let s2 = ensure_string(name, ¶ms[1], &args[1])?; - Ok(Value::from_float(match s1.find(s2.as_ref()) { + Ok(Value::from(Number::from(match s1.find(s2.as_ref()) { Some(pos) => pos as i64, _ => -1, - } as Float)) + }))) } #[allow(dead_code)] @@ -111,7 +119,7 @@ fn indexof_n(span: &Span, params: &[Ref], args: &[Value]) -> Result let mut idx = 0; while idx < s1.len() { if let Some(pos) = s1.find(s2.as_ref()) { - positions.push(Value::from_float(pos as Float)); + positions.push(Value::from(pos as u64)); idx = pos + 1; } else { break; @@ -201,64 +209,81 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value]) -> Result { _ => (), } - let get_sign_value = |f: &Number| match (emit_sign, f.0 .0) { - (_, v) if v < 0.0 => ("-", v), - (true, v) => ("+", v), - (false, v) if leave_space_for_elided_sign => (" ", v), - (false, v) => ("", v), + let get_sign_value = |f: &Number| match (emit_sign, f) { + (_, v) if v < &Number::from(0.0) => ("-", v.clone()), + (true, v) => ("+", v.clone()), + (false, v) if leave_space_for_elided_sign => (" ", v.clone()), + (false, v) => ("", v.clone()), }; // Handle Golang printing verbs. // https://pkg.go.dev/fmt match (verb, arg) { ('v', _) => s += format!("{arg}").as_str(), - ('b', Value::Number(f)) if f.0 == f.0.floor() => { + ('b', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); s += sign; - s += format!("{:b}", v as u64).as_str() + s += v.format_bin().as_str() } - ('c', Value::Number(f)) if f.0 == f.0.floor() => { + ('c', Value::Number(f)) if f.is_integer() => { // TODO: range error - let ival = f.0 .0 as u32; - match char::from_u32(ival) { - Some(c) => s.push(c), - None => { + let ch_opt = f.as_u64().map(|ival| char::from_u32(ival as u32)); + match ch_opt { + Some(Some(c)) => s.push(c), + _ => { bail!(args_span.error( - format!("invalid integer value {ival} for format verb c.").as_str() + format!("invalid value {} for format verb c.", f.format_decimal()) + .as_str() )) } } } - ('d', Value::Number(f)) if f.0 == f.0.floor() => { + ('d', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); s += sign; - s += format!("{v}").as_str() + s += v.format_decimal().as_str() } - ('o', Value::Number(f)) if f.0 == f.0.floor() => { + ('o', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); s += sign; - s += format!("{:o}", v as u64).as_str() + s += ("0O".to_owned() + &v.format_octal()).as_str() } - ('O', Value::Number(f)) if f.0 == f.0.floor() => { + ('O', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); s += sign; - s += format!("0o{:o}", v as u64).as_str() + s += ("0o".to_owned() + &v.format_octal()).as_str() } - ('x', Value::Number(f)) if f.0 == f.0.floor() => { + ('x', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); s += sign; - s += format!("{:x}", v as u64).as_str() + s += v.format_hex().as_str() } - ('X', Value::Number(f)) if f.0 == f.0.floor() => { + ('X', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); s += sign; - s += format!("{:X}", v as u64).as_str() + s += v.format_big_hex().as_str() } - ('e', Value::Number(f)) => s += format!("{:e}", f.0).as_str(), - ('E', Value::Number(f)) => s += format!("{:E}", f.0).as_str(), - ('f' | 'F', Value::Number(f)) => s += format!("{}", f.0).as_str(), + ('e', Value::Number(f)) => { + s += match f.as_f64() { + Some(f) => format!("{:e}", f), + _ => bail!(span.error("cannot print large float using e format specifier")), + } + .as_str() + } + ('E', Value::Number(f)) => { + s += match f.as_f64() { + Some(f) => format!("{:E}", f), + _ => bail!(span.error("cannot print large float using E format specifier")), + } + .as_str() + } + ('f' | 'F', Value::Number(f)) => s += f.format_decimal().as_str(), ('g', Value::Number(f)) => { let (sign, v) = get_sign_value(f); + let v = match v.as_f64() { + Some(v) => v, + _ => bail!(span.error("cannot print large float using g format specified")), + }; s += sign; let bits = v.to_bits(); let exponent = (bits >> 52) & 0x7ff; @@ -271,6 +296,10 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value]) -> Result { } ('G', Value::Number(f)) => { let (sign, v) = get_sign_value(f); + let v = match v.as_f64() { + Some(v) => v, + _ => bail!("cannot print large float using g format specified"), + }; s += sign; let bits = v.to_bits(); let exponent = (bits >> 52) & 0x7ff; @@ -423,25 +452,22 @@ fn substring(span: &Span, params: &[Ref], args: &[Value]) -> Result let offset = ensure_numeric(name, ¶ms[1], &args[1])?; let length = ensure_numeric(name, ¶ms[2], &args[2])?; - if offset.floor() != offset || length.floor() != length { - return Ok(Value::Undefined); - } - - if offset < 0.0 || length < 0.0 { - return Ok(Value::Undefined); - } - - let offset = offset as usize; - let length = length as usize; - // TODO: distinguish between 20.0 and 20 // Also: behavior of // x = substring("hello", 20 + 0.0, 25) - if offset > s.len() || length <= offset { - return Ok(Value::String("".into())); - } + match (offset.as_u64(), length.as_u64()) { + (Some(offset), Some(length)) => { + let offset = offset as usize; + let length = length as usize; - Ok(Value::String(s[offset..offset + length].into())) + if offset > s.len() || length <= offset { + return Ok(Value::String("".into())); + } + + Ok(Value::String(s[offset..offset + length].into())) + } + _ => Ok(Value::Undefined), + } } fn trim(span: &Span, params: &[Ref], args: &[Value]) -> Result { diff --git a/src/builtins/time.rs b/src/builtins/time.rs index ac310ab8..2b692feb 100644 --- a/src/builtins/time.rs +++ b/src/builtins/time.rs @@ -26,5 +26,5 @@ fn now_ns(span: &Span, params: &[Ref], args: &[Value]) -> Result { Err(e) => bail!(span.error(format!("could not fetch elapsed time. {e}").as_str())), }; let nanos = elapsed.as_nanos(); - Ok(Value::from_u128(nanos)) + Ok(Value::from(nanos)) } diff --git a/src/builtins/units.rs b/src/builtins/units.rs index 04eb0cb6..df67d1de 100644 --- a/src/builtins/units.rs +++ b/src/builtins/units.rs @@ -5,18 +5,67 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; use crate::lexer::Span; -use crate::value::{Float, Number, Value}; +use crate::number::Number; +use crate::value::Value; use std::collections::HashMap; use anyhow::{bail, Context, Result}; -use ordered_float::OrderedFloat; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("units.parse", (parse, 1)); m.insert("units.parse_bytes", (parse_bytes, 1)); } +fn ten_exp(suffix: &str) -> Option { + Some(match suffix { + "E" | "e" => 18, + "P" | "p" => 15, + "T" | "t" => 12, + "G" | "g" => 9, + "M" => 6, + "K" | "k" => 3, + "m" => -3, + + // The following are not supported by OPA + "Q" => 30, + "R" => 27, + "Y" => 24, + "Z" => 21, + "h" => 2, + "da" => 1, + "d" => -1, + "c" => -2, + + "μ" => -6, + "n" => -9, + "f" => -15, + "a" => -18, + "z" => -21, + "y" => -24, + "r" => -27, + "q" => -30, + + // No suffix specified. + "" => 0, + _ => return None, + }) +} + +fn two_exp(suffix: &str) -> Option { + Some(match suffix.to_ascii_lowercase().as_str() { + "ki" => 10, + "mi" => 20, + "gi" => 30, + "ti" => 40, + "pi" => 50, + "ei" => 60, + "zi" => 70, + "yi" => 80, + _ => return None, + }) +} + fn parse(span: &Span, params: &[Ref], args: &[Value]) -> Result { let name = "units.parse"; ensure_args_count(span, name, params, args, 1)?; @@ -40,61 +89,58 @@ fn parse(span: &Span, params: &[Ref], args: &[Value]) -> Result { _ => (string, ""), }; - let n: Float = if number_part.starts_with('.') { + let v: Value = if number_part.starts_with('.') { serde_json::from_str(format!("0{number_part}").as_str()) } else { serde_json::from_str(number_part) } .with_context(|| span.error("could not parse number"))?; - Ok(Value::Number(Number(OrderedFloat( - n * 10f64.powf(match suffix { - "E" | "e" => 18, - "P" | "p" => 15, - "T" | "t" => 12, - "G" | "g" => 9, - "M" => 6, - "K" | "k" => 3, - "m" => -3, - - // The following are not supported by OPA - "Q" => 30, - "R" => 27, - "Y" => 24, - "Z" => 21, - "h" => 2, - "da" => 1, - "d" => -1, - "c" => -2, - - "μ" => -6, - "n" => -9, - "f" => -15, - "a" => -18, - "z" => -21, - "y" => -24, - "r" => -27, - "q" => -30, - - // No suffix specified. - "" => 0, - _ => { - return Ok(Value::Number(Number(OrderedFloat( - n * 2f64.powf(match suffix.to_ascii_lowercase().as_str() { - "ki" => 10, - "mi" => 20, - "gi" => 30, - "ti" => 40, - "pi" => 50, - "ei" => 60, - "zi" => 70, - "yi" => 80, - _ => return Ok(Value::Undefined), - } as f64), - )))); - } - } as f64), - )))) + let mut n = match v { + Value::Number(n) => n.clone(), + _ => bail!(span.error("could not parse number")), + }; + + if let Some(e) = ten_exp(suffix) { + n.mul_assign(&Number::ten_pow(e))?; + Ok(Value::from(n)) + } else if let Some(e) = two_exp(suffix) { + n.mul_assign(&Number::two_pow(e))?; + Ok(Value::from(n)) + } else { + return Ok(Value::Undefined); + } +} + +fn twob_exp(suffix: &str) -> Option { + Some(match suffix.to_ascii_lowercase().as_str() { + "yi" | "yib" => 80, + "zi" | "zib" => 70, + "ei" | "eib" => 60, + "pi" | "pib" => 50, + "ti" | "tib" => 40, + "gi" | "gib" => 30, + "mi" | "mib" => 20, + "ki" | "kib" => 10, + "" => 0, + _ => return None, + }) +} + +fn tenb_exp(suffix: &str) -> Option { + Some(match suffix.to_ascii_lowercase().as_str() { + "q" | "qb" => 30, + "r" | "rb" => 27, + "y" | "yb" => 24, + "z" | "zb" => 21, + "e" | "eb" => 18, + "p" | "pb" => 15, + "t" | "tb" => 12, + "g" | "gb" => 9, + "m" | "mb" => 6, + "k" | "kb" => 3, + _ => return None, + }) } fn parse_bytes(span: &Span, params: &[Ref], args: &[Value]) -> Result { @@ -120,43 +166,25 @@ fn parse_bytes(span: &Span, params: &[Ref], args: &[Value]) -> Result (string, ""), }; - let n: Float = if number_part.starts_with('.') { + let v: Value = if number_part.starts_with('.') { serde_json::from_str(format!("0{number_part}").as_str()) } else { serde_json::from_str(number_part) } .with_context(|| span.error("could not parse number"))?; - Ok(Value::Number(Number(OrderedFloat(f64::round( - n * 2f64.powf(match suffix.to_ascii_lowercase().as_str() { - "yi" | "yib" => 80, - "zi" | "zib" => 70, - "ei" | "eib" => 60, - "pi" | "pib" => 50, - "ti" | "tib" => 40, - "gi" | "gib" => 30, - "mi" | "mib" => 20, - "ki" | "kib" => 10, - "" => 0, - _ => { - return Ok(Value::Number(Number(OrderedFloat( - n * 10f64.powf(match suffix.to_ascii_lowercase().as_str() { - "q" | "qb" => 30, - "r" | "rb" => 27, - "y" | "yb" => 24, - "z" | "zb" => 21, - "e" | "eb" => 18, - "p" | "pb" => 15, - "t" | "tb" => 12, - "g" | "gb" => 9, - "m" | "mb" => 6, - "k" | "kb" => 3, - _ => { - return Ok(Value::Undefined); - } - } as f64), - )))) - } - } as f64), - ))))) + let mut n = match v { + Value::Number(n) => n.clone(), + _ => bail!(span.error("could not parse number")), + }; + + if let Some(e) = twob_exp(suffix) { + n.mul_assign(&Number::two_pow(e))?; + Ok(Value::from(n.round())) + } else if let Some(e) = tenb_exp(suffix) { + n.mul_assign(&Number::ten_pow(e))?; + Ok(Value::from(n.round())) + } else { + Ok(Value::Undefined) + } } diff --git a/src/builtins/utils.rs b/src/builtins/utils.rs index 467d3cc2..ec0696c7 100644 --- a/src/builtins/utils.rs +++ b/src/builtins/utils.rs @@ -3,7 +3,8 @@ use crate::ast::{Expr, Ref}; use crate::lexer::Span; -use crate::value::{Float, Value}; +use crate::number::Number; +use crate::value::Value; use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; @@ -31,9 +32,9 @@ pub fn ensure_args_count( Ok(()) } -pub fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result { +pub fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result { Ok(match &v { - Value::Number(n) => n.0 .0, + Value::Number(n) => n.clone(), _ => { let span = arg.span(); bail!( diff --git a/src/interpreter.rs b/src/interpreter.rs index 09797ad2..89e1435a 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -446,7 +446,13 @@ impl Interpreter { } } - fn eval_arith_expr(&mut self, op: &ArithOp, lhs: &ExprRef, rhs: &ExprRef) -> Result { + fn eval_arith_expr( + &mut self, + span: &Span, + op: &ArithOp, + lhs: &ExprRef, + rhs: &ExprRef, + ) -> Result { let lhs_value = self.eval_expr(lhs)?; let rhs_value = self.eval_expr(rhs)?; @@ -458,7 +464,7 @@ impl Interpreter { (ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => { builtins::sets::difference(lhs, rhs, lhs_value, rhs_value) } - _ => builtins::numbers::arithmetic_operation(op, lhs, rhs, lhs_value, rhs_value), + _ => builtins::numbers::arithmetic_operation(span, op, lhs, rhs, lhs_value, rhs_value), } } @@ -607,7 +613,7 @@ impl Interpreter { for (idx, v) in a.iter().enumerate() { self.add_variable(&value.source_str(), v.clone())?; if let Some(key) = key { - self.add_variable(&key.source_str(), Value::from_float(idx as Float))?; + self.add_variable(&key.source_str(), Value::from(idx))?; } if !self.eval_query(query)? { r = false; @@ -815,7 +821,7 @@ impl Interpreter { &mut type_match, &mut cache, (key_expr, value_expr), - (&Value::from_float(idx as Float), value), + (&Value::from(idx), value), )? { continue; } @@ -877,14 +883,8 @@ impl Interpreter { fn make_expression_result(span: &Span, v: &Value) -> Value { let mut loc = BTreeMap::new(); - loc.insert( - Value::String("row".into()), - Value::from_float(span.line as f64), - ); - loc.insert( - Value::String("col".into()), - Value::from_float(span.col as f64), - ); + loc.insert(Value::String("row".into()), Value::from(span.line as i64)); + loc.insert(Value::String("col".into()), Value::from(span.col as i64)); let mut expr = BTreeMap::new(); expr.insert(Value::String("value".into()), v.clone()); @@ -1142,7 +1142,7 @@ impl Interpreter { for (idx, v) in items.iter().enumerate() { self.loop_var_values .insert(loop_expr.expr.clone(), v.clone()); - self.add_variable(&loop_expr.index, Value::from_float(idx as Float))?; + self.add_variable(&loop_expr.index, Value::from(idx))?; result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result; self.loop_var_values.remove(&loop_expr.expr); @@ -1960,7 +1960,7 @@ impl Interpreter { Expr::RefBrack { .. } => self.eval_chained_ref_dot_or_brack(expr), // Expressions with operators - Expr::ArithExpr { op, lhs, rhs, .. } => self.eval_arith_expr(op, lhs, rhs), + Expr::ArithExpr { op, lhs, rhs, .. } => self.eval_arith_expr(expr.span(), op, lhs, rhs), Expr::AssignExpr { op, lhs, rhs, .. } => self.eval_assign_expr(op, lhs, rhs), Expr::BinExpr { op, lhs, rhs, .. } => self.eval_bin_expr(op, lhs, rhs), Expr::BoolExpr { op, lhs, rhs, .. } => self.eval_bool_expr(op, lhs, rhs), diff --git a/src/lib.rs b/src/lib.rs index d7dac774..a9b0404b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod builtins; pub mod engine; pub mod interpreter; pub mod lexer; +pub mod number; pub mod parser; pub mod scheduler; mod utils; @@ -15,6 +16,7 @@ pub use ast::*; pub use engine::*; pub use interpreter::*; pub use lexer::*; +pub use number::*; pub use parser::*; pub use scheduler::*; pub use value::*; diff --git a/src/number.rs b/src/number.rs new file mode 100644 index 00000000..fec3d615 --- /dev/null +++ b/src/number.rs @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::cmp::{Ord, Ordering}; +use std::ops::{AddAssign, Div, MulAssign, Rem, SubAssign}; +use std::rc::Rc; +use std::str::FromStr; + +use anyhow::{bail, Result}; +use num::{FromPrimitive, ToPrimitive}; +use rust_decimal; + +use serde::ser::Serializer; +use serde::Serialize; + +pub type BigInt = i128; + +#[derive(Clone, Debug, PartialEq)] +pub struct BigDecimal { + d: rust_decimal::Decimal, +} + +impl AsRef for BigDecimal { + fn as_ref(&self) -> &rust_decimal::Decimal { + &self.d + } +} + +impl AsMut for BigDecimal { + fn as_mut(&mut self) -> &mut rust_decimal::Decimal { + &mut self.d + } +} + +impl From for BigDecimal { + fn from(value: rust_decimal::Decimal) -> Self { + BigDecimal { d: value } + } +} + +impl From for BigDecimal { + fn from(value: i128) -> Self { + BigDecimal { d: value.into() } + } +} + +impl Serialize for BigDecimal { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = self.d.to_string(); + let v = serde_json::Number::from_str(&s) + .map_err(|_| serde::ser::Error::custom("could not serialize big number"))?; + v.serialize(serializer) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Number { + // TODO: maybe specialize for u64, i64, f64 + Big(Rc), +} + +use Number::*; + +impl From for Number { + fn from(n: u64) -> Self { + Self::Big(BigDecimal { d: n.into() }.into()) + } +} + +impl From for Number { + fn from(n: usize) -> Self { + Self::Big(BigDecimal { d: n.into() }.into()) + } +} + +impl From for Number { + fn from(n: u128) -> Self { + Self::Big(BigDecimal { d: n.into() }.into()) + } +} + +impl From for Number { + fn from(n: i128) -> Self { + Self::Big(BigDecimal { d: n.into() }.into()) + } +} + +impl From for Number { + fn from(n: i64) -> Self { + Self::Big(BigDecimal { d: n.into() }.into()) + } +} + +impl From for Number { + fn from(n: f64) -> Self { + match rust_decimal::Decimal::from_f64(n) { + Some(v) => v.into(), + _ => rust_decimal::Decimal::ZERO.into(), + } + } +} + +impl From for Number { + fn from(d: rust_decimal::Decimal) -> Self { + Self::Big(BigDecimal { d }.into()) + } +} + +impl Number { + pub fn as_u64(&self) -> Option { + match self { + Big(b) if b.d.is_integer() => b.d.to_u64(), + _ => None, + } + } + + pub fn as_i64(&self) -> Option { + match self { + Big(b) if b.d.is_integer() => b.d.to_i64(), + _ => None, + } + } + + pub fn as_f64(&self) -> Option { + match self { + Big(b) => b.d.to_f64(), + } + } + + pub fn as_big(&self) -> Option> { + Some(match self { + Big(b) => b.clone(), + }) + } + + pub fn to_big(&self) -> Result> { + match self.as_big() { + Some(b) => Ok(b), + _ => bail!("Number::to_big failed"), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ParseNumberError; + +impl FromStr for Number { + type Err = ParseNumberError; + + fn from_str(s: &str) -> Result { + Ok(match rust_decimal::Decimal::from_str(s) { + Ok(v) => v.into(), + _ => f64::from_str(s).map_err(|_| ParseNumberError)?.into(), + }) + } +} + +impl Eq for Number {} + +impl PartialEq for Number { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Big(a), Big(b)) => a.d == b.d, + } + } +} + +impl Ord for Number { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Big(a), Big(b)) => a.d.cmp(&b.d), + } + } +} + +impl PartialOrd for Number { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Number { + pub fn add_assign(&mut self, rhs: &Self) -> Result<()> { + match (self, rhs) { + (Big(ref mut a), Big(b)) => { + Rc::make_mut(a).d.add_assign(b.d); + } + } + Ok(()) + } + + pub fn add(&self, rhs: &Self) -> Result { + let mut c = self.clone(); + c.add_assign(rhs)?; + Ok(c) + } + + pub fn sub_assign(&mut self, rhs: &Self) -> Result<()> { + match (self, rhs) { + (Big(ref mut a), Big(b)) => { + Rc::make_mut(a).d.sub_assign(b.d); + } + } + Ok(()) + } + + pub fn sub(&self, rhs: &Self) -> Result { + let mut c = self.clone(); + c.sub_assign(rhs)?; + Ok(c) + } + + pub fn mul_assign(&mut self, rhs: &Self) -> Result<()> { + match (self, rhs) { + (Big(ref mut a), Big(b)) => { + Rc::make_mut(a).d.mul_assign(b.d); + } + } + Ok(()) + } + + pub fn mul(&self, rhs: &Self) -> Result { + let mut c = self.clone(); + c.mul_assign(rhs)?; + Ok(c) + } + + pub fn divide(self, rhs: &Self) -> Result { + Ok(match (self, rhs) { + (Big(a), Big(b)) => a.d.div(b.d).into(), + }) + } + + pub fn modulo(self, rhs: &Self) -> Result { + Ok(match (self, rhs) { + (Big(a), Big(b)) => a.d.rem(b.d).into(), + }) + } + + pub fn is_integer(&self) -> bool { + match self { + Big(b) => b.d.is_integer(), + } + } + + fn ensure_integers(a: &Number, b: &Number) -> Option<(BigInt, BigInt)> { + match (a, b) { + (Big(a), Big(b)) if a.d.is_integer() && b.d.is_integer() => { + Some((a.d.mantissa(), b.d.mantissa())) + } + _ => None, + } + } + + fn ensure_integer(&self) -> Option { + match self { + Big(a) if a.d.is_integer() => Some(a.d.mantissa()), + _ => None, + } + } + + pub fn and(&self, rhs: &Self) -> Option { + match Self::ensure_integers(self, rhs) { + Some((a, b)) => Some((a & b).into()), + _ => None, + } + } + + pub fn or(&self, rhs: &Self) -> Option { + match Self::ensure_integers(self, rhs) { + Some((a, b)) => Some((a | b).into()), + _ => None, + } + } + + pub fn xor(&self, rhs: &Self) -> Option { + match Self::ensure_integers(self, rhs) { + Some((a, b)) => Some((a ^ b).into()), + _ => None, + } + } + + pub fn lsh(&self, rhs: &Self) -> Option { + match Self::ensure_integers(self, rhs) { + Some((a, b)) => a.checked_shl(b as u32).map(|v| v.into()), + _ => None, + } + } + + pub fn rsh(&self, rhs: &Self) -> Option { + match Self::ensure_integers(self, rhs) { + Some((a, b)) => a.checked_shr(b as u32).map(|v| v.into()), + _ => None, + } + } + + pub fn neg(&self) -> Option { + self.ensure_integer().map(|a| (!a).into()) + } + + pub fn abs(&self) -> Number { + match self { + Big(b) => b.d.abs().into(), + } + } + + pub fn floor(&self) -> Number { + match self { + Big(b) => b.d.floor().into(), + } + } + + pub fn ceil(&self) -> Number { + match self { + Big(b) => b.d.ceil().into(), + } + } + + pub fn round(&self) -> Number { + match self { + Big(b) => b.d.round().into(), + } + } + + pub fn two_pow(e: i32) -> Number { + 2.0_f64.powi(e).into() + } + + pub fn ten_pow(e: i32) -> Number { + 10.0_f64.powi(e).into() + } + + pub fn format_bin(&self) -> String { + self.ensure_integer() + .map(|a| format!("{:b}", a)) + .unwrap_or("".to_string()) + } + + pub fn format_octal(&self) -> String { + self.ensure_integer() + .map(|a| format!("{:o}", a)) + .unwrap_or("".to_string()) + } + + pub fn format_decimal(&self) -> String { + self.ensure_integer() + .map(|a| format!("{}", a)) + .unwrap_or("".to_string()) + } + + pub fn format_hex(&self) -> String { + self.ensure_integer() + .map(|a| format!("{:x}", a)) + .unwrap_or("".to_string()) + } + + pub fn format_big_hex(&self) -> String { + self.ensure_integer() + .map(|a| format!("{:X}", a)) + .unwrap_or("".to_string()) + } +} diff --git a/src/value.rs b/src/value.rs index 5ddc80e9..f515d64c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,90 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::number::Number; + use core::fmt; use std::collections::{BTreeMap, BTreeSet}; use std::ops; use std::rc::Rc; +use std::str::FromStr; use anyhow::{anyhow, bail, Result}; -use ordered_float::OrderedFloat; -use serde::de::{self, Deserializer}; +use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::ser::{SerializeMap, Serializer}; use serde::{Deserialize, Serialize}; -pub type Float = f64; - -// TODO: rego uses BigNum which has arbitrary precision. But there seems -// to be some bugs with it e.g ((a + b) -a) == b doesn't return true for large -// values of a and b. -// Json doesn't specify a limit on precision, but in practice double (f64) seems -// to be enough to support most use cases and portability too. -// See discussions in jq's repository. -// For now we use OrderedFloat. We can't use f64 directly since it doesn't -// implement Ord trait. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Number(pub OrderedFloat); - -impl Serialize for Number { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let n_float = self.0 .0; - let n_i64 = n_float as i64; - let n_u64 = n_float as u64; - - if n_u64 as f64 == n_float { - serializer.serialize_u64(n_u64) - } else if n_i64 as f64 == n_float { - serializer.serialize_i64(n_i64) - } else { - serializer.serialize_f64(n_float) - } - } -} - -struct NumberVisitor; -impl<'de> de::Visitor<'de> for NumberVisitor { - type Value = Number; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a json number") - } - - fn visit_f64(self, v: f64) -> Result { - Ok(Number(OrderedFloat(v))) - } - - fn visit_u64(self, v: u64) -> Result { - Ok(Number(OrderedFloat(v as f64))) - } - - fn visit_i64(self, v: i64) -> Result { - Ok(Number(OrderedFloat(v as f64))) - } -} - -impl<'de> Deserialize<'de> for Number { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_f64(NumberVisitor) - } -} - -impl fmt::Display for Number { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - // We cannot use serde_json::Value because Rego has set type and object's key can be // other rego values. -// BTree is more efficient that a hast table. Another alternative is a sorted vector. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)] -#[serde(untagged)] +// BTree is more efficient than a hash table. Another alternative is a sorted vector. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Value { // Json data types. serde will automatically map json to these variants. Null, @@ -136,6 +69,123 @@ impl Serialize for Value { } } +struct ValueVisitor; + +impl<'de> Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a value") + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(Value::Null) + } + + fn visit_bool(self, v: bool) -> Result + where + E: de::Error, + { + Ok(Value::Bool(v)) + } + + fn visit_u64(self, v: u64) -> Result + where + E: de::Error, + { + Ok(Value::from(v)) + } + + fn visit_i64(self, v: i64) -> Result + where + E: de::Error, + { + Ok(Value::from(v)) + } + + fn visit_u128(self, v: u128) -> Result + where + E: de::Error, + { + Ok(Value::from(v)) + } + + fn visit_i128(self, v: i128) -> Result + where + E: de::Error, + { + Ok(Value::from(v)) + } + + fn visit_f64(self, v: f64) -> Result + where + E: de::Error, + { + Ok(Value::from(Number::from(v))) + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + Ok(Value::String(s.to_string().into())) + } + + fn visit_string(self, s: String) -> Result + where + E: de::Error, + { + Ok(Value::String(s.into())) + } + + fn visit_seq(self, mut visitor: V) -> Result + where + V: SeqAccess<'de>, + { + let mut arr = vec![]; + while let Some(v) = visitor.next_element()? { + arr.push(v); + } + Ok(Value::from(arr)) + } + + fn visit_map(self, mut visitor: V) -> Result + where + V: MapAccess<'de>, + { + if let Some((key, value)) = visitor.next_entry()? { + if let (Value::String(k), Value::String(v)) = (&key, &value) { + if k.as_ref() == "$serde_json::private::Number" { + match Number::from_str(v) { + Ok(n) => return Ok(Value::from(n)), + _ => return Err(de::Error::custom("failed to read big number")), + } + } + } + let mut map = BTreeMap::new(); + map.insert(key, value); + while let Some((key, value)) = visitor.next_entry()? { + map.insert(key, value); + } + Ok(Value::from(map)) + } else { + Ok(Value::new_object()) + } + } +} + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(ValueVisitor) + } +} + impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match serde_json::to_string(self) { @@ -147,15 +197,15 @@ impl fmt::Display for Value { impl Value { pub fn new_object() -> Value { - Value::from_map(BTreeMap::new()) + Value::from(BTreeMap::new()) } pub fn new_set() -> Value { - Value::from_set(BTreeSet::new()) + Value::from(BTreeSet::new()) } pub fn new_array() -> Value { - Value::from_array(vec![]) + Value::from(vec![]) } pub fn from_json_str(json: &str) -> Result { @@ -185,26 +235,77 @@ impl Value { } } -impl Value { - pub fn from_float(v: Float) -> Value { - Value::Number(Number(OrderedFloat(v))) +impl From for Value { + fn from(n: u128) -> Self { + Value::Number(Number::from(n)) + } +} + +impl From for Value { + fn from(n: i128) -> Self { + Value::Number(Number::from(n)) } +} - pub fn from_u128(v: u128) -> Value { - // TODO: fix precision loss - Value::Number(Number(OrderedFloat(v as f64))) +impl From for Value { + fn from(n: u64) -> Self { + Value::Number(Number::from(n)) } +} - pub fn from_array(a: Vec) -> Value { +impl From for Value { + fn from(n: i64) -> Self { + Value::Number(Number::from(n)) + } +} + +impl From for Value { + fn from(n: f64) -> Self { + Value::Number(Number::from(n)) + } +} + +impl From for Value { + fn from(n: usize) -> Self { + Value::Number(Number::from(n)) + } +} + +impl From for Value { + fn from(n: Number) -> Self { + Value::Number(n) + } +} + +impl From> for Value { + fn from(a: Vec) -> Self { Value::Array(Rc::new(a)) } +} - pub fn from_set(s: BTreeSet) -> Value { +impl From> for Value { + fn from(s: BTreeSet) -> Self { Value::Set(Rc::new(s)) } +} + +impl From> for Value { + fn from(s: BTreeMap) -> Self { + Value::Object(Rc::new(s)) + } +} + +impl Value { + pub fn from_array(a: Vec) -> Value { + Value::from(a) + } + + pub fn from_set(s: BTreeSet) -> Value { + Value::from(s) + } pub fn from_map(m: BTreeMap) -> Value { - Value::Object(Rc::new(m)) + Value::from(m) } pub fn is_null(&self) -> bool { @@ -404,14 +505,10 @@ impl ops::Index<&Value> for Value { Some(v) => v, _ => &Value::Undefined, }, - (Value::Array(a), Value::Number(n)) => { - let index = n.0 .0 as usize; - if index < a.len() { - &a[index] - } else { - &Value::Undefined - } - } + (Value::Array(a), Value::Number(n)) => match n.as_u64() { + Some(index) if (index as usize) < a.len() => &a[index as usize], + _ => &Value::Undefined, + }, _ => &Value::Undefined, } } diff --git a/tests/interpreter/cases/builtins/numbers/div.yaml b/tests/interpreter/cases/builtins/numbers/div.yaml index 1d74ead9..b37900f8 100644 --- a/tests/interpreter/cases/builtins/numbers/div.yaml +++ b/tests/interpreter/cases/builtins/numbers/div.yaml @@ -9,18 +9,25 @@ cases: package test import future.keywords.if - x = 1/3 - # Undefined y if false - a = 1 / 0 b = y / 1 c = 1 / y - d = 13.3 % 3 - e = 13 % 3.1 + d = 15.3 / 3 + e = 13 / 4 + + + + z { + x = 1/3 + y = 0.3333333333333333333333333333 + x == y + } query: data.test want_result: - x: 0.3333333333333333 + d: 5.1 + e: 3.25 + z: true - note: non-numeric data: {} @@ -30,3 +37,13 @@ cases: x = "1" / 9 query: data.test.x error: "`div` expects numeric argument." + + - note: div by zero + data: {} + modules: + - | + package test + + a = 1/ 0 + query: data.test + error: divide by zero diff --git a/tests/interpreter/cases/builtins/numbers/mod.yaml b/tests/interpreter/cases/builtins/numbers/mod.yaml index a8882964..b552c2de 100644 --- a/tests/interpreter/cases/builtins/numbers/mod.yaml +++ b/tests/interpreter/cases/builtins/numbers/mod.yaml @@ -11,7 +11,6 @@ cases: # Undefined y { false } - a = 1 % 0 b = y % 1 c = 1 % y query: data.test @@ -44,4 +43,22 @@ cases: package test x = 1%0 query: data.test - want_result: {} + error: modulo by zero + + - note: float numerator + data: {} + modules: + - | + package test + x = 1.1 % 1 + query: data.test + error: modulo on floating-point number + + - note: float denom + data: {} + modules: + - | + package test + x = 1 % 1.1 + query: data.test + error: modulo on floating-point number diff --git a/tests/interpreter/cases/builtins/units/parse_bytes.yaml b/tests/interpreter/cases/builtins/units/parse_bytes.yaml index 6ef18844..8ba1c804 100644 --- a/tests/interpreter/cases/builtins/units/parse_bytes.yaml +++ b/tests/interpreter/cases/builtins/units/parse_bytes.yaml @@ -58,8 +58,8 @@ cases: query: data.test.results want_result: p1: - - 1.2089258196146292e24 - - 1.1805916207174113e21 + - 1208925819614629174706176 + - 1180591620717411303424 - 1152921504606846976 - 1125899906842624 - 1099511627776 diff --git a/tests/opa.passing b/tests/opa.passing index 1e45e182..6269d6aa 100644 --- a/tests/opa.passing +++ b/tests/opa.passing @@ -1,11 +1,13 @@ aggregates all any +arithmetic array assignments bitsand bitsnegate bitsor +bitsshiftleft bitsshiftright bitsxor comparisonexpr @@ -16,6 +18,7 @@ embeddedvirtualdoc evaltermexpr example fix1863 +helloworld indexing intersection invalidkeyerror diff --git a/tests/value/mod.rs b/tests/value/mod.rs index b59117c9..ba11fc3d 100644 --- a/tests/value/mod.rs +++ b/tests/value/mod.rs @@ -13,12 +13,12 @@ fn non_string_key() -> Result<()> { obj.as_object_mut()?.insert(Value::Null, Value::Null); obj.as_object_mut()?.insert(Value::Bool(false), Value::Null); obj.as_object_mut()? - .insert(Value::from_float(std::f64::consts::PI), Value::Null); + .insert(Value::from(std::f64::consts::PI), Value::Null); obj.as_object_mut()?.insert( Value::from_array(vec![ Value::Bool(true), Value::Null, - Value::from_float(std::f64::consts::PI), + Value::from(std::f64::consts::PI), ]), Value::Null, ); @@ -27,8 +27,7 @@ fn non_string_key() -> Result<()> { set.as_set_mut()?.insert(Value::Bool(true)); set.as_set_mut()?.insert(Value::Bool(false)); set.as_set_mut()?.insert(Value::Bool(true)); - set.as_set_mut()? - .insert(Value::from_float(std::f64::consts::PI)); + set.as_set_mut()?.insert(Value::from(std::f64::consts::PI)); obj.as_object_mut()?.insert(set, Value::Null); obj.as_object_mut()?.insert(Value::Undefined, Value::Null); @@ -57,30 +56,20 @@ fn non_string_key() -> Result<()> { #[test] fn serialize_number() -> Result<()> { // Check that integer values are serialized without fractional part - assert_eq!(serde_json::to_string_pretty(&Value::from_float(1.0))?, "1"); - assert_eq!( - serde_json::to_string_pretty(&Value::from_float(-1.0))?, - "-1" - ); + assert_eq!(serde_json::to_string_pretty(&Value::from(1.0))?, "1"); + assert_eq!(serde_json::to_string_pretty(&Value::from(-1.0))?, "-1"); // Ensure that fractional parts are also serialized. - assert_eq!( - serde_json::to_string_pretty(&Value::from_float(1.1))?, - "1.1" - ); - assert_eq!( - serde_json::to_string_pretty(&Value::from_float(-1.1))?, - "-1.1" - ); + assert_eq!(serde_json::to_string_pretty(&Value::from(1.1))?, "1.1"); + assert_eq!(serde_json::to_string_pretty(&Value::from(-1.1))?, "-1.1"); Ok(()) } #[test] fn display_number() { - use ordered_float::OrderedFloat; - let n = Number(OrderedFloat(123456f64)); - assert_eq!(format!("{}", &n), "123456"); + let n = Number::from(123456f64); + assert_eq!(format!("{}", n.format_decimal()), "123456"); } #[test] @@ -101,18 +90,18 @@ fn constructors() -> Result<()> { #[test] fn value_as_index() -> Result<()> { - let idx = Value::from_float(2.0); + let idx = Value::from(2.0); let mut item = Value::new_array(); - item.as_array_mut()?.push(Value::from_float(3.0)); - item.as_array_mut()?.push(Value::from_float(4.0)); - item.as_array_mut()?.push(Value::from_float(5.0)); + item.as_array_mut()?.push(Value::from(3.0)); + item.as_array_mut()?.push(Value::from(4.0)); + item.as_array_mut()?.push(Value::from(5.0)); // Check case of item present. assert_eq!(&Value::from_json_str("[1, 2, [3, 4, 5]]")?[&idx], &item); // Check case of item not present. - let idx = Value::from_float(5.0); + let idx = Value::from(5.0); assert_eq!( &Value::from_json_str("[1, 2, [3, 4, 5]]")?[&idx], &Value::Undefined @@ -131,17 +120,14 @@ fn value_as_index() -> Result<()> { #[test] fn string_as_index() -> Result<()> { let obj = Value::from_json_str(r#"{ "a" : 5, "b" : 6 }"#)?; - assert_eq!(&obj["a"], &Value::from_float(5.0)); - assert_eq!(&obj[&"b".to_owned()], &Value::from_float(6.0)); + assert_eq!(&obj["a"], &Value::from(5.0)); + assert_eq!(&obj[&"b".to_owned()], &Value::from(6.0)); Ok(()) } #[test] fn usize_as_index() -> Result<()> { - assert_eq!( - &Value::from_json_str("[1, 2, 3]")?[0], - &Value::from_float(1.0) - ); + assert_eq!(&Value::from_json_str("[1, 2, 3]")?[0], &Value::from(1.0)); assert_eq!(&Value::from_json_str("[1, 2, 3]")?[5], &Value::Undefined); Ok(()) } @@ -151,8 +137,8 @@ fn api() -> Result<()> { assert!(&Value::from_json_str("{}")?.as_object()?.is_empty()); let mut v = Value::new_object(); v.as_object_mut()? - .insert(Value::String("a".into()), Value::from_float(3.145)); - assert_eq!(v["a"], Value::from_float(3.145)); + .insert(Value::String("a".into()), Value::from(3.145)); + assert_eq!(v["a"], Value::from(3.145)); assert_eq!(v.as_object()?.len(), 1); // Null @@ -174,7 +160,7 @@ fn api() -> Result<()> { assert!(Value::new_object().as_number().is_err()); assert!(Value::new_object().as_number_mut().is_err()); - assert!(Value::from_float(5.6).as_bool().is_err()); - assert!(Value::from_float(5.6).as_bool_mut().is_err()); + assert!(Value::from(5.6).as_bool().is_err()); + assert!(Value::from(5.6).as_bool_mut().is_err()); Ok(()) }