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:
Jonathan Turner
2020-04-18 13:50:58 +12:00
committed by GitHub
parent 52d2d2b888
commit 7974e09eeb
21 changed files with 721 additions and 193 deletions

View File

@ -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,

View File

@ -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",
})
}
}

View File

@ -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))
}

View File

@ -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!(