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

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
/target /target
/scratch
**/*.rs.bk **/*.rs.bk
history.txt 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)", "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]] [[package]]
name = "bincode" name = "bincode"
version = "1.1.4" version = "1.1.4"
@ -1560,6 +1571,7 @@ dependencies = [
"app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.2.2" version = "0.2.2"
@ -1647,15 +1646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-integer 0.1.41 (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)", "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 = "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)",
] ]
[[package]] [[package]]
@ -1683,7 +1674,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "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-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)", "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" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.15" 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 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 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 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 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 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" "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 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 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 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-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-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-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" "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 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-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-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-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-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" "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" bytes = "0.4.12"
log = "0.4.8" log = "0.4.8"
pretty_env_logger = "0.3.1" pretty_env_logger = "0.3.1"
rust_decimal = "1.0.3"
serde = { version = "1.0.99", features = ["derive"] } serde = { version = "1.0.99", features = ["derive"] }
bson = { version = "0.14.0", features = ["decimal128"] } bson = { version = "0.14.0", features = ["decimal128"] }
serde_json = "1.0.40" serde_json = "1.0.40"
@ -81,6 +80,8 @@ clipboard = {version = "0.5", optional = true }
shellexpand = "1.0.0" shellexpand = "1.0.0"
futures-timer = "0.3.0" futures-timer = "0.3.0"
pin-utils = "0.1.0-alpha.4" pin-utils = "0.1.0-alpha.4"
num-bigint = { version = "0.2.2", features = ["serde"] }
bigdecimal = { version = "0.1.0", features = ["serde"] }
[features] [features]
raw-key = ["rawkey", "neso"] raw-key = ["rawkey", "neso"]

View File

@ -5,6 +5,11 @@ use crate::prelude::*;
pub struct First; pub struct First;
#[derive(Deserialize)]
pub struct FirstArgs {
amount: Tagged<u64>,
}
impl WholeStreamCommand for First { impl WholeStreamCommand for First {
fn name(&self) -> &str { fn name(&self) -> &str {
"first" "first"
@ -24,27 +29,13 @@ impl WholeStreamCommand for First {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
first(args, registry) args.process(registry, first)?.run()
} }
} }
fn first(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn first(
let args = args.evaluate_once(registry)?; FirstArgs { amount }: FirstArgs,
context: RunnableContext,
let amount = args.expect_nth(0)?.as_i64(); ) -> Result<OutputStream, ShellError> {
Ok(OutputStream::from_input(context.input.values.take(*amount)))
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),
))
} }

View File

@ -1,4 +1,5 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::errors::ExpectedRange;
use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::object::{Primitive, TaggedDictBuilder, Value};
use crate::prelude::*; use crate::prelude::*;
use bson::{decode_document, spec::BinarySubtype, Bson}; 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(); let tag = tag.into();
match v { Ok(match v {
Bson::FloatingPoint(n) => Value::Primitive(Primitive::from(*n)).tagged(tag), Bson::FloatingPoint(n) => Value::Primitive(Primitive::from(*n)).tagged(tag),
Bson::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag), Bson::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
Bson::Array(a) => Value::List( Bson::Array(a) => Value::List(bson_array(a, tag)?).tagged(tag),
a.iter()
.map(|x| convert_bson_value_to_nu_value(x, tag))
.collect(),
)
.tagged(tag),
Bson::Document(doc) => { Bson::Document(doc) => {
let mut collected = TaggedDictBuilder::new(tag); let mut collected = TaggedDictBuilder::new(tag);
for (k, v) in doc.iter() { 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() 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() collected.into_tagged_value()
} }
// TODO: Add Int32 to nushell? Bson::I32(n) => Value::number(n).tagged(tag),
Bson::I32(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), Bson::I64(n) => Value::number(n).tagged(tag),
Bson::I64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag),
Bson::Decimal128(n) => { 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) Value::Primitive(Primitive::Decimal(decimal)).tagged(tag)
} }
Bson::JavaScriptCode(js) => { 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( collected.insert_tagged(
"$scope".to_string(), "$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() collected.into_tagged_value()
} }
Bson::TimeStamp(ts) => { Bson::TimeStamp(ts) => {
let mut collected = TaggedDictBuilder::new(tag); let mut collected = TaggedDictBuilder::new(tag);
collected.insert_tagged( collected.insert_tagged("$timestamp".to_string(), Value::number(ts).tagged(tag));
"$timestamp".to_string(),
Value::Primitive(Primitive::Int(*ts as i64)).tagged(tag),
);
collected.into_tagged_value() collected.into_tagged_value()
} }
Bson::Binary(bst, bytes) => { 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( collected.insert_tagged(
"$binary_subtype".to_string(), "$binary_subtype".to_string(),
match bst { 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))), _ => Value::Primitive(Primitive::String(binary_subtype_to_string(*bst))),
} }
.tagged(tag), .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() collected.into_tagged_value()
} }
} })
} }
fn binary_subtype_to_string(bst: BinarySubtype) -> String { 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) { while let Ok(v) = decode_document(&mut b_reader) {
docs.push(Bson::Document(v)); 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> { 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 { match v {
serde_hjson::Value::Null => Value::Primitive(Primitive::Nothing).tagged(tag), 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::Bool(b) => Value::boolean(*b).tagged(tag),
serde_hjson::Value::F64(n) => Value::Primitive(Primitive::from(*n)).tagged(tag), serde_hjson::Value::F64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::U64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), serde_hjson::Value::U64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::I64(n) => Value::Primitive(Primitive::Int(*n as i64)).tagged(tag), serde_hjson::Value::I64(n) => Value::number(n).tagged(tag),
serde_hjson::Value::String(s) => { serde_hjson::Value::String(s) => {
Value::Primitive(Primitive::String(String::from(s))).tagged(tag) 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> { fn convert_sqlite_value_to_nu_value(value: ValueRef, tag: impl Into<Tag> + Clone) -> Tagged<Value> {
match value { match value {
ValueRef::Null => Value::Primitive(Primitive::String(String::from(""))).tagged(tag), 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), ValueRef::Real(f) => Value::number(f).tagged(tag),
t @ ValueRef::Text(_) => { t @ ValueRef::Text(_) => {
// this unwrap is safe because we know the ValueRef is 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(); let tag = tag.into();
match v { match v {
toml::Value::Boolean(b) => Value::Primitive(Primitive::Boolean(*b)).tagged(tag), toml::Value::Boolean(b) => Value::boolean(*b).tagged(tag),
toml::Value::Integer(n) => Value::Primitive(Primitive::Int(*n)).tagged(tag), toml::Value::Integer(n) => Value::number(n).tagged(tag),
toml::Value::Float(n) => Value::Primitive(Primitive::from(*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::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag),
toml::Value::Array(a) => Value::List( toml::Value::Array(a) => Value::List(
a.iter() 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(); let tag = tag.into();
match v { 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() => { 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() => { serde_yaml::Value::Number(n) if n.is_f64() => {
Value::Primitive(Primitive::from(n.as_f64().unwrap())).tagged(tag) Value::Primitive(Primitive::from(n.as_f64().unwrap())).tagged(tag)

View File

@ -5,6 +5,11 @@ use crate::prelude::*;
pub struct Last; pub struct Last;
#[derive(Deserialize)]
pub struct LastArgs {
amount: Tagged<u64>,
}
impl WholeStreamCommand for Last { impl WholeStreamCommand for Last {
fn name(&self) -> &str { fn name(&self) -> &str {
"last" "last"
@ -24,37 +29,18 @@ impl WholeStreamCommand for Last {
args: CommandArgs, args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
last(args, registry) args.process(registry, last)?.run()
// last(args, registry)
} }
} }
fn last(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { fn last(
let args = args.evaluate_once(registry)?; LastArgs { amount }: LastArgs,
context: RunnableContext,
let amount = args.expect_nth(0)?.as_i64(); ) -> Result<OutputStream, ShellError> {
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(),
));
}
let stream = async_stream_block! { let stream = async_stream_block! {
let v: Vec<_> = args.input.into_vec().await; let v: Vec<_> = context.input.into_vec().await;
let k = v.len() - (amount as usize); let k = v.len() - (*amount as usize);
for x in v[k..].iter() { for x in v[k..].iter() {
let y: Tagged<Value> = x.clone(); let y: Tagged<Value> = x.clone();
yield ReturnSuccess::value(y) yield ReturnSuccess::value(y)

View File

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

View File

@ -1,5 +1,4 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::errors::ranged;
use crate::object::{Primitive, Value}; use crate::object::{Primitive, Value};
use crate::prelude::*; use crate::prelude::*;
@ -27,12 +26,10 @@ impl WholeStreamCommand for ToTOML {
} }
} }
pub fn value_to_toml_value(v: &Value) -> Result<toml::Value, ShellError> { pub fn value_to_toml_value(v: &Tagged<Value>) -> Result<toml::Value, ShellError> {
Ok(match v { Ok(match v.item() {
Value::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b), Value::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b),
Value::Primitive(Primitive::Bytes(b)) => { Value::Primitive(Primitive::Bytes(b)) => toml::Value::Integer(*b as i64),
toml::Value::Integer(ranged(b.to_i64(), "i64", b.tagged_unknown())?)
}
Value::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()), Value::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()),
Value::Primitive(Primitive::EndOfStream) => { Value::Primitive(Primitive::EndOfStream) => {
toml::Value::String("<End of Stream>".to_string()) 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()) toml::Value::String("<Beginning of Stream>".to_string())
} }
Value::Primitive(Primitive::Decimal(f)) => { 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::Nothing) => toml::Value::String("<Nothing>".to_string()),
Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()), Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()), 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 { pub fn value_to_yaml_value(v: &Tagged<Value>) -> Result<serde_yaml::Value, ShellError> {
match v { Ok(match v.item() {
Value::Primitive(Primitive::Boolean(b)) => serde_yaml::Value::Bool(*b), Value::Primitive(Primitive::Boolean(b)) => serde_yaml::Value::Bool(*b),
Value::Primitive(Primitive::Bytes(b)) => { Value::Primitive(Primitive::Bytes(b)) => {
serde_yaml::Value::Number(serde_yaml::Number::from(b.to_f64().unwrap())) 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)) => { Value::Primitive(Primitive::Decimal(f)) => {
serde_yaml::Value::Number(serde_yaml::Number::from(f.to_f64().unwrap())) serde_yaml::Value::Number(serde_yaml::Number::from(f.to_f64().unwrap()))
} }
Value::Primitive(Primitive::Int(i)) => { Value::Primitive(Primitive::Int(i)) => serde_yaml::Value::Number(serde_yaml::Number::from(
serde_yaml::Value::Number(serde_yaml::Number::from(*i)) CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to YAML number")?,
} )),
Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null, Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null,
Value::Primitive(Primitive::String(s)) => serde_yaml::Value::String(s.clone()), 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::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()),
Value::List(l) => { 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::Block(_) => serde_yaml::Value::Null,
Value::Binary(b) => serde_yaml::Value::Sequence( 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) => { Value::Object(o) => {
let mut m = serde_yaml::Mapping::new(); let mut m = serde_yaml::Mapping::new();
for (k, v) in o.entries.iter() { 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) serde_yaml::Value::Mapping(m)
} }
} })
} }
fn to_yaml(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> { 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 Ok(out
.values .values
.map( .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( Ok(x) => ReturnSuccess::value(
Value::Primitive(Primitive::String(x)).simple_spanned(name_span), Value::Primitive(Primitive::String(x)).simple_spanned(name_span),
), ),

View File

@ -85,12 +85,14 @@ impl ShellError {
} }
pub(crate) fn range_error( pub(crate) fn range_error(
expected: impl Into<String>, expected: impl Into<ExpectedRange>,
actual: Tagged<impl fmt::Debug>, actual: &Tagged<impl fmt::Debug>,
operation: String,
) -> ShellError { ) -> ShellError {
ProximateShellError::RangeError { ProximateShellError::RangeError {
kind: expected.into(), kind: expected.into(),
actual_kind: actual.map(|a| format!("{:?}", a)), actual_kind: actual.copy_span(format!("{:?}", actual.item)),
operation,
} }
.start() .start()
} }
@ -255,6 +257,7 @@ impl ShellError {
ProximateShellError::RangeError { ProximateShellError::RangeError {
kind, kind,
operation,
actual_kind: actual_kind:
Tagged { Tagged {
item, item,
@ -262,8 +265,10 @@ impl ShellError {
}, },
} => Diagnostic::new(Severity::Error, "Range Error").with_label( } => Diagnostic::new(Severity::Error, "Range Error").with_label(
Label::new_primary(span).with_message(format!( Label::new_primary(span).with_message(format!(
"Expected to covert {} to {}, but it was out of range", "Expected to convert {} to {} while {}, but it was out of range",
item, kind 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)] #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
pub enum ProximateShellError { pub enum ProximateShellError {
String(StringError), String(StringError),
@ -370,8 +414,9 @@ pub enum ProximateShellError {
span: Span, span: Span,
}, },
RangeError { RangeError {
kind: String, kind: ExpectedRange,
actual_kind: Tagged<String>, actual_kind: Tagged<String>,
operation: String,
}, },
Diagnostic(ShellDiagnostic), Diagnostic(ShellDiagnostic),
CoerceError { CoerceError {
@ -557,13 +602,94 @@ impl<T> ShellErrorUtils<Tagged<T>> for Option<Tagged<T>> {
} }
} }
pub fn ranged<T>( pub trait CoerceInto<U> {
input: Option<T>, fn coerce_into(self, operation: impl Into<String>) -> Result<U, ShellError>;
expected: impl Into<String>, }
actual: Tagged<impl fmt::Debug>,
) -> Result<T, ShellError> { trait ToExpectedRange {
match input { 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), Some(v) => Ok(v),
None => Err(ShellError::range_error(expected, actual)), 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, source: &Text,
) -> Result<Tagged<Value>, ShellError> { ) -> Result<Tagged<Value>, ShellError> {
match &expr.item { 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::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())),
RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()), RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()),
RawExpression::Variable(var) => evaluate_reference(var, scope, source), 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 { let result = match literal.item {
hir::Literal::Number(int) => int.into(), hir::Literal::Number(int) => int.into(),
hir::Literal::Size(int, unit) => unit.compute(int), 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::plugin::{serve_plugin, Plugin};
pub use crate::utils::{AbsoluteFile, AbsolutePath, RelativePath}; pub use crate::utils::{AbsoluteFile, AbsolutePath, RelativePath};
pub use cli::cli; pub use cli::cli;
pub use errors::ShellError; pub use errors::{CoerceInto, ShellError};
pub use num_traits::cast::ToPrimitive; pub use num_traits::cast::ToPrimitive;
pub use object::base::{Primitive, Value}; pub use object::base::{Primitive, Value};
pub use object::dict::{Dictionary, TaggedDictBuilder}; 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)] #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]
pub enum Primitive { pub enum Primitive {
Nothing, Nothing,
Int(i64), Int(BigInt),
Decimal(Decimal), Decimal(BigDecimal),
Bytes(u64), Bytes(u64),
String(String), String(String),
Boolean(bool), Boolean(bool),
@ -29,21 +29,15 @@ pub enum Primitive {
EndOfStream, EndOfStream,
} }
impl From<i64> for Primitive { impl From<BigDecimal> for Primitive {
fn from(int: i64) -> Primitive { fn from(decimal: BigDecimal) -> Primitive {
Primitive::Int(int)
}
}
impl From<Decimal> for Primitive {
fn from(decimal: Decimal) -> Primitive {
Primitive::Decimal(decimal) Primitive::Decimal(decimal)
} }
} }
impl From<f64> for Primitive { impl From<f64> for Primitive {
fn from(float: f64) -> 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<'_> { pub fn debug_list(values: &Vec<Tagged<Value>>) -> ValuesDebug<'_> {
ValuesDebug { values } ValuesDebug { values }
} }
@ -251,7 +254,9 @@ impl std::convert::TryFrom<&Tagged<Value>> for i64 {
fn try_from(value: &Tagged<Value>) -> Result<i64, ShellError> { fn try_from(value: &Tagged<Value>) -> Result<i64, ShellError> {
match value.item() { 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( v => Err(ShellError::type_error(
"Integer", "Integer",
value.copy_span(v.type_name()), 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 { pub(crate) fn is_true(&self) -> bool {
match self { match self {
Value::Primitive(Primitive::Boolean(true)) => true, Value::Primitive(Primitive::Boolean(true)) => true,
@ -629,11 +622,11 @@ impl Value {
Value::Primitive(Primitive::Bytes(s.into())) 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())) 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())) Value::Primitive(Primitive::Decimal(s.into()))
} }
@ -727,18 +720,24 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool
_ => false, _ => false,
}, },
Value::Primitive(Primitive::Bytes(i)) => match (op, rhs) { 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))) => { (Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => {
i > (*i2 as u64) BigInt::from(i) > *i2
} }
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => { (Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i <= (*i2 as u64) BigInt::from(i) <= *i2
} }
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(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, _ => false,
}, },
Value::Primitive(Primitive::Int(i)) => match (op, rhs) { 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::Equal, Value::Primitive(Primitive::Decimal(i2))) => i == *i2,
(Operator::NotEqual, Value::Primitive(Primitive::Decimal(i2))) => i != *i2, (Operator::NotEqual, Value::Primitive(Primitive::Decimal(i2))) => i != *i2,
(Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => { (Operator::LessThan, Value::Primitive(Primitive::Int(i2))) => {
i < Decimal::from(*i2) i < BigDecimal::from(i2.clone())
} }
(Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => { (Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => {
i > Decimal::from(*i2) i > BigDecimal::from(i2.clone())
} }
(Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => { (Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i <= Decimal::from(*i2) i <= BigDecimal::from(i2.clone())
} }
(Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => { (Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => {
i >= Decimal::from(*i2) i >= BigDecimal::from(i2.clone())
} }
(Operator::Equal, Value::Primitive(Primitive::Int(i2))) => { (Operator::Equal, Value::Primitive(Primitive::Int(i2))) => {
i == Decimal::from(*i2) i == BigDecimal::from(i2.clone())
} }
(Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => { (Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => {
i != Decimal::from(*i2) i != BigDecimal::from(i2.clone())
} }
_ => false, _ => false,
@ -796,9 +795,8 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool
} }
enum CompareValues { enum CompareValues {
Ints(i64, i64), Ints(BigInt, BigInt),
Decimals(Decimal, Decimal), Decimals(BigDecimal, BigDecimal),
Bytes(u64, u64),
String(String, String), String(String, String),
} }
@ -807,7 +805,6 @@ impl CompareValues {
match self { match self {
CompareValues::Ints(left, right) => left.cmp(right), CompareValues::Ints(left, right) => left.cmp(right),
CompareValues::Decimals(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), CompareValues::String(left, right) => left.cmp(right),
} }
} }
@ -828,17 +825,21 @@ fn coerce_compare_primitive(
use Primitive::*; use Primitive::*;
Ok(match (left, right) { Ok(match (left, right) {
(Int(left), Int(right)) => CompareValues::Ints(*left, *right), (Int(left), Int(right)) => CompareValues::Ints(left.clone(), right.clone()),
(Int(left), Decimal(right)) => CompareValues::Decimals((*left).into(), *right), (Int(left), Decimal(right)) => {
(Int(left), Bytes(right)) => CompareValues::Bytes(*left as u64, *right), CompareValues::Decimals(BigDecimal::zero() + left, right.clone())
(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))
} }
(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)) => { (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()), (String(left), String(right)) => CompareValues::String(left.clone(), right.clone()),
_ => return Err((left.type_name(), right.type_name())), _ => 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"); let filename = location.join("config.toml");
touch(&filename)?; 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)?; let contents = toml::to_string(&contents)?;

View File

@ -135,11 +135,25 @@ impl ExtractType for i64 {
fn extract(value: &Tagged<Value>) -> Result<i64, ShellError> { fn extract(value: &Tagged<Value>) -> Result<i64, ShellError> {
trace!("Extracting {:?} for i64", value); trace!("Extracting {:?} for i64", value);
match value { match &value {
&Tagged { &Tagged {
item: Value::Primitive(Primitive::Int(int)), 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())), 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 { pub enum Literal {
Number(Number), Number(Number),
Size(Number, Unit), 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 { pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression {
match *token.item() { match *token.item() {
RawToken::Number(number) => hir::Expression::number(number, token.span()), RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()),
RawToken::Size(int, unit) => hir::Expression::size(int, unit, 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::String(span) => hir::Expression::string(span, token.span()),
RawToken::Variable(span) if span.slice(source) == "it" => { RawToken::Variable(span) if span.slice(source) == "it" => {
hir::Expression::it_variable(span, token.span()) 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::External(span) => hir::Expression::external_command(span, token.span()),
RawToken::Variable(span) => hir::Expression::variable(span, token.span()), RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
RawToken::Number(number) => hir::Expression::number(number, token.span()), RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()),
RawToken::Size(number, unit) => hir::Expression::size(number, unit, token.span()), RawToken::Size(number, unit) => {
hir::Expression::size(number.to_number(source), unit, token.span())
}
RawToken::Bare => hir::Expression::bare(token.span()), RawToken::Bare => hir::Expression::bare(token.span()),
RawToken::String(span) => hir::Expression::string(span, 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 { pub enum Number {
Int(i64), Int(BigInt),
Decimal(Decimal), 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 { impl std::ops::Mul for Number {
type Output = Number; type Output = Number;
fn mul(self, other: Number) -> Number { fn mul(self, other: Number) -> Number {
match (self, other) { match (self, other) {
(Number::Int(a), Number::Int(b)) => Number::Int(a * b), (Number::Int(a), Number::Int(b)) => Number::Int(a * b),
(Number::Int(a), Number::Decimal(b)) => Number::Decimal(Decimal::from(a) * b), (Number::Int(a), Number::Decimal(b)) => Number::Decimal(BigDecimal::from(a) * b),
(Number::Decimal(a), Number::Int(b)) => Number::Decimal(a * Decimal::from(b)), (Number::Decimal(a), Number::Int(b)) => Number::Decimal(a * BigDecimal::from(b)),
(Number::Decimal(a), Number::Decimal(b)) => Number::Decimal(a * 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 { fn mul(self, other: u32) -> Number {
match self { match self {
Number::Int(left) => Number::Int(left * (other as i64)), 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 { impl Into<Number> for BigDecimal {
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 {
fn into(self) -> Number { fn into(self) -> Number {
Number::Decimal(self) 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 original = input;
let start = input.offset; let start = input.offset;
trace_step(input, "raw_decimal", move |input| { 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, Ok((input, dot)) => input,
// it's just an integer // it's just an integer
Err(_) => { Err(_) => return Ok((input, RawNumber::int((start, input.offset)))),
return Ok((
input,
Tagged::from_simple_spanned_item(
Number::Int(int(head.fragment, neg)),
(start, input.offset),
),
))
}
}; };
let (input, tail) = digit1(input)?; let (input, tail) = digit1(input)?;
let end = input.offset; let end = input.offset;
let decimal = Decimal::from_str(&format!("{}.{}", head.fragment, tail.fragment)) Ok((input, RawNumber::decimal((start, end))))
.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)),
))
}) })
} }
@ -708,12 +716,12 @@ mod tests {
fn test_integer() { fn test_integer() {
assert_leaf! { assert_leaf! {
parsers [ size ] parsers [ size ]
"123" -> 0..3 { Number(Number::Int(123)) } "123" -> 0..3 { Number(RawNumber::int((0, 3)).item) }
} }
assert_leaf! { assert_leaf! {
parsers [ size ] 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() { fn test_size() {
assert_leaf! { assert_leaf! {
parsers [ size ] parsers [ size ]
"123MB" -> 0..5 { Size(Number::Int(123), Unit::MB) } "123MB" -> 0..5 { Size(RawNumber::int((0, 3)).item, Unit::MB) }
} }
assert_leaf! { assert_leaf! {
parsers [ size ] 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::flag::{Flag, FlagKind};
use crate::parser::parse::operator::Operator; use crate::parser::parse::operator::Operator;
use crate::parser::parse::parser::Number;
use crate::parser::parse::pipeline::{Pipeline, PipelineElement}; use crate::parser::parse::pipeline::{Pipeline, PipelineElement};
use crate::parser::parse::token_tree::{DelimitedNode, Delimiter, PathNode, TokenNode}; 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::parse::unit::Unit;
use crate::parser::CallNode; use crate::parser::CallNode;
use crate::Span; 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(); let int = input.into();
Box::new(move |b| { Box::new(move |b| {
let (start, end) = b.consume(&int.to_string()); let (start, end) = b.consume(&int.to_string());
b.pos = end; 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 { pub fn decimal(input: impl Into<BigDecimal>) -> CurriedToken {
match input.into() { let decimal = input.into();
Number::Int(int) => TokenTreeBuilder::spanned_int(int, span),
Number::Decimal(decimal) => TokenTreeBuilder::spanned_decimal(decimal, span), 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 { pub fn spanned_number(input: impl Into<RawNumber>, span: impl Into<Span>) -> TokenNode {
TokenNode::Token(Token::from_simple_spanned_item( TokenNode::Token(Tagged::from_simple_spanned_item(
RawToken::Number(Number::Int(input)), RawToken::Number(input.into()),
span, span.into(),
))
}
fn spanned_decimal(input: Decimal, span: impl Into<Span>) -> TokenNode {
TokenNode::Token(Token::from_simple_spanned_item(
RawToken::Number(Number::Decimal(input)),
span,
)) ))
} }
@ -197,16 +193,19 @@ impl TokenTreeBuilder {
let unit = unit.into(); let unit = unit.into();
Box::new(move |b| { Box::new(move |b| {
let (start, _) = b.consume(&int.to_string()); let (start_int, end_int) = b.consume(&int.to_string());
let (_, end) = b.consume(unit.as_str()); let (start_unit, end_unit) = b.consume(unit.as_str());
b.pos = end; 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( pub fn spanned_size(
input: (impl Into<Number>, impl Into<Unit>), input: (impl Into<RawNumber>, impl Into<Unit>),
span: impl Into<Span>, span: impl Into<Span>,
) -> TokenNode { ) -> TokenNode {
let (int, unit) = (input.0.into(), input.1.into()); 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::prelude::*;
use crate::{Span, Tagged, Text}; use crate::{Span, Tagged, Text};
use std::fmt; use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RawToken { pub enum RawToken {
Number(Number), Number(RawNumber),
Size(Number, Unit), Size(RawNumber, Unit),
String(Span), String(Span),
Variable(Span), Variable(Span),
External(Span), External(Span),
Bare, 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 { impl RawToken {
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
match self { 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 { Value::number(match self {
Unit::B => size, Unit::B => size,
Unit::KB => size * 1024, Unit::KB => size * 1024,

View File

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

View File

@ -1,11 +1,12 @@
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, serve_plugin, CallInfo, CoerceInto, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
SyntaxType, Tagged, Value, Signature, SyntaxType, Tagged, TaggedItem, Value,
}; };
struct Skip { struct Skip {
skip_amount: i64, skip_amount: i64,
} }
impl Skip { impl Skip {
fn new() -> Skip { fn new() -> Skip {
Skip { skip_amount: 0 } Skip { skip_amount: 0 }
@ -25,9 +26,9 @@ impl Plugin for Skip {
match arg { match arg {
Tagged { Tagged {
item: Value::Primitive(Primitive::Int(i)), 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( return Err(ShellError::labeled_error(

View File

@ -258,13 +258,13 @@ fn main() {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Action, ReplaceAction, Str}; use super::{Action, ReplaceAction, Str};
use indexmap::IndexMap; use indexmap::IndexMap;
use nu::{ use nu::{
CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, SourceMap, Span, Tag, Tagged, CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, SourceMap, Span, Tag, Tagged,
TaggedDictBuilder, TaggedItem, Value, TaggedDictBuilder, TaggedItem, Value,
}; };
use num_bigint::BigInt;
impl Str { impl Str {
fn replace_with(&mut self, value: &str) { fn replace_with(&mut self, value: &str) {
@ -600,7 +600,7 @@ mod tests {
ReturnSuccess::Value(Tagged { ReturnSuccess::Value(Tagged {
item: Value::Primitive(Primitive::Int(i)), 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> { fn sum(&mut self, value: Tagged<Value>) -> Result<(), ShellError> {
match value.item { match value.item() {
Value::Primitive(Primitive::Nothing) => Ok(()), Value::Primitive(Primitive::Nothing) => Ok(()),
Value::Primitive(Primitive::Int(i)) => { Value::Primitive(Primitive::Int(i)) => {
match self.total { match &self.total {
Some(Tagged { Some(Tagged {
item: Value::Primitive(Primitive::Int(j)), item: Value::Primitive(Primitive::Int(j)),
tag: Tag { span, .. }, tag: Tag { span, .. },
@ -26,7 +26,7 @@ impl Sum {
Ok(()) Ok(())
} }
None => { None => {
self.total = Some(value); self.total = Some(value.clone());
Ok(()) Ok(())
} }
_ => Err(ShellError::string(format!( _ => 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 { match futures::future::try_join(heim::cpu::logical_count(), heim::cpu::frequency()).await {
Ok((num_cpu, cpu_speed)) => { Ok((num_cpu, cpu_speed)) => {
let mut cpu_idx = TaggedDictBuilder::with_capacity(tag, 4); 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 = let current_speed =
(cpu_speed.current().get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0) (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::context::{Context, SpanSource};
pub(crate) use crate::env::host::handle_unexpected; pub(crate) use crate::env::host::handle_unexpected;
pub(crate) use crate::env::Host; 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::base as value;
pub(crate) use crate::object::meta::{Tag, Tagged, TaggedItem}; pub(crate) use crate::object::meta::{Tag, Tagged, TaggedItem};
pub(crate) use crate::object::types::ExtractType; 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::traits::{HasSpan, ToDebug};
pub(crate) use crate::Span; pub(crate) use crate::Span;
pub(crate) use crate::Text; pub(crate) use crate::Text;
pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::stream::BoxStream; pub(crate) use futures::stream::BoxStream;
pub(crate) use futures::{FutureExt, Stream, StreamExt}; pub(crate) use futures::{FutureExt, Stream, StreamExt};
pub(crate) use futures_async_stream::async_stream_block; 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 num_traits::cast::{FromPrimitive, ToPrimitive};
pub(crate) use rust_decimal::Decimal; pub(crate) use num_traits::identities::Zero;
#[allow(unused)] pub(crate) use serde::Deserialize;
pub(crate) use serde::{Deserialize, Serialize};
pub(crate) use std::collections::VecDeque; pub(crate) use std::collections::VecDeque;
pub(crate) use std::future::Future; pub(crate) use std::future::Future;
pub(crate) use std::sync::{Arc, Mutex}; pub(crate) use std::sync::{Arc, Mutex};