From 138b5af82bfa3fd62a2aacd93f059f42d5c8aa01 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 30 Aug 2019 10:29:04 -0700 Subject: [PATCH] Basic support for decimal numbers This commit is more substantial than it looks: there was basically no real support for decimals before, and that impacted values all the way through. I also made Size contain a decimal instead of an integer (`1.6kb` is a reasonable thing to type), which impacted a bunch of code. The biggest impact of this commit is that it creates many more possible ways for valid nu types to fail to serialize as toml, json, etc. which typically can't support the full range of Decimal (or Bigint, which I also think we should support). This commit makes to-toml fallible, and a similar effort is necessary for the rest of the serializations. We also need to figure out how to clearly communicate to users what has happened, but failing to serialize to toml seems clearly superior to me than weird errors in basic math operations. --- Cargo.lock | 50 ++++++++- Cargo.toml | 3 +- rust-toolchain | 2 +- src/commands/from_bson.rs | 3 +- src/commands/from_json.rs | 5 +- src/commands/from_sqlite.rs | 3 +- src/commands/from_toml.rs | 3 +- src/commands/from_yaml.rs | 3 +- src/commands/ps.rs | 2 +- src/commands/save.rs | 3 +- src/commands/to_bson.rs | 9 +- src/commands/to_csv.rs | 2 +- src/commands/to_json.rs | 15 +-- src/commands/to_sqlite.rs | 2 +- src/commands/to_toml.rs | 29 ++++-- src/commands/to_tsv.rs | 2 +- src/commands/to_yaml.rs | 6 +- src/commands/where_.rs | 8 +- src/errors.rs | 41 ++++++++ src/evaluate/evaluator.rs | 2 +- src/lib.rs | 2 +- src/object/base.rs | 129 +++++++++++++++--------- src/object/config.rs | 2 +- src/object/process.rs | 29 ++++++ src/parser/hir.rs | 20 ++-- src/parser/hir/baseline_parse.rs | 10 +- src/parser/hir/baseline_parse_tokens.rs | 2 +- src/parser/parse/parser.rs | 105 +++++++++++++++++-- src/parser/parse/token_tree_builder.rs | 21 +++- src/parser/parse/tokens.rs | 7 +- src/parser/parse/unit.rs | 5 +- src/plugins/sys.rs | 125 ++++++++++++++--------- src/prelude.rs | 3 + src/shell/helper.rs | 2 +- 34 files changed, 477 insertions(+), 178 deletions(-) create mode 100644 src/object/process.rs diff --git a/Cargo.lock b/Cargo.lock index b91214b5b..49dcb8aca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,8 +1614,8 @@ 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-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)", - "ordered-float 1.0.2 (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)", "pretty-hex 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1626,6 +1626,7 @@ 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.98 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1648,6 +1649,37 @@ 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" +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)", +] + [[package]] name = "num-derive" version = "0.2.5" @@ -1683,6 +1715,7 @@ 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)", ] @@ -1789,7 +1822,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2303,6 +2335,16 @@ 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.98 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.15" @@ -3319,6 +3361,9 @@ 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-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" "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" @@ -3393,6 +3438,7 @@ 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_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustyline 5.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f8ee0838a6594169a1c5f4bb9af0fe692cc99691941710a8cc6576395ede804e" diff --git a/Cargo.toml b/Cargo.toml index faa162f8c..d90bc665e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,14 +25,15 @@ dunce = "1.0.0" indexmap = { version = "1.0.2", features = ["serde-1"] } chrono-humanize = "0.0.11" byte-unit = "3.0.1" -ordered-float = {version = "1.0.2", features = ["serde"]} futures-preview = { version = "=0.3.0-alpha.18", features = ["compat", "io-compat"] } futures-async-stream = "=0.1.0-alpha.5" futures_codec = "0.2.5" +num-traits = "0.2.8" 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.98", features = ["derive"] } bson = "=0.13.0" serde_json = "1.0.40" diff --git a/rust-toolchain b/rust-toolchain index 9647abec0..e6ae9d224 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2019-08-22 +nightly-2019-08-30 diff --git a/src/commands/from_bson.rs b/src/commands/from_bson.rs index ff1fc267c..9edca220e 100644 --- a/src/commands/from_bson.rs +++ b/src/commands/from_bson.rs @@ -1,5 +1,4 @@ use crate::commands::WholeStreamCommand; -use crate::object::base::OF64; use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::prelude::*; use bson::{decode_document, spec::BinarySubtype, Bson}; @@ -28,7 +27,7 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into) -> Tagged Value::Primitive(Primitive::Float(OF64::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::Array(a) => Value::List( a.iter() diff --git a/src/commands/from_json.rs b/src/commands/from_json.rs index 52ec333f3..b4b1f7980 100644 --- a/src/commands/from_json.rs +++ b/src/commands/from_json.rs @@ -1,5 +1,4 @@ use crate::commands::WholeStreamCommand; -use crate::object::base::OF64; use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::prelude::*; @@ -34,9 +33,7 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into) - 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::Float(OF64::from(*n))).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::String(s) => { diff --git a/src/commands/from_sqlite.rs b/src/commands/from_sqlite.rs index d92b50f80..bc288ff39 100644 --- a/src/commands/from_sqlite.rs +++ b/src/commands/from_sqlite.rs @@ -1,6 +1,5 @@ use crate::commands::WholeStreamCommand; use crate::errors::ShellError; -use crate::object::base::OF64; use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::prelude::*; use rusqlite::{types::ValueRef, Connection, Row, NO_PARAMS}; @@ -94,7 +93,7 @@ fn convert_sqlite_value_to_nu_value(value: ValueRef, tag: impl Into + Clone match value { ValueRef::Null => Value::Primitive(Primitive::String(String::from(""))).tagged(tag), ValueRef::Integer(i) => Value::Primitive(Primitive::Int(i)).tagged(tag), - ValueRef::Real(f) => Value::Primitive(Primitive::Float(OF64::from(f))).tagged(tag), + ValueRef::Real(f) => Value::number(f).tagged(tag), t @ ValueRef::Text(_) => { // this unwrap is safe because we know the ValueRef is Text. Value::Primitive(Primitive::String(t.as_str().unwrap().to_string())).tagged(tag) diff --git a/src/commands/from_toml.rs b/src/commands/from_toml.rs index be9a8cc6c..6df13d686 100644 --- a/src/commands/from_toml.rs +++ b/src/commands/from_toml.rs @@ -1,5 +1,4 @@ use crate::commands::WholeStreamCommand; -use crate::object::base::OF64; use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::prelude::*; @@ -29,7 +28,7 @@ pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into) -> T 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::Float(OF64::from(*n))).tagged(tag), + toml::Value::Float(n) => Value::Primitive(Primitive::from(*n)).tagged(tag), toml::Value::String(s) => Value::Primitive(Primitive::String(String::from(s))).tagged(tag), toml::Value::Array(a) => Value::List( a.iter() diff --git a/src/commands/from_yaml.rs b/src/commands/from_yaml.rs index 2294a39ee..a5f229a03 100644 --- a/src/commands/from_yaml.rs +++ b/src/commands/from_yaml.rs @@ -1,5 +1,4 @@ use crate::commands::WholeStreamCommand; -use crate::object::base::OF64; use crate::object::{Primitive, TaggedDictBuilder, Value}; use crate::prelude::*; @@ -52,7 +51,7 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, tag: impl Into) -> Value::Primitive(Primitive::Int(n.as_i64().unwrap())).tagged(tag) } serde_yaml::Value::Number(n) if n.is_f64() => { - Value::Primitive(Primitive::Float(OF64::from(n.as_f64().unwrap()))).tagged(tag) + Value::Primitive(Primitive::from(n.as_f64().unwrap())).tagged(tag) } serde_yaml::Value::String(s) => Value::string(s).tagged(tag), serde_yaml::Value::Sequence(a) => Value::List( diff --git a/src/commands/ps.rs b/src/commands/ps.rs index 370b7b73d..15a19a857 100644 --- a/src/commands/ps.rs +++ b/src/commands/ps.rs @@ -62,7 +62,7 @@ fn ps(args: CommandArgs, registry: &CommandRegistry) -> Result() as f64)); + dict.insert("cpu", Value::number(usage.get::())); yield ReturnSuccess::value(dict.into_tagged_value()); } } diff --git a/src/commands/save.rs b/src/commands/save.rs index 11b245ef9..eee4774d2 100644 --- a/src/commands/save.rs +++ b/src/commands/save.rs @@ -1,5 +1,4 @@ -use crate::commands::UnevaluatedCallInfo; -use crate::commands::WholeStreamCommand; +use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand}; use crate::errors::ShellError; use crate::object::Value; use crate::prelude::*; diff --git a/src/commands/to_bson.rs b/src/commands/to_bson.rs index 60dc1cf2c..025484d8d 100644 --- a/src/commands/to_bson.rs +++ b/src/commands/to_bson.rs @@ -27,11 +27,16 @@ impl WholeStreamCommand for ToBSON { pub fn value_to_bson_value(v: &Value) -> Bson { match v { Value::Primitive(Primitive::Boolean(b)) => Bson::Boolean(*b), - Value::Primitive(Primitive::Bytes(b)) => Bson::I64(*b as i64), + // FIXME: What about really big decimals? + Value::Primitive(Primitive::Bytes(decimal)) => Bson::FloatingPoint( + (*decimal) + .to_f64() + .expect("Unimplemented BUG: What about big decimals?"), + ), Value::Primitive(Primitive::Date(d)) => Bson::UtcDatetime(*d), Value::Primitive(Primitive::EndOfStream) => Bson::Null, Value::Primitive(Primitive::BeginningOfStream) => Bson::Null, - Value::Primitive(Primitive::Float(f)) => Bson::FloatingPoint(f.into_inner()), + Value::Primitive(Primitive::Decimal(d)) => Bson::FloatingPoint(d.to_f64().unwrap()), Value::Primitive(Primitive::Int(i)) => Bson::I64(*i), Value::Primitive(Primitive::Nothing) => Bson::Null, Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()), diff --git a/src/commands/to_csv.rs b/src/commands/to_csv.rs index 7bfe6dd11..65651396a 100644 --- a/src/commands/to_csv.rs +++ b/src/commands/to_csv.rs @@ -45,7 +45,7 @@ pub fn value_to_csv_value(v: &Value) -> Value { fn to_string_helper(v: &Value) -> Result> { match v { Value::Primitive(Primitive::Date(d)) => Ok(d.to_string()), - Value::Primitive(Primitive::Bytes(b)) => Ok(format!("{}", *b as u64)), + Value::Primitive(Primitive::Bytes(b)) => Ok(format!("{}", b)), Value::Primitive(Primitive::Boolean(_)) => Ok(v.as_string()?), Value::List(_) => return Ok(String::from("[list list]")), Value::Object(_) => return Ok(String::from("[object]")), diff --git a/src/commands/to_json.rs b/src/commands/to_json.rs index d61ce909b..3559459cd 100644 --- a/src/commands/to_json.rs +++ b/src/commands/to_json.rs @@ -25,15 +25,18 @@ impl WholeStreamCommand for ToJSON { pub fn value_to_json_value(v: &Value) -> serde_json::Value { match v { Value::Primitive(Primitive::Boolean(b)) => serde_json::Value::Bool(*b), - Value::Primitive(Primitive::Bytes(b)) => { - serde_json::Value::Number(serde_json::Number::from(*b as u64)) - } + Value::Primitive(Primitive::Bytes(b)) => serde_json::Value::Number( + serde_json::Number::from(b.to_u64().expect("What about really big numbers")), + ), Value::Primitive(Primitive::Date(d)) => serde_json::Value::String(d.to_string()), Value::Primitive(Primitive::EndOfStream) => serde_json::Value::Null, Value::Primitive(Primitive::BeginningOfStream) => serde_json::Value::Null, - Value::Primitive(Primitive::Float(f)) => { - serde_json::Value::Number(serde_json::Number::from_f64(f.into_inner()).unwrap()) - } + Value::Primitive(Primitive::Decimal(f)) => serde_json::Value::Number( + serde_json::Number::from_f64( + f.to_f64().expect("TODO: What about really big decimals?"), + ) + .unwrap(), + ), Value::Primitive(Primitive::Int(i)) => { serde_json::Value::Number(serde_json::Number::from(*i)) } diff --git a/src/commands/to_sqlite.rs b/src/commands/to_sqlite.rs index 05a2e9c13..fcbf96e3b 100644 --- a/src/commands/to_sqlite.rs +++ b/src/commands/to_sqlite.rs @@ -73,7 +73,7 @@ fn nu_value_to_sqlite_string(v: Value) -> String { Value::Primitive(p) => match p { Primitive::Nothing => "NULL".into(), Primitive::Int(i) => format!("{}", i), - Primitive::Float(f) => format!("{}", f.into_inner()), + Primitive::Decimal(f) => format!("{}", f), Primitive::Bytes(u) => format!("{}", u), Primitive::String(s) => format!("'{}'", s.replace("'", "''")), Primitive::Boolean(true) => "1".into(), diff --git a/src/commands/to_toml.rs b/src/commands/to_toml.rs index 074a74a51..5d6080739 100644 --- a/src/commands/to_toml.rs +++ b/src/commands/to_toml.rs @@ -1,4 +1,5 @@ use crate::commands::WholeStreamCommand; +use crate::errors::ranged; use crate::object::{Primitive, Value}; use crate::prelude::*; @@ -22,10 +23,12 @@ impl WholeStreamCommand for ToTOML { } } -pub fn value_to_toml_value(v: &Value) -> toml::Value { - match v { +pub fn value_to_toml_value(v: &Value) -> Result { + Ok(match v { Value::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b), - Value::Primitive(Primitive::Bytes(b)) => toml::Value::Integer(*b as i64), + Value::Primitive(Primitive::Bytes(b)) => { + 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::EndOfStream) => { toml::Value::String("".to_string()) @@ -33,13 +36,15 @@ pub fn value_to_toml_value(v: &Value) -> toml::Value { Value::Primitive(Primitive::BeginningOfStream) => { toml::Value::String("".to_string()) } - Value::Primitive(Primitive::Float(f)) => toml::Value::Float(f.into_inner()), + Value::Primitive(Primitive::Decimal(f)) => { + toml::Value::Float(ranged(f.to_f64(), "f64", f.tagged_unknown())?) + } Value::Primitive(Primitive::Int(i)) => toml::Value::Integer(*i), Value::Primitive(Primitive::Nothing) => toml::Value::String("".to_string()), Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()), Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()), - Value::List(l) => toml::Value::Array(l.iter().map(|x| value_to_toml_value(x)).collect()), + Value::List(l) => toml::Value::Array(collect_values(l)?), Value::Block(_) => toml::Value::String("".to_string()), Value::Binary(b) => { toml::Value::Array(b.iter().map(|x| toml::Value::Integer(*x as i64)).collect()) @@ -47,11 +52,21 @@ pub fn value_to_toml_value(v: &Value) -> toml::Value { Value::Object(o) => { let mut m = toml::map::Map::new(); for (k, v) in o.entries.iter() { - m.insert(k.clone(), value_to_toml_value(v)); + m.insert(k.clone(), value_to_toml_value(v)?); } toml::Value::Table(m) } + }) +} + +fn collect_values(input: &Vec>) -> Result, ShellError> { + let mut out = vec![]; + + for value in input { + out.push(value_to_toml_value(value)?); } + + Ok(out) } fn to_toml(args: CommandArgs, registry: &CommandRegistry) -> Result { @@ -61,7 +76,7 @@ fn to_toml(args: CommandArgs, registry: &CommandRegistry) -> Result { return ReturnSuccess::value( Value::Primitive(Primitive::String(val)).simple_spanned(name_span), diff --git a/src/commands/to_tsv.rs b/src/commands/to_tsv.rs index 5a8cb1de4..b999ce12d 100644 --- a/src/commands/to_tsv.rs +++ b/src/commands/to_tsv.rs @@ -45,7 +45,7 @@ pub fn value_to_tsv_value(v: &Value) -> Value { fn to_string_helper(v: &Value) -> Result> { match v { Value::Primitive(Primitive::Date(d)) => Ok(d.to_string()), - Value::Primitive(Primitive::Bytes(b)) => Ok(format!("{}", *b as u64)), + Value::Primitive(Primitive::Bytes(b)) => Ok(format!("{}", b)), Value::Primitive(Primitive::Boolean(_)) => Ok(v.as_string()?), Value::List(_) => return Ok(String::from("[list list]")), Value::Object(_) => return Ok(String::from("[object]")), diff --git a/src/commands/to_yaml.rs b/src/commands/to_yaml.rs index e490954f2..f0aa0219e 100644 --- a/src/commands/to_yaml.rs +++ b/src/commands/to_yaml.rs @@ -26,13 +26,13 @@ pub fn value_to_yaml_value(v: &Value) -> serde_yaml::Value { match v { Value::Primitive(Primitive::Boolean(b)) => serde_yaml::Value::Bool(*b), Value::Primitive(Primitive::Bytes(b)) => { - serde_yaml::Value::Number(serde_yaml::Number::from(*b as u64)) + serde_yaml::Value::Number(serde_yaml::Number::from(b.to_f64().unwrap())) } Value::Primitive(Primitive::Date(d)) => serde_yaml::Value::String(d.to_string()), Value::Primitive(Primitive::EndOfStream) => serde_yaml::Value::Null, Value::Primitive(Primitive::BeginningOfStream) => serde_yaml::Value::Null, - Value::Primitive(Primitive::Float(f)) => { - serde_yaml::Value::Number(serde_yaml::Number::from(f.into_inner())) + 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)) diff --git a/src/commands/where_.rs b/src/commands/where_.rs index f1d9b2002..5f037ded4 100644 --- a/src/commands/where_.rs +++ b/src/commands/where_.rs @@ -27,7 +27,7 @@ impl PerItemCommand for Where { let stream = match condition { Tagged { item: Value::Block(block), - tag, + .. } => { let result = block.invoke(&input_clone); match result { @@ -39,11 +39,7 @@ impl PerItemCommand for Where { } } Err(e) => { - return Err(ShellError::labeled_error( - format!("Could not evaluate ({})", e.to_string()), - "could not evaluate", - tag.span, - )) + return Err(e) } } } diff --git a/src/errors.rs b/src/errors.rs index 0e2d1ff65..3bf0538be 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -84,6 +84,17 @@ impl ShellError { .start() } + pub(crate) fn range_error( + expected: impl Into, + actual: Tagged, + ) -> ShellError { + ProximateShellError::RangeError { + kind: expected.into(), + actual_kind: actual.map(|a| format!("{:?}", a)), + } + .start() + } + pub(crate) fn syntax_error(problem: Tagged>) -> ShellError { ProximateShellError::SyntaxError { problem: problem.map(|p| p.into()), @@ -242,6 +253,20 @@ impl ShellError { } => Diagnostic::new(Severity::Error, "Type Error") .with_label(Label::new_primary(span).with_message(expected)), + ProximateShellError::RangeError { + kind, + actual_kind: + Tagged { + item, + tag: Tag { span, .. }, + }, + } => 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 + )), + ), + ProximateShellError::SyntaxError { problem: Tagged { @@ -344,6 +369,10 @@ pub enum ProximateShellError { error: ArgumentError, span: Span, }, + RangeError { + kind: String, + actual_kind: Tagged, + }, Diagnostic(ShellDiagnostic), CoerceError { left: Tagged, @@ -423,6 +452,7 @@ impl std::fmt::Display for ShellError { ProximateShellError::MissingValue { .. } => write!(f, "MissingValue"), ProximateShellError::InvalidCommand { .. } => write!(f, "InvalidCommand"), ProximateShellError::TypeError { .. } => write!(f, "TypeError"), + ProximateShellError::RangeError { .. } => write!(f, "RangeError"), ProximateShellError::SyntaxError { .. } => write!(f, "SyntaxError"), ProximateShellError::MissingProperty { .. } => write!(f, "MissingProperty"), ProximateShellError::ArgumentError { .. } => write!(f, "ArgumentError"), @@ -526,3 +556,14 @@ impl ShellErrorUtils> for Option> { } } } + +pub fn ranged( + input: Option, + expected: impl Into, + actual: Tagged, +) -> Result { + match input { + Some(v) => Ok(v), + None => Err(ShellError::range_error(expected, actual)), + } +} diff --git a/src/evaluate/evaluator.rs b/src/evaluate/evaluator.rs index d03c39bc7..511625b36 100644 --- a/src/evaluate/evaluator.rs +++ b/src/evaluate/evaluator.rs @@ -106,7 +106,7 @@ pub(crate) fn evaluate_baseline_expr( fn evaluate_literal(literal: Tagged, source: &Text) -> Tagged { let result = match literal.item { - hir::Literal::Integer(int) => Value::int(int), + hir::Literal::Number(int) => int.into(), hir::Literal::Size(int, unit) => unit.compute(int), hir::Literal::String(span) => Value::string(span.slice(source)), hir::Literal::Bare => Value::string(literal.span().slice(source)), diff --git a/src/lib.rs b/src/lib.rs index b258e75ae..e1a068cde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,12 +25,12 @@ mod utils; pub use crate::commands::command::{CallInfo, ReturnSuccess, ReturnValue}; pub use crate::context::{SourceMap, SpanSource}; pub use crate::env::host::BasicHost; -pub use crate::object::base::OF64; 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 num_traits::cast::ToPrimitive; pub use object::base::{Primitive, Value}; pub use object::dict::{Dictionary, TaggedDictBuilder}; pub use object::meta::{Span, Tag, Tagged, TaggedItem}; diff --git a/src/object/base.rs b/src/object/base.rs index 08379240b..e3782326d 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -8,35 +8,16 @@ use crate::Text; use chrono::{DateTime, Utc}; use chrono_humanize::Humanize; use derive_new::new; -use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; use std::fmt; use std::path::PathBuf; use std::time::SystemTime; -#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, new, Serialize, Deserialize)] -pub struct OF64 { - pub(crate) inner: OrderedFloat, -} - -impl OF64 { - pub(crate) fn into_inner(&self) -> f64 { - self.inner.into_inner() - } -} - -impl From for OF64 { - fn from(float: f64) -> Self { - OF64::new(OrderedFloat(float)) - } -} - #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] pub enum Primitive { Nothing, Int(i64), - #[allow(unused)] - Float(OF64), + Decimal(Decimal), Bytes(u64), String(String), Boolean(bool), @@ -48,6 +29,24 @@ pub enum Primitive { EndOfStream, } +impl From for Primitive { + fn from(int: i64) -> Primitive { + Primitive::Int(int) + } +} + +impl From for Primitive { + fn from(decimal: Decimal) -> Primitive { + Primitive::Decimal(decimal) + } +} + +impl From for Primitive { + fn from(float: f64) -> Primitive { + Primitive::Decimal(Decimal::from_f64(float).unwrap()) + } +} + impl Primitive { pub(crate) fn type_name(&self) -> String { use Primitive::*; @@ -58,7 +57,7 @@ impl Primitive { EndOfStream => "end-of-stream", Path(_) => "path", Int(_) => "int", - Float(_) => "float", + Decimal(_) => "decimal", Bytes(_) => "bytes", String(_) => "string", Boolean(_) => "boolean", @@ -76,7 +75,7 @@ impl Primitive { EndOfStream => write!(f, "EndOfStream"), Int(int) => write!(f, "{}", int), Path(path) => write!(f, "{}", path.display()), - Float(float) => write!(f, "{:?}", float), + Decimal(decimal) => write!(f, "{}", decimal), Bytes(bytes) => write!(f, "{}", bytes), String(string) => write!(f, "{:?}", string), Boolean(boolean) => write!(f, "{}", boolean), @@ -84,6 +83,15 @@ impl Primitive { } } + pub fn number(number: impl Into) -> Primitive { + let number = number.into(); + + match number { + Number::Int(int) => Primitive::Int(int), + Number::Decimal(decimal) => Primitive::Decimal(decimal), + } + } + pub fn format(&self, field_name: Option<&String>) -> String { match self { Primitive::Nothing => String::new(), @@ -105,7 +113,7 @@ impl Primitive { } } Primitive::Int(i) => format!("{}", i), - Primitive::Float(OF64 { inner: f }) => format!("{:.*}", 2, f.into_inner()), + Primitive::Decimal(decimal) => format!("{}", decimal), Primitive::String(s) => format!("{}", s), Primitive::Boolean(b) => match (b, field_name) { (true, None) => format!("Yes"), @@ -122,7 +130,7 @@ impl Primitive { pub fn style(&self) -> &'static str { match self { Primitive::Bytes(0) => "c", // centre 'missing' indicator - Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Float(_) => "r", + Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Decimal(_) => "r", _ => "", } } @@ -176,6 +184,15 @@ pub enum Value { Block(Block), } +impl Into for Number { + fn into(self) -> Value { + match self { + Number::Int(int) => Value::int(int), + Number::Decimal(decimal) => Value::decimal(decimal), + } + } +} + pub fn debug_list(values: &Vec>) -> ValuesDebug<'_> { ValuesDebug { values } } @@ -518,7 +535,11 @@ impl Value { } #[allow(unused)] - pub(crate) fn compare(&self, operator: &Operator, other: &Value) -> Result { + pub(crate) fn compare( + &self, + operator: &Operator, + other: &Value, + ) -> Result { match operator { _ => { let coerced = coerce_compare(self, other)?; @@ -566,7 +587,7 @@ impl Value { match self { Value::Primitive(Primitive::String(s)) => Ok(s.clone()), Value::Primitive(Primitive::Boolean(x)) => Ok(format!("{}", x)), - Value::Primitive(Primitive::Float(x)) => Ok(format!("{}", x.into_inner())), + Value::Primitive(Primitive::Decimal(x)) => Ok(format!("{}", x)), Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)), Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)), // TODO: this should definitely be more general with better errors @@ -612,8 +633,17 @@ impl Value { Value::Primitive(Primitive::Int(s.into())) } - pub fn float(s: impl Into) -> Value { - Value::Primitive(Primitive::Float(s.into())) + pub fn decimal(s: impl Into) -> Value { + Value::Primitive(Primitive::Decimal(s.into())) + } + + pub fn number(s: impl Into) -> Value { + let num = s.into(); + + match num { + Number::Int(int) => Value::int(int), + Number::Decimal(decimal) => Value::decimal(decimal), + } } pub fn boolean(s: impl Into) -> Value { @@ -722,32 +752,34 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool (Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => i != *i2, _ => false, }, - Value::Primitive(Primitive::Float(i)) => match (op, rhs) { - (Operator::LessThan, Value::Primitive(Primitive::Float(i2))) => i < *i2, - (Operator::GreaterThan, Value::Primitive(Primitive::Float(i2))) => i > *i2, - (Operator::LessThanOrEqual, Value::Primitive(Primitive::Float(i2))) => i <= *i2, - (Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Float(i2))) => { + Value::Primitive(Primitive::Decimal(i)) => match (op, rhs) { + (Operator::LessThan, Value::Primitive(Primitive::Decimal(i2))) => i < *i2, + (Operator::GreaterThan, Value::Primitive(Primitive::Decimal(i2))) => i > *i2, + (Operator::LessThanOrEqual, Value::Primitive(Primitive::Decimal(i2))) => { + i <= *i2 + } + (Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Decimal(i2))) => { i >= *i2 } - (Operator::Equal, Value::Primitive(Primitive::Float(i2))) => i == *i2, - (Operator::NotEqual, Value::Primitive(Primitive::Float(i2))) => i != *i2, + (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.into_inner()) < *i2 as f64 + i < Decimal::from(*i2) } (Operator::GreaterThan, Value::Primitive(Primitive::Int(i2))) => { - i.into_inner() > *i2 as f64 + i > Decimal::from(*i2) } (Operator::LessThanOrEqual, Value::Primitive(Primitive::Int(i2))) => { - i.into_inner() <= *i2 as f64 + i <= Decimal::from(*i2) } (Operator::GreaterThanOrEqual, Value::Primitive(Primitive::Int(i2))) => { - i.into_inner() >= *i2 as f64 + i >= Decimal::from(*i2) } (Operator::Equal, Value::Primitive(Primitive::Int(i2))) => { - i.into_inner() == *i2 as f64 + i == Decimal::from(*i2) } (Operator::NotEqual, Value::Primitive(Primitive::Int(i2))) => { - i.into_inner() != *i2 as f64 + i != Decimal::from(*i2) } _ => false, @@ -765,8 +797,8 @@ pub(crate) fn find(obj: &Value, field: &str, op: &Operator, rhs: &Value) -> bool enum CompareValues { Ints(i64, i64), - Floats(OF64, OF64), - Bytes(i128, i128), + Decimals(Decimal, Decimal), + Bytes(u64, u64), String(String, String), } @@ -774,7 +806,7 @@ impl CompareValues { fn compare(&self) -> std::cmp::Ordering { match self { CompareValues::Ints(left, right) => left.cmp(right), - CompareValues::Floats(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), } @@ -797,10 +829,11 @@ fn coerce_compare_primitive( Ok(match (left, right) { (Int(left), Int(right)) => CompareValues::Ints(*left, *right), - (Float(left), Int(right)) => CompareValues::Floats(*left, (*right as f64).into()), - (Int(left), Float(right)) => CompareValues::Floats((*left as f64).into(), *right), - (Int(left), Bytes(right)) => CompareValues::Bytes(*left as i128, *right as i128), - (Bytes(left), Int(right)) => CompareValues::Bytes(*left as i128, *right as i128), + (Decimal(left), Decimal(right)) => CompareValues::Decimals(*left, *right), + (Decimal(left), Int(right)) => CompareValues::Decimals(*left, (*right).into()), + (Int(left), Decimal(right)) => CompareValues::Decimals((*left).into(), *right), + (Int(left), Bytes(right)) => CompareValues::Bytes(*left as u64, *right), + (Bytes(left), Int(right)) => CompareValues::Bytes(*left, *right as u64), (String(left), String(right)) => CompareValues::String(left.clone(), right.clone()), _ => return Err((left.type_name(), right.type_name())), }) diff --git a/src/object/config.rs b/src/object/config.rs index d4793d24d..fae3155ae 100644 --- a/src/object/config.rs +++ b/src/object/config.rs @@ -36,7 +36,7 @@ pub(crate) fn write_config(config: &IndexMap>) -> 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())))?; let contents = toml::to_string(&contents)?; diff --git a/src/object/process.rs b/src/object/process.rs new file mode 100644 index 000000000..337f731b5 --- /dev/null +++ b/src/object/process.rs @@ -0,0 +1,29 @@ +use crate::object::{TaggedDictBuilder, Value}; +use crate::prelude::*; +use itertools::join; +use sysinfo::ProcessExt; + +pub(crate) fn process_dict(proc: &sysinfo::Process, tag: impl Into) -> Tagged { + let mut dict = TaggedDictBuilder::new(tag); + + let cmd = proc.cmd(); + + let cmd_value = if cmd.len() == 0 { + Value::nothing() + } else { + Value::string(join(cmd, "")) + }; + + dict.insert("pid", Value::int(proc.pid() as i64)); + dict.insert("status", Value::string(proc.status().to_string())); + dict.insert("cpu", Value::number(proc.cpu_usage())); + + match cmd_value { + Value::Primitive(Primitive::Nothing) => { + dict.insert("name", Value::string(proc.name())); + } + _ => dict.insert("name", cmd_value), + } + + dict.into_tagged_value() +} diff --git a/src/parser/hir.rs b/src/parser/hir.rs index 5421d39cd..3f0ab2ce4 100644 --- a/src/parser/hir.rs +++ b/src/parser/hir.rs @@ -129,11 +129,15 @@ impl RawExpression { pub type Expression = Tagged; impl Expression { - pub(crate) fn int(i: impl Into, span: impl Into) -> Expression { - Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Integer(i.into())), span) + pub(crate) fn number(i: impl Into, span: impl Into) -> Expression { + Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Number(i.into())), span) } - pub(crate) fn size(i: impl Into, unit: impl Into, span: impl Into) -> Expression { + pub(crate) fn size( + i: impl Into, + unit: impl Into, + span: impl Into, + ) -> Expression { Tagged::from_simple_spanned_item( RawExpression::Literal(Literal::Size(i.into(), unit.into())), span, @@ -224,8 +228,8 @@ impl From> for Expression { #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum Literal { - Integer(i64), - Size(i64, Unit), + Number(Number), + Size(Number, Unit), String(Span), Bare, } @@ -233,8 +237,8 @@ pub enum Literal { impl ToDebug for Tagged<&Literal> { fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { match self.item() { - Literal::Integer(int) => write!(f, "{}", *int), - Literal::Size(int, unit) => write!(f, "{}{:?}", *int, unit), + Literal::Number(number) => write!(f, "{:?}", *number), + Literal::Size(number, unit) => write!(f, "{:?}{:?}", *number, unit), Literal::String(span) => write!(f, "{}", span.slice(source)), Literal::Bare => write!(f, "{}", self.span().slice(source)), } @@ -244,7 +248,7 @@ impl ToDebug for Tagged<&Literal> { impl Literal { fn type_name(&self) -> &'static str { match self { - Literal::Integer(_) => "integer", + Literal::Number(..) => "number", Literal::Size(..) => "size", Literal::String(..) => "string", Literal::Bare => "string", diff --git a/src/parser/hir/baseline_parse.rs b/src/parser/hir/baseline_parse.rs index a894f2bd7..5b4e3878e 100644 --- a/src/parser/hir/baseline_parse.rs +++ b/src/parser/hir/baseline_parse.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression { match *token.item() { - RawToken::Integer(int) => hir::Expression::int(int, token.span()), + RawToken::Number(number) => hir::Expression::number(number, token.span()), RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span()), RawToken::String(span) => hir::Expression::string(span, token.span()), RawToken::Variable(span) if span.slice(source) == "it" => { @@ -24,8 +24,8 @@ 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::Integer(int) => hir::Expression::int(int, token.span()), - RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span()), + RawToken::Number(number) => hir::Expression::number(number, token.span()), + RawToken::Size(number, unit) => hir::Expression::size(number, unit, token.span()), RawToken::Bare => hir::Expression::bare(token.span()), RawToken::String(span) => hir::Expression::string(span, token.span()), } @@ -38,7 +38,7 @@ pub fn baseline_parse_token_as_string(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::Integer(_) => hir::Expression::bare(token.span()), + RawToken::Number(_) => hir::Expression::bare(token.span()), RawToken::Size(_, _) => hir::Expression::bare(token.span()), RawToken::Bare => hir::Expression::bare(token.span()), RawToken::String(span) => hir::Expression::string(span, token.span()), @@ -56,7 +56,7 @@ pub fn baseline_parse_token_as_path( } RawToken::External(span) => hir::Expression::external_command(span, token.span()), RawToken::Variable(span) => hir::Expression::variable(span, token.span()), - RawToken::Integer(_) => hir::Expression::bare(token.span()), + RawToken::Number(_) => hir::Expression::bare(token.span()), RawToken::Size(_, _) => hir::Expression::bare(token.span()), RawToken::Bare => hir::Expression::file_path( expand_path(token.span().slice(source), context), diff --git a/src/parser/hir/baseline_parse_tokens.rs b/src/parser/hir/baseline_parse_tokens.rs index be056b5b5..a1119bc76 100644 --- a/src/parser/hir/baseline_parse_tokens.rs +++ b/src/parser/hir/baseline_parse_tokens.rs @@ -294,7 +294,7 @@ pub fn baseline_parse_path( TokenNode::Token(token) => match token.item() { RawToken::Bare => token.span().slice(source), RawToken::String(span) => span.slice(source), - RawToken::Integer(_) + RawToken::Number(_) | RawToken::Size(..) | RawToken::Variable(_) | RawToken::External(_) => { diff --git a/src/parser/parse/parser.rs b/src/parser/parse/parser.rs index 94ff2976e..2597ded60 100644 --- a/src/parser/parse/parser.rs +++ b/src/parser/parse/parser.rs @@ -4,6 +4,7 @@ use crate::parser::parse::{ call_node::*, flag::*, operator::*, pipeline::*, token_tree::*, token_tree_builder::*, tokens::*, unit::*, }; +use crate::prelude::*; use crate::{Span, Tagged}; use nom; use nom::branch::*; @@ -18,6 +19,7 @@ use nom::dbg; use nom::*; use nom::{AsBytes, FindSubstring, IResult, InputLength, InputTake, Slice}; use nom5_locate::{position, LocatedSpan}; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::str::FromStr; @@ -68,16 +70,94 @@ fn trace_step<'a, T: Debug>( } } -pub fn raw_integer(input: NomSpan) -> IResult> { +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] +pub enum Number { + Int(i64), + Decimal(Decimal), +} + +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::Decimal(a), Number::Decimal(b)) => Number::Decimal(a * b), + } + } +} + +// For literals +impl std::ops::Mul for Number { + type Output = 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)), + } + } +} + +impl Into for f32 { + fn into(self) -> Number { + Number::Decimal(Decimal::from_f32(self).unwrap()) + } +} + +impl Into for f64 { + fn into(self) -> Number { + Number::Decimal(Decimal::from_f64(self).unwrap()) + } +} + +impl Into for i64 { + fn into(self) -> Number { + Number::Int(self) + } +} + +impl Into for Decimal { + fn into(self) -> Number { + Number::Decimal(self) + } +} + +pub fn raw_number(input: NomSpan) -> IResult> { + let original = input; let start = input.offset; - trace_step(input, "raw_integer", move |input| { + trace_step(input, "raw_decimal", move |input| { let (input, neg) = opt(tag("-"))(input)?; - let (input, num) = digit1(input)?; + let (input, head) = digit1(input)?; + let dot: IResult = tag(".")(input); + + let input = match dot { + 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), + ), + )) + } + }; + + 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(int(num.fragment, neg), (start, end)), + Tagged::from_simple_spanned_item(Number::Decimal(decimal), (start, end)), )) }) } @@ -245,7 +325,7 @@ pub fn size(input: NomSpan) -> IResult { trace_step(input, "size", move |input| { let mut is_size = false; let start = input.offset; - let (input, int) = raw_integer(input)?; + let (input, number) = raw_number(input)?; if let Ok((input, Some(size))) = opt(raw_unit)(input) { let end = input.offset; @@ -256,7 +336,7 @@ pub fn size(input: NomSpan) -> IResult { Ok(( input, - TokenTreeBuilder::spanned_size((*int, *size), (start, end)), + TokenTreeBuilder::spanned_size((number.item, *size), (start, end)), )) } else { let end = input.offset; @@ -266,7 +346,10 @@ pub fn size(input: NomSpan) -> IResult { return Err(nom::Err::Error((input, nom::error::ErrorKind::Char))); } - Ok((input, TokenTreeBuilder::spanned_int((*int), (start, end)))) + Ok(( + input, + TokenTreeBuilder::spanned_number(number.item, number.tag), + )) } }) } @@ -625,12 +708,12 @@ mod tests { fn test_integer() { assert_leaf! { parsers [ size ] - "123" -> 0..3 { Integer(123) } + "123" -> 0..3 { Number(Number::Int(123)) } } assert_leaf! { parsers [ size ] - "-123" -> 0..4 { Integer(-123) } + "-123" -> 0..4 { Number(Number::Int(-123)) } } } @@ -638,12 +721,12 @@ mod tests { fn test_size() { assert_leaf! { parsers [ size ] - "123MB" -> 0..5 { Size(123, Unit::MB) } + "123MB" -> 0..5 { Size(Number::Int(123), Unit::MB) } } assert_leaf! { parsers [ size ] - "10GB" -> 0..4 { Size(10, Unit::GB) } + "10GB" -> 0..4 { Size(Number::Int(10), Unit::GB) } } } diff --git a/src/parser/parse/token_tree_builder.rs b/src/parser/parse/token_tree_builder.rs index a30b3d74d..19a7f07a4 100644 --- a/src/parser/parse/token_tree_builder.rs +++ b/src/parser/parse/token_tree_builder.rs @@ -3,6 +3,7 @@ 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}; @@ -170,9 +171,23 @@ impl TokenTreeBuilder { }) } - pub fn spanned_int(input: impl Into, span: impl Into) -> TokenNode { + pub fn spanned_number(input: impl Into, span: impl Into) -> TokenNode { + match input.into() { + Number::Int(int) => TokenTreeBuilder::spanned_int(int, span), + Number::Decimal(decimal) => TokenTreeBuilder::spanned_decimal(decimal, span), + } + } + + fn spanned_int(input: i64, span: impl Into) -> TokenNode { TokenNode::Token(Token::from_simple_spanned_item( - RawToken::Integer(input.into()), + RawToken::Number(Number::Int(input)), + span, + )) + } + + fn spanned_decimal(input: Decimal, span: impl Into) -> TokenNode { + TokenNode::Token(Token::from_simple_spanned_item( + RawToken::Number(Number::Decimal(input)), span, )) } @@ -191,7 +206,7 @@ impl TokenTreeBuilder { } pub fn spanned_size( - input: (impl Into, impl Into), + input: (impl Into, impl Into), span: impl Into, ) -> TokenNode { let (int, unit) = (input.0.into(), input.1.into()); diff --git a/src/parser/parse/tokens.rs b/src/parser/parse/tokens.rs index 717f5845e..79f08630f 100644 --- a/src/parser/parse/tokens.rs +++ b/src/parser/parse/tokens.rs @@ -1,11 +1,12 @@ use crate::parser::parse::unit::*; +use crate::prelude::*; use crate::{Span, Tagged, Text}; use std::fmt; #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum RawToken { - Integer(i64), - Size(i64, Unit), + Number(Number), + Size(Number, Unit), String(Span), Variable(Span), External(Span), @@ -15,7 +16,7 @@ pub enum RawToken { impl RawToken { pub fn type_name(&self) -> &'static str { match self { - RawToken::Integer(_) => "Integer", + RawToken::Number(_) => "Number", RawToken::Size(..) => "Size", RawToken::String(_) => "String", RawToken::Variable(_) => "Variable", diff --git a/src/parser/parse/unit.rs b/src/parser/parse/unit.rs index 54465fc5e..47129ccf2 100644 --- a/src/parser/parse/unit.rs +++ b/src/parser/parse/unit.rs @@ -1,4 +1,5 @@ use crate::object::base::Value; +use crate::prelude::*; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -24,8 +25,8 @@ impl Unit { } } - pub(crate) fn compute(&self, size: i64) -> Value { - Value::int(match self { + pub(crate) fn compute(&self, size: Number) -> Value { + Value::number(match self { Unit::B => size, Unit::KB => size * 1024, Unit::MB => size * 1024 * 1024, diff --git a/src/plugins/sys.rs b/src/plugins/sys.rs index d92f0b377..74d87befe 100644 --- a/src/plugins/sys.rs +++ b/src/plugins/sys.rs @@ -2,12 +2,12 @@ use std::ffi::OsStr; use futures::executor::block_on; use futures::stream::StreamExt; +use heim::units::{frequency, information, thermodynamic_temperature, time}; +use heim::{disk, host, memory, net, sensors}; use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, Tag, Tagged, TaggedDictBuilder, Value, }; -use heim::{disk, memory, net, sensors, host}; -use heim::units::{frequency, information, time, thermodynamic_temperature}; struct Sys; impl Sys { @@ -17,30 +17,33 @@ impl Sys { } async fn cpu(tag: Tag) -> Option> { - 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)) => { let mut cpu_idx = TaggedDictBuilder::with_capacity(tag, 4); cpu_idx.insert("cores", Primitive::Int(num_cpu as i64)); let current_speed = - (cpu_speed.current().get::() as f64 / 1_000_000_000.0 * 100.0).round() / 100.0; - cpu_idx.insert("current ghz", Primitive::Float(current_speed.into())); + (cpu_speed.current().get::() as f64 / 1_000_000_000.0 * 100.0) + .round() + / 100.0; + cpu_idx.insert("current ghz", Primitive::number(current_speed)); if let Some(min_speed) = cpu_speed.min() { - let min_speed = (min_speed.get::() as f64 / 1_000_000_000.0 * 100.0).round() / 100.0; - cpu_idx.insert("min ghz", Primitive::Float(min_speed.into())); + let min_speed = + (min_speed.get::() as f64 / 1_000_000_000.0 * 100.0).round() + / 100.0; + cpu_idx.insert("min ghz", Primitive::number(min_speed)); } if let Some(max_speed) = cpu_speed.max() { - let max_speed = (max_speed.get::() as f64 / 1_000_000_000.0 * 100.0).round() / 100.0; - cpu_idx.insert("max ghz", Primitive::Float(max_speed.into())); + let max_speed = + (max_speed.get::() as f64 / 1_000_000_000.0 * 100.0).round() + / 100.0; + cpu_idx.insert("max ghz", Primitive::number(max_speed)); } Some(cpu_idx.into_tagged_value()) - }, + } Err(_) => None, } } @@ -48,19 +51,29 @@ async fn cpu(tag: Tag) -> Option> { async fn mem(tag: Tag) -> Tagged { let mut dict = TaggedDictBuilder::with_capacity(tag, 4); - let (memory_result, swap_result) = futures::future::join( - memory::memory(), - memory::swap() - ).await; + let (memory_result, swap_result) = + futures::future::join(memory::memory(), memory::swap()).await; if let Ok(memory) = memory_result { - dict.insert("total", Value::bytes(memory.total().get::())); - dict.insert("free", Value::bytes(memory.free().get::())); + dict.insert( + "total", + Value::bytes(memory.total().get::()), + ); + dict.insert( + "free", + Value::bytes(memory.free().get::()), + ); } if let Ok(swap) = swap_result { - dict.insert("swap total", Value::bytes(swap.total().get::())); - dict.insert("swap free", Value::bytes(swap.free().get::())); + dict.insert( + "swap total", + Value::bytes(swap.total().get::()), + ); + dict.insert( + "swap free", + Value::bytes(swap.free().get::()), + ); } dict.into_tagged_value() @@ -69,10 +82,8 @@ async fn mem(tag: Tag) -> Tagged { async fn host(tag: Tag) -> Tagged { let mut dict = TaggedDictBuilder::with_capacity(tag, 6); - let (platform_result, uptime_result) = futures::future::join( - host::platform(), - host::uptime(), - ).await; + let (platform_result, uptime_result) = + futures::future::join(host::platform(), host::uptime()).await; // OS if let Ok(platform) = platform_result { @@ -133,9 +144,18 @@ async fn disks(tag: Tag) -> Option { dict.insert("mount", Value::string(part.mount_point().to_string_lossy())); if let Ok(usage) = disk::usage(part.mount_point().to_path_buf()).await { - dict.insert("total", Value::bytes(usage.total().get::())); - dict.insert("used", Value::bytes(usage.used().get::())); - dict.insert("free", Value::bytes(usage.free().get::())); + dict.insert( + "total", + Value::bytes(usage.total().get::()), + ); + dict.insert( + "used", + Value::bytes(usage.used().get::()), + ); + dict.insert( + "free", + Value::bytes(usage.free().get::()), + ); } output.push(dict.into_tagged_value()); @@ -169,13 +189,13 @@ async fn battery(tag: Tag) -> Option { if let Some(time_to_full) = battery.time_to_full() { dict.insert( "mins to full", - Value::float(time_to_full.get::() as f64), + Value::number(time_to_full.get::()), ); } if let Some(time_to_empty) = battery.time_to_empty() { dict.insert( "mins to empty", - Value::float(time_to_empty.get::() as f64), + Value::number(time_to_empty.get::()), ); } output.push(dict.into_tagged_value()); @@ -202,12 +222,25 @@ async fn temp(tag: Tag) -> Option { if let Some(label) = sensor.label() { dict.insert("label", Value::string(label)); } - dict.insert("temp", Value::float(sensor.current().get::() as f64)); + dict.insert( + "temp", + Value::number( + sensor + .current() + .get::(), + ), + ); if let Some(high) = sensor.high() { - dict.insert("high", Value::float(high.get::() as f64)); + dict.insert( + "high", + Value::number(high.get::()), + ); } if let Some(critical) = sensor.critical() { - dict.insert("critical", Value::float(critical.get::() as f64)); + dict.insert( + "critical", + Value::number(critical.get::()), + ); } output.push(dict.into_tagged_value()); @@ -228,8 +261,14 @@ async fn net(tag: Tag) -> Option { if let Ok(nic) = nic { let mut network_idx = TaggedDictBuilder::with_capacity(tag, 3); network_idx.insert("name", Value::string(nic.interface())); - network_idx.insert("sent", Value::bytes(nic.bytes_sent().get::())); - network_idx.insert("recv", Value::bytes(nic.bytes_recv().get::())); + network_idx.insert( + "sent", + Value::bytes(nic.bytes_sent().get::()), + ); + network_idx.insert( + "recv", + Value::bytes(nic.bytes_recv().get::()), + ); output.push(network_idx.into_tagged_value()); } } @@ -242,18 +281,10 @@ async fn net(tag: Tag) -> Option { async fn sysinfo(tag: Tag) -> Vec> { let mut sysinfo = TaggedDictBuilder::with_capacity(tag, 7); - - let (host, cpu, disks, memory, temp) = futures::future::join5( - host(tag), - cpu(tag), - disks(tag), - mem(tag), - temp(tag), - ).await; - let (net, battery) = futures::future::join( - net(tag), - battery(tag), - ).await; + + let (host, cpu, disks, memory, temp) = + futures::future::join5(host(tag), cpu(tag), disks(tag), mem(tag), temp(tag)).await; + let (net, battery) = futures::future::join(net(tag), battery(tag)).await; sysinfo.insert_tagged("host", host); if let Some(cpu) = cpu { diff --git a/src/prelude.rs b/src/prelude.rs index 2e4a9d346..8e511f82c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -63,6 +63,7 @@ pub(crate) use crate::object::meta::{Tag, Tagged, TaggedItem}; pub(crate) use crate::object::types::ExtractType; pub(crate) use crate::object::{Primitive, Value}; pub(crate) use crate::parser::hir::SyntaxType; +pub(crate) use crate::parser::parse::parser::Number; pub(crate) use crate::parser::registry::Signature; pub(crate) use crate::shell::filesystem_shell::FilesystemShell; pub(crate) use crate::shell::shell_manager::ShellManager; @@ -74,6 +75,8 @@ pub(crate) use crate::Text; 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_traits::cast::{FromPrimitive, ToPrimitive}; +pub(crate) use rust_decimal::Decimal; #[allow(unused)] pub(crate) use serde::{Deserialize, Serialize}; pub(crate) use std::collections::VecDeque; diff --git a/src/shell/helper.rs b/src/shell/helper.rs index 039a4b2bc..9feffcb4c 100644 --- a/src/shell/helper.rs +++ b/src/shell/helper.rs @@ -116,7 +116,7 @@ fn paint_token_node(token_node: &TokenNode, line: &str) -> String { TokenNode::Operator(..) => Color::White.normal().paint(token_node.span().slice(line)), TokenNode::Pipeline(..) => Color::Blue.normal().paint(token_node.span().slice(line)), TokenNode::Token(Tagged { - item: RawToken::Integer(..), + item: RawToken::Number(..), .. }) => Color::Purple.bold().paint(token_node.span().slice(line)), TokenNode::Token(Tagged {