mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 13:06:08 +02: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:
@ -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 {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
@ -389,33 +408,30 @@ impl Unit {
|
||||
let size = size.clone();
|
||||
|
||||
match self {
|
||||
Unit::Byte => number(size),
|
||||
Unit::Kilobyte => number(size * 1024),
|
||||
Unit::Megabyte => number(size * 1024 * 1024),
|
||||
Unit::Gigabyte => number(size * 1024 * 1024 * 1024),
|
||||
Unit::Terabyte => number(size * 1024 * 1024 * 1024 * 1024),
|
||||
Unit::Petabyte => number(size * 1024 * 1024 * 1024 * 1024 * 1024),
|
||||
Unit::Second => duration(convert_number_to_u64(&size)),
|
||||
Unit::Minute => duration(60 * convert_number_to_u64(&size)),
|
||||
Unit::Hour => duration(60 * 60 * convert_number_to_u64(&size)),
|
||||
Unit::Day => duration(24 * 60 * 60 * convert_number_to_u64(&size)),
|
||||
Unit::Week => duration(7 * 24 * 60 * 60 * convert_number_to_u64(&size)),
|
||||
Unit::Month => duration(30 * 24 * 60 * 60 * convert_number_to_u64(&size)),
|
||||
Unit::Year => duration(365 * 24 * 60 * 60 * convert_number_to_u64(&size)),
|
||||
Unit::Byte => bytes(convert_number_to_u64(&size)),
|
||||
Unit::Kilobyte => bytes(convert_number_to_u64(&size) * 1024),
|
||||
Unit::Megabyte => bytes(convert_number_to_u64(&size) * 1024 * 1024),
|
||||
Unit::Gigabyte => bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024),
|
||||
Unit::Terabyte => bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024 * 1024),
|
||||
Unit::Petabyte => {
|
||||
bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024 * 1024 * 1024)
|
||||
}
|
||||
Unit::Second => duration(convert_number_to_i64(&size)),
|
||||
Unit::Minute => duration(60 * convert_number_to_i64(&size)),
|
||||
Unit::Hour => duration(60 * 60 * convert_number_to_i64(&size)),
|
||||
Unit::Day => duration(24 * 60 * 60 * convert_number_to_i64(&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 {
|
||||
let number = number.into();
|
||||
|
||||
match number {
|
||||
Number::Int(int) => UntaggedValue::Primitive(Primitive::Int(int)),
|
||||
Number::Decimal(decimal) => UntaggedValue::Primitive(Primitive::Decimal(decimal)),
|
||||
}
|
||||
pub fn bytes(size: u64) -> UntaggedValue {
|
||||
UntaggedValue::Primitive(Primitive::Bytes(size))
|
||||
}
|
||||
|
||||
pub fn duration(secs: u64) -> UntaggedValue {
|
||||
pub fn duration(secs: i64) -> UntaggedValue {
|
||||
UntaggedValue::Primitive(Primitive::Duration(secs))
|
||||
}
|
||||
|
||||
@ -429,6 +445,31 @@ impl SpannedExpression {
|
||||
pub fn new(expr: Expression, span: Span) -> SpannedExpression {
|
||||
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 {
|
||||
@ -546,7 +587,7 @@ pub enum Variable {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
||||
pub enum CompareOperator {
|
||||
pub enum Operator {
|
||||
Equal,
|
||||
NotEqual,
|
||||
LessThan,
|
||||
@ -555,12 +596,19 @@ pub enum CompareOperator {
|
||||
GreaterThanOrEqual,
|
||||
Contains,
|
||||
NotContains,
|
||||
Plus,
|
||||
Minus,
|
||||
Multiply,
|
||||
Divide,
|
||||
In,
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize, new)]
|
||||
pub struct Binary {
|
||||
pub left: SpannedExpression,
|
||||
pub op: SpannedExpression, //Spanned<CompareOperator>,
|
||||
pub op: SpannedExpression,
|
||||
pub right: SpannedExpression,
|
||||
}
|
||||
|
||||
@ -618,7 +666,7 @@ impl PrettyDebugWithSource for Range {
|
||||
pub enum Literal {
|
||||
Number(Number),
|
||||
Size(Spanned<Number>, Spanned<Unit>),
|
||||
Operator(CompareOperator),
|
||||
Operator(Operator),
|
||||
String(String),
|
||||
GlobPattern(String),
|
||||
ColumnPath(Vec<Member>),
|
||||
@ -779,7 +827,7 @@ impl Expression {
|
||||
Expression::Literal(Literal::String(s))
|
||||
}
|
||||
|
||||
pub fn operator(operator: CompareOperator) -> Expression {
|
||||
pub fn operator(operator: Operator) -> Expression {
|
||||
Expression::Literal(Literal::Operator(operator))
|
||||
}
|
||||
|
||||
@ -952,7 +1000,7 @@ pub enum FlatShape {
|
||||
Identifier,
|
||||
ItVariable,
|
||||
Variable,
|
||||
CompareOperator,
|
||||
Operator,
|
||||
Dot,
|
||||
DotDot,
|
||||
InternalCommand,
|
||||
|
@ -30,8 +30,10 @@ pub enum SyntaxShape {
|
||||
Unit,
|
||||
/// An operator
|
||||
Operator,
|
||||
/// A condition, eg `foo > 1`
|
||||
Condition,
|
||||
/// A parenthesized math expression, eg `(1 + 3)`
|
||||
Parenthesized,
|
||||
/// A math expression, eg `foo > 1`
|
||||
Math,
|
||||
}
|
||||
|
||||
impl PrettyDebug for SyntaxShape {
|
||||
@ -51,7 +53,8 @@ impl PrettyDebug for SyntaxShape {
|
||||
SyntaxShape::Table => "table",
|
||||
SyntaxShape::Unit => "unit",
|
||||
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
|
||||
pub fn duration(secs: u64) -> UntaggedValue {
|
||||
pub fn duration(secs: i64) -> UntaggedValue {
|
||||
UntaggedValue::Primitive(Primitive::Duration(secs))
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ pub enum Primitive {
|
||||
/// A date value, in UTC
|
||||
Date(DateTime<Utc>),
|
||||
/// A count in the number of seconds
|
||||
Duration(u64),
|
||||
Duration(i64),
|
||||
/// A range of values
|
||||
Range(Box<Range>),
|
||||
/// 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
|
||||
pub fn format_duration(sec: u64) -> String {
|
||||
pub fn format_duration(sec: i64) -> String {
|
||||
let (minutes, seconds) = (sec / 60, sec % 60);
|
||||
let (hours, minutes) = (minutes / 60, minutes % 60);
|
||||
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)
|
||||
pub fn format_date(d: &DateTime<Utc>) -> String {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
|
||||
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;
|
||||
|
||||
format!(
|
||||
|
Reference in New Issue
Block a user