Migrated numerics to BigInt/BigDecimal

This commit migrates Value's numeric types to BigInt and BigDecimal. The
basic idea is that overflow errors aren't great in a shell environment,
and not really necessary.

The main immediate consequence is that new errors can occur when
serializing Nu values to other formats. You can see this in changes to
the various serialization formats (JSON, TOML, etc.). There's a new
`CoerceInto` trait that uses the `ToPrimitive` trait from `num_traits`
to attempt to coerce a `BigNum` or `BigDecimal` into a target type, and
produces a `RangeError` (kind of `ShellError`) if the coercion fails.

Another possible future consequence is that certain performance-critical
numeric operations might be too slow. If that happens, we can introduce
specialized numeric types to help improve the performance of those
situations, based on the real-world experience.
This commit is contained in:
Yehuda Katz 2019-09-01 09:20:31 -07:00
parent 9e17b937c3
commit 8a29c9e6ab
32 changed files with 525 additions and 340 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
/target
/scratch
**/*.rs.bk
history.txt
tests/fixtures/nuplayground
tests/fixtures/nuplayground

52
Cargo.lock generated
View File

@ -111,6 +111,17 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bigdecimal"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bincode"
version = "1.1.4"
@ -1560,6 +1571,7 @@ dependencies = [
"app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"battery 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"bigdecimal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bson 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-unit 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1592,6 +1604,7 @@ dependencies = [
"neso 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"nom5_locate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1604,7 +1617,6 @@ dependencies = [
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"roxmltree 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rust_decimal 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1627,19 +1639,6 @@ dependencies = [
"which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-bigint"
version = "0.2.2"
@ -1647,15 +1646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-complex"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1683,7 +1674,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2286,16 +2276,6 @@ name = "rust-ini"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rust_decimal"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.15"
@ -3130,6 +3110,7 @@ dependencies = [
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum battery 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6d6fe5630049e900227cd89afce4c1204b88ec8e61a2581bb96fcce26f047b"
"checksum bigdecimal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "460825c9e21708024d67c07057cd5560e5acdccac85de0de624a81d3de51bacb"
"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum blake2b_simd 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "461f4b879a8eb70c1debf7d0788a9a5ff15f1ea9d25925fea264ef4258bed6b2"
@ -3285,9 +3266,7 @@ dependencies = [
"checksum nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9761d859320e381010a4f7f8ed425f2c924de33ad121ace447367c713ad561b"
"checksum nom5_locate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3d4312467f8b28d909344b934207e502212fa5a3adf1bff7428b0b86a666223d"
"checksum ntapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f26e041cd983acbc087e30fcba770380cfa352d0e392e175b2344ebaf7ea0602"
"checksum num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db"
"checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718"
"checksum num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fcb0cf31fb3ff77e6d2a6ebd6800df7fdcd106f2ad89113c9130bcd07f93dffc"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454"
@ -3360,7 +3339,6 @@ dependencies = [
"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051"
"checksum rust-argon2 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81ed8d04228b44a740c8d46ff872a28e50fff3d659f307ab4da2cc502e019ff3"
"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
"checksum rust_decimal 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f7a28ded8f10361cefb69a8d8e1d195acf59344150534c165c401d6611cf013d"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"

View File

@ -34,7 +34,6 @@ term = "0.5.2"
bytes = "0.4.12"
log = "0.4.8"
pretty_env_logger = "0.3.1"
rust_decimal = "1.0.3"
serde = { version = "1.0.99", features = ["derive"] }
bson = { version = "0.14.0", features = ["decimal128"] }
serde_json = "1.0.40"
@ -81,6 +80,8 @@ clipboard = {version = "0.5", optional = true }
shellexpand = "1.0.0"
futures-timer = "0.3.0"
pin-utils = "0.1.0-alpha.4"
num-bigint = { version = "0.2.2", features = ["serde"] }
bigdecimal = { version = "0.1.0", features = ["serde"] }
[features]
raw-key = ["rawkey", "neso"]

View File

@ -5,6 +5,11 @@ use crate::prelude::*;
pub struct First;
#[derive(Deserialize)]
pub struct FirstArgs {
amount: Tagged<u64>,
}
impl WholeStreamCommand for First {
fn name(&self) -> &str {
"first"
@ -24,27 +29,13 @@ impl WholeStreamCommand for First {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
first(args, registry)
args.process(registry, first)?.run()
}
}
fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let amount = args.expect_nth(0)?.as_i64();
let amount = match amount {
Ok(o) => o,
Err(_) => {
return Err(ShellError::labeled_error(
"Value is not a number",
"expected integer",
args.expect_nth(0)?.span(),
))
}
};
Ok(OutputStream::from_input(
args.input.values.take(amount as u64),
))
fn first(
FirstArgs { amount }: FirstArgs,
context: RunnableContext,
) -> Result<OutputStream, ShellError> {
Ok(OutputStream::from_input(context.input.values.take(*amount)))
}

View File

@ -1,4 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::errors::ExpectedRange;
use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*;
use bson::{decode_document, spec::BinarySubtype, Bson};
@ -28,22 +29,30 @@ impl WholeStreamCommand for FromBSON {
}
}
fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Tagged<Value> {
fn bson_array(input: &Vec<Bson>, tag: Tag) -> Result<Vec<Tagged<Value>>, ShellError> {
let mut out = vec![];
for value in input {
out.push(convert_bson_value_to_nu_value(value, tag)?);
}
Ok(out)
}
fn convert_bson_value_to_nu_value(
v: &Bson,
tag: impl Into<Tag>,
) -> Result<Tagged<Value>, ShellError> {
let tag = tag.into();
match v {
Ok(match v {
Bson::FloatingPoint(n) => Value::Primitive(Primitive::from(*n)).tagged(tag),
Bson::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
Bson::Array(a) => Value::List(
a.iter()
.map(|x| convert_bson_value_to_nu_value(x, tag))
.collect(),
)
.tagged(tag),
Bson::Array(a) => Value::List(bson_array(a, tag)?).tagged(tag),
Bson::Document(doc) => {
let mut collected = TaggedDictBuilder::new(tag);
for (k, v) in doc.iter() {
collected.insert_tagged(k.clone(), convert_bson_value_to_nu_value(v, tag));
collected.insert_tagged(k.clone(), convert_bson_value_to_nu_value(v, tag)?);
}
collected.into_tagged_value()
@ -62,11 +71,18 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Tagged<Value
);
collected.into_tagged_value()
}
// TODO: Add Int32 to nushell?
Bson::I32(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag),
Bson::I64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag),
Bson::I32(n) => Value::number(n).tagged(tag),
Bson::I64(n) => Value::number(n).tagged(tag),
Bson::Decimal128(n) => {
let decimal = Decimal::from_str(&format!("{}", n)).unwrap();
// TODO: this really isn't great, and we should update this to do a higher
// fidelity translation
let decimal = BigDecimal::from_str(&format!("{}", n)).map_err(|_| {
ShellError::range_error(
ExpectedRange::BigDecimal,
&n.tagged(tag),
format!("converting BSON Decimal128 to BigDecimal"),
)
})?;
Value::Primitive(Primitive::Decimal(decimal)).tagged(tag)
}
Bson::JavaScriptCode(js) => {
@ -85,16 +101,13 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Tagged<Value
);
collected.insert_tagged(
"$scope".to_string(),
convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag),
convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag)?,
);
collected.into_tagged_value()
}
Bson::TimeStamp(ts) => {
let mut collected = TaggedDictBuilder::new(tag);
collected.insert_tagged(
"$timestamp".to_string(),
Value::Primitive(Primitive::Int(*ts as i64)).tagged(tag),
);
collected.insert_tagged("$timestamp".to_string(), Value::number(ts).tagged(tag));
collected.into_tagged_value()
}
Bson::Binary(bst, bytes) => {
@ -102,7 +115,7 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Tagged<Value
collected.insert_tagged(
"$binary_subtype".to_string(),
match bst {
BinarySubtype::UserDefined(u) => Value::Primitive(Primitive::Int(*u as i64)),
BinarySubtype::UserDefined(u) => Value::number(u),
_ => Value::Primitive(Primitive::String(binary_subtype_to_string(*bst))),
}
.tagged(tag),
@ -130,7 +143,7 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Tagged<Value
);
collected.into_tagged_value()
}
}
})
}
fn binary_subtype_to_string(bst: BinarySubtype) -> String {
@ -179,7 +192,7 @@ pub fn from_bson_bytes_to_value(
while let Ok(v) = decode_document(&mut b_reader) {
docs.push(Bson::Document(v));
}
Ok(convert_bson_value_to_nu_value(&Bson::Array(docs), tag))
Ok(convert_bson_value_to_nu_value(&Bson::Array(docs), tag).expect("FIXME: Don't commit like this"))
}
fn from_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {

View File

@ -37,10 +37,10 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -
match v {
serde_hjson::Value::Null => Value::Primitive(Primitive::Nothing).tagged(tag),
serde_hjson::Value::Bool(b) => Value::Primitive(Primitive::Boolean(*b)).tagged(tag),
serde_hjson::Value::F64(n) => Value::Primitive(Primitive::from(*n)).tagged(tag),
serde_hjson::Value::U64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag),
serde_hjson::Value::I64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag),
serde_hjson::Value::Bool(b) => Value::boolean(*b).tagged(tag),
serde_hjson::Value::F64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::U64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::I64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::String(s) => {
Value::Primitive(Primitive::String(String::from(s))).tagged(tag)
}

View File

@ -100,7 +100,7 @@ fn convert_sqlite_row_to_nu_value(
fn convert_sqlite_value_to_nu_value(value: ValueRef, tag: impl Into<Tag> + Clone) -> Tagged<Value> {
match value {
ValueRef::Null => Value::Primitive(Primitive::String(String::from(""))).tagged(tag),
ValueRef::Integer(i) => Value::Primitive(Primitive::Int(i)).tagged(tag),
ValueRef::Integer(i) => Value::number(i).tagged(tag),
ValueRef::Real(f) => Value::number(f).tagged(tag),
t @ ValueRef::Text(_) => {
// this unwrap is safe because we know the ValueRef is Text.

View File

@ -30,9 +30,9 @@ pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> T
let tag = tag.into();
match v {
toml::Value::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)).tagged(tag),
toml::Value::Integer(n) => Value::Primitive(Primitive::Int(*n)).tagged(tag),
toml::Value::Float(n) => Value::Primitive(Primitive::from(*n)).tagged(tag),
toml::Value::Boolean(b) => Value::boolean(*b).tagged(tag),
toml::Value::Integer(n) => Value::number(n).tagged(tag),
toml::Value::Float(n) => Value::number(n).tagged(tag),
toml::Value::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
toml::Value::Array(a) => Value::List(
a.iter()

View File

@ -54,9 +54,9 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, tag: impl Into<Tag>) ->
let tag = tag.into();
match v {
serde_yaml::Value::Bool(b) => Value::Primitive(Primitive::Boolean(*b)).tagged(tag),
serde_yaml::Value::Bool(b) => Value::boolean(*b).tagged(tag),
serde_yaml::Value::Number(n) if n.is_i64() => {
Value::Primitive(Primitive::Int(n.as_i64().unwrap())).tagged(tag)
Value::number(n.as_i64().unwrap()).tagged(tag)
}
serde_yaml::Value::Number(n) if n.is_f64() => {
Value::Primitive(Primitive::from(n.as_f64().unwrap())).tagged(tag)

View File

@ -5,6 +5,11 @@ use crate::prelude::*;
pub struct Last;
#[derive(Deserialize)]
pub struct LastArgs {
amount: Tagged<u64>,
}
impl WholeStreamCommand for Last {
fn name(&self) -> &str {
"last"
@ -24,37 +29,18 @@ impl WholeStreamCommand for Last {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
last(args, registry)
args.process(registry, last)?.run()
// last(args, registry)
}
}
fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let amount = args.expect_nth(0)?.as_i64();
let amount = match amount {
Ok(o) => o,
Err(_) => {
return Err(ShellError::labeled_error(
"Value is not a number",
"expected integer",
args.expect_nth(0)?.span(),
))
}
};
if amount <= 0 {
return Err(ShellError::labeled_error(
"Value is too low",
"expected a positive integer",
args.expect_nth(0)?.span(),
));
}
fn last(
LastArgs { amount }: LastArgs,
context: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream_block! {
let v: Vec<_> = args.input.into_vec().await;
let k = v.len() - (amount as usize);
let v: Vec<_> = context.input.into_vec().await;
let k = v.len() - (*amount as usize);
for x in v[k..].iter() {
let y: Tagged<Value> = x.clone();
yield ReturnSuccess::value(y)

View File

@ -28,12 +28,12 @@ impl WholeStreamCommand for ToBSON {
}
}
pub fn value_to_bson_value(v: &Value) -> Bson {
match v {
pub fn value_to_bson_value(v: &Tagged<Value>) -> Result<Bson, ShellError> {
Ok(match &v.item {
Value::Primitive(Primitive::Boolean(b)) => Bson::Boolean(*b),
// FIXME: What about really big decimals?
Value::Primitive(Primitive::Bytes(decimal)) => Bson::FloatingPoint(
(*decimal)
(decimal)
.to_f64()
.expect("Unimplemented BUG: What about big decimals?"),
),
@ -41,20 +41,26 @@ pub fn value_to_bson_value(v: &Value) -> Bson {
Value::Primitive(Primitive::EndOfStream) => Bson::Null,
Value::Primitive(Primitive::BeginningOfStream) => Bson::Null,
Value::Primitive(Primitive::Decimal(d)) => Bson::FloatingPoint(d.to_f64().unwrap()),
Value::Primitive(Primitive::Int(i)) => Bson::I64(*i),
Value::Primitive(Primitive::Int(i)) => {
Bson::I64(i.tagged(v.tag).coerce_into("converting to BSON")?)
}
Value::Primitive(Primitive::Nothing) => Bson::Null,
Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()),
Value::List(l) => Bson::Array(l.iter().map(|x| value_to_bson_value(x)).collect()),
Value::List(l) => Bson::Array(
l.iter()
.map(|x| value_to_bson_value(x))
.collect::<Result<_, _>>()?,
),
Value::Block(_) => Bson::Null,
Value::Binary(b) => Bson::Binary(BinarySubtype::Generic, b.clone()),
Value::Object(o) => object_value_to_bson(o),
}
Value::Object(o) => object_value_to_bson(o)?,
})
}
// object_value_to_bson handles all Objects, even those that correspond to special
// types (things like regex or javascript code).
fn object_value_to_bson(o: &Dictionary) -> Bson {
fn object_value_to_bson(o: &Dictionary) -> Result<Bson, ShellError> {
let mut it = o.entries.iter();
if it.len() > 2 {
return generic_object_value_to_bson(o);
@ -67,7 +73,7 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
if r.is_err() || opts.is_err() {
generic_object_value_to_bson(o)
} else {
Bson::RegExp(r.unwrap(), opts.unwrap())
Ok(Bson::RegExp(r.unwrap(), opts.unwrap()))
}
}
_ => generic_object_value_to_bson(o),
@ -80,8 +86,8 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
if js.is_err() || s.is_err() {
generic_object_value_to_bson(o)
} else {
if let Bson::Document(doc) = object_value_to_bson(s.unwrap()) {
Bson::JavaScriptCodeWithScope(js.unwrap(), doc)
if let Bson::Document(doc) = object_value_to_bson(s.unwrap())? {
Ok(Bson::JavaScriptCodeWithScope(js.unwrap(), doc))
} else {
generic_object_value_to_bson(o)
}
@ -89,10 +95,10 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
}
None => {
let js: Result<String, _> = tagged_javascript_value.try_into();
if js.is_err() {
generic_object_value_to_bson(o)
} else {
Bson::JavaScriptCode(js.unwrap())
match js {
Err(_) => generic_object_value_to_bson(o),
Ok(v) => Ok(Bson::JavaScriptCode(v)),
}
}
_ => generic_object_value_to_bson(o),
@ -103,7 +109,7 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
if ts.is_err() {
generic_object_value_to_bson(o)
} else {
Bson::TimeStamp(ts.unwrap())
Ok(Bson::TimeStamp(ts.unwrap()))
}
}
Some((binary_subtype, tagged_binary_subtype_value))
@ -113,10 +119,10 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
Some((binary, tagged_bin_value)) if binary == "$binary" => {
let bst = get_binary_subtype(tagged_binary_subtype_value);
let bin: Result<Vec<u8>, _> = tagged_bin_value.try_into();
if bst.is_none() || bin.is_err() {
generic_object_value_to_bson(o)
} else {
Bson::Binary(bst.unwrap(), bin.unwrap())
match bst {
Err(_) => generic_object_value_to_bson(o),
Ok(v) => Ok(Bson::Binary(v, bin.unwrap())),
}
}
_ => generic_object_value_to_bson(o),
@ -131,7 +137,7 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
if obj_id.is_err() {
generic_object_value_to_bson(o)
} else {
Bson::ObjectId(obj_id.unwrap())
Ok(Bson::ObjectId(obj_id.unwrap()))
}
}
}
@ -140,16 +146,16 @@ fn object_value_to_bson(o: &Dictionary) -> Bson {
if sym.is_err() {
generic_object_value_to_bson(o)
} else {
Bson::Symbol(sym.unwrap())
Ok(Bson::Symbol(sym.unwrap()))
}
}
_ => generic_object_value_to_bson(o),
}
}
fn get_binary_subtype<'a>(tagged_value: &'a Tagged<Value>) -> Option<BinarySubtype> {
fn get_binary_subtype<'a>(tagged_value: &'a Tagged<Value>) -> Result<BinarySubtype, ShellError> {
match tagged_value.item() {
Value::Primitive(Primitive::String(s)) => Some(match s.as_ref() {
Value::Primitive(Primitive::String(s)) => Ok(match s.as_ref() {
"generic" => BinarySubtype::Generic,
"function" => BinarySubtype::Function,
"binary_old" => BinarySubtype::BinaryOld,
@ -158,19 +164,25 @@ fn get_binary_subtype<'a>(tagged_value: &'a Tagged<Value>) -> Option<BinarySubty
"md5" => BinarySubtype::Md5,
_ => unreachable!(),
}),
Value::Primitive(Primitive::Int(i)) => Some(BinarySubtype::UserDefined(*i as u8)),
_ => None,
Value::Primitive(Primitive::Int(i)) => Ok(BinarySubtype::UserDefined(
i.tagged(tagged_value.tag)
.coerce_into("converting to BSON binary subtype")?,
)),
_ => Err(ShellError::type_error(
"bson binary",
tagged_value.tagged_type_name(),
)),
}
}
// generic_object_value_bson handles any Object that does not
// correspond to a special bson type (things like regex or javascript code).
fn generic_object_value_to_bson(o: &Dictionary) -> Bson {
fn generic_object_value_to_bson(o: &Dictionary) -> Result<Bson, ShellError> {
let mut doc = Document::new();
for (k, v) in o.entries.iter() {
doc.insert(k.clone(), value_to_bson_value(v));
doc.insert(k.clone(), value_to_bson_value(v)?);
}
Bson::Document(doc)
Ok(Bson::Document(doc))
}
fn shell_encode_document(
@ -225,7 +237,7 @@ fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
Ok(out
.values
.map(
move |a| match bson_value_to_bytes(value_to_bson_value(&a), name_span) {
move |a| match bson_value_to_bytes(value_to_bson_value(&a)?, name_span) {
Ok(x) => ReturnSuccess::value(Value::Binary(x).simple_spanned(name_span)),
_ => Err(ShellError::labeled_error_with_secondary(
"Expected an object with BSON-compatible structure from pipeline",

View File

@ -26,8 +26,8 @@ impl WholeStreamCommand for ToJSON {
}
}
pub fn value_to_json_value(v: &Value) -> serde_json::Value {
match v {
pub fn value_to_json_value(v: &Tagged<Value>) -> Result<serde_json::Value, ShellError> {
Ok(match v.item() {
Value::Primitive(Primitive::Boolean(b)) => serde_json::Value::Bool(*b),
Value::Primitive(Primitive::Bytes(b)) => serde_json::Value::Number(
serde_json::Number::from(b.to_u64().expect("What about really big numbers")),
@ -41,16 +41,14 @@ pub fn value_to_json_value(v: &Value) -> serde_json::Value {
)
.unwrap(),
),
Value::Primitive(Primitive::Int(i)) => {
serde_json::Value::Number(serde_json::Number::from(*i))
}
Value::Primitive(Primitive::Int(i)) => serde_json::Value::Number(serde_json::Number::from(
CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to JSON number")?,
)),
Value::Primitive(Primitive::Nothing) => serde_json::Value::Null,
Value::Primitive(Primitive::String(s)) => serde_json::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()),
Value::List(l) => {
serde_json::Value::Array(l.iter().map(|x| value_to_json_value(x)).collect())
}
Value::List(l) => serde_json::Value::Array(json_list(l)?),
Value::Block(_) => serde_json::Value::Null,
Value::Binary(b) => serde_json::Value::Array(
b.iter()
@ -62,11 +60,21 @@ pub fn value_to_json_value(v: &Value) -> serde_json::Value {
Value::Object(o) => {
let mut m = serde_json::Map::new();
for (k, v) in o.entries.iter() {
m.insert(k.clone(), value_to_json_value(v));
m.insert(k.clone(), value_to_json_value(v)?);
}
serde_json::Value::Object(m)
}
})
}
fn json_list(input: &Vec<Tagged<Value>>) -> Result<Vec<serde_json::Value>, ShellError> {
let mut out = vec![];
for value in input {
out.push(value_to_json_value(value)?);
}
Ok(out)
}
fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
@ -77,7 +85,7 @@ fn to_json(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
Ok(out
.values
.map(
move |a| match serde_json::to_string(&value_to_json_value(&a)) {
move |a| match serde_json::to_string(&value_to_json_value(&a)?) {
Ok(x) => ReturnSuccess::value(
Value::Primitive(Primitive::String(x)).simple_spanned(name_span),
),

View File

@ -1,5 +1,4 @@
use crate::commands::WholeStreamCommand;
use crate::errors::ranged;
use crate::object::{Primitive, Value};
use crate::prelude::*;
@ -27,12 +26,10 @@ impl WholeStreamCommand for ToTOML {
}
}
pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
Ok(match v {
pub fn value_to_toml_value(v: &Tagged<Value>) -> Result<toml::Value, ShellError> {
Ok(match v.item() {
Value::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b),
Value::Primitive(Primitive::Bytes(b)) => {
toml::Value::Integer(ranged(b.to_i64(), "i64", b.tagged_unknown())?)
}
Value::Primitive(Primitive::Bytes(b)) => toml::Value::Integer(*b as i64),
Value::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()),
Value::Primitive(Primitive::EndOfStream) => {
toml::Value::String("<End of Stream>".to_string())
@ -41,9 +38,11 @@ pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> {
toml::Value::String("<Beginning of Stream>".to_string())
}
Value::Primitive(Primitive::Decimal(f)) => {
toml::Value::Float(ranged(f.to_f64(), "f64", f.tagged_unknown())?)
toml::Value::Float(f.tagged(v.tag).coerce_into("converting to TOML float")?)
}
Value::Primitive(Primitive::Int(i)) => {
toml::Value::Integer(i.tagged(v.tag).coerce_into("converting to TOML integer")?)
}
Value::Primitive(Primitive::Int(i)) => toml::Value::Integer(*i),
Value::Primitive(Primitive::Nothing) => toml::Value::String("<Nothing>".to_string()),
Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()),

View File

@ -26,8 +26,8 @@ impl WholeStreamCommand for ToYAML {
}
}
pub fn value_to_yaml_value(v: &Value) -> serde_yaml::Value {
match v {
pub fn value_to_yaml_value(v: &Tagged<Value>) -> Result<serde_yaml::Value, ShellError> {
Ok(match v.item() {
Value::Primitive(Primitive::Boolean(b)) => serde_yaml::Value::Bool(*b),
Value::Primitive(Primitive::Bytes(b)) => {
serde_yaml::Value::Number(serde_yaml::Number::from(b.to_f64().unwrap()))
@ -38,15 +38,21 @@ pub fn value_to_yaml_value(v: &Value) -> serde_yaml::Value {
Value::Primitive(Primitive::Decimal(f)) => {
serde_yaml::Value::Number(serde_yaml::Number::from(f.to_f64().unwrap()))
}
Value::Primitive(Primitive::Int(i)) => {
serde_yaml::Value::Number(serde_yaml::Number::from(*i))
}
Value::Primitive(Primitive::Int(i)) => serde_yaml::Value::Number(serde_yaml::Number::from(
CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to YAML number")?,
)),
Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null,
Value::Primitive(Primitive::String(s)) => serde_yaml::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()),
Value::List(l) => {
serde_yaml::Value::Sequence(l.iter().map(|x| value_to_yaml_value(x)).collect())
let mut out = vec![];
for value in l {
out.push(value_to_yaml_value(value)?);
}
serde_yaml::Value::Sequence(out)
}
Value::Block(_) => serde_yaml::Value::Null,
Value::Binary(b) => serde_yaml::Value::Sequence(
@ -57,11 +63,14 @@ pub fn value_to_yaml_value(v: &Value) -> serde_yaml::Value {
Value::Object(o) => {
let mut m = serde_yaml::Mapping::new();
for (k, v) in o.entries.iter() {
m.insert(serde_yaml::Value::String(k.clone()), value_to_yaml_value(v));
m.insert(
serde_yaml::Value::String(k.clone()),
value_to_yaml_value(v)?,
);
}
serde_yaml::Value::Mapping(m)
}
}
})
}
fn to_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
@ -71,7 +80,7 @@ fn to_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream
Ok(out
.values
.map(
move |a| match serde_yaml::to_string(&value_to_yaml_value(&a)) {
move |a| match serde_yaml::to_string(&value_to_yaml_value(&a)?) {
Ok(x) => ReturnSuccess::value(
Value::Primitive(Primitive::String(x)).simple_spanned(name_span),
),

View File

@ -85,12 +85,14 @@ impl ShellError {
}
pub(crate) fn range_error(
expected: impl Into<String>,
actual: Tagged<impl fmt::Debug>,
expected: impl Into<ExpectedRange>,
actual: &Tagged<impl fmt::Debug>,
operation: String,
) -> ShellError {
ProximateShellError::RangeError {
kind: expected.into(),
actual_kind: actual.map(|a| format!("{:?}", a)),
actual_kind: actual.copy_span(format!("{:?}", actual.item)),
operation,
}
.start()
}
@ -255,6 +257,7 @@ impl ShellError {
ProximateShellError::RangeError {
kind,
operation,
actual_kind:
Tagged {
item,
@ -262,8 +265,10 @@ impl ShellError {
},
} => Diagnostic::new(Severity::Error, "Range Error").with_label(
Label::new_primary(span).with_message(format!(
"Expected to covert {} to {}, but it was out of range",
item, kind
"Expected to convert {} to {} while {}, but it was out of range",
item,
kind.desc(),
operation
)),
),
@ -343,6 +348,45 @@ impl ShellError {
}
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
pub enum ExpectedRange {
I8,
I16,
I32,
I64,
I128,
U8,
U16,
U32,
U64,
U128,
F32,
F64,
BigInt,
BigDecimal,
}
impl ExpectedRange {
fn desc(&self) -> &'static str {
match self {
ExpectedRange::I8 => "an 8-bit signed integer",
ExpectedRange::I16 => "a 16-bit signed integer",
ExpectedRange::I32 => "a 32-bit signed integer",
ExpectedRange::I64 => "a 64-bit signed integer",
ExpectedRange::I128 => "a 128-bit signed integer",
ExpectedRange::U8 => "an 8-bit unsigned integer",
ExpectedRange::U16 => "a 16-bit unsigned integer",
ExpectedRange::U32 => "a 32-bit unsigned integer",
ExpectedRange::U64 => "a 64-bit unsigned integer",
ExpectedRange::U128 => "a 128-bit unsigned integer",
ExpectedRange::F32 => "a 32-bit float",
ExpectedRange::F64 => "a 64-bit float",
ExpectedRange::BigDecimal => "a decimal",
ExpectedRange::BigInt => "an integer",
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
pub enum ProximateShellError {
String(StringError),
@ -370,8 +414,9 @@ pub enum ProximateShellError {
span: Span,
},
RangeError {
kind: String,
kind: ExpectedRange,
actual_kind: Tagged<String>,
operation: String,
},
Diagnostic(ShellDiagnostic),
CoerceError {
@ -557,13 +602,94 @@ impl<T> ShellErrorUtils<Tagged<T>> for Option<Tagged<T>> {
}
}
pub fn ranged<T>(
input: Option<T>,
expected: impl Into<String>,
actual: Tagged<impl fmt::Debug>,
) -> Result<T, ShellError> {
match input {
Some(v) => Ok(v),
None => Err(ShellError::range_error(expected, actual)),
}
pub trait CoerceInto<U> {
fn coerce_into(self, operation: impl Into<String>) -> Result<U, ShellError>;
}
trait ToExpectedRange {
fn to_expected_range() -> ExpectedRange;
}
macro_rules! ranged_int {
($ty:tt -> $op:tt -> $variant:tt) => {
impl ToExpectedRange for $ty {
fn to_expected_range() -> ExpectedRange {
ExpectedRange::$variant
}
}
impl CoerceInto<$ty> for Tagged<BigInt> {
fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
match self.$op() {
Some(v) => Ok(v),
None => Err(ShellError::range_error(
$ty::to_expected_range(),
&self,
operation.into(),
)),
}
}
}
impl CoerceInto<$ty> for Tagged<&BigInt> {
fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
match self.$op() {
Some(v) => Ok(v),
None => Err(ShellError::range_error(
$ty::to_expected_range(),
&self,
operation.into(),
)),
}
}
}
};
}
ranged_int!(u8 -> to_u8 -> U8);
ranged_int!(u16 -> to_u16 -> U16);
ranged_int!(u32 -> to_u32 -> U32);
ranged_int!(u64 -> to_u64 -> U64);
ranged_int!(i8 -> to_i8 -> I8);
ranged_int!(i16 -> to_i16 -> I16);
ranged_int!(i32 -> to_i32 -> I32);
ranged_int!(i64 -> to_i64 -> I64);
macro_rules! ranged_decimal {
($ty:tt -> $op:tt -> $variant:tt) => {
impl ToExpectedRange for $ty {
fn to_expected_range() -> ExpectedRange {
ExpectedRange::$variant
}
}
impl CoerceInto<$ty> for Tagged<BigDecimal> {
fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
match self.$op() {
Some(v) => Ok(v),
None => Err(ShellError::range_error(
$ty::to_expected_range(),
&self,
operation.into(),
)),
}
}
}
impl CoerceInto<$ty> for Tagged<&BigDecimal> {
fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
match self.$op() {
Some(v) => Ok(v),
None => Err(ShellError::range_error(
$ty::to_expected_range(),
&self,
operation.into(),
)),
}
}
}
};
}
ranged_decimal!(f32 -> to_f32 -> F32);
ranged_decimal!(f64 -> to_f64 -> F64);

View File

@ -38,7 +38,7 @@ pub(crate) fn evaluate_baseline_expr(
source: &Text,
) -> Result<Tagged<Value>, ShellError> {
match &expr.item {
RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(*literal), source)),
RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(literal), source)),
RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())),
RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()),
RawExpression::Variable(var) => evaluate_reference(var, scope, source),
@ -104,7 +104,7 @@ pub(crate) fn evaluate_baseline_expr(
}
}
fn evaluate_literal(literal: Tagged<hir::Literal>, source: &Text) -> Tagged<Value> {
fn evaluate_literal(literal: Tagged<&hir::Literal>, source: &Text) -> Tagged<Value> {
let result = match literal.item {
hir::Literal::Number(int) => int.into(),
hir::Literal::Size(int, unit) => unit.compute(int),

View File

@ -29,7 +29,7 @@ pub use crate::parser::hir::SyntaxType;
pub use crate::plugin::{serve_plugin, Plugin};
pub use crate::utils::{AbsoluteFile, AbsolutePath, RelativePath};
pub use cli::cli;
pub use errors::ShellError;
pub use errors::{CoerceInto, ShellError};
pub use num_traits::cast::ToPrimitive;
pub use object::base::{Primitive, Value};
pub use object::dict::{Dictionary, TaggedDictBuilder};

View File

@ -16,8 +16,8 @@ use std::time::SystemTime;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]
pub enum Primitive {
Nothing,
Int(i64),
Decimal(Decimal),
Int(BigInt),
Decimal(BigDecimal),
Bytes(u64),
String(String),
Boolean(bool),
@ -29,21 +29,15 @@ pub enum Primitive {
EndOfStream,
}
impl From<i64> for Primitive {
fn from(int: i64) -> Primitive {
Primitive::Int(int)
}
}
impl From<Decimal> for Primitive {
fn from(decimal: Decimal) -> Primitive {
impl From<BigDecimal> for Primitive {
fn from(decimal: BigDecimal) -> Primitive {
Primitive::Decimal(decimal)
}
}
impl From<f64> for Primitive {
fn from(float: f64) -> Primitive {
Primitive::Decimal(Decimal::from_f64(float).unwrap())
Primitive::Decimal(BigDecimal::from_f64(float).unwrap())
}
}
@ -193,6 +187,15 @@ impl Into<Value> for Number {
}
}
impl Into<Value> for &Number {
fn into(self) -> Value {
match self {
Number::Int(int) => Value::int(int.clone()),
Number::Decimal(decimal) => Value::decimal(decimal.clone()),
}
}
}
pub fn debug_list(values: &Vec<Tagged<Value>>) -> ValuesDebug<'_> {
ValuesDebug { values }
}
@ -251,7 +254,9 @@ impl std::convert::TryFrom<&Tagged<Value>> for i64 {
fn try_from(value: &Tagged<Value>) -> Result<i64, ShellError> {
match value.item() {
Value::Primitive(Primitive::Int(int)) => Ok(*int),
Value::Primitive(Primitive::Int(int)) => {
int.tagged(value.tag).coerce_into("converting to i64")
}
v => Err(ShellError::type_error(
"Integer",
value.copy_span(v.type_name()),
@ -598,18 +603,6 @@ impl Value {
}
}
pub(crate) fn as_i64(&self) -> Result<i64, ShellError> {
match self {
Value::Primitive(Primitive::Int(i)) => Ok(*i),
Value::Primitive(Primitive::Bytes(b)) => Ok(*b as i64),
// TODO: this should definitely be more general with better errors
other => Err(ShellError::string(format!(
"Expected integer, got {:?}",
other
))),
}
}
pub(crate) fn is_true(&self) -> bool {
match self {
Value::Primitive(Primitive::Boolean(true)) => true,
@ -629,11 +622,11 @@ impl Value {
Value::Primitive(Primitive::Bytes(s.into()))
}
pub fn int(s: impl Into<i64>) -> Value {
pub fn int(s: impl Into<BigInt>) -> Value {
Value::Primitive(Primitive::Int(s.into()))
}
pub fn decimal(s: impl Into<Decimal>) -> Value {
pub fn decimal(s: impl Into<BigDecimal>) -> Value {
Value::Primitive(Primitive::Decimal(s.into()))
}
@ -727,18 +720,24 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool
_ => false,
},
Value::Primitive(Primitive::Bytes(i)) => match (op, rhs) {
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => i < (*i2 as u64),
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => {
BigInt::from(i) < *i2
}
(Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => {
i > (*i2 as u64)
BigInt::from(i) > *i2
}
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i <= (*i2 as u64)
BigInt::from(i) <= *i2
}
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i >= (*i2 as u64)
BigInt::from(i) >= *i2
}
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => {
BigInt::from(i) == *i2
}
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => {
BigInt::from(i) != *i2
}
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => i == (*i2 as u64),
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => i != (*i2 as u64),
_ => false,
},
Value::Primitive(Primitive::Int(i)) => match (op, rhs) {
@ -764,22 +763,22 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool
(Operator::Equal, Value::Primitive(Primitive::Decimal(i2))) => i == *i2,
(Operator::NotEqual, Value::Primitive(Primitive::Decimal(i2))) => i != *i2,
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => {
i < Decimal::from(*i2)
i < BigDecimal::from(i2.clone())
}
(Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => {
i > Decimal::from(*i2)
i > BigDecimal::from(i2.clone())
}
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i <= Decimal::from(*i2)
i <= BigDecimal::from(i2.clone())
}
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i >= Decimal::from(*i2)
i >= BigDecimal::from(i2.clone())
}
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => {
i == Decimal::from(*i2)
i == BigDecimal::from(i2.clone())
}
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => {
i != Decimal::from(*i2)
i != BigDecimal::from(i2.clone())
}
_ => false,
@ -796,9 +795,8 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool
}
enum CompareValues {
Ints(i64, i64),
Decimals(Decimal, Decimal),
Bytes(u64, u64),
Ints(BigInt, BigInt),
Decimals(BigDecimal, BigDecimal),
String(String, String),
}
@ -807,7 +805,6 @@ impl CompareValues {
match self {
CompareValues::Ints(left, right) => left.cmp(right),
CompareValues::Decimals(left, right) => left.cmp(right),
CompareValues::Bytes(left, right) => left.cmp(right),
CompareValues::String(left, right) => left.cmp(right),
}
}
@ -828,17 +825,21 @@ fn coerce_compare_primitive(
use Primitive::*;
Ok(match (left, right) {
(Int(left), Int(right)) => CompareValues::Ints(*left, *right),
(Int(left), Decimal(right)) => CompareValues::Decimals((*left).into(), *right),
(Int(left), Bytes(right)) => CompareValues::Bytes(*left as u64, *right),
(Decimal(left), Decimal(right)) => CompareValues::Decimals(*left, *right),
(Decimal(left), Int(right)) => CompareValues::Decimals(*left, (*right).into()),
(Decimal(left), Bytes(right)) => {
CompareValues::Decimals(*left, rust_decimal::Decimal::from(*right))
(Int(left), Int(right)) => CompareValues::Ints(left.clone(), right.clone()),
(Int(left), Decimal(right)) => {
CompareValues::Decimals(BigDecimal::zero() + left, right.clone())
}
(Bytes(left), Int(right)) => CompareValues::Bytes(*left, *right as u64),
(Int(left), Bytes(right)) => CompareValues::Ints(left.clone(), BigInt::from(*right)),
(Decimal(left), Decimal(right)) => CompareValues::Decimals(left.clone(), right.clone()),
(Decimal(left), Int(right)) => {
CompareValues::Decimals(left.clone(), BigDecimal::zero() + right)
}
(Decimal(left), Bytes(right)) => {
CompareValues::Decimals(left.clone(), BigDecimal::from(*right))
}
(Bytes(left), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()),
(Bytes(left), Decimal(right)) => {
CompareValues::Decimals(rust_decimal::Decimal::from(*left), *right)
CompareValues::Decimals(BigDecimal::from(*left), right.clone())
}
(String(left), String(right)) => CompareValues::String(left.clone(), right.clone()),
_ => return Err((left.type_name(), right.type_name())),

View File

@ -36,7 +36,8 @@ pub(crate) fn write_config(config: &IndexMap<String, Tagged<Value>>) -> Result<(
let filename = location.join("config.toml");
touch(&filename)?;
let contents = value_to_toml_value(&Value::Object(Dictionary::new(config.clone())))?;
let contents =
value_to_toml_value(&Value::Object(Dictionary::new(config.clone())).tagged_unknown())?;
let contents = toml::to_string(&contents)?;

View File

@ -135,11 +135,25 @@ impl ExtractType for i64 {
fn extract(value: &Tagged<Value>) -> Result<i64, ShellError> {
trace!("Extracting {:?} for i64", value);
match value {
match &value {
&Tagged {
item: Value::Primitive(Primitive::Int(int)),
..
} => Ok(int),
} => Ok(int.tagged(value.tag).coerce_into("converting to i64")?),
other => Err(ShellError::type_error("Integer", other.tagged_type_name())),
}
}
}
impl ExtractType for u64 {
fn extract(value: &Tagged<Value>) -> Result<u64, ShellError> {
trace!("Extracting {:?} for u64", value);
match &value {
&Tagged {
item: Value::Primitive(Primitive::Int(int)),
..
} => Ok(int.tagged(value.tag).coerce_into("converting to u64")?),
other => Err(ShellError::type_error("Integer", other.tagged_type_name())),
}
}

View File

@ -226,7 +226,7 @@ impl From<Tagged<Path>> for Expression {
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Literal {
Number(Number),
Size(Number, Unit),

View File

@ -5,8 +5,10 @@ use std::path::PathBuf;
pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression {
match *token.item() {
RawToken::Number(number) => hir::Expression::number(number, token.span()),
RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span()),
RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()),
RawToken::Size(int, unit) => {
hir::Expression::size(int.to_number(source), unit, token.span())
}
RawToken::String(span) => hir::Expression::string(span, token.span()),
RawToken::Variable(span) if span.slice(source) == "it" => {
hir::Expression::it_variable(span, token.span())
@ -24,8 +26,10 @@ pub fn baseline_parse_token_as_number(token: &Token, source: &Text) -> hir::Expr
}
RawToken::External(span) => hir::Expression::external_command(span, token.span()),
RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
RawToken::Number(number) => hir::Expression::number(number, token.span()),
RawToken::Size(number, unit) => hir::Expression::size(number, unit, token.span()),
RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()),
RawToken::Size(number, unit) => {
hir::Expression::size(number.to_number(source), unit, token.span())
}
RawToken::Bare => hir::Expression::bare(token.span()),
RawToken::String(span) => hir::Expression::string(span, token.span()),
}

View File

@ -70,20 +70,60 @@ fn trace_step<'a, T: Debug>(
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
pub enum Number {
Int(i64),
Decimal(Decimal),
Int(BigInt),
Decimal(BigDecimal),
}
macro_rules! primitive_int {
($($ty:ty)*) => {
$(
impl From<$ty> for Number {
fn from(int: $ty) -> Number {
Number::Int(BigInt::zero() + int)
}
}
impl From<&$ty> for Number {
fn from(int: &$ty) -> Number {
Number::Int(BigInt::zero() + *int)
}
}
)*
}
}
primitive_int!(i8 u8 i16 u16 i32 u32 i64 u64 i128 u128);
macro_rules! primitive_decimal {
($($ty:tt -> $from:tt),*) => {
$(
impl From<$ty> for Number {
fn from(decimal: $ty) -> Number {
Number::Decimal(BigDecimal::$from(decimal).unwrap())
}
}
impl From<&$ty> for Number {
fn from(decimal: &$ty) -> Number {
Number::Decimal(BigDecimal::$from(*decimal).unwrap())
}
}
)*
}
}
primitive_decimal!(f32 -> from_f32, f64 -> from_f64);
impl std::ops::Mul for Number {
type Output = Number;
fn mul(self, other: Number) -> Number {
match (self, other) {
(Number::Int(a), Number::Int(b)) => Number::Int(a * b),
(Number::Int(a), Number::Decimal(b)) => Number::Decimal(Decimal::from(a) * b),
(Number::Decimal(a), Number::Int(b)) => Number::Decimal(a * Decimal::from(b)),
(Number::Int(a), Number::Decimal(b)) => Number::Decimal(BigDecimal::from(a) * b),
(Number::Decimal(a), Number::Int(b)) => Number::Decimal(a * BigDecimal::from(b)),
(Number::Decimal(a), Number::Decimal(b)) => Number::Decimal(a * b),
}
}
@ -96,36 +136,18 @@ impl std::ops::Mul<u32> for Number {
fn mul(self, other: u32) -> Number {
match self {
Number::Int(left) => Number::Int(left * (other as i64)),
Number::Decimal(left) => Number::Decimal(left * Decimal::from(other)),
Number::Decimal(left) => Number::Decimal(left * BigDecimal::from(other)),
}
}
}
impl Into<Number> for f32 {
fn into(self) -> Number {
Number::Decimal(Decimal::from_f32(self).unwrap())
}
}
impl Into<Number> for f64 {
fn into(self) -> Number {
Number::Decimal(Decimal::from_f64(self).unwrap())
}
}
impl Into<Number> for i64 {
fn into(self) -> Number {
Number::Int(self)
}
}
impl Into<Number> for Decimal {
impl Into<Number> for BigDecimal {
fn into(self) -> Number {
Number::Decimal(self)
}
}
pub fn raw_number(input: NomSpan) -> IResult<NomSpan, Tagged<Number>> {
pub fn raw_number(input: NomSpan) -> IResult<NomSpan, Tagged<RawNumber>> {
let original = input;
let start = input.offset;
trace_step(input, "raw_decimal", move |input| {
@ -137,28 +159,14 @@ pub fn raw_number(input: NomSpan) -> IResult<NomSpan, Tagged<Number>> {
Ok((input, dot)) => input,
// it's just an integer
Err(_) => {
return Ok((
input,
Tagged::from_simple_spanned_item(
Number::Int(int(head.fragment, neg)),
(start, input.offset),
),
))
}
Err(_) => return Ok((input, RawNumber::int((start, input.offset)))),
};
let (input, tail) = digit1(input)?;
let end = input.offset;
let decimal = Decimal::from_str(&format!("{}.{}", head.fragment, tail.fragment))
.expect("BUG: Should have already ensured that the input is a valid decimal");
Ok((
input,
Tagged::from_simple_spanned_item(Number::Decimal(decimal), (start, end)),
))
Ok((input, RawNumber::decimal((start, end))))
})
}
@ -708,12 +716,12 @@ mod tests {
fn test_integer() {
assert_leaf! {
parsers [ size ]
"123" -> 0..3 { Number(Number::Int(123)) }
"123" -> 0..3 { Number(RawNumber::int((0, 3)).item) }
}
assert_leaf! {
parsers [ size ]
"-123" -> 0..4 { Number(Number::Int(-123)) }
"-123" -> 0..4 { Number(RawNumber::int((0, 4)).item) }
}
}
@ -721,12 +729,12 @@ mod tests {
fn test_size() {
assert_leaf! {
parsers [ size ]
"123MB" -> 0..5 { Size(Number::Int(123), Unit::MB) }
"123MB" -> 0..5 { Size(RawNumber::int((0, 3)).item, Unit::MB) }
}
assert_leaf! {
parsers [ size ]
"10GB" -> 0..4 { Size(Number::Int(10), Unit::GB) }
"10GB" -> 0..4 { Size(RawNumber::int((0, 2)).item, Unit::GB) }
}
}

View File

@ -3,10 +3,9 @@ use crate::prelude::*;
use crate::parser::parse::flag::{Flag, FlagKind};
use crate::parser::parse::operator::Operator;
use crate::parser::parse::parser::Number;
use crate::parser::parse::pipeline::{Pipeline, PipelineElement};
use crate::parser::parse::token_tree::{DelimitedNode, Delimiter, PathNode, TokenNode};
use crate::parser::parse::tokens::{RawToken, Token};
use crate::parser::parse::tokens::{RawNumber, RawToken};
use crate::parser::parse::unit::Unit;
use crate::parser::CallNode;
use crate::Span;
@ -160,35 +159,32 @@ impl TokenTreeBuilder {
))
}
pub fn int(input: impl Into<i64>) -> CurriedToken {
pub fn int(input: impl Into<BigInt>) -> CurriedToken {
let int = input.into();
Box::new(move |b| {
let (start, end) = b.consume(&int.to_string());
b.pos = end;
TokenTreeBuilder::spanned_int(int, (start, end))
TokenTreeBuilder::spanned_number(RawNumber::Int((start, end).into()), (start, end))
})
}
pub fn spanned_number(input: impl Into<Number>, span: impl Into<Span>) -> TokenNode {
match input.into() {
Number::Int(int) => TokenTreeBuilder::spanned_int(int, span),
Number::Decimal(decimal) => TokenTreeBuilder::spanned_decimal(decimal, span),
}
pub fn decimal(input: impl Into<BigDecimal>) -> CurriedToken {
let decimal = input.into();
Box::new(move |b| {
let (start, end) = b.consume(&decimal.to_string());
b.pos = end;
TokenTreeBuilder::spanned_number(RawNumber::Decimal((start, end).into()), (start, end))
})
}
fn spanned_int(input: i64, span: impl Into<Span>) -> TokenNode {
TokenNode::Token(Token::from_simple_spanned_item(
RawToken::Number(Number::Int(input)),
span,
))
}
fn spanned_decimal(input: Decimal, span: impl Into<Span>) -> TokenNode {
TokenNode::Token(Token::from_simple_spanned_item(
RawToken::Number(Number::Decimal(input)),
span,
pub fn spanned_number(input: impl Into<RawNumber>, span: impl Into<Span>) -> TokenNode {
TokenNode::Token(Tagged::from_simple_spanned_item(
RawToken::Number(input.into()),
span.into(),
))
}
@ -197,16 +193,19 @@ impl TokenTreeBuilder {
let unit = unit.into();
Box::new(move |b| {
let (start, _) = b.consume(&int.to_string());
let (_, end) = b.consume(unit.as_str());
b.pos = end;
let (start_int, end_int) = b.consume(&int.to_string());
let (start_unit, end_unit) = b.consume(unit.as_str());
b.pos = end_unit;
TokenTreeBuilder::spanned_size((int, unit), (start, end))
TokenTreeBuilder::spanned_size(
(RawNumber::Int((start_int, end_int).into()), unit),
(start_int, end_unit),
)
})
}
pub fn spanned_size(
input: (impl Into<Number>, impl Into<Unit>),
input: (impl Into<RawNumber>, impl Into<Unit>),
span: impl Into<Span>,
) -> TokenNode {
let (int, unit) = (input.0.into(), input.1.into());

View File

@ -2,17 +2,47 @@ use crate::parser::parse::unit::*;
use crate::prelude::*;
use crate::{Span, Tagged, Text};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RawToken {
Number(Number),
Size(Number, Unit),
Number(RawNumber),
Size(RawNumber, Unit),
String(Span),
Variable(Span),
External(Span),
Bare,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RawNumber {
Int(Span),
Decimal(Span),
}
impl RawNumber {
pub fn int(span: impl Into<Span>) -> Tagged<RawNumber> {
let span = span.into();
RawNumber::Int(span).tagged(span)
}
pub fn decimal(span: impl Into<Span>) -> Tagged<RawNumber> {
let span = span.into();
RawNumber::Decimal(span).tagged(span)
}
pub(crate) fn to_number(self, source: &Text) -> Number {
match self {
RawNumber::Int(span) => Number::Int(BigInt::from_str(span.slice(source)).unwrap()),
RawNumber::Decimal(span) => {
Number::Decimal(BigDecimal::from_str(span.slice(source)).unwrap())
}
}
}
}
impl RawToken {
pub fn type_name(&self) -> &'static str {
match self {

View File

@ -25,7 +25,9 @@ impl Unit {
}
}
pub(crate) fn compute(&self, size: Number) -> Value {
pub(crate) fn compute(&self, size: &Number) -> Value {
let size = size.clone();
Value::number(match self {
Unit::B => size,
Unit::KB => size * 1024,

View File

@ -75,7 +75,7 @@ impl Inc {
}
fn inc(&self, value: Tagged<Value>) -> Result<Tagged<Value>, ShellError> {
match value.item {
match value.item() {
Value::Primitive(Primitive::Int(i)) => Ok(Value::int(i + 1).tagged(value.tag())),
Value::Primitive(Primitive::Bytes(b)) => {
Ok(Value::bytes(b + 1 as u64).tagged(value.tag()))

View File

@ -1,11 +1,12 @@
use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
SyntaxType, Tagged, Value,
serve_plugin, CallInfo, CoerceInto, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
Signature, SyntaxType, Tagged, TaggedItem, Value,
};
struct Skip {
skip_amount: i64,
}
impl Skip {
fn new() -> Skip {
Skip { skip_amount: 0 }
@ -25,9 +26,9 @@ impl Plugin for Skip {
match arg {
Tagged {
item: Value::Primitive(Primitive::Int(i)),
..
tag,
} => {
self.skip_amount = i;
self.skip_amount = i.tagged(tag).coerce_into("converting for skip")?;
}
_ => {
return Err(ShellError::labeled_error(

View File

@ -258,13 +258,13 @@ fn main() {
#[cfg(test)]
mod tests {
use super::{Action, ReplaceAction, Str};
use indexmap::IndexMap;
use nu::{
CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, SourceMap, Span, Tag, Tagged,
TaggedDictBuilder, TaggedItem, Value,
};
use num_bigint::BigInt;
impl Str {
fn replace_with(&mut self, value: &str) {
@ -600,7 +600,7 @@ mod tests {
ReturnSuccess::Value(Tagged {
item: Value::Primitive(Primitive::Int(i)),
..
}) => assert_eq!(*i, 10),
}) => assert_eq!(*i, BigInt::from(10)),
_ => {}
}
}

View File

@ -12,10 +12,10 @@ impl Sum {
}
fn sum(&mut self, value: Tagged<Value>) -> Result<(), ShellError> {
match value.item {
match value.item() {
Value::Primitive(Primitive::Nothing) => Ok(()),
Value::Primitive(Primitive::Int(i)) => {
match self.total {
match &self.total {
Some(Tagged {
item: Value::Primitive(Primitive::Int(j)),
tag: Tag { span, .. },
@ -26,7 +26,7 @@ impl Sum {
Ok(())
}
None => {
self.total = Some(value);
self.total = Some(value.clone());
Ok(())
}
_ => Err(ShellError::string(format!(

View File

@ -20,7 +20,7 @@ async fn cpu(tag: Tag) -> Option<Tagged<Value>> {
match futures::future::try_join(heim::cpu::logical_count(), heim::cpu::frequency()).await {
Ok((num_cpu, cpu_speed)) => {
let mut cpu_idx = TaggedDictBuilder::with_capacity(tag, 4);
cpu_idx.insert("cores", Primitive::Int(num_cpu as i64));
cpu_idx.insert("cores", Primitive::number(num_cpu));
let current_speed =
(cpu_speed.current().get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0)

View File

@ -57,7 +57,7 @@ pub(crate) use crate::context::CommandRegistry;
pub(crate) use crate::context::{Context, SpanSource};
pub(crate) use crate::env::host::handle_unexpected;
pub(crate) use crate::env::Host;
pub(crate) use crate::errors::ShellError;
pub(crate) use crate::errors::{CoerceInto, ShellError};
pub(crate) use crate::object::base as value;
pub(crate) use crate::object::meta::{Tag, Tagged, TaggedItem};
pub(crate) use crate::object::types::ExtractType;
@ -73,13 +73,14 @@ pub(crate) use crate::stream::{InputStream, OutputStream};
pub(crate) use crate::traits::{HasSpan, ToDebug};
pub(crate) use crate::Span;
pub(crate) use crate::Text;
pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::stream::BoxStream;
pub(crate) use futures::{FutureExt, Stream, StreamExt};
pub(crate) use futures_async_stream::async_stream_block;
pub(crate) use num_bigint::BigInt;
pub(crate) use num_traits::cast::{FromPrimitive, ToPrimitive};
pub(crate) use rust_decimal::Decimal;
#[allow(unused)]
pub(crate) use serde::{Deserialize, Serialize};
pub(crate) use num_traits::identities::Zero;
pub(crate) use serde::Deserialize;
pub(crate) use std::collections::VecDeque;
pub(crate) use std::future::Future;
pub(crate) use std::sync::{Arc, Mutex};