mirror of
https://github.com/nushell/nushell.git
synced 2024-11-15 21:14:40 +01:00
Merge pull request #107 from arthur-targaryen/in-not-in-operators
Add `in` and `not-in` operators support
This commit is contained in:
commit
53f3d2572c
@ -190,6 +190,8 @@ pub fn eval_expression(
|
||||
Operator::GreaterThanOrEqual => lhs.gte(op_span, &rhs),
|
||||
Operator::Equal => lhs.eq(op_span, &rhs),
|
||||
Operator::NotEqual => lhs.ne(op_span, &rhs),
|
||||
Operator::In => lhs.r#in(op_span, &rhs),
|
||||
Operator::NotIn => lhs.not_in(op_span, &rhs),
|
||||
x => Err(ShellError::UnsupportedOperator(x, op_span)),
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,50 @@ pub fn math_result_type(
|
||||
)
|
||||
}
|
||||
},
|
||||
Operator::In => match (&lhs.ty, &rhs.ty) {
|
||||
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
|
||||
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
|
||||
(Type::String, Type::String) => (Type::Bool, None),
|
||||
(Type::String, Type::Record(_, _)) => (Type::Bool, None),
|
||||
|
||||
(Type::Unknown, _) => (Type::Bool, None),
|
||||
(_, Type::Unknown) => (Type::Bool, None),
|
||||
_ => {
|
||||
*op = Expression::garbage(op.span);
|
||||
(
|
||||
Type::Unknown,
|
||||
Some(ParseError::UnsupportedOperation(
|
||||
op.span,
|
||||
lhs.span,
|
||||
lhs.ty.clone(),
|
||||
rhs.span,
|
||||
rhs.ty.clone(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
},
|
||||
Operator::NotIn => match (&lhs.ty, &rhs.ty) {
|
||||
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
|
||||
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
|
||||
(Type::String, Type::String) => (Type::Bool, None),
|
||||
(Type::String, Type::Record(_, _)) => (Type::Bool, None),
|
||||
|
||||
(Type::Unknown, _) => (Type::Bool, None),
|
||||
(_, Type::Unknown) => (Type::Bool, None),
|
||||
_ => {
|
||||
*op = Expression::garbage(op.span);
|
||||
(
|
||||
Type::Unknown,
|
||||
Some(ParseError::UnsupportedOperation(
|
||||
op.span,
|
||||
lhs.span,
|
||||
lhs.ty.clone(),
|
||||
rhs.span,
|
||||
rhs.ty.clone(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
*op = Expression::garbage(op.span);
|
||||
|
@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
pub use stream::*;
|
||||
pub use unit::*;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{cmp::Ordering, fmt::Debug};
|
||||
|
||||
use crate::ast::{CellPath, PathMember};
|
||||
use crate::{span, BlockId, Span, Type};
|
||||
@ -441,96 +441,83 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Value {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// Compare two floating point numbers. The decision interval for equality is dynamically
|
||||
// scaled as the value being compared increases in magnitude.
|
||||
fn compare_floats(val: f64, other: f64) -> Option<Ordering> {
|
||||
let prec = f64::EPSILON.max(val.abs() * f64::EPSILON);
|
||||
|
||||
if (other - val).abs() < prec {
|
||||
return Some(Ordering::Equal);
|
||||
}
|
||||
|
||||
val.partial_cmp(&other)
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => lhs.partial_cmp(rhs),
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs.partial_cmp(rhs),
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
compare_floats(*lhs, *rhs)
|
||||
}
|
||||
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
|
||||
lhs.partial_cmp(rhs)
|
||||
}
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
|
||||
compare_floats(*lhs as f64, *rhs)
|
||||
}
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
compare_floats(*lhs, *rhs as f64)
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
lhs.partial_cmp(rhs)
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
lhs.partial_cmp(rhs)
|
||||
}
|
||||
(Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) if b1 == b2 => {
|
||||
Some(Ordering::Equal)
|
||||
}
|
||||
(Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => lhs.partial_cmp(rhs),
|
||||
(
|
||||
Value::Record {
|
||||
vals: lhs,
|
||||
cols: lhs_headers,
|
||||
..
|
||||
},
|
||||
Value::Record {
|
||||
vals: rhs,
|
||||
cols: rhs_headers,
|
||||
..
|
||||
},
|
||||
) if lhs_headers == rhs_headers && lhs == rhs => Some(Ordering::Equal),
|
||||
(Value::Stream { stream: lhs, .. }, Value::Stream { stream: rhs, .. }) => {
|
||||
lhs.clone().partial_cmp(rhs.clone())
|
||||
}
|
||||
(Value::Stream { stream: lhs, .. }, Value::String { val: rhs, .. }) => {
|
||||
lhs.clone().collect_string().partial_cmp(rhs)
|
||||
}
|
||||
(Value::String { val: lhs, .. }, Value::Stream { stream: rhs, .. }) => {
|
||||
lhs.partial_cmp(&rhs.clone().collect_string())
|
||||
}
|
||||
// NOTE: This may look a bit strange, but a `Stream` is still just a `List`, it just
|
||||
// happens to be in an iterator form instead of a concrete form. The contained values
|
||||
// can be compared.
|
||||
(Value::Stream { stream: lhs, .. }, Value::List { vals: rhs, .. }) => {
|
||||
lhs.clone().collect::<Vec<Value>>().partial_cmp(rhs)
|
||||
}
|
||||
(Value::List { vals: lhs, .. }, Value::Stream { stream: rhs, .. }) => {
|
||||
lhs.partial_cmp(&rhs.clone().collect::<Vec<Value>>())
|
||||
}
|
||||
(_, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => lhs == rhs,
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs,
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs,
|
||||
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs,
|
||||
(Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) => b1 == b2,
|
||||
(Value::List { vals: vals_lhs, .. }, Value::List { vals: vals_rhs, .. }) => {
|
||||
for (lhs, rhs) in vals_lhs.iter().zip(vals_rhs) {
|
||||
if lhs != rhs {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
(
|
||||
Value::Record {
|
||||
cols: cols_lhs,
|
||||
vals: vals_lhs,
|
||||
..
|
||||
},
|
||||
Value::Record {
|
||||
cols: cols_rhs,
|
||||
vals: vals_rhs,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
if cols_lhs != cols_rhs {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (lhs, rhs) in vals_lhs.iter().zip(vals_rhs) {
|
||||
if lhs != rhs {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
(
|
||||
Value::Stream {
|
||||
stream: stream_lhs, ..
|
||||
},
|
||||
Value::Stream {
|
||||
stream: stream_rhs, ..
|
||||
},
|
||||
) => {
|
||||
let vals_lhs: Vec<Value> = stream_lhs.clone().collect();
|
||||
let vals_rhs: Vec<Value> = stream_rhs.clone().collect();
|
||||
|
||||
vals_lhs == vals_rhs
|
||||
}
|
||||
// Note: This may look a bit strange, but a Stream is still just a List,
|
||||
// it just happens to be in an iterator form instead of a concrete form. If the contained
|
||||
// values are the same then it should be treated as equal
|
||||
(
|
||||
Value::Stream {
|
||||
stream: stream_lhs, ..
|
||||
},
|
||||
Value::List {
|
||||
vals: stream_rhs, ..
|
||||
},
|
||||
) => {
|
||||
let vals_lhs: Vec<Value> = stream_lhs.clone().collect();
|
||||
let vals_rhs: Vec<Value> =
|
||||
stream_rhs.clone().into_iter().into_value_stream().collect();
|
||||
|
||||
vals_lhs == vals_rhs
|
||||
}
|
||||
// Note: This may look a bit strange, but a Stream is still just a List,
|
||||
// it just happens to be in an iterator form instead of a concrete form. If the contained
|
||||
// values are the same then it should be treated as equal
|
||||
(
|
||||
Value::List {
|
||||
vals: stream_lhs, ..
|
||||
},
|
||||
Value::Stream {
|
||||
stream: stream_rhs, ..
|
||||
},
|
||||
) => {
|
||||
let vals_lhs: Vec<Value> =
|
||||
stream_lhs.clone().into_iter().into_value_stream().collect();
|
||||
let vals_rhs: Vec<Value> = stream_rhs.clone().collect();
|
||||
|
||||
vals_lhs == vals_rhs
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
self.partial_cmp(other).map_or(false, Ordering::is_eq)
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,37 +704,12 @@ impl Value {
|
||||
pub fn lt(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs < rhs,
|
||||
match self.partial_cmp(rhs) {
|
||||
Some(ordering) => Ok(Value::Bool {
|
||||
val: matches!(ordering, Ordering::Less),
|
||||
span,
|
||||
}),
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: (*lhs as f64) < *rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: *lhs < *rhs as f64,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs < rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs < rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs < rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
None => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
@ -759,36 +721,12 @@ impl Value {
|
||||
pub fn lte(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs <= rhs,
|
||||
match self.partial_cmp(rhs) {
|
||||
Some(ordering) => Ok(Value::Bool {
|
||||
val: matches!(ordering, Ordering::Less | Ordering::Equal),
|
||||
span,
|
||||
}),
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: (*lhs as f64) <= *rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: *lhs <= *rhs as f64,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs <= rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs <= rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs <= rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
None => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
@ -800,36 +738,12 @@ impl Value {
|
||||
pub fn gt(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs > rhs,
|
||||
match self.partial_cmp(rhs) {
|
||||
Some(ordering) => Ok(Value::Bool {
|
||||
val: matches!(ordering, Ordering::Greater),
|
||||
span,
|
||||
}),
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: (*lhs as f64) > *rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: *lhs > *rhs as f64,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs > rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs > rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs > rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
None => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
@ -841,36 +755,12 @@ impl Value {
|
||||
pub fn gte(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs >= rhs,
|
||||
match self.partial_cmp(rhs) {
|
||||
Some(ordering) => Ok(Value::Bool {
|
||||
val: matches!(ordering, Ordering::Greater | Ordering::Equal),
|
||||
span,
|
||||
}),
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: (*lhs as f64) >= *rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: *lhs >= *rhs as f64,
|
||||
span,
|
||||
}),
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs >= rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs >= rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs >= rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
None => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
@ -882,62 +772,12 @@ impl Value {
|
||||
pub fn eq(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs == rhs,
|
||||
match self.partial_cmp(rhs) {
|
||||
Some(ordering) => Ok(Value::Bool {
|
||||
val: matches!(ordering, Ordering::Equal),
|
||||
span,
|
||||
}),
|
||||
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs == rhs,
|
||||
span,
|
||||
}),
|
||||
// FIXME: these should consider machine epsilon
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: (*lhs as f64) == *rhs,
|
||||
span,
|
||||
}),
|
||||
// FIXME: these should consider machine epsilon
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: *lhs == *rhs as f64,
|
||||
span,
|
||||
}),
|
||||
// FIXME: these should consider machine epsilon
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs == rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs == rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs == rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs == rhs,
|
||||
span,
|
||||
}),
|
||||
(
|
||||
Value::Record {
|
||||
vals: lhs,
|
||||
cols: lhs_headers,
|
||||
..
|
||||
},
|
||||
Value::Record {
|
||||
vals: rhs,
|
||||
cols: rhs_headers,
|
||||
..
|
||||
},
|
||||
) => Ok(Value::Bool {
|
||||
val: lhs_headers == rhs_headers && lhs == rhs,
|
||||
span,
|
||||
}),
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
None => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
@ -949,62 +789,79 @@ impl Value {
|
||||
pub fn ne(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match self.partial_cmp(rhs) {
|
||||
Some(ordering) => Ok(Value::Bool {
|
||||
val: !matches!(ordering, Ordering::Equal),
|
||||
span,
|
||||
}),
|
||||
None => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
rhs_ty: rhs.get_type(),
|
||||
rhs_span: rhs.span(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#in(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs != rhs,
|
||||
(lhs, Value::Range { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs != rhs,
|
||||
val: rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
// FIXME: these should consider machine epsilon
|
||||
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: (*lhs as f64) != *rhs,
|
||||
(lhs, Value::List { vals: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
// FIXME: these should consider machine epsilon
|
||||
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: *lhs != *rhs as f64,
|
||||
(Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
// FIXME: these should consider machine epsilon
|
||||
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs != rhs,
|
||||
(lhs, Value::Stream { stream: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.clone().any(|x| lhs == &x),
|
||||
span,
|
||||
}),
|
||||
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs != rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Bool {
|
||||
val: lhs != rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool {
|
||||
val: lhs != rhs,
|
||||
span,
|
||||
}),
|
||||
(
|
||||
Value::Record {
|
||||
vals: lhs,
|
||||
cols: lhs_headers,
|
||||
..
|
||||
},
|
||||
Value::Record {
|
||||
vals: rhs,
|
||||
cols: rhs_headers,
|
||||
..
|
||||
},
|
||||
) => Ok(Value::Bool {
|
||||
val: lhs_headers != rhs_headers || lhs != rhs,
|
||||
span,
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
lhs_span: self.span(),
|
||||
rhs_ty: rhs.get_type(),
|
||||
rhs_span: rhs.span(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not_in(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
|
||||
let span = span(&[self.span(), rhs.span()]);
|
||||
|
||||
match (self, rhs) {
|
||||
(lhs, Value::Range { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: !rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: !rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(lhs, Value::List { vals: rhs, .. }) => Ok(Value::Bool {
|
||||
val: !rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool {
|
||||
val: !rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(lhs, Value::Stream { stream: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.clone().all(|x| lhs != &x),
|
||||
span,
|
||||
}),
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: self.get_type(),
|
||||
|
@ -103,6 +103,25 @@ impl Range {
|
||||
inclusion: operator.inclusion,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn moves_up(&self) -> bool {
|
||||
self.from <= self.to
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_end_inclusive(&self) -> bool {
|
||||
matches!(self.inclusion, RangeInclusion::Inclusive)
|
||||
}
|
||||
|
||||
pub fn contains(&self, item: &Value) -> bool {
|
||||
match (item.partial_cmp(&self.from), item.partial_cmp(&self.to)) {
|
||||
(Some(Ordering::Greater | Ordering::Equal), Some(Ordering::Less)) => self.moves_up(),
|
||||
(Some(Ordering::Less | Ordering::Equal), Some(Ordering::Greater)) => !self.moves_up(),
|
||||
(Some(_), Some(Ordering::Equal)) => self.is_end_inclusive(),
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Range {
|
||||
@ -129,6 +148,9 @@ pub struct RangeIterator {
|
||||
|
||||
impl RangeIterator {
|
||||
pub fn new(range: Range, span: Span) -> RangeIterator {
|
||||
let moves_up = range.moves_up();
|
||||
let is_end_inclusive = range.is_end_inclusive();
|
||||
|
||||
let start = match range.from {
|
||||
Value::Nothing { .. } => Value::Int { val: 0, span },
|
||||
x => x,
|
||||
@ -143,29 +165,17 @@ impl RangeIterator {
|
||||
};
|
||||
|
||||
RangeIterator {
|
||||
moves_up: matches!(start.lte(span, &end), Ok(Value::Bool { val: true, .. })),
|
||||
moves_up,
|
||||
curr: start,
|
||||
end,
|
||||
span,
|
||||
is_end_inclusive: matches!(range.inclusion, RangeInclusion::Inclusive),
|
||||
is_end_inclusive,
|
||||
done: false,
|
||||
incr: range.incr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare two floating point numbers. The decision interval for equality is dynamically scaled
|
||||
// as the value being compared increases in magnitude.
|
||||
fn compare_floats(val: f64, other: f64) -> Option<Ordering> {
|
||||
let prec = f64::EPSILON.max(val.abs() * f64::EPSILON);
|
||||
|
||||
if (other - val).abs() < prec {
|
||||
return Some(Ordering::Equal);
|
||||
}
|
||||
|
||||
val.partial_cmp(&other)
|
||||
}
|
||||
|
||||
impl Iterator for RangeIterator {
|
||||
type Item = Value;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@ -176,19 +186,7 @@ impl Iterator for RangeIterator {
|
||||
let ordering = if matches!(self.end, Value::Nothing { .. }) {
|
||||
Some(Ordering::Less)
|
||||
} else {
|
||||
match (&self.curr, &self.end) {
|
||||
(Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)),
|
||||
(Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => {
|
||||
compare_floats(*curr, *end)
|
||||
}
|
||||
(Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => {
|
||||
compare_floats(*curr, *end as f64)
|
||||
}
|
||||
(Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => {
|
||||
compare_floats(*curr as f64, *end)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
self.curr.partial_cmp(&self.end)
|
||||
};
|
||||
|
||||
let ordering = if let Some(ord) = ordering {
|
||||
|
73
src/tests.rs
73
src/tests.rs
@ -569,3 +569,76 @@ fn split_column() -> TestResult {
|
||||
fn for_loops() -> TestResult {
|
||||
run_test(r#"(for x in [1, 2, 3] { $x + 10 }).1"#, "12")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_in_list_of_this_type() -> TestResult {
|
||||
run_test(r#"42 in [41 42 43]"#, "true")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_in_list_of_non_this_type() -> TestResult {
|
||||
fail_test(r#"'hello' in [41 42 43]"#, "mismatched for operation")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_in_string() -> TestResult {
|
||||
run_test(r#"'z' in 'abc'"#, "false")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_string_in_string() -> TestResult {
|
||||
fail_test(r#"42 in 'abc'"#, "mismatched for operation")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_in_inc_range() -> TestResult {
|
||||
run_test(r#"1 in -4..9.42"#, "true")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_in_dec_range() -> TestResult {
|
||||
run_test(r#"1 in 9.42..-4"#, "true")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_in_exclusive_range() -> TestResult {
|
||||
run_test(r#"3 in 0..<3"#, "false")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_number_in_range() -> TestResult {
|
||||
fail_test(r#"'a' in 1..3"#, "mismatched for operation")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_in_record() -> TestResult {
|
||||
run_test(r#""a" in ('{ "a": 13, "b": 14 }' | from json)"#, "true")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_string_in_record() -> TestResult {
|
||||
fail_test(
|
||||
r#"4 in ('{ "a": 13, "b": 14 }' | from json)"#,
|
||||
"mismatch during operation",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_in_valuestream() -> TestResult {
|
||||
run_test(
|
||||
r#"
|
||||
'Hello' in ("Hello
|
||||
World" | lines)"#,
|
||||
"true",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_not_in_string() -> TestResult {
|
||||
run_test(r#"'d' not-in 'abc'"#, "true")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_not_in_inc_range() -> TestResult {
|
||||
run_test(r#"1.4 not-in 2..9.42"#, "true")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user