mirror of
https://github.com/nushell/nushell.git
synced 2024-11-28 19:33:47 +01:00
Math operators (#1601)
* Add some math operations * WIP for adding compound expressions * precedence parsing * paren expressions * better lhs handling * add compound comparisons and shorthand lefthand parsing * Add or comparison and shorthand paths
This commit is contained in:
parent
52d2d2b888
commit
7974e09eeb
@ -710,6 +710,7 @@ async fn process_line(
|
|||||||
|
|
||||||
let pipeline = nu_parser::classify_pipeline(&result, ctx.registry());
|
let pipeline = nu_parser::classify_pipeline(&result, ctx.registry());
|
||||||
|
|
||||||
|
debug!("{:#?}", pipeline);
|
||||||
//println!("{:#?}", pipeline);
|
//println!("{:#?}", pipeline);
|
||||||
|
|
||||||
if let Some(failure) = pipeline.failed {
|
if let Some(failure) = pipeline.failed {
|
||||||
|
@ -156,6 +156,12 @@ pub fn autoview(context: RunnableContext) -> Result<OutputStream, ShellError> {
|
|||||||
} => {
|
} => {
|
||||||
out!("{}", n);
|
out!("{}", n);
|
||||||
}
|
}
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
out!("{}", b);
|
||||||
|
}
|
||||||
|
|
||||||
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
|
Value { value: UntaggedValue::Primitive(Primitive::Binary(ref b)), .. } => {
|
||||||
if let Some(binary) = binary {
|
if let Some(binary) = binary {
|
||||||
|
@ -16,7 +16,7 @@ impl WholeStreamCommand for SkipWhile {
|
|||||||
Signature::build("skip-while")
|
Signature::build("skip-while")
|
||||||
.required(
|
.required(
|
||||||
"condition",
|
"condition",
|
||||||
SyntaxShape::Condition,
|
SyntaxShape::Math,
|
||||||
"the condition that must be met to continue skipping",
|
"the condition that must be met to continue skipping",
|
||||||
)
|
)
|
||||||
.filter()
|
.filter()
|
||||||
|
@ -18,7 +18,7 @@ impl PerItemCommand for Where {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("where").required(
|
Signature::build("where").required(
|
||||||
"condition",
|
"condition",
|
||||||
SyntaxShape::Condition,
|
SyntaxShape::Math,
|
||||||
"the condition that must match",
|
"the condition that must match",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ use std::time::SystemTime;
|
|||||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
|
||||||
pub struct Operation {
|
pub struct Operation {
|
||||||
pub(crate) left: Value,
|
pub(crate) left: Value,
|
||||||
pub(crate) operator: hir::CompareOperator,
|
pub(crate) operator: hir::Operator,
|
||||||
pub(crate) right: Value,
|
pub(crate) right: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ pub(crate) enum CompareValues {
|
|||||||
Decimals(BigDecimal, BigDecimal),
|
Decimals(BigDecimal, BigDecimal),
|
||||||
String(String, String),
|
String(String, String),
|
||||||
Date(DateTime<Utc>, DateTime<Utc>),
|
Date(DateTime<Utc>, DateTime<Utc>),
|
||||||
DateDuration(DateTime<Utc>, u64),
|
DateDuration(DateTime<Utc>, i64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompareValues {
|
impl CompareValues {
|
||||||
@ -101,7 +101,11 @@ impl CompareValues {
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// Create the datetime we're comparing against, as duration is an offset from now
|
// Create the datetime we're comparing against, as duration is an offset from now
|
||||||
let right: DateTime<Utc> = (SystemTime::now() - Duration::from_secs(*right)).into();
|
let right: DateTime<Utc> = if *right < 0 {
|
||||||
|
(SystemTime::now() + Duration::from_secs((*right * -1) as u64)).into()
|
||||||
|
} else {
|
||||||
|
(SystemTime::now() - Duration::from_secs(*right as u64)).into()
|
||||||
|
};
|
||||||
right.cmp(left)
|
right.cmp(left)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ pub enum InlineShape {
|
|||||||
Pattern(String),
|
Pattern(String),
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
Date(DateTime<Utc>),
|
Date(DateTime<Utc>),
|
||||||
Duration(u64),
|
Duration(i64),
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
Binary,
|
Binary,
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ use crate::data::base::shape::{Column, InlineShape};
|
|||||||
use crate::data::primitive::style_primitive;
|
use crate::data::primitive::style_primitive;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::CompareOperator;
|
use nu_protocol::hir::Operator;
|
||||||
|
use nu_protocol::ShellTypeName;
|
||||||
use nu_protocol::{Primitive, Type, UntaggedValue};
|
use nu_protocol::{Primitive, Type, UntaggedValue};
|
||||||
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged};
|
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged};
|
||||||
|
|
||||||
@ -21,8 +22,91 @@ pub fn date_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
|||||||
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
|
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn compute_values(
|
||||||
|
operator: Operator,
|
||||||
|
left: &UntaggedValue,
|
||||||
|
right: &UntaggedValue,
|
||||||
|
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||||
|
match (left, right) {
|
||||||
|
(UntaggedValue::Primitive(lhs), UntaggedValue::Primitive(rhs)) => match (lhs, rhs) {
|
||||||
|
(Primitive::Bytes(x), Primitive::Bytes(y)) => {
|
||||||
|
let result = match operator {
|
||||||
|
Operator::Plus => Ok(x + y),
|
||||||
|
Operator::Minus => Ok(x - y),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}?;
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Bytes(result)))
|
||||||
|
}
|
||||||
|
(Primitive::Int(x), Primitive::Int(y)) => match operator {
|
||||||
|
Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))),
|
||||||
|
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
|
||||||
|
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
|
||||||
|
Operator::Divide => {
|
||||||
|
if x - (y * (x / y)) == num_bigint::BigInt::from(0) {
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Int(x / y)))
|
||||||
|
} else {
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Decimal(
|
||||||
|
bigdecimal::BigDecimal::from(x.clone())
|
||||||
|
/ bigdecimal::BigDecimal::from(y.clone()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
},
|
||||||
|
(Primitive::Decimal(x), Primitive::Int(y)) => {
|
||||||
|
let result = match operator {
|
||||||
|
Operator::Plus => Ok(x + bigdecimal::BigDecimal::from(y.clone())),
|
||||||
|
Operator::Minus => Ok(x - bigdecimal::BigDecimal::from(y.clone())),
|
||||||
|
Operator::Multiply => Ok(x * bigdecimal::BigDecimal::from(y.clone())),
|
||||||
|
Operator::Divide => Ok(x / bigdecimal::BigDecimal::from(y.clone())),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}?;
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||||
|
}
|
||||||
|
(Primitive::Int(x), Primitive::Decimal(y)) => {
|
||||||
|
let result = match operator {
|
||||||
|
Operator::Plus => Ok(bigdecimal::BigDecimal::from(x.clone()) + y),
|
||||||
|
Operator::Minus => Ok(bigdecimal::BigDecimal::from(x.clone()) - y),
|
||||||
|
Operator::Multiply => Ok(bigdecimal::BigDecimal::from(x.clone()) * y),
|
||||||
|
Operator::Divide => Ok(bigdecimal::BigDecimal::from(x.clone()) / y),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}?;
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||||
|
}
|
||||||
|
(Primitive::Decimal(x), Primitive::Decimal(y)) => {
|
||||||
|
let result = match operator {
|
||||||
|
Operator::Plus => Ok(x + y),
|
||||||
|
Operator::Minus => Ok(x - y),
|
||||||
|
Operator::Multiply => Ok(x * y),
|
||||||
|
Operator::Divide => Ok(x / y),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}?;
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||||
|
}
|
||||||
|
(Primitive::Date(x), Primitive::Date(y)) => {
|
||||||
|
let result = match operator {
|
||||||
|
Operator::Minus => Ok(x.signed_duration_since(*y).num_seconds()),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}?;
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Duration(result)))
|
||||||
|
}
|
||||||
|
(Primitive::Date(x), Primitive::Duration(y)) => {
|
||||||
|
let result = match operator {
|
||||||
|
Operator::Plus => Ok(x
|
||||||
|
.checked_add_signed(chrono::Duration::seconds(*y as i64))
|
||||||
|
.expect("Overflowing add of duration")),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}?;
|
||||||
|
Ok(UntaggedValue::Primitive(Primitive::Date(result)))
|
||||||
|
}
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
},
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compare_values(
|
pub fn compare_values(
|
||||||
operator: CompareOperator,
|
operator: Operator,
|
||||||
left: &UntaggedValue,
|
left: &UntaggedValue,
|
||||||
right: &UntaggedValue,
|
right: &UntaggedValue,
|
||||||
) -> Result<bool, (&'static str, &'static str)> {
|
) -> Result<bool, (&'static str, &'static str)> {
|
||||||
@ -34,15 +118,16 @@ pub fn compare_values(
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
let result = match (operator, ordering) {
|
let result = match (operator, ordering) {
|
||||||
(CompareOperator::Equal, Ordering::Equal) => true,
|
(Operator::Equal, Ordering::Equal) => true,
|
||||||
(CompareOperator::NotEqual, Ordering::Less)
|
(Operator::NotEqual, Ordering::Less) | (Operator::NotEqual, Ordering::Greater) => {
|
||||||
| (CompareOperator::NotEqual, Ordering::Greater) => true,
|
true
|
||||||
(CompareOperator::LessThan, Ordering::Less) => true,
|
}
|
||||||
(CompareOperator::GreaterThan, Ordering::Greater) => true,
|
(Operator::LessThan, Ordering::Less) => true,
|
||||||
(CompareOperator::GreaterThanOrEqual, Ordering::Greater)
|
(Operator::GreaterThan, Ordering::Greater) => true,
|
||||||
| (CompareOperator::GreaterThanOrEqual, Ordering::Equal) => true,
|
(Operator::GreaterThanOrEqual, Ordering::Greater)
|
||||||
(CompareOperator::LessThanOrEqual, Ordering::Less)
|
| (Operator::GreaterThanOrEqual, Ordering::Equal) => true,
|
||||||
| (CompareOperator::LessThanOrEqual, Ordering::Equal) => true,
|
(Operator::LessThanOrEqual, Ordering::Less)
|
||||||
|
| (Operator::LessThanOrEqual, Ordering::Equal) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ pub(crate) fn evaluate_baseline_expr(
|
|||||||
Expression::Command(_) => evaluate_command(tag, scope),
|
Expression::Command(_) => evaluate_command(tag, scope),
|
||||||
Expression::ExternalCommand(external) => evaluate_external(external, scope),
|
Expression::ExternalCommand(external) => evaluate_external(external, scope),
|
||||||
Expression::Binary(binary) => {
|
Expression::Binary(binary) => {
|
||||||
|
// TODO: If we want to add short-circuiting, we'll need to move these down
|
||||||
let left = evaluate_baseline_expr(&binary.left, registry, scope)?;
|
let left = evaluate_baseline_expr(&binary.left, registry, scope)?;
|
||||||
let right = evaluate_baseline_expr(&binary.right, registry, scope)?;
|
let right = evaluate_baseline_expr(&binary.right, registry, scope)?;
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ pub(crate) fn evaluate_baseline_expr(
|
|||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Unknown column",
|
"Unknown column",
|
||||||
format!("did you mean '{}'?", possible_matches[0].1),
|
format!("did you mean '{}'?", possible_matches[0].1),
|
||||||
&tag,
|
&member.span,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
|
@ -1,30 +1,43 @@
|
|||||||
use crate::data::value;
|
use crate::data::value;
|
||||||
use nu_protocol::hir::CompareOperator;
|
use nu_protocol::hir::Operator;
|
||||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
|
|
||||||
pub fn apply_operator(
|
pub fn apply_operator(
|
||||||
op: CompareOperator,
|
op: Operator,
|
||||||
left: &Value,
|
left: &Value,
|
||||||
right: &Value,
|
right: &Value,
|
||||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||||
match op {
|
match op {
|
||||||
CompareOperator::Equal
|
Operator::Equal
|
||||||
| CompareOperator::NotEqual
|
| Operator::NotEqual
|
||||||
| CompareOperator::LessThan
|
| Operator::LessThan
|
||||||
| CompareOperator::GreaterThan
|
| Operator::GreaterThan
|
||||||
| CompareOperator::LessThanOrEqual
|
| Operator::LessThanOrEqual
|
||||||
| CompareOperator::GreaterThanOrEqual => {
|
| Operator::GreaterThanOrEqual => {
|
||||||
value::compare_values(op, left, right).map(UntaggedValue::boolean)
|
value::compare_values(op, left, right).map(UntaggedValue::boolean)
|
||||||
}
|
}
|
||||||
CompareOperator::Contains => contains(left, right).map(UntaggedValue::boolean),
|
Operator::Contains => string_contains(left, right).map(UntaggedValue::boolean),
|
||||||
CompareOperator::NotContains => contains(left, right)
|
Operator::NotContains => string_contains(left, right)
|
||||||
.map(Not::not)
|
.map(Not::not)
|
||||||
.map(UntaggedValue::boolean),
|
.map(UntaggedValue::boolean),
|
||||||
|
Operator::Plus => value::compute_values(op, left, right),
|
||||||
|
Operator::Minus => value::compute_values(op, left, right),
|
||||||
|
Operator::Multiply => value::compute_values(op, left, right),
|
||||||
|
Operator::Divide => value::compute_values(op, left, right),
|
||||||
|
Operator::In => table_contains(left, right).map(UntaggedValue::boolean),
|
||||||
|
Operator::And => match (left.as_bool(), right.as_bool()) {
|
||||||
|
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left && right)),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
},
|
||||||
|
Operator::Or => match (left.as_bool(), right.as_bool()) {
|
||||||
|
(Ok(left), Ok(right)) => Ok(UntaggedValue::boolean(left || right)),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains(
|
fn string_contains(
|
||||||
left: &UntaggedValue,
|
left: &UntaggedValue,
|
||||||
right: &UntaggedValue,
|
right: &UntaggedValue,
|
||||||
) -> Result<bool, (&'static str, &'static str)> {
|
) -> Result<bool, (&'static str, &'static str)> {
|
||||||
@ -48,3 +61,14 @@ fn contains(
|
|||||||
_ => Err((left.type_name(), right.type_name())),
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn table_contains(
|
||||||
|
left: &UntaggedValue,
|
||||||
|
right: &UntaggedValue,
|
||||||
|
) -> Result<bool, (&'static str, &'static str)> {
|
||||||
|
let left = left.clone();
|
||||||
|
match right {
|
||||||
|
UntaggedValue::Table(values) => Ok(values.iter().any(|x| x.value == left)),
|
||||||
|
_ => Err((left.type_name(), right.type_name())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -120,7 +120,7 @@ impl Painter {
|
|||||||
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
|
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
|
||||||
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
|
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
|
||||||
FlatShape::Type => Color::Blue.bold(),
|
FlatShape::Type => Color::Blue.bold(),
|
||||||
FlatShape::CompareOperator => Color::Yellow.normal(),
|
FlatShape::Operator => Color::Yellow.normal(),
|
||||||
FlatShape::DotDot => Color::Yellow.bold(),
|
FlatShape::DotDot => Color::Yellow.bold(),
|
||||||
FlatShape::Dot => Style::new().fg(Color::White),
|
FlatShape::Dot => Style::new().fg(Color::White),
|
||||||
FlatShape::InternalCommand => Color::Cyan.bold(),
|
FlatShape::InternalCommand => Color::Cyan.bold(),
|
||||||
|
@ -2,7 +2,7 @@ use crate::data::value::compare_values;
|
|||||||
use crate::data::TaggedListBuilder;
|
use crate::data::TaggedListBuilder;
|
||||||
use chrono::{DateTime, NaiveDate, Utc};
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::CompareOperator;
|
use nu_protocol::hir::Operator;
|
||||||
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
|
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
|
||||||
use nu_source::{SpannedItem, Tag, Tagged, TaggedItem};
|
use nu_source::{SpannedItem, Tag, Tagged, TaggedItem};
|
||||||
use nu_value_ext::{get_data_by_key, ValueExt};
|
use nu_value_ext::{get_data_by_key, ValueExt};
|
||||||
@ -317,7 +317,7 @@ pub fn map_max(
|
|||||||
let right = &acc.value;
|
let right = &acc.value;
|
||||||
|
|
||||||
if let Ok(is_greater_than) =
|
if let Ok(is_greater_than) =
|
||||||
compare_values(CompareOperator::GreaterThan, left, right)
|
compare_values(Operator::GreaterThan, left, right)
|
||||||
{
|
{
|
||||||
if is_greater_than {
|
if is_greater_than {
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -336,9 +336,7 @@ pub fn map_max(
|
|||||||
let left = &value.value;
|
let left = &value.value;
|
||||||
let right = &max.value;
|
let right = &max.value;
|
||||||
|
|
||||||
if let Ok(is_greater_than) =
|
if let Ok(is_greater_than) = compare_values(Operator::GreaterThan, left, right) {
|
||||||
compare_values(CompareOperator::GreaterThan, left, right)
|
|
||||||
{
|
|
||||||
if is_greater_than {
|
if is_greater_than {
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
|
133
crates/nu-cli/tests/commands/math.rs
Normal file
133
crates/nu-cli/tests/commands/math.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_arg() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 1
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 1 + 1
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_compount() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 1 + 2 + 2
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn precedence_of_operators() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 1 + 2 * 2
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn precedence_of_operators2() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 1 + 2 * 2 + 1
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "6");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn division_of_ints() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 4 / 2
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn division_of_ints2() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 1 / 4
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "0.25");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parens_precedence() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 4 * (6 - 3)
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "12");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compound_comparison() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 4 > 3 && 2 > 1
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compound_comparison2() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
= 4 < 3 || 2 > 1
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compound_where() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from-json | where a == 2 && b == 1 | to-json
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, r#"{"a":2,"b":1}"#);
|
||||||
|
}
|
@ -18,6 +18,7 @@ mod insert;
|
|||||||
mod last;
|
mod last;
|
||||||
mod lines;
|
mod lines;
|
||||||
mod ls;
|
mod ls;
|
||||||
|
mod math;
|
||||||
mod mkdir;
|
mod mkdir;
|
||||||
mod mv;
|
mod mv;
|
||||||
mod open;
|
mod open;
|
||||||
|
@ -78,6 +78,12 @@ fn bare(src: &mut Input, span_offset: usize) -> Result<Spanned<String>, ParseErr
|
|||||||
if let Some('{') = block_level.last() {
|
if let Some('{') = block_level.last() {
|
||||||
let _ = block_level.pop();
|
let _ = block_level.pop();
|
||||||
}
|
}
|
||||||
|
} else if c == '(' {
|
||||||
|
block_level.push(c);
|
||||||
|
} else if c == ')' {
|
||||||
|
if let Some('(') = block_level.last() {
|
||||||
|
let _ = block_level.pop();
|
||||||
|
}
|
||||||
} else if block_level.is_empty() && c.is_whitespace() {
|
} else if block_level.is_empty() && c.is_whitespace() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -162,9 +168,21 @@ fn pipeline(src: &mut Input, span_offset: usize) -> Result<LitePipeline, ParseEr
|
|||||||
// The first character tells us a lot about each argument
|
// The first character tells us a lot about each argument
|
||||||
match c {
|
match c {
|
||||||
'|' => {
|
'|' => {
|
||||||
// this is the end of this command
|
|
||||||
let _ = src.next();
|
let _ = src.next();
|
||||||
break;
|
if let Some((pos, next_c)) = src.peek() {
|
||||||
|
if *next_c == '|' {
|
||||||
|
// this isn't actually a pipeline but a comparison
|
||||||
|
let span = Span::new(pos - 1 + span_offset, pos + 1 + span_offset);
|
||||||
|
cmd.args.push("||".to_string().spanned(span));
|
||||||
|
let _ = src.next();
|
||||||
|
} else {
|
||||||
|
// this is the end of this command
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this is the end of this command
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'"' | '\'' => {
|
'"' | '\'' => {
|
||||||
let c = *c;
|
let c = *c;
|
||||||
|
@ -3,11 +3,12 @@ use std::path::Path;
|
|||||||
use crate::lite_parse::{lite_parse, LiteCommand, LitePipeline};
|
use crate::lite_parse::{lite_parse, LiteCommand, LitePipeline};
|
||||||
use crate::path::expand_path;
|
use crate::path::expand_path;
|
||||||
use crate::signature::SignatureRegistry;
|
use crate::signature::SignatureRegistry;
|
||||||
|
use log::trace;
|
||||||
use nu_errors::{ArgumentError, ParseError};
|
use nu_errors::{ArgumentError, ParseError};
|
||||||
use nu_protocol::hir::{
|
use nu_protocol::hir::{
|
||||||
self, Binary, ClassifiedCommand, ClassifiedPipeline, Commands, CompareOperator, Expression,
|
self, Binary, ClassifiedCommand, ClassifiedPipeline, Commands, Expression, ExternalArg,
|
||||||
ExternalArg, ExternalArgs, ExternalCommand, Flag, FlagKind, InternalCommand, Member,
|
ExternalArgs, ExternalCommand, Flag, FlagKind, InternalCommand, Member, NamedArguments,
|
||||||
NamedArguments, SpannedExpression, Unit,
|
Operator, SpannedExpression, Unit,
|
||||||
};
|
};
|
||||||
use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember};
|
use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember};
|
||||||
use nu_source::{Span, Spanned, SpannedItem, Tag};
|
use nu_source::{Span, Spanned, SpannedItem, Tag};
|
||||||
@ -219,28 +220,39 @@ fn parse_range(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseEr
|
|||||||
|
|
||||||
fn parse_operator(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
fn parse_operator(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
||||||
let operator = if lite_arg.item == "==" {
|
let operator = if lite_arg.item == "==" {
|
||||||
CompareOperator::Equal
|
Operator::Equal
|
||||||
} else if lite_arg.item == "!=" {
|
} else if lite_arg.item == "!=" {
|
||||||
CompareOperator::NotEqual
|
Operator::NotEqual
|
||||||
} else if lite_arg.item == "<" {
|
} else if lite_arg.item == "<" {
|
||||||
CompareOperator::LessThan
|
Operator::LessThan
|
||||||
} else if lite_arg.item == "<=" {
|
} else if lite_arg.item == "<=" {
|
||||||
CompareOperator::LessThanOrEqual
|
Operator::LessThanOrEqual
|
||||||
} else if lite_arg.item == ">" {
|
} else if lite_arg.item == ">" {
|
||||||
CompareOperator::GreaterThan
|
Operator::GreaterThan
|
||||||
} else if lite_arg.item == ">=" {
|
} else if lite_arg.item == ">=" {
|
||||||
CompareOperator::GreaterThanOrEqual
|
Operator::GreaterThanOrEqual
|
||||||
} else if lite_arg.item == "=~" {
|
} else if lite_arg.item == "=~" {
|
||||||
CompareOperator::Contains
|
Operator::Contains
|
||||||
} else if lite_arg.item == "!~" {
|
} else if lite_arg.item == "!~" {
|
||||||
CompareOperator::NotContains
|
Operator::NotContains
|
||||||
|
} else if lite_arg.item == "+" {
|
||||||
|
Operator::Plus
|
||||||
|
} else if lite_arg.item == "-" {
|
||||||
|
Operator::Minus
|
||||||
|
} else if lite_arg.item == "*" {
|
||||||
|
Operator::Multiply
|
||||||
|
} else if lite_arg.item == "/" {
|
||||||
|
Operator::Divide
|
||||||
|
} else if lite_arg.item == "in:" {
|
||||||
|
Operator::In
|
||||||
|
} else if lite_arg.item == "&&" {
|
||||||
|
Operator::And
|
||||||
|
} else if lite_arg.item == "||" {
|
||||||
|
Operator::Or
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage(lite_arg.span),
|
garbage(lite_arg.span),
|
||||||
Some(ParseError::mismatch(
|
Some(ParseError::mismatch("operator", lite_arg.clone())),
|
||||||
"comparison operator",
|
|
||||||
lite_arg.clone(),
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -252,23 +264,23 @@ fn parse_operator(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<Pars
|
|||||||
|
|
||||||
fn parse_unit(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
fn parse_unit(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
||||||
let unit_groups = [
|
let unit_groups = [
|
||||||
(Unit::Byte, true, vec!["b", "B"]),
|
(Unit::Byte, vec!["b", "B"]),
|
||||||
(Unit::Kilobyte, true, vec!["kb", "KB", "Kb"]),
|
(Unit::Kilobyte, vec!["kb", "KB", "Kb"]),
|
||||||
(Unit::Megabyte, true, vec!["mb", "MB", "Mb"]),
|
(Unit::Megabyte, vec!["mb", "MB", "Mb"]),
|
||||||
(Unit::Gigabyte, true, vec!["gb", "GB", "Gb"]),
|
(Unit::Gigabyte, vec!["gb", "GB", "Gb"]),
|
||||||
(Unit::Terabyte, true, vec!["tb", "TB", "Tb"]),
|
(Unit::Terabyte, vec!["tb", "TB", "Tb"]),
|
||||||
(Unit::Petabyte, true, vec!["pb", "PB", "Pb"]),
|
(Unit::Petabyte, vec!["pb", "PB", "Pb"]),
|
||||||
(Unit::Second, false, vec!["s"]),
|
(Unit::Second, vec!["s"]),
|
||||||
(Unit::Minute, false, vec!["m"]),
|
(Unit::Minute, vec!["m"]),
|
||||||
(Unit::Hour, false, vec!["h"]),
|
(Unit::Hour, vec!["h"]),
|
||||||
(Unit::Day, false, vec!["d"]),
|
(Unit::Day, vec!["d"]),
|
||||||
(Unit::Week, false, vec!["w"]),
|
(Unit::Week, vec!["w"]),
|
||||||
(Unit::Month, false, vec!["M"]),
|
(Unit::Month, vec!["M"]),
|
||||||
(Unit::Year, false, vec!["y"]),
|
(Unit::Year, vec!["y"]),
|
||||||
];
|
];
|
||||||
|
|
||||||
for unit_group in unit_groups.iter() {
|
for unit_group in unit_groups.iter() {
|
||||||
for unit in unit_group.2.iter() {
|
for unit in unit_group.1.iter() {
|
||||||
if lite_arg.item.ends_with(unit) {
|
if lite_arg.item.ends_with(unit) {
|
||||||
let mut lhs = lite_arg.item.clone();
|
let mut lhs = lite_arg.item.clone();
|
||||||
|
|
||||||
@ -276,42 +288,19 @@ fn parse_unit(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseErr
|
|||||||
lhs.pop();
|
lhs.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if unit_group.1 {
|
// these units are allowed to signed
|
||||||
// these units are allowed to signed
|
if let Ok(x) = lhs.parse::<i64>() {
|
||||||
if let Ok(x) = lhs.parse::<i64>() {
|
let lhs_span =
|
||||||
let lhs_span =
|
Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
|
||||||
Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
|
let unit_span =
|
||||||
let unit_span =
|
Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
|
||||||
Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
|
return (
|
||||||
return (
|
SpannedExpression::new(
|
||||||
SpannedExpression::new(
|
Expression::unit(x.spanned(lhs_span), unit_group.0.spanned(unit_span)),
|
||||||
Expression::unit(
|
lite_arg.span,
|
||||||
x.spanned(lhs_span),
|
),
|
||||||
unit_group.0.spanned(unit_span),
|
None,
|
||||||
),
|
);
|
||||||
lite_arg.span,
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// these units are unsigned
|
|
||||||
if let Ok(x) = lhs.parse::<u64>() {
|
|
||||||
let lhs_span =
|
|
||||||
Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
|
|
||||||
let unit_span =
|
|
||||||
Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
|
|
||||||
return (
|
|
||||||
SpannedExpression::new(
|
|
||||||
Expression::unit(
|
|
||||||
(x as i64).spanned(lhs_span),
|
|
||||||
unit_group.0.spanned(unit_span),
|
|
||||||
),
|
|
||||||
lite_arg.span,
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,6 +392,7 @@ fn parse_arg(
|
|||||||
SyntaxShape::Unit,
|
SyntaxShape::Unit,
|
||||||
SyntaxShape::Block,
|
SyntaxShape::Block,
|
||||||
SyntaxShape::Table,
|
SyntaxShape::Table,
|
||||||
|
SyntaxShape::Parenthesized,
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
];
|
];
|
||||||
for shape in shapes.iter() {
|
for shape in shapes.iter() {
|
||||||
@ -460,7 +450,35 @@ fn parse_arg(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SyntaxShape::Block | SyntaxShape::Condition => {
|
SyntaxShape::Parenthesized => {
|
||||||
|
let mut chars = lite_arg.item.chars();
|
||||||
|
|
||||||
|
match (chars.next(), chars.next_back()) {
|
||||||
|
(Some('('), Some(')')) => {
|
||||||
|
// We have a literal row
|
||||||
|
let string: String = chars.collect();
|
||||||
|
|
||||||
|
// We haven't done much with the inner string, so let's go ahead and work with it
|
||||||
|
let mut lite_pipeline = match lite_parse(&string, lite_arg.span.start() + 1) {
|
||||||
|
Ok(lp) => lp,
|
||||||
|
Err(e) => return (garbage(lite_arg.span), Some(e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut collection = vec![];
|
||||||
|
for lite_cmd in lite_pipeline.commands.iter_mut() {
|
||||||
|
collection.push(lite_cmd.name.clone());
|
||||||
|
collection.append(&mut lite_cmd.args);
|
||||||
|
}
|
||||||
|
let (_, expr, err) = parse_math_expression(0, &collection[..], registry, false);
|
||||||
|
(expr, err)
|
||||||
|
}
|
||||||
|
_ => (
|
||||||
|
garbage(lite_arg.span),
|
||||||
|
Some(ParseError::mismatch("table", lite_arg.clone())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxShape::Block | SyntaxShape::Math => {
|
||||||
// Blocks have one of two forms: the literal block and the implied block
|
// Blocks have one of two forms: the literal block and the implied block
|
||||||
// To parse a literal block, we need to detect that what we have is itself a block
|
// To parse a literal block, we need to detect that what we have is itself a block
|
||||||
let mut chars = lite_arg.item.chars();
|
let mut chars = lite_arg.item.chars();
|
||||||
@ -560,6 +578,159 @@ fn get_flags_from_flag(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shorthand_reparse(
|
||||||
|
left: SpannedExpression,
|
||||||
|
orig_left: Option<Spanned<String>>,
|
||||||
|
registry: &dyn SignatureRegistry,
|
||||||
|
shorthand_mode: bool,
|
||||||
|
) -> (SpannedExpression, Option<ParseError>) {
|
||||||
|
// If we're in shorthand mode, we need to reparse the left-hand side if possibe
|
||||||
|
if shorthand_mode {
|
||||||
|
if let Some(orig_left) = orig_left {
|
||||||
|
parse_arg(SyntaxShape::FullColumnPath, registry, &orig_left)
|
||||||
|
} else {
|
||||||
|
(left, None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(left, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_math_expression(
|
||||||
|
incoming_idx: usize,
|
||||||
|
lite_args: &[Spanned<String>],
|
||||||
|
registry: &dyn SignatureRegistry,
|
||||||
|
shorthand_mode: bool,
|
||||||
|
) -> (usize, SpannedExpression, Option<ParseError>) {
|
||||||
|
// Precedence parsing is included
|
||||||
|
// Some notes:
|
||||||
|
// * short_hand mode means that the left-hand side of an expression can point to a column-path. To make this possible,
|
||||||
|
// we parse as normal, but then go back and when we detect a left-hand side, reparse that value if it's a string
|
||||||
|
// * parens are handled earlier, so they're not handled explicitly here
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut error = None;
|
||||||
|
|
||||||
|
let mut working_exprs = vec![];
|
||||||
|
let mut prec = vec![];
|
||||||
|
|
||||||
|
let (lhs, err) = parse_arg(SyntaxShape::Any, registry, &lite_args[idx]);
|
||||||
|
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
working_exprs.push((Some(lite_args[idx].clone()), lhs));
|
||||||
|
idx += 1;
|
||||||
|
|
||||||
|
prec.push(0);
|
||||||
|
|
||||||
|
while idx < lite_args.len() {
|
||||||
|
let (op, err) = parse_arg(SyntaxShape::Operator, registry, &lite_args[idx]);
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
idx += 1;
|
||||||
|
|
||||||
|
if idx < lite_args.len() {
|
||||||
|
trace!(
|
||||||
|
"idx: {} working_exprs: {:#?} prec: {:?}",
|
||||||
|
idx,
|
||||||
|
working_exprs,
|
||||||
|
prec
|
||||||
|
);
|
||||||
|
let (rhs, err) = parse_arg(SyntaxShape::Any, registry, &lite_args[idx]);
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_prec = op.precedence();
|
||||||
|
|
||||||
|
if !prec.is_empty() && next_prec > *prec.last().expect("this shouldn't happen") {
|
||||||
|
prec.push(next_prec);
|
||||||
|
working_exprs.push((None, op));
|
||||||
|
working_exprs.push((Some(lite_args[idx].clone()), rhs));
|
||||||
|
} else {
|
||||||
|
while !prec.is_empty()
|
||||||
|
&& *prec.last().expect("This shouldn't happen") >= next_prec
|
||||||
|
&& next_prec > 0 // Not garbage
|
||||||
|
&& working_exprs.len() >= 3
|
||||||
|
{
|
||||||
|
// Pop 3 and create and expression, push and repeat
|
||||||
|
trace!(
|
||||||
|
"idx: {} working_exprs: {:#?} prec: {:?}",
|
||||||
|
idx,
|
||||||
|
working_exprs,
|
||||||
|
prec
|
||||||
|
);
|
||||||
|
let (_, right) = working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
let (_, op) = working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
let (orig_left, left) =
|
||||||
|
working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
|
||||||
|
// If we're in shorthand mode, we need to reparse the left-hand side if possibe
|
||||||
|
let (left, err) = shorthand_reparse(left, orig_left, registry, shorthand_mode);
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = Span::new(left.span.start(), right.span.end());
|
||||||
|
working_exprs.push((
|
||||||
|
None,
|
||||||
|
SpannedExpression {
|
||||||
|
expr: Expression::Binary(Box::new(Binary { left, op, right })),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
prec.pop();
|
||||||
|
}
|
||||||
|
working_exprs.push((None, op));
|
||||||
|
working_exprs.push((Some(lite_args[idx].clone()), rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
} else {
|
||||||
|
if error.is_none() {
|
||||||
|
error = Some(ParseError::argument_error(
|
||||||
|
lite_args[idx - 1].clone(),
|
||||||
|
ArgumentError::MissingMandatoryPositional("right hand side".into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
working_exprs.push((None, garbage(op.span)));
|
||||||
|
working_exprs.push((None, garbage(op.span)));
|
||||||
|
prec.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while working_exprs.len() >= 3 {
|
||||||
|
// Pop 3 and create and expression, push and repeat
|
||||||
|
let (_, right) = working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
let (_, op) = working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
let (orig_left, left) = working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
|
||||||
|
let (left, err) = shorthand_reparse(left, orig_left, registry, shorthand_mode);
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = Span::new(left.span.start(), right.span.end());
|
||||||
|
working_exprs.push((
|
||||||
|
None,
|
||||||
|
SpannedExpression {
|
||||||
|
expr: Expression::Binary(Box::new(Binary { left, op, right })),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (orig_left, left) = working_exprs.pop().expect("This shouldn't be possible");
|
||||||
|
let (left, err) = shorthand_reparse(left, orig_left, registry, shorthand_mode);
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
(incoming_idx + idx, left, error)
|
||||||
|
}
|
||||||
|
|
||||||
fn classify_positional_arg(
|
fn classify_positional_arg(
|
||||||
idx: usize,
|
idx: usize,
|
||||||
lite_cmd: &LiteCommand,
|
lite_cmd: &LiteCommand,
|
||||||
@ -569,39 +740,35 @@ fn classify_positional_arg(
|
|||||||
let mut idx = idx;
|
let mut idx = idx;
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
let arg = match positional_type {
|
let arg = match positional_type {
|
||||||
PositionalType::Mandatory(_, SyntaxShape::Condition)
|
PositionalType::Mandatory(_, SyntaxShape::Math)
|
||||||
| PositionalType::Optional(_, SyntaxShape::Condition) => {
|
| PositionalType::Optional(_, SyntaxShape::Math) => {
|
||||||
// A condition can take up multiple arguments, as we build the operation as <arg> <operator> <arg>
|
// A condition can take up multiple arguments, as we build the operation as <arg> <operator> <arg>
|
||||||
// We need to do this here because in parse_arg, we have access to only one arg at a time
|
// We need to do this here because in parse_arg, we have access to only one arg at a time
|
||||||
if (idx + 2) < lite_cmd.args.len() {
|
|
||||||
let (lhs, err) =
|
if idx < lite_cmd.args.len() {
|
||||||
parse_arg(SyntaxShape::FullColumnPath, registry, &lite_cmd.args[idx]);
|
if lite_cmd.args[idx].item.starts_with('{') {
|
||||||
if error.is_none() {
|
// It's an explicit math expression, so parse it deeper in
|
||||||
error = err;
|
let (arg, err) = parse_arg(SyntaxShape::Math, registry, &lite_cmd.args[idx]);
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
arg
|
||||||
|
} else {
|
||||||
|
let (new_idx, arg, err) =
|
||||||
|
parse_math_expression(idx, &lite_cmd.args[idx..], registry, true);
|
||||||
|
|
||||||
|
let span = arg.span;
|
||||||
|
let mut commands = hir::Commands::new(span);
|
||||||
|
commands.push(ClassifiedCommand::Expr(Box::new(arg)));
|
||||||
|
|
||||||
|
let arg = SpannedExpression::new(Expression::Block(commands), span);
|
||||||
|
|
||||||
|
idx = new_idx;
|
||||||
|
if error.is_none() {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
arg
|
||||||
}
|
}
|
||||||
let (op, err) = parse_arg(SyntaxShape::Operator, registry, &lite_cmd.args[idx + 1]);
|
|
||||||
if error.is_none() {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
let (rhs, err) = parse_arg(SyntaxShape::Any, registry, &lite_cmd.args[idx + 2]);
|
|
||||||
if error.is_none() {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
idx += 2;
|
|
||||||
let span = Span::new(lhs.span.start(), rhs.span.end());
|
|
||||||
let binary = SpannedExpression::new(
|
|
||||||
Expression::Binary(Box::new(Binary::new(lhs, op, rhs))),
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
let mut commands = hir::Commands::new(span);
|
|
||||||
commands.push(ClassifiedCommand::Expr(Box::new(binary)));
|
|
||||||
SpannedExpression::new(Expression::Block(commands), span)
|
|
||||||
} else if idx < lite_cmd.args.len() {
|
|
||||||
let (arg, err) = parse_arg(SyntaxShape::Condition, registry, &lite_cmd.args[idx]);
|
|
||||||
if error.is_none() {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
arg
|
|
||||||
} else {
|
} else {
|
||||||
if error.is_none() {
|
if error.is_none() {
|
||||||
error = Some(ParseError::argument_error(
|
error = Some(ParseError::argument_error(
|
||||||
@ -800,31 +967,12 @@ pub fn classify_pipeline(
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
} else if lite_cmd.name.item == "=" {
|
} else if lite_cmd.name.item == "=" {
|
||||||
let idx = 0;
|
let expr = if !lite_cmd.args.is_empty() {
|
||||||
let expr = if (idx + 2) < lite_cmd.args.len() {
|
let (_, expr, err) = parse_math_expression(0, &lite_cmd.args[0..], registry, false);
|
||||||
let (lhs, err) = parse_arg(SyntaxShape::Any, registry, &lite_cmd.args[idx]);
|
|
||||||
if error.is_none() {
|
if error.is_none() {
|
||||||
error = err;
|
error = err;
|
||||||
}
|
}
|
||||||
let (op, err) = parse_arg(SyntaxShape::Operator, registry, &lite_cmd.args[idx + 1]);
|
expr
|
||||||
if error.is_none() {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
let (rhs, err) = parse_arg(SyntaxShape::Any, registry, &lite_cmd.args[idx + 2]);
|
|
||||||
if error.is_none() {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
let span = Span::new(lhs.span.start(), rhs.span.end());
|
|
||||||
SpannedExpression::new(
|
|
||||||
Expression::Binary(Box::new(Binary::new(lhs, op, rhs))),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
} else if idx < lite_cmd.args.len() {
|
|
||||||
let (arg, err) = parse_arg(SyntaxShape::Any, registry, &lite_cmd.args[idx]);
|
|
||||||
if error.is_none() {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
arg
|
|
||||||
} else {
|
} else {
|
||||||
if error.is_none() {
|
if error.is_none() {
|
||||||
error = Some(ParseError::argument_error(
|
error = Some(ParseError::argument_error(
|
||||||
|
@ -32,9 +32,7 @@ pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec<Spanned<FlatShape>
|
|||||||
vec![FlatShape::GlobPattern.spanned(e.span)]
|
vec![FlatShape::GlobPattern.spanned(e.span)]
|
||||||
}
|
}
|
||||||
Expression::Literal(Literal::Number(_)) => vec![FlatShape::Int.spanned(e.span)],
|
Expression::Literal(Literal::Number(_)) => vec![FlatShape::Int.spanned(e.span)],
|
||||||
Expression::Literal(Literal::Operator(_)) => {
|
Expression::Literal(Literal::Operator(_)) => vec![FlatShape::Operator.spanned(e.span)],
|
||||||
vec![FlatShape::CompareOperator.spanned(e.span)]
|
|
||||||
}
|
|
||||||
Expression::Literal(Literal::Size(number, unit)) => vec![FlatShape::Size {
|
Expression::Literal(Literal::Size(number, unit)) => vec![FlatShape::Size {
|
||||||
number: number.span,
|
number: number.span,
|
||||||
unit: unit.span,
|
unit: unit.span,
|
||||||
@ -48,7 +46,7 @@ pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec<Spanned<FlatShape>
|
|||||||
Expression::Binary(binary) => {
|
Expression::Binary(binary) => {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
output.append(&mut expression_to_flat_shape(&binary.left));
|
output.append(&mut expression_to_flat_shape(&binary.left));
|
||||||
output.push(FlatShape::CompareOperator.spanned(binary.op.span));
|
output.push(FlatShape::Operator.spanned(binary.op.span));
|
||||||
output.append(&mut expression_to_flat_shape(&binary.right));
|
output.append(&mut expression_to_flat_shape(&binary.right));
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
@ -366,6 +366,25 @@ fn convert_number_to_u64(number: &Number) -> u64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_number_to_i64(number: &Number) -> i64 {
|
||||||
|
match number {
|
||||||
|
Number::Int(big_int) => {
|
||||||
|
if let Some(x) = big_int.to_i64() {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
unreachable!("Internal error: convert_number_to_u64 given incompatible number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Number::Decimal(big_decimal) => {
|
||||||
|
if let Some(x) = big_decimal.to_i64() {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
unreachable!("Internal error: convert_number_to_u64 given incompatible number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Unit {
|
impl Unit {
|
||||||
pub fn as_str(self) -> &'static str {
|
pub fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@ -389,33 +408,30 @@ impl Unit {
|
|||||||
let size = size.clone();
|
let size = size.clone();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Unit::Byte => number(size),
|
Unit::Byte => bytes(convert_number_to_u64(&size)),
|
||||||
Unit::Kilobyte => number(size * 1024),
|
Unit::Kilobyte => bytes(convert_number_to_u64(&size) * 1024),
|
||||||
Unit::Megabyte => number(size * 1024 * 1024),
|
Unit::Megabyte => bytes(convert_number_to_u64(&size) * 1024 * 1024),
|
||||||
Unit::Gigabyte => number(size * 1024 * 1024 * 1024),
|
Unit::Gigabyte => bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024),
|
||||||
Unit::Terabyte => number(size * 1024 * 1024 * 1024 * 1024),
|
Unit::Terabyte => bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024 * 1024),
|
||||||
Unit::Petabyte => number(size * 1024 * 1024 * 1024 * 1024 * 1024),
|
Unit::Petabyte => {
|
||||||
Unit::Second => duration(convert_number_to_u64(&size)),
|
bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024 * 1024 * 1024)
|
||||||
Unit::Minute => duration(60 * convert_number_to_u64(&size)),
|
}
|
||||||
Unit::Hour => duration(60 * 60 * convert_number_to_u64(&size)),
|
Unit::Second => duration(convert_number_to_i64(&size)),
|
||||||
Unit::Day => duration(24 * 60 * 60 * convert_number_to_u64(&size)),
|
Unit::Minute => duration(60 * convert_number_to_i64(&size)),
|
||||||
Unit::Week => duration(7 * 24 * 60 * 60 * convert_number_to_u64(&size)),
|
Unit::Hour => duration(60 * 60 * convert_number_to_i64(&size)),
|
||||||
Unit::Month => duration(30 * 24 * 60 * 60 * convert_number_to_u64(&size)),
|
Unit::Day => duration(24 * 60 * 60 * convert_number_to_i64(&size)),
|
||||||
Unit::Year => duration(365 * 24 * 60 * 60 * convert_number_to_u64(&size)),
|
Unit::Week => duration(7 * 24 * 60 * 60 * convert_number_to_i64(&size)),
|
||||||
|
Unit::Month => duration(30 * 24 * 60 * 60 * convert_number_to_i64(&size)),
|
||||||
|
Unit::Year => duration(365 * 24 * 60 * 60 * convert_number_to_i64(&size)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn number(number: impl Into<Number>) -> UntaggedValue {
|
pub fn bytes(size: u64) -> UntaggedValue {
|
||||||
let number = number.into();
|
UntaggedValue::Primitive(Primitive::Bytes(size))
|
||||||
|
|
||||||
match number {
|
|
||||||
Number::Int(int) => UntaggedValue::Primitive(Primitive::Int(int)),
|
|
||||||
Number::Decimal(decimal) => UntaggedValue::Primitive(Primitive::Decimal(decimal)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn duration(secs: u64) -> UntaggedValue {
|
pub fn duration(secs: i64) -> UntaggedValue {
|
||||||
UntaggedValue::Primitive(Primitive::Duration(secs))
|
UntaggedValue::Primitive(Primitive::Duration(secs))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,6 +445,31 @@ impl SpannedExpression {
|
|||||||
pub fn new(expr: Expression, span: Span) -> SpannedExpression {
|
pub fn new(expr: Expression, span: Span) -> SpannedExpression {
|
||||||
SpannedExpression { expr, span }
|
SpannedExpression { expr, span }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn precedence(&self) -> usize {
|
||||||
|
match self.expr {
|
||||||
|
Expression::Literal(Literal::Operator(operator)) => {
|
||||||
|
// Higher precedence binds tighter
|
||||||
|
|
||||||
|
match operator {
|
||||||
|
Operator::Multiply | Operator::Divide => 100,
|
||||||
|
Operator::Plus | Operator::Minus => 90,
|
||||||
|
Operator::NotContains
|
||||||
|
| Operator::Contains
|
||||||
|
| Operator::LessThan
|
||||||
|
| Operator::LessThanOrEqual
|
||||||
|
| Operator::GreaterThan
|
||||||
|
| Operator::GreaterThanOrEqual
|
||||||
|
| Operator::Equal
|
||||||
|
| Operator::NotEqual
|
||||||
|
| Operator::In => 80,
|
||||||
|
Operator::And => 50,
|
||||||
|
Operator::Or => 40, // TODO: should we have And and Or be different precedence?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for SpannedExpression {
|
impl std::ops::Deref for SpannedExpression {
|
||||||
@ -546,7 +587,7 @@ pub enum Variable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialOrd, Ord, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum CompareOperator {
|
pub enum Operator {
|
||||||
Equal,
|
Equal,
|
||||||
NotEqual,
|
NotEqual,
|
||||||
LessThan,
|
LessThan,
|
||||||
@ -555,12 +596,19 @@ pub enum CompareOperator {
|
|||||||
GreaterThanOrEqual,
|
GreaterThanOrEqual,
|
||||||
Contains,
|
Contains,
|
||||||
NotContains,
|
NotContains,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
In,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize, new)]
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize, new)]
|
||||||
pub struct Binary {
|
pub struct Binary {
|
||||||
pub left: SpannedExpression,
|
pub left: SpannedExpression,
|
||||||
pub op: SpannedExpression, //Spanned<CompareOperator>,
|
pub op: SpannedExpression,
|
||||||
pub right: SpannedExpression,
|
pub right: SpannedExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,7 +666,7 @@ impl PrettyDebugWithSource for Range {
|
|||||||
pub enum Literal {
|
pub enum Literal {
|
||||||
Number(Number),
|
Number(Number),
|
||||||
Size(Spanned<Number>, Spanned<Unit>),
|
Size(Spanned<Number>, Spanned<Unit>),
|
||||||
Operator(CompareOperator),
|
Operator(Operator),
|
||||||
String(String),
|
String(String),
|
||||||
GlobPattern(String),
|
GlobPattern(String),
|
||||||
ColumnPath(Vec<Member>),
|
ColumnPath(Vec<Member>),
|
||||||
@ -779,7 +827,7 @@ impl Expression {
|
|||||||
Expression::Literal(Literal::String(s))
|
Expression::Literal(Literal::String(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn operator(operator: CompareOperator) -> Expression {
|
pub fn operator(operator: Operator) -> Expression {
|
||||||
Expression::Literal(Literal::Operator(operator))
|
Expression::Literal(Literal::Operator(operator))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,7 +1000,7 @@ pub enum FlatShape {
|
|||||||
Identifier,
|
Identifier,
|
||||||
ItVariable,
|
ItVariable,
|
||||||
Variable,
|
Variable,
|
||||||
CompareOperator,
|
Operator,
|
||||||
Dot,
|
Dot,
|
||||||
DotDot,
|
DotDot,
|
||||||
InternalCommand,
|
InternalCommand,
|
||||||
|
@ -30,8 +30,10 @@ pub enum SyntaxShape {
|
|||||||
Unit,
|
Unit,
|
||||||
/// An operator
|
/// An operator
|
||||||
Operator,
|
Operator,
|
||||||
/// A condition, eg `foo > 1`
|
/// A parenthesized math expression, eg `(1 + 3)`
|
||||||
Condition,
|
Parenthesized,
|
||||||
|
/// A math expression, eg `foo > 1`
|
||||||
|
Math,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyDebug for SyntaxShape {
|
impl PrettyDebug for SyntaxShape {
|
||||||
@ -51,7 +53,8 @@ impl PrettyDebug for SyntaxShape {
|
|||||||
SyntaxShape::Table => "table",
|
SyntaxShape::Table => "table",
|
||||||
SyntaxShape::Unit => "unit",
|
SyntaxShape::Unit => "unit",
|
||||||
SyntaxShape::Operator => "operator",
|
SyntaxShape::Operator => "operator",
|
||||||
SyntaxShape::Condition => "condition",
|
SyntaxShape::Parenthesized => "math with parentheses",
|
||||||
|
SyntaxShape::Math => "condition",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ impl UntaggedValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for creating date duration values
|
/// Helper for creating date duration values
|
||||||
pub fn duration(secs: u64) -> UntaggedValue {
|
pub fn duration(secs: i64) -> UntaggedValue {
|
||||||
UntaggedValue::Primitive(Primitive::Duration(secs))
|
UntaggedValue::Primitive(Primitive::Duration(secs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ pub enum Primitive {
|
|||||||
/// A date value, in UTC
|
/// A date value, in UTC
|
||||||
Date(DateTime<Utc>),
|
Date(DateTime<Utc>),
|
||||||
/// A count in the number of seconds
|
/// A count in the number of seconds
|
||||||
Duration(u64),
|
Duration(i64),
|
||||||
/// A range of values
|
/// A range of values
|
||||||
Range(Box<Range>),
|
Range(Box<Range>),
|
||||||
/// A file path
|
/// A file path
|
||||||
@ -276,7 +276,7 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format a duration in seconds into a string
|
/// Format a duration in seconds into a string
|
||||||
pub fn format_duration(sec: u64) -> String {
|
pub fn format_duration(sec: i64) -> String {
|
||||||
let (minutes, seconds) = (sec / 60, sec % 60);
|
let (minutes, seconds) = (sec / 60, sec % 60);
|
||||||
let (hours, minutes) = (minutes / 60, minutes % 60);
|
let (hours, minutes) = (minutes / 60, minutes % 60);
|
||||||
let (days, hours) = (hours / 24, hours % 24);
|
let (days, hours) = (hours / 24, hours % 24);
|
||||||
@ -290,13 +290,73 @@ pub fn format_duration(sec: u64) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
/// Format a UTC date value into a humanized string (eg "1 week ago" instead of a formal date string)
|
/// Format a UTC date value into a humanized string (eg "1 week ago" instead of a formal date string)
|
||||||
pub fn format_date(d: &DateTime<Utc>) -> String {
|
pub fn format_date(d: &DateTime<Utc>) -> String {
|
||||||
let utc: DateTime<Utc> = Utc::now();
|
let utc: DateTime<Utc> = Utc::now();
|
||||||
|
|
||||||
let duration = utc.signed_duration_since(*d);
|
let duration = utc.signed_duration_since(*d);
|
||||||
|
|
||||||
if duration.num_weeks() >= 52 {
|
if duration.num_seconds() < 0 {
|
||||||
|
// Our duration is negative, so we need to speak about the future
|
||||||
|
if -duration.num_weeks() >= 52 {
|
||||||
|
let num_years = -duration.num_weeks() / 52;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} year{} from now",
|
||||||
|
num_years,
|
||||||
|
if num_years == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else if -duration.num_weeks() >= 4 {
|
||||||
|
let num_months = -duration.num_weeks() / 4;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} month{} from now",
|
||||||
|
num_months,
|
||||||
|
if num_months == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else if -duration.num_weeks() >= 1 {
|
||||||
|
let num_weeks = -duration.num_weeks();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} week{} from now",
|
||||||
|
num_weeks,
|
||||||
|
if num_weeks == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else if -duration.num_days() >= 1 {
|
||||||
|
let num_days = -duration.num_days();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} day{} from now",
|
||||||
|
num_days,
|
||||||
|
if num_days == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else if -duration.num_hours() >= 1 {
|
||||||
|
let num_hours = -duration.num_hours();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} hour{} from now",
|
||||||
|
num_hours,
|
||||||
|
if num_hours == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else if -duration.num_minutes() >= 1 {
|
||||||
|
let num_minutes = -duration.num_minutes();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} min{} from now",
|
||||||
|
num_minutes,
|
||||||
|
if num_minutes == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let num_seconds = -duration.num_seconds();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{} sec{} from now",
|
||||||
|
num_seconds,
|
||||||
|
if num_seconds == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if duration.num_weeks() >= 52 {
|
||||||
let num_years = duration.num_weeks() / 52;
|
let num_years = duration.num_weeks() / 52;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
|
@ -97,7 +97,7 @@ async fn host(tag: Tag) -> Value {
|
|||||||
|
|
||||||
// Uptime
|
// Uptime
|
||||||
if let Ok(uptime) = uptime_result {
|
if let Ok(uptime) = uptime_result {
|
||||||
let uptime = uptime.get::<time::second>().round() as u64;
|
let uptime = uptime.get::<time::second>().round() as i64;
|
||||||
|
|
||||||
dict.insert_untagged("uptime", UntaggedValue::duration(uptime));
|
dict.insert_untagged("uptime", UntaggedValue::duration(uptime));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user