Skip to content

Commit

Permalink
Formalize concept of a Number (#55)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
anakrish authored Dec 1, 2023
1 parent bb0ca29 commit ed3492f
Show file tree
Hide file tree
Showing 21 changed files with 948 additions and 423 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ 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"
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"] }
Expand Down
47 changes: 24 additions & 23 deletions src/builtins/aggregates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,18 +24,18 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
fn count(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
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<Expr>], args: &[Value]) -> Result<Value> {
Expand Down Expand Up @@ -70,26 +71,26 @@ fn min(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
fn product(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
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", &params[0], e)?;
v.mul_assign(&ensure_numeric("product", &params[0], e)?)?;
}
Value::from_float(v)
v
}

Value::Set(a) => {
for e in a.iter() {
v *= ensure_numeric("product", &params[0], e)?;
v.mul_assign(&ensure_numeric("product", &params[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<Expr>], args: &[Value]) -> Result<Value> {
Expand All @@ -98,10 +99,10 @@ fn sort(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
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::<Vec<Value>>()),
a => {
let span = params[0].span();
bail!(span.error(format!("`sort` requires array/set argument. Got `{a}`.").as_str()))
Expand All @@ -112,24 +113,24 @@ fn sort(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
fn sum(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
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", &params[0], e)?;
v.add_assign(&ensure_numeric("sum", &params[0], e)?)?;
}
Value::from_float(v)
v
}

Value::Set(a) => {
for e in a.iter() {
v += ensure_numeric("sum", &params[0], e)?;
v.add_assign(&ensure_numeric("sum", &params[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()))
}
})
}))
}
20 changes: 13 additions & 7 deletions src/builtins/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,27 @@ fn slice(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let start = ensure_numeric(name, &params[1], &args[1].clone())?;
let stop = ensure_numeric(name, &params[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 {
return Ok(Value::new_array());
}

let slice = &array[start..stop];
Ok(Value::from_array(slice.to_vec()))
Ok(Value::from(slice.to_vec()))
}
83 changes: 25 additions & 58 deletions src/builtins/bitwise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -28,14 +28,10 @@ fn and(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let v1 = ensure_numeric(name, &params[0], &args[0])?;
let v2 = ensure_numeric(name, &params[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<Expr>], args: &[Value]) -> Result<Value> {
Expand All @@ -45,19 +41,10 @@ fn lsh(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let v1 = ensure_numeric(name, &params[0], &args[0])?;
let v2 = ensure_numeric(name, &params[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<Expr>], args: &[Value]) -> Result<Value> {
Expand All @@ -66,13 +53,10 @@ fn negate(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {

let v = ensure_numeric(name, &params[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<Expr>], args: &[Value]) -> Result<Value> {
Expand All @@ -82,14 +66,10 @@ fn or(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let v1 = ensure_numeric(name, &params[0], &args[0])?;
let v2 = ensure_numeric(name, &params[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<Expr>], args: &[Value]) -> Result<Value> {
Expand All @@ -99,19 +79,10 @@ fn rsh(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let v1 = ensure_numeric(name, &params[0], &args[0])?;
let v2 = ensure_numeric(name, &params[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<Expr>], args: &[Value]) -> Result<Value> {
Expand All @@ -121,12 +92,8 @@ fn xor(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let v1 = ensure_numeric(name, &params[0], &args[0])?;
let v2 = ensure_numeric(name, &params[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,
})
}
4 changes: 2 additions & 2 deletions src/builtins/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ fn to_number(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>

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
Expand Down
1 change: 1 addition & 0 deletions src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod comparison;
mod conversions;
mod debugging;
pub mod deprecated;

mod encoding;
pub mod numbers;
mod objects;
Expand Down
Loading

0 comments on commit ed3492f

Please sign in to comment.