nushell/crates/nu-protocol/src/value/mod.rs
Ian Manske 7f61cbbfd6
Add Filesize type (#14369)
# Description
Adds a new `Filesize` type so that `FromValue` can be used to convert a
`Value::Filesize` to a `Filesize`. Currently, to extract a filesize from
a `Value` using `FromValue`, you have to extract an `i64` which coerces
`Value::Int`, `Value::Duration`, and `Value::Filesize` to an `i64`.

Having a separate type also allows us to enforce checked math to catch
overflows. Similarly, it allows us to specify other trait
implementations like `Display` in a common place.

# User-Facing Changes
Multiplication with filesizes now error on overflow. Should not be a
breaking change for plugins (i.e., serialization) since `Filesize` is
marked with `serde(transparent)`.

# Tests + Formatting
Updated some tests.
2024-11-29 21:24:17 +00:00

3878 lines
158 KiB
Rust

mod custom_value;
mod duration;
mod filesize;
mod from_value;
mod glob;
mod into_value;
mod range;
#[cfg(test)]
mod test_derive;
pub mod record;
pub use custom_value::CustomValue;
pub use duration::*;
pub use filesize::*;
pub use from_value::FromValue;
pub use glob::*;
pub use into_value::{IntoValue, TryIntoValue};
pub use range::{FloatRange, IntRange, Range};
pub use record::Record;
use crate::{
ast::{Bits, Boolean, CellPath, Comparison, Math, Operator, PathMember},
did_you_mean,
engine::{Closure, EngineState},
BlockId, Config, ShellError, Signals, Span, Type,
};
use chrono::{DateTime, Datelike, FixedOffset, Locale, TimeZone};
use chrono_humanize::HumanTime;
use fancy_regex::Regex;
use nu_utils::{
contains_emoji,
locale::{get_system_locale_string, LOCALE_OVERRIDE_ENV_VAR},
IgnoreCaseExt, SharedCow,
};
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
cmp::Ordering,
fmt::{Debug, Display, Write},
ops::Bound,
path::PathBuf,
};
/// Core structured values that pass through the pipeline in Nushell.
// NOTE: Please do not reorder these enum cases without thinking through the
// impact on the PartialOrd implementation and the global sort order
#[derive(Debug, Serialize, Deserialize)]
pub enum Value {
Bool {
val: bool,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Int {
val: i64,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Float {
val: f64,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
String {
val: String,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Glob {
val: String,
no_expand: bool,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Filesize {
val: Filesize,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Duration {
val: i64,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Date {
val: DateTime<FixedOffset>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Range {
val: Box<Range>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Record {
val: SharedCow<Record>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
List {
vals: Vec<Value>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Closure {
val: Box<Closure>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Error {
error: Box<ShellError>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Binary {
val: Vec<u8>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
CellPath {
val: CellPath,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Custom {
val: Box<dyn CustomValue>,
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
Nothing {
// note: spans are being refactored out of Value
// please use .span() instead of matching this span value
#[serde(rename = "span")]
internal_span: Span,
},
}
impl Clone for Value {
fn clone(&self) -> Self {
match self {
Value::Bool { val, internal_span } => Value::bool(*val, *internal_span),
Value::Int { val, internal_span } => Value::int(*val, *internal_span),
Value::Filesize { val, internal_span } => Value::Filesize {
val: *val,
internal_span: *internal_span,
},
Value::Duration { val, internal_span } => Value::Duration {
val: *val,
internal_span: *internal_span,
},
Value::Date { val, internal_span } => Value::Date {
val: *val,
internal_span: *internal_span,
},
Value::Range { val, internal_span } => Value::Range {
val: val.clone(),
internal_span: *internal_span,
},
Value::Float { val, internal_span } => Value::float(*val, *internal_span),
Value::String { val, internal_span } => Value::String {
val: val.clone(),
internal_span: *internal_span,
},
Value::Glob {
val,
no_expand: quoted,
internal_span,
} => Value::Glob {
val: val.clone(),
no_expand: *quoted,
internal_span: *internal_span,
},
Value::Record { val, internal_span } => Value::Record {
val: val.clone(),
internal_span: *internal_span,
},
Value::List {
vals,
internal_span,
} => Value::List {
vals: vals.clone(),
internal_span: *internal_span,
},
Value::Closure { val, internal_span } => Value::Closure {
val: val.clone(),
internal_span: *internal_span,
},
Value::Nothing { internal_span } => Value::Nothing {
internal_span: *internal_span,
},
Value::Error {
error,
internal_span,
} => Value::Error {
error: error.clone(),
internal_span: *internal_span,
},
Value::Binary { val, internal_span } => Value::Binary {
val: val.clone(),
internal_span: *internal_span,
},
Value::CellPath { val, internal_span } => Value::CellPath {
val: val.clone(),
internal_span: *internal_span,
},
Value::Custom { val, internal_span } => val.clone_value(*internal_span),
}
}
}
impl Value {
fn cant_convert_to<T>(&self, typ: &str) -> Result<T, ShellError> {
Err(ShellError::CantConvert {
to_type: typ.into(),
from_type: self.get_type().to_string(),
span: self.span(),
help: None,
})
}
/// Returns the inner `bool` value or an error if this `Value` is not a bool
pub fn as_bool(&self) -> Result<bool, ShellError> {
if let Value::Bool { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("boolean")
}
}
/// Returns the inner `i64` value or an error if this `Value` is not an int
pub fn as_int(&self) -> Result<i64, ShellError> {
if let Value::Int { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("int")
}
}
/// Returns the inner `f64` value or an error if this `Value` is not a float
pub fn as_float(&self) -> Result<f64, ShellError> {
if let Value::Float { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("float")
}
}
/// Returns this `Value` converted to a `f64` or an error if it cannot be converted
///
/// Only the following `Value` cases will return an `Ok` result:
/// - `Int`
/// - `Float`
///
/// ```
/// # use nu_protocol::Value;
/// for val in Value::test_values() {
/// assert_eq!(
/// matches!(val, Value::Float { .. } | Value::Int { .. }),
/// val.coerce_float().is_ok(),
/// );
/// }
/// ```
pub fn coerce_float(&self) -> Result<f64, ShellError> {
match self {
Value::Float { val, .. } => Ok(*val),
Value::Int { val, .. } => Ok(*val as f64),
val => val.cant_convert_to("float"),
}
}
/// Returns the inner `i64` filesize value or an error if this `Value` is not a filesize
pub fn as_filesize(&self) -> Result<Filesize, ShellError> {
if let Value::Filesize { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("filesize")
}
}
/// Returns the inner `i64` duration value or an error if this `Value` is not a duration
pub fn as_duration(&self) -> Result<i64, ShellError> {
if let Value::Duration { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("duration")
}
}
/// Returns the inner [`DateTime`] value or an error if this `Value` is not a date
pub fn as_date(&self) -> Result<DateTime<FixedOffset>, ShellError> {
if let Value::Date { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("date")
}
}
/// Returns a reference to the inner [`Range`] value or an error if this `Value` is not a range
pub fn as_range(&self) -> Result<Range, ShellError> {
if let Value::Range { val, .. } = self {
Ok(**val)
} else {
self.cant_convert_to("range")
}
}
/// Unwraps the inner [`Range`] value or returns an error if this `Value` is not a range
pub fn into_range(self) -> Result<Range, ShellError> {
if let Value::Range { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("range")
}
}
/// Returns a reference to the inner `str` value or an error if this `Value` is not a string
pub fn as_str(&self) -> Result<&str, ShellError> {
if let Value::String { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("string")
}
}
/// Unwraps the inner `String` value or returns an error if this `Value` is not a string
pub fn into_string(self) -> Result<String, ShellError> {
if let Value::String { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("string")
}
}
/// Returns this `Value` converted to a `str` or an error if it cannot be converted
///
/// Only the following `Value` cases will return an `Ok` result:
/// - `Int`
/// - `Float`
/// - `String`
/// - `Glob`
/// - `Binary` (only if valid utf-8)
/// - `Date`
///
/// ```
/// # use nu_protocol::Value;
/// for val in Value::test_values() {
/// assert_eq!(
/// matches!(
/// val,
/// Value::Int { .. }
/// | Value::Float { .. }
/// | Value::String { .. }
/// | Value::Glob { .. }
/// | Value::Binary { .. }
/// | Value::Date { .. }
/// ),
/// val.coerce_str().is_ok(),
/// );
/// }
/// ```
pub fn coerce_str(&self) -> Result<Cow<str>, ShellError> {
match self {
Value::Int { val, .. } => Ok(Cow::Owned(val.to_string())),
Value::Float { val, .. } => Ok(Cow::Owned(val.to_string())),
Value::String { val, .. } => Ok(Cow::Borrowed(val)),
Value::Glob { val, .. } => Ok(Cow::Borrowed(val)),
Value::Binary { val, .. } => match std::str::from_utf8(val) {
Ok(s) => Ok(Cow::Borrowed(s)),
Err(_) => self.cant_convert_to("string"),
},
Value::Date { val, .. } => Ok(Cow::Owned(
val.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
)),
val => val.cant_convert_to("string"),
}
}
/// Returns this `Value` converted to a `String` or an error if it cannot be converted
///
/// # Note
/// This function is equivalent to `value.coerce_str().map(Cow::into_owned)`
/// which might allocate a new `String`.
///
/// To avoid this allocation, prefer [`coerce_str`](Self::coerce_str)
/// if you do not need an owned `String`,
/// or [`coerce_into_string`](Self::coerce_into_string)
/// if you do not need to keep the original `Value` around.
///
/// Only the following `Value` cases will return an `Ok` result:
/// - `Int`
/// - `Float`
/// - `String`
/// - `Glob`
/// - `Binary` (only if valid utf-8)
/// - `Date`
///
/// ```
/// # use nu_protocol::Value;
/// for val in Value::test_values() {
/// assert_eq!(
/// matches!(
/// val,
/// Value::Int { .. }
/// | Value::Float { .. }
/// | Value::String { .. }
/// | Value::Glob { .. }
/// | Value::Binary { .. }
/// | Value::Date { .. }
/// ),
/// val.coerce_string().is_ok(),
/// );
/// }
/// ```
pub fn coerce_string(&self) -> Result<String, ShellError> {
self.coerce_str().map(Cow::into_owned)
}
/// Returns this `Value` converted to a `String` or an error if it cannot be converted
///
/// Only the following `Value` cases will return an `Ok` result:
/// - `Int`
/// - `Float`
/// - `String`
/// - `Glob`
/// - `Binary` (only if valid utf-8)
/// - `Date`
///
/// ```
/// # use nu_protocol::Value;
/// for val in Value::test_values() {
/// assert_eq!(
/// matches!(
/// val,
/// Value::Int { .. }
/// | Value::Float { .. }
/// | Value::String { .. }
/// | Value::Glob { .. }
/// | Value::Binary { .. }
/// | Value::Date { .. }
/// ),
/// val.coerce_into_string().is_ok(),
/// );
/// }
/// ```
pub fn coerce_into_string(self) -> Result<String, ShellError> {
let span = self.span();
match self {
Value::Int { val, .. } => Ok(val.to_string()),
Value::Float { val, .. } => Ok(val.to_string()),
Value::String { val, .. } => Ok(val),
Value::Glob { val, .. } => Ok(val),
Value::Binary { val, .. } => match String::from_utf8(val) {
Ok(s) => Ok(s),
Err(err) => Value::binary(err.into_bytes(), span).cant_convert_to("string"),
},
Value::Date { val, .. } => Ok(val.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)),
val => val.cant_convert_to("string"),
}
}
/// Returns this `Value` as a `char` or an error if it is not a single character string
pub fn as_char(&self) -> Result<char, ShellError> {
let span = self.span();
if let Value::String { val, .. } = self {
let mut chars = val.chars();
match (chars.next(), chars.next()) {
(Some(c), None) => Ok(c),
_ => Err(ShellError::MissingParameter {
param_name: "single character separator".into(),
span,
}),
}
} else {
self.cant_convert_to("char")
}
}
/// Converts this `Value` to a `PathBuf` or returns an error if it is not a string
pub fn to_path(&self) -> Result<PathBuf, ShellError> {
if let Value::String { val, .. } = self {
Ok(PathBuf::from(val))
} else {
self.cant_convert_to("path")
}
}
/// Returns a reference to the inner [`Record`] value or an error if this `Value` is not a record
pub fn as_record(&self) -> Result<&Record, ShellError> {
if let Value::Record { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("record")
}
}
/// Unwraps the inner [`Record`] value or returns an error if this `Value` is not a record
pub fn into_record(self) -> Result<Record, ShellError> {
if let Value::Record { val, .. } = self {
Ok(val.into_owned())
} else {
self.cant_convert_to("record")
}
}
/// Returns a reference to the inner list slice or an error if this `Value` is not a list
pub fn as_list(&self) -> Result<&[Value], ShellError> {
if let Value::List { vals, .. } = self {
Ok(vals)
} else {
self.cant_convert_to("list")
}
}
/// Unwraps the inner list `Vec` or returns an error if this `Value` is not a list
pub fn into_list(self) -> Result<Vec<Value>, ShellError> {
if let Value::List { vals, .. } = self {
Ok(vals)
} else {
self.cant_convert_to("list")
}
}
/// Returns a reference to the inner [`Closure`] value or an error if this `Value` is not a closure
pub fn as_closure(&self) -> Result<&Closure, ShellError> {
if let Value::Closure { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("closure")
}
}
/// Unwraps the inner [`Closure`] value or returns an error if this `Value` is not a closure
pub fn into_closure(self) -> Result<Closure, ShellError> {
if let Value::Closure { val, .. } = self {
Ok(*val)
} else {
self.cant_convert_to("closure")
}
}
/// Returns a reference to the inner binary slice or an error if this `Value` is not a binary value
pub fn as_binary(&self) -> Result<&[u8], ShellError> {
if let Value::Binary { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("binary")
}
}
/// Unwraps the inner binary `Vec` or returns an error if this `Value` is not a binary value
pub fn into_binary(self) -> Result<Vec<u8>, ShellError> {
if let Value::Binary { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("binary")
}
}
/// Returns this `Value` as a `u8` slice or an error if it cannot be converted
///
/// Prefer [`coerce_into_binary`](Self::coerce_into_binary)
/// if you do not need to keep the original `Value` around.
///
/// Only the following `Value` cases will return an `Ok` result:
/// - `Binary`
/// - `String`
///
/// ```
/// # use nu_protocol::Value;
/// for val in Value::test_values() {
/// assert_eq!(
/// matches!(val, Value::Binary { .. } | Value::String { .. }),
/// val.coerce_binary().is_ok(),
/// );
/// }
/// ```
pub fn coerce_binary(&self) -> Result<&[u8], ShellError> {
match self {
Value::Binary { val, .. } => Ok(val),
Value::String { val, .. } => Ok(val.as_bytes()),
val => val.cant_convert_to("binary"),
}
}
/// Returns this `Value` as a `Vec<u8>` or an error if it cannot be converted
///
/// Only the following `Value` cases will return an `Ok` result:
/// - `Binary`
/// - `String`
///
/// ```
/// # use nu_protocol::Value;
/// for val in Value::test_values() {
/// assert_eq!(
/// matches!(val, Value::Binary { .. } | Value::String { .. }),
/// val.coerce_into_binary().is_ok(),
/// );
/// }
/// ```
pub fn coerce_into_binary(self) -> Result<Vec<u8>, ShellError> {
match self {
Value::Binary { val, .. } => Ok(val),
Value::String { val, .. } => Ok(val.into_bytes()),
val => val.cant_convert_to("binary"),
}
}
/// Returns a reference to the inner [`CellPath`] value or an error if this `Value` is not a cell path
pub fn as_cell_path(&self) -> Result<&CellPath, ShellError> {
if let Value::CellPath { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("cell path")
}
}
/// Unwraps the inner [`CellPath`] value or returns an error if this `Value` is not a cell path
pub fn into_cell_path(self) -> Result<CellPath, ShellError> {
if let Value::CellPath { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("cell path")
}
}
/// Returns a reference to the inner [`CustomValue`] trait object or an error if this `Value` is not a custom value
pub fn as_custom_value(&self) -> Result<&dyn CustomValue, ShellError> {
if let Value::Custom { val, .. } = self {
Ok(val.as_ref())
} else {
self.cant_convert_to("custom value")
}
}
/// Unwraps the inner [`CustomValue`] trait object or returns an error if this `Value` is not a custom value
pub fn into_custom_value(self) -> Result<Box<dyn CustomValue>, ShellError> {
if let Value::Custom { val, .. } = self {
Ok(val)
} else {
self.cant_convert_to("custom value")
}
}
/// Get the span for the current value
pub fn span(&self) -> Span {
match self {
Value::Bool { internal_span, .. }
| Value::Int { internal_span, .. }
| Value::Float { internal_span, .. }
| Value::Filesize { internal_span, .. }
| Value::Duration { internal_span, .. }
| Value::Date { internal_span, .. }
| Value::Range { internal_span, .. }
| Value::String { internal_span, .. }
| Value::Glob { internal_span, .. }
| Value::Record { internal_span, .. }
| Value::List { internal_span, .. }
| Value::Closure { internal_span, .. }
| Value::Nothing { internal_span, .. }
| Value::Binary { internal_span, .. }
| Value::CellPath { internal_span, .. }
| Value::Custom { internal_span, .. }
| Value::Error { internal_span, .. } => *internal_span,
}
}
/// Set the value's span to a new span
pub fn set_span(&mut self, new_span: Span) {
match self {
Value::Bool { internal_span, .. }
| Value::Int { internal_span, .. }
| Value::Float { internal_span, .. }
| Value::Filesize { internal_span, .. }
| Value::Duration { internal_span, .. }
| Value::Date { internal_span, .. }
| Value::Range { internal_span, .. }
| Value::String { internal_span, .. }
| Value::Glob { internal_span, .. }
| Value::Record { internal_span, .. }
| Value::List { internal_span, .. }
| Value::Closure { internal_span, .. }
| Value::Nothing { internal_span, .. }
| Value::Binary { internal_span, .. }
| Value::CellPath { internal_span, .. }
| Value::Custom { internal_span, .. } => *internal_span = new_span,
Value::Error { .. } => (),
}
}
/// Update the value with a new span
pub fn with_span(mut self, new_span: Span) -> Value {
self.set_span(new_span);
self
}
/// Get the type of the current Value
pub fn get_type(&self) -> Type {
match self {
Value::Bool { .. } => Type::Bool,
Value::Int { .. } => Type::Int,
Value::Float { .. } => Type::Float,
Value::Filesize { .. } => Type::Filesize,
Value::Duration { .. } => Type::Duration,
Value::Date { .. } => Type::Date,
Value::Range { .. } => Type::Range,
Value::String { .. } => Type::String,
Value::Glob { .. } => Type::Glob,
Value::Record { val, .. } => {
Type::Record(val.iter().map(|(x, y)| (x.clone(), y.get_type())).collect())
}
Value::List { vals, .. } => {
let mut ty = None;
for val in vals {
let val_ty = val.get_type();
match &ty {
Some(x) => {
if &val_ty != x {
if x.is_numeric() && val_ty.is_numeric() {
ty = Some(Type::Number)
} else {
ty = Some(Type::Any);
break;
}
}
}
None => ty = Some(val_ty),
}
}
match ty {
Some(Type::Record(columns)) => Type::Table(columns),
Some(ty) => Type::List(Box::new(ty)),
None => Type::List(Box::new(Type::Any)),
}
}
Value::Nothing { .. } => Type::Nothing,
Value::Closure { .. } => Type::Closure,
Value::Error { .. } => Type::Error,
Value::Binary { .. } => Type::Binary,
Value::CellPath { .. } => Type::CellPath,
Value::Custom { val, .. } => Type::Custom(val.type_name().into()),
}
}
pub fn get_data_by_key(&self, name: &str) -> Option<Value> {
let span = self.span();
match self {
Value::Record { val, .. } => val.get(name).cloned(),
Value::List { vals, .. } => {
let out = vals
.iter()
.map(|item| {
item.as_record()
.ok()
.and_then(|val| val.get(name).cloned())
.unwrap_or(Value::nothing(span))
})
.collect::<Vec<_>>();
if !out.is_empty() {
Some(Value::list(out, span))
} else {
None
}
}
_ => None,
}
}
fn format_datetime<Tz: TimeZone>(&self, date_time: &DateTime<Tz>, formatter: &str) -> String
where
Tz::Offset: Display,
{
let mut formatter_buf = String::new();
let locale = if let Ok(l) =
std::env::var(LOCALE_OVERRIDE_ENV_VAR).or_else(|_| std::env::var("LC_TIME"))
{
let locale_str = l.split('.').next().unwrap_or("en_US");
locale_str.try_into().unwrap_or(Locale::en_US)
} else {
// LC_ALL > LC_CTYPE > LANG else en_US
get_system_locale_string()
.map(|l| l.replace('-', "_")) // `chrono::Locale` needs something like `xx_xx`, rather than `xx-xx`
.unwrap_or_else(|| String::from("en_US"))
.as_str()
.try_into()
.unwrap_or(Locale::en_US)
};
let format = date_time.format_localized(formatter, locale);
match formatter_buf.write_fmt(format_args!("{format}")) {
Ok(_) => (),
Err(_) => formatter_buf = format!("Invalid format string {}", formatter),
}
formatter_buf
}
/// Converts this `Value` to a string according to the given [`Config`] and separator
///
/// This functions recurses into records and lists,
/// returning a string that contains the stringified form of all nested `Value`s.
pub fn to_expanded_string(&self, separator: &str, config: &Config) -> String {
let span = self.span();
match self {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_string(),
Value::Filesize { val, .. } => format_filesize_from_conf(*val, config),
Value::Duration { val, .. } => format_duration(*val),
Value::Date { val, .. } => match &config.datetime_format.normal {
Some(format) => self.format_datetime(val, format),
None => {
format!(
"{} ({})",
if val.year() >= 0 {
val.to_rfc2822()
} else {
val.to_rfc3339()
},
HumanTime::from(*val),
)
}
},
Value::Range { val, .. } => val.to_string(),
Value::String { val, .. } => val.clone(),
Value::Glob { val, .. } => val.clone(),
Value::List { vals: val, .. } => format!(
"[{}]",
val.iter()
.map(|x| x.to_expanded_string(", ", config))
.collect::<Vec<_>>()
.join(separator)
),
Value::Record { val, .. } => format!(
"{{{}}}",
val.iter()
.map(|(x, y)| format!("{}: {}", x, y.to_expanded_string(", ", config)))
.collect::<Vec<_>>()
.join(separator)
),
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id.get()),
Value::Nothing { .. } => String::new(),
Value::Error { error, .. } => format!("{error:?}"),
Value::Binary { val, .. } => format!("{val:?}"),
Value::CellPath { val, .. } => val.to_string(),
// If we fail to collapse the custom value, just print <{type_name}> - failure is not
// that critical here
Value::Custom { val, .. } => val
.to_base_value(span)
.map(|val| val.to_expanded_string(separator, config))
.unwrap_or_else(|_| format!("<{}>", val.type_name())),
}
}
/// Converts this `Value` to a string according to the given [`Config`]
///
/// This functions does not recurse into records and lists.
/// Instead, it will shorten the first list or record it finds like so:
/// - "[table {n} rows]"
/// - "[list {n} items]"
/// - "[record {n} fields]"
pub fn to_abbreviated_string(&self, config: &Config) -> String {
match self {
Value::Date { val, .. } => match &config.datetime_format.table {
Some(format) => self.format_datetime(val, format),
None => HumanTime::from(*val).to_string(),
},
Value::List { ref vals, .. } => {
if !vals.is_empty() && vals.iter().all(|x| matches!(x, Value::Record { .. })) {
format!(
"[table {} row{}]",
vals.len(),
if vals.len() == 1 { "" } else { "s" }
)
} else {
format!(
"[list {} item{}]",
vals.len(),
if vals.len() == 1 { "" } else { "s" }
)
}
}
Value::Record { val, .. } => format!(
"{{record {} field{}}}",
val.len(),
if val.len() == 1 { "" } else { "s" }
),
val => val.to_expanded_string(", ", config),
}
}
/// Converts this `Value` to a string according to the given [`Config`] and separator
///
/// This function adds quotes around strings,
/// so that the returned string can be parsed by nushell.
/// The other `Value` cases are already parsable when converted strings
/// or are not yet handled by this function.
///
/// This functions behaves like [`to_expanded_string`](Self::to_expanded_string)
/// and will recurse into records and lists.
pub fn to_parsable_string(&self, separator: &str, config: &Config) -> String {
match self {
// give special treatment to the simple types to make them parsable
Value::String { val, .. } => format!("'{}'", val),
// recurse back into this function for recursive formatting
Value::List { vals: val, .. } => format!(
"[{}]",
val.iter()
.map(|x| x.to_parsable_string(", ", config))
.collect::<Vec<_>>()
.join(separator)
),
Value::Record { val, .. } => format!(
"{{{}}}",
val.iter()
.map(|(x, y)| format!("{}: {}", x, y.to_parsable_string(", ", config)))
.collect::<Vec<_>>()
.join(separator)
),
// defer to standard handling for types where standard representation is parsable
_ => self.to_expanded_string(separator, config),
}
}
/// Convert this `Value` to a debug string
///
/// In general, this function should only be used for debug purposes,
/// and the resulting string should not be displayed to the user (not even in an error).
pub fn to_debug_string(&self) -> String {
match self {
Value::String { val, .. } => {
if contains_emoji(val) {
// This has to be an emoji, so let's display the code points that make it up.
format!(
"{:#?}",
Value::string(val.escape_unicode().to_string(), self.span())
)
} else {
format!("{self:#?}")
}
}
_ => format!("{self:#?}"),
}
}
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
pub fn follow_cell_path(
self,
cell_path: &[PathMember],
insensitive: bool,
) -> Result<Value, ShellError> {
let mut current = self;
for member in cell_path {
match member {
PathMember::Int {
val: count,
span: origin_span,
optional,
} => {
// Treat a numeric path member as `select <val>`
match current {
Value::List { mut vals, .. } => {
if *count < vals.len() {
// `vals` is owned and will be dropped right after this,
// so we can `swap_remove` the value at index `count`
// without worrying about preserving order.
current = vals.swap_remove(*count);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else {
return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *origin_span,
});
}
}
Value::Binary { val, .. } => {
if let Some(item) = val.get(*count) {
current = Value::int(*item as i64, *origin_span);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if val.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else {
return Err(ShellError::AccessBeyondEnd {
max_idx: val.len() - 1,
span: *origin_span,
});
}
}
Value::Range { ref val, .. } => {
if let Some(item) = val
.into_range_iter(current.span(), Signals::empty())
.nth(*count)
{
current = item;
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else {
return Err(ShellError::AccessBeyondEndOfStream {
span: *origin_span,
});
}
}
Value::Custom { ref val, .. } => {
current =
match val.follow_path_int(current.span(), *count, *origin_span) {
Ok(val) => val,
Err(err) => {
if *optional {
return Ok(Value::nothing(*origin_span));
// short-circuit
} else {
return Err(err);
}
}
};
}
Value::Nothing { .. } if *optional => {
return Ok(Value::nothing(*origin_span)); // short-circuit
}
// Records (and tables) are the only built-in which support column names,
// so only use this message for them.
Value::Record { .. } => {
return Err(ShellError::TypeMismatch {
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
span: *origin_span,
});
}
Value::Error { error, .. } => return Err(*error),
x => {
return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
});
}
}
}
PathMember::String {
val: column_name,
span: origin_span,
optional,
} => {
let span = current.span();
match current {
Value::Record { mut val, .. } => {
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
if let Some(found) = val.to_mut().iter_mut().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
}) {
current = std::mem::take(found.1);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
return Err(ShellError::DidYouMean {
suggestion,
span: *origin_span,
});
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: span,
});
}
}
// String access of Lists always means Table access.
// Create a List which contains each matching value for contained
// records in the source list.
Value::List { vals, .. } => {
let list = vals
.into_iter()
.map(|val| {
let val_span = val.span();
match val {
Value::Record { mut val, .. } => {
if let Some(found) =
val.to_mut().iter_mut().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
})
{
Ok(std::mem::take(found.1))
} else if *optional {
Ok(Value::nothing(*origin_span))
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
Err(ShellError::DidYouMean {
suggestion,
span: *origin_span,
})
} else {
Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: val_span,
})
}
}
Value::Nothing { .. } if *optional => {
Ok(Value::nothing(*origin_span))
}
_ => Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: val_span,
}),
}
})
.collect::<Result<_, _>>()?;
current = Value::list(list, span);
}
Value::Custom { ref val, .. } => {
current = match val.follow_path_string(
current.span(),
column_name.clone(),
*origin_span,
) {
Ok(val) => val,
Err(err) => {
if *optional {
return Ok(Value::nothing(*origin_span));
// short-circuit
} else {
return Err(err);
}
}
}
}
Value::Nothing { .. } if *optional => {
return Ok(Value::nothing(*origin_span)); // short-circuit
}
Value::Error { error, .. } => return Err(*error),
x => {
return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
});
}
}
}
}
}
// If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now.
// Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used.
if let Value::Error { error, .. } = current {
Err(*error)
} else {
Ok(current)
}
}
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
pub fn upsert_cell_path(
&mut self,
cell_path: &[PathMember],
callback: Box<dyn FnOnce(&Value) -> Value>,
) -> Result<(), ShellError> {
let orig = self.clone();
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
match new_val {
Value::Error { error, .. } => Err(*error),
new_val => self.upsert_data_at_cell_path(cell_path, new_val),
}
}
pub fn upsert_data_at_cell_path(
&mut self,
cell_path: &[PathMember],
new_val: Value,
) -> Result<(), ShellError> {
let v_span = self.span();
if let Some((member, path)) = cell_path.split_first() {
match member {
PathMember::String {
val: col_name,
span,
..
} => match self {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
match val {
Value::Record { val: record, .. } => {
let record = record.to_mut();
if let Some(val) = record.get_mut(col_name) {
val.upsert_data_at_cell_path(path, new_val.clone())?;
} else {
let new_col = if path.is_empty() {
new_val.clone()
} else {
let mut new_col =
Value::record(Record::new(), new_val.span());
new_col
.upsert_data_at_cell_path(path, new_val.clone())?;
new_col
};
record.push(col_name, new_col);
}
}
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
});
}
}
}
}
Value::Record { val: record, .. } => {
let record = record.to_mut();
if let Some(val) = record.get_mut(col_name) {
val.upsert_data_at_cell_path(path, new_val)?;
} else {
let new_col = if path.is_empty() {
new_val
} else {
let mut new_col = Value::record(Record::new(), new_val.span());
new_col.upsert_data_at_cell_path(path, new_val)?;
new_col
};
record.push(col_name, new_col);
}
}
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
});
}
},
PathMember::Int {
val: row_num, span, ..
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.upsert_data_at_cell_path(path, new_val)?;
} else if vals.len() != *row_num {
return Err(ShellError::InsertAfterNextFreeIndex {
available_idx: vals.len(),
span: *span,
});
} else if !path.is_empty() {
return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *span,
});
} else {
// If the upsert is at 1 + the end of the list, it's OK.
vals.push(new_val);
}
}
Value::Error { error, .. } => return Err(*error.clone()),
_ => {
return Err(ShellError::NotAList {
dst_span: *span,
src_span: v_span,
});
}
},
}
} else {
*self = new_val;
}
Ok(())
}
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
pub fn update_cell_path<'a>(
&mut self,
cell_path: &[PathMember],
callback: Box<dyn FnOnce(&Value) -> Value + 'a>,
) -> Result<(), ShellError> {
let orig = self.clone();
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
match new_val {
Value::Error { error, .. } => Err(*error),
new_val => self.update_data_at_cell_path(cell_path, new_val),
}
}
pub fn update_data_at_cell_path(
&mut self,
cell_path: &[PathMember],
new_val: Value,
) -> Result<(), ShellError> {
let v_span = self.span();
if let Some((member, path)) = cell_path.split_first() {
match member {
PathMember::String {
val: col_name,
span,
..
} => match self {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
if let Some(val) = record.to_mut().get_mut(col_name) {
val.update_data_at_cell_path(path, new_val.clone())?;
} else {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v_span,
});
}
}
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
});
}
}
}
}
Value::Record { val: record, .. } => {
if let Some(val) = record.to_mut().get_mut(col_name) {
val.update_data_at_cell_path(path, new_val)?;
} else {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v_span,
});
}
}
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
});
}
},
PathMember::Int {
val: row_num, span, ..
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.update_data_at_cell_path(path, new_val)?;
} else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *span });
} else {
return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *span,
});
}
}
Value::Error { error, .. } => return Err(*error.clone()),
v => {
return Err(ShellError::NotAList {
dst_span: *span,
src_span: v.span(),
});
}
},
}
} else {
*self = new_val;
}
Ok(())
}
pub fn remove_data_at_cell_path(&mut self, cell_path: &[PathMember]) -> Result<(), ShellError> {
match cell_path {
[] => Ok(()),
[member] => {
let v_span = self.span();
match member {
PathMember::String {
val: col_name,
span,
optional,
} => match self {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
if record.to_mut().remove(col_name).is_none() && !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v_span,
});
}
}
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
});
}
}
}
Ok(())
}
Value::Record { val: record, .. } => {
if record.to_mut().remove(col_name).is_none() && !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v_span,
});
}
Ok(())
}
v => Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
}),
},
PathMember::Int {
val: row_num,
span,
optional,
} => match self {
Value::List { vals, .. } => {
if vals.get_mut(*row_num).is_some() {
vals.remove(*row_num);
Ok(())
} else if *optional {
Ok(())
} else if vals.is_empty() {
Err(ShellError::AccessEmptyContent { span: *span })
} else {
Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *span,
})
}
}
v => Err(ShellError::NotAList {
dst_span: *span,
src_span: v.span(),
}),
},
}
}
[member, path @ ..] => {
let v_span = self.span();
match member {
PathMember::String {
val: col_name,
span,
optional,
} => match self {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
if let Some(val) = record.to_mut().get_mut(col_name) {
val.remove_data_at_cell_path(path)?;
} else if !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v_span,
});
}
}
v => {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
});
}
}
}
Ok(())
}
Value::Record { val: record, .. } => {
if let Some(val) = record.to_mut().get_mut(col_name) {
val.remove_data_at_cell_path(path)?;
} else if !optional {
return Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v_span,
});
}
Ok(())
}
v => Err(ShellError::CantFindColumn {
col_name: col_name.clone(),
span: Some(*span),
src_span: v.span(),
}),
},
PathMember::Int {
val: row_num,
span,
optional,
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
v.remove_data_at_cell_path(path)
} else if *optional {
Ok(())
} else if vals.is_empty() {
Err(ShellError::AccessEmptyContent { span: *span })
} else {
Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *span,
})
}
}
v => Err(ShellError::NotAList {
dst_span: *span,
src_span: v.span(),
}),
},
}
}
}
}
pub fn insert_data_at_cell_path(
&mut self,
cell_path: &[PathMember],
new_val: Value,
head_span: Span,
) -> Result<(), ShellError> {
let v_span = self.span();
if let Some((member, path)) = cell_path.split_first() {
match member {
PathMember::String {
val: col_name,
span,
..
} => match self {
Value::List { vals, .. } => {
for val in vals.iter_mut() {
let v_span = val.span();
match val {
Value::Record { val: record, .. } => {
let record = record.to_mut();
if let Some(val) = record.get_mut(col_name) {
if path.is_empty() {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
} else {
val.insert_data_at_cell_path(
path,
new_val.clone(),
head_span,
)?;
}
} else {
let new_col = if path.is_empty() {
new_val.clone()
} else {
let mut new_col =
Value::record(Record::new(), new_val.span());
new_col.insert_data_at_cell_path(
path,
new_val.clone(),
head_span,
)?;
new_col
};
record.push(col_name, new_col);
}
}
Value::Error { error, .. } => return Err(*error.clone()),
_ => {
return Err(ShellError::UnsupportedInput {
msg: "expected table or record".into(),
input: format!("input type: {:?}", val.get_type()),
msg_span: head_span,
input_span: *span,
});
}
}
}
}
Value::Record { val: record, .. } => {
let record = record.to_mut();
if let Some(val) = record.get_mut(col_name) {
if path.is_empty() {
return Err(ShellError::ColumnAlreadyExists {
col_name: col_name.clone(),
span: *span,
src_span: v_span,
});
} else {
val.insert_data_at_cell_path(path, new_val, head_span)?;
}
} else {
let new_col = if path.is_empty() {
new_val
} else {
let mut new_col = Value::record(Record::new(), new_val.span());
new_col.insert_data_at_cell_path(path, new_val, head_span)?;
new_col
};
record.push(col_name, new_col);
}
}
other => {
return Err(ShellError::UnsupportedInput {
msg: "table or record".into(),
input: format!("input type: {:?}", other.get_type()),
msg_span: head_span,
input_span: *span,
});
}
},
PathMember::Int {
val: row_num, span, ..
} => match self {
Value::List { vals, .. } => {
if let Some(v) = vals.get_mut(*row_num) {
if path.is_empty() {
vals.insert(*row_num, new_val);
} else {
v.insert_data_at_cell_path(path, new_val, head_span)?;
}
} else if vals.len() != *row_num {
return Err(ShellError::InsertAfterNextFreeIndex {
available_idx: vals.len(),
span: *span,
});
} else if !path.is_empty() {
return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *span,
});
} else {
// If the insert is at 1 + the end of the list, it's OK.
vals.push(new_val);
}
}
_ => {
return Err(ShellError::NotAList {
dst_span: *span,
src_span: v_span,
});
}
},
}
} else {
*self = new_val;
}
Ok(())
}
/// Visits all values contained within the value (including this value) with a mutable reference
/// given to the closure.
///
/// If the closure returns `Err`, the traversal will stop.
///
/// Captures of closure values are currently visited, as they are values owned by the closure.
pub fn recurse_mut<E>(
&mut self,
f: &mut impl FnMut(&mut Value) -> Result<(), E>,
) -> Result<(), E> {
// Visit this value
f(self)?;
// Check for contained values
match self {
Value::Record { ref mut val, .. } => val
.to_mut()
.iter_mut()
.try_for_each(|(_, rec_value)| rec_value.recurse_mut(f)),
Value::List { ref mut vals, .. } => vals
.iter_mut()
.try_for_each(|list_value| list_value.recurse_mut(f)),
// Closure captures are visited. Maybe these don't have to be if they are changed to
// more opaque references.
Value::Closure { ref mut val, .. } => val
.captures
.iter_mut()
.map(|(_, captured_value)| captured_value)
.try_for_each(|captured_value| captured_value.recurse_mut(f)),
// All of these don't contain other values
Value::Bool { .. }
| Value::Int { .. }
| Value::Float { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::String { .. }
| Value::Glob { .. }
| Value::Nothing { .. }
| Value::Error { .. }
| Value::Binary { .. }
| Value::CellPath { .. } => Ok(()),
// These could potentially contain values, but we expect the closure to handle them
Value::Custom { .. } => Ok(()),
}
}
/// Check if the content is empty
pub fn is_empty(&self) -> bool {
match self {
Value::String { val, .. } => val.is_empty(),
Value::List { vals, .. } => vals.is_empty(),
Value::Record { val, .. } => val.is_empty(),
Value::Binary { val, .. } => val.is_empty(),
Value::Nothing { .. } => true,
_ => false,
}
}
pub fn is_nothing(&self) -> bool {
matches!(self, Value::Nothing { .. })
}
pub fn is_error(&self) -> bool {
matches!(self, Value::Error { .. })
}
pub fn is_true(&self) -> bool {
matches!(self, Value::Bool { val: true, .. })
}
pub fn is_false(&self) -> bool {
matches!(self, Value::Bool { val: false, .. })
}
pub fn columns(&self) -> impl Iterator<Item = &String> {
let opt = match self {
Value::Record { val, .. } => Some(val.columns()),
_ => None,
};
opt.into_iter().flatten()
}
pub fn bool(val: bool, span: Span) -> Value {
Value::Bool {
val,
internal_span: span,
}
}
pub fn int(val: i64, span: Span) -> Value {
Value::Int {
val,
internal_span: span,
}
}
pub fn float(val: f64, span: Span) -> Value {
Value::Float {
val,
internal_span: span,
}
}
pub fn filesize(val: impl Into<Filesize>, span: Span) -> Value {
Value::Filesize {
val: val.into(),
internal_span: span,
}
}
pub fn duration(val: i64, span: Span) -> Value {
Value::Duration {
val,
internal_span: span,
}
}
pub fn date(val: DateTime<FixedOffset>, span: Span) -> Value {
Value::Date {
val,
internal_span: span,
}
}
pub fn range(val: Range, span: Span) -> Value {
Value::Range {
val: val.into(),
internal_span: span,
}
}
pub fn string(val: impl Into<String>, span: Span) -> Value {
Value::String {
val: val.into(),
internal_span: span,
}
}
pub fn glob(val: impl Into<String>, no_expand: bool, span: Span) -> Value {
Value::Glob {
val: val.into(),
no_expand,
internal_span: span,
}
}
pub fn record(val: Record, span: Span) -> Value {
Value::Record {
val: SharedCow::new(val),
internal_span: span,
}
}
pub fn list(vals: Vec<Value>, span: Span) -> Value {
Value::List {
vals,
internal_span: span,
}
}
pub fn closure(val: Closure, span: Span) -> Value {
Value::Closure {
val: val.into(),
internal_span: span,
}
}
/// Create a new `Nothing` value
pub fn nothing(span: Span) -> Value {
Value::Nothing {
internal_span: span,
}
}
pub fn error(error: ShellError, span: Span) -> Value {
Value::Error {
error: Box::new(error),
internal_span: span,
}
}
pub fn binary(val: impl Into<Vec<u8>>, span: Span) -> Value {
Value::Binary {
val: val.into(),
internal_span: span,
}
}
pub fn cell_path(val: CellPath, span: Span) -> Value {
Value::CellPath {
val,
internal_span: span,
}
}
pub fn custom(val: Box<dyn CustomValue>, span: Span) -> Value {
Value::Custom {
val,
internal_span: span,
}
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_bool(val: bool) -> Value {
Value::bool(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_int(val: i64) -> Value {
Value::int(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_float(val: f64) -> Value {
Value::float(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_filesize(val: impl Into<Filesize>) -> Value {
Value::filesize(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_duration(val: i64) -> Value {
Value::duration(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_date(val: DateTime<FixedOffset>) -> Value {
Value::date(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_range(val: Range) -> Value {
Value::range(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_string(val: impl Into<String>) -> Value {
Value::string(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_glob(val: impl Into<String>) -> Value {
Value::glob(val, false, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_record(val: Record) -> Value {
Value::record(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_list(vals: Vec<Value>) -> Value {
Value::list(vals, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_closure(val: Closure) -> Value {
Value::closure(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_nothing() -> Value {
Value::nothing(Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_binary(val: impl Into<Vec<u8>>) -> Value {
Value::binary(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_cell_path(val: CellPath) -> Value {
Value::cell_path(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_custom_value(val: Box<dyn CustomValue>) -> Value {
Value::custom(val, Span::test_data())
}
/// Note: Only use this for test data, *not* live data,
/// as it will point into unknown source when used in errors.
///
/// Returns a `Vec` containing one of each value case (`Value::Int`, `Value::String`, etc.)
/// except for `Value::CustomValue`.
pub fn test_values() -> Vec<Value> {
vec![
Value::test_bool(false),
Value::test_int(0),
Value::test_filesize(0),
Value::test_duration(0),
Value::test_date(DateTime::UNIX_EPOCH.into()),
Value::test_range(Range::IntRange(IntRange {
start: 0,
step: 1,
end: Bound::Excluded(0),
})),
Value::test_float(0.0),
Value::test_string(String::new()),
Value::test_record(Record::new()),
Value::test_list(Vec::new()),
Value::test_closure(Closure {
block_id: BlockId::new(0),
captures: Vec::new(),
}),
Value::test_nothing(),
Value::error(
ShellError::NushellFailed { msg: String::new() },
Span::test_data(),
),
Value::test_binary(Vec::new()),
Value::test_cell_path(CellPath {
members: Vec::new(),
}),
// Value::test_custom_value(Box::new(todo!())),
]
}
}
impl Default for Value {
fn default() -> Self {
Value::Nothing {
internal_span: Span::unknown(),
}
}
}
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, .. }, rhs) => match rhs {
Value::Bool { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Int { .. } => Some(Ordering::Less),
Value::Float { .. } => Some(Ordering::Less),
Value::String { .. } => Some(Ordering::Less),
Value::Glob { .. } => Some(Ordering::Less),
Value::Filesize { .. } => Some(Ordering::Less),
Value::Duration { .. } => Some(Ordering::Less),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Int { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Float { val: rhs, .. } => compare_floats(*lhs as f64, *rhs),
Value::String { .. } => Some(Ordering::Less),
Value::Glob { .. } => Some(Ordering::Less),
Value::Filesize { .. } => Some(Ordering::Less),
Value::Duration { .. } => Some(Ordering::Less),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Float { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { val: rhs, .. } => compare_floats(*lhs, *rhs as f64),
Value::Float { val: rhs, .. } => compare_floats(*lhs, *rhs),
Value::String { .. } => Some(Ordering::Less),
Value::Glob { .. } => Some(Ordering::Less),
Value::Filesize { .. } => Some(Ordering::Less),
Value::Duration { .. } => Some(Ordering::Less),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::String { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Glob { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Filesize { .. } => Some(Ordering::Less),
Value::Duration { .. } => Some(Ordering::Less),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Glob { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Glob { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Filesize { .. } => Some(Ordering::Less),
Value::Duration { .. } => Some(Ordering::Less),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Filesize { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Duration { .. } => Some(Ordering::Less),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Duration { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Date { .. } => Some(Ordering::Less),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Date { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Range { .. } => Some(Ordering::Less),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Range { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Record { .. } => Some(Ordering::Less),
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Record { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { val: rhs, .. } => {
// reorder cols and vals to make more logically compare.
// more general, if two record have same col and values,
// the order of cols shouldn't affect the equal property.
let mut lhs = lhs.clone().into_owned();
let mut rhs = rhs.clone().into_owned();
lhs.sort_cols();
rhs.sort_cols();
// Check columns first
for (a, b) in lhs.columns().zip(rhs.columns()) {
let result = a.partial_cmp(b);
if result != Some(Ordering::Equal) {
return result;
}
}
// Then check the values
for (a, b) in lhs.values().zip(rhs.values()) {
let result = a.partial_cmp(b);
if result != Some(Ordering::Equal) {
return result;
}
}
// If all of the comparisons were equal, then lexicographical order dictates
// that the shorter sequence is less than the longer one
lhs.len().partial_cmp(&rhs.len())
}
Value::List { .. } => Some(Ordering::Less),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::List { vals: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { .. } => Some(Ordering::Greater),
Value::List { vals: rhs, .. } => lhs.partial_cmp(rhs),
Value::Closure { .. } => Some(Ordering::Less),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Closure { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
Value::Closure { val: rhs, .. } => lhs.block_id.partial_cmp(&rhs.block_id),
Value::Error { .. } => Some(Ordering::Less),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Error { .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
Value::Closure { .. } => Some(Ordering::Greater),
Value::Error { .. } => Some(Ordering::Equal),
Value::Binary { .. } => Some(Ordering::Less),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Binary { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
Value::Closure { .. } => Some(Ordering::Greater),
Value::Error { .. } => Some(Ordering::Greater),
Value::Binary { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::CellPath { .. } => Some(Ordering::Less),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::CellPath { val: lhs, .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
Value::Closure { .. } => Some(Ordering::Greater),
Value::Error { .. } => Some(Ordering::Greater),
Value::Binary { .. } => Some(Ordering::Greater),
Value::CellPath { val: rhs, .. } => lhs.partial_cmp(rhs),
Value::Custom { .. } => Some(Ordering::Less),
Value::Nothing { .. } => Some(Ordering::Less),
},
(Value::Custom { val: lhs, .. }, rhs) => lhs.partial_cmp(rhs),
(Value::Nothing { .. }, rhs) => match rhs {
Value::Bool { .. } => Some(Ordering::Greater),
Value::Int { .. } => Some(Ordering::Greater),
Value::Float { .. } => Some(Ordering::Greater),
Value::String { .. } => Some(Ordering::Greater),
Value::Glob { .. } => Some(Ordering::Greater),
Value::Filesize { .. } => Some(Ordering::Greater),
Value::Duration { .. } => Some(Ordering::Greater),
Value::Date { .. } => Some(Ordering::Greater),
Value::Range { .. } => Some(Ordering::Greater),
Value::Record { .. } => Some(Ordering::Greater),
Value::List { .. } => Some(Ordering::Greater),
Value::Closure { .. } => Some(Ordering::Greater),
Value::Error { .. } => Some(Ordering::Greater),
Value::Binary { .. } => Some(Ordering::Greater),
Value::CellPath { .. } => Some(Ordering::Greater),
Value::Custom { .. } => Some(Ordering::Greater),
Value::Nothing { .. } => Some(Ordering::Equal),
},
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
self.partial_cmp(other).map_or(false, Ordering::is_eq)
}
}
impl Value {
pub fn add(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = lhs.checked_add(*rhs) {
Ok(Value::int(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "add operation overflowed".into(),
span,
help: Some("Consider using floating point values for increased range by promoting operand with 'into float'. Note: float has reduced precision!".into()),
})
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(*lhs as f64 + *rhs, span))
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::float(*lhs + *rhs as f64, span))
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(lhs + rhs, span))
}
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::string(lhs.to_string() + rhs, span))
}
(Value::Duration { val: lhs, .. }, Value::Date { val: rhs, .. }) => {
if let Some(val) = rhs.checked_add_signed(chrono::Duration::nanoseconds(*lhs)) {
Ok(Value::date(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "addition operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Date { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if let Some(val) = lhs.checked_add_signed(chrono::Duration::nanoseconds(*rhs)) {
Ok(Value::date(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "addition operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if let Some(val) = lhs.checked_add(*rhs) {
Ok(Value::duration(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "add operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if let Some(val) = *lhs + *rhs {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "add operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Plus), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => {
Ok(Value::list([lhs.as_slice(), rhs.as_slice()].concat(), span))
}
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::string([lhs.as_str(), rhs.as_str()].join(""), span))
}
(Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => Ok(Value::binary(
[lhs.as_slice(), rhs.as_slice()].concat(),
span,
)),
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Concat), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn sub(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = lhs.checked_sub(*rhs) {
Ok(Value::int(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "subtraction operation overflowed".into(),
span,
help: Some("Consider using floating point values for increased range by promoting operand with 'into float'. Note: float has reduced precision!".into()),
})
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(*lhs as f64 - *rhs, span))
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::float(*lhs - *rhs as f64, span))
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(lhs - rhs, span))
}
(Value::Date { val: lhs, .. }, Value::Date { val: rhs, .. }) => {
let result = lhs.signed_duration_since(*rhs);
if let Some(v) = result.num_nanoseconds() {
Ok(Value::duration(v, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "subtraction operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Date { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
match lhs.checked_sub_signed(chrono::Duration::nanoseconds(*rhs)) {
Some(val) => Ok(Value::date(val, span)),
_ => Err(ShellError::OperatorOverflow {
msg: "subtraction operation overflowed".into(),
span,
help: None,
}),
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if let Some(val) = lhs.checked_sub(*rhs) {
Ok(Value::duration(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "subtraction operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if let Some(val) = *lhs - *rhs {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "add operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Minus), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn mul(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = lhs.checked_mul(*rhs) {
Ok(Value::int(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "multiply operation overflowed".into(),
span,
help: Some("Consider using floating point values for increased range by promoting operand with 'into float'. Note: float has reduced precision!".into()),
})
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(*lhs as f64 * *rhs, span))
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::float(*lhs * *rhs as f64, span))
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(lhs * rhs, span))
}
(Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if let Some(val) = *lhs * *rhs {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "multiply operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = *lhs * *rhs {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "multiply operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Float { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if let Some(val) = *lhs * *rhs {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "multiply operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = *lhs * *rhs {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "multiply operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
Ok(Value::duration(*lhs * *rhs, span))
}
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::duration(*lhs * *rhs, span))
}
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::duration((*lhs as f64 * *rhs) as i64, span))
}
(Value::Float { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
Ok(Value::duration((*lhs * *rhs as f64) as i64, span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Multiply), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Ok(Value::float(*lhs as f64 / *rhs as f64, span))
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
Ok(Value::float(*lhs as f64 / *rhs, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if *rhs != 0 {
Ok(Value::float(*lhs / *rhs as f64, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
Ok(Value::float(lhs / rhs, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if *rhs == Filesize::ZERO {
Err(ShellError::DivisionByZero { span: op })
} else {
Ok(Value::float(lhs.get() as f64 / rhs.get() as f64, span))
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = lhs.get().checked_div(*rhs) {
Ok(Value::filesize(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
if let Ok(val) = Filesize::try_from(lhs.get() as f64 / rhs) {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Ok(Value::float(*lhs as f64 / *rhs as f64, span))
}
}
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = lhs.checked_div(*rhs) {
Ok(Value::duration(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if *rhs != 0.0 {
let val = *lhs as f64 / rhs;
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
Ok(Value::duration(val as i64, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
// Taken from the unstable `div_floor` function in the std library.
fn checked_div_floor_i64(dividend: i64, divisor: i64) -> Option<i64> {
let quotient = dividend.checked_div(divisor)?;
let remainder = dividend.checked_rem(divisor)?;
if (remainder > 0 && divisor < 0) || (remainder < 0 && divisor > 0) {
// Note that `quotient - 1` cannot overflow, because:
// `quotient` would have to be `i64::MIN`
// => `divisor` would have to be `1`
// => `remainder` would have to be `0`
// But `remainder == 0` is excluded from the check above.
Some(quotient - 1)
} else {
Some(quotient)
}
}
fn checked_div_floor_f64(dividend: f64, divisor: f64) -> Option<f64> {
if divisor == 0.0 {
None
} else {
Some((dividend / divisor).floor())
}
}
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_i64(*lhs, *rhs) {
Ok(Value::int(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_f64(*lhs as f64, *rhs) {
Ok(Value::float(val, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_f64(*lhs, *rhs as f64) {
Ok(Value::float(val, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_f64(*lhs, *rhs) {
Ok(Value::float(val, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_i64(lhs.get(), rhs.get()) {
Ok(Value::int(val, span))
} else if *rhs == Filesize::ZERO {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_i64(lhs.get(), *rhs) {
Ok(Value::filesize(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_f64(lhs.get() as f64, *rhs) {
if let Ok(val) = Filesize::try_from(val) {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_i64(*lhs, *rhs) {
Ok(Value::int(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_i64(*lhs, *rhs) {
Ok(Value::duration(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_div_floor_f64(*lhs as f64, *rhs) {
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
Ok(Value::duration(val as i64, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
// Based off the unstable `div_floor` function in the std library.
fn checked_mod_i64(dividend: i64, divisor: i64) -> Option<i64> {
let remainder = dividend.checked_rem(divisor)?;
if (remainder > 0 && divisor < 0) || (remainder < 0 && divisor > 0) {
// Note that `remainder + divisor` cannot overflow, because `remainder` and
// `divisor` have opposite signs.
Some(remainder + divisor)
} else {
Some(remainder)
}
}
fn checked_mod_f64(dividend: f64, divisor: f64) -> Option<f64> {
if divisor == 0.0 {
None
} else {
let remainder = dividend % divisor;
if (remainder > 0.0 && divisor < 0.0) || (remainder < 0.0 && divisor > 0.0) {
Some(remainder + divisor)
} else {
Some(remainder)
}
}
}
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
Ok(Value::int(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "modulo operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
Ok(Value::float(val, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_mod_f64(*lhs, *rhs as f64) {
Ok(Value::float(val, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_mod_f64(*lhs, *rhs) {
Ok(Value::float(val, span))
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
if let Some(val) = checked_mod_i64(lhs.get(), rhs.get()) {
Ok(Value::filesize(val, span))
} else if *rhs == Filesize::ZERO {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "modulo operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_mod_i64(lhs.get(), *rhs) {
Ok(Value::filesize(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "modulo operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_mod_f64(lhs.get() as f64, *rhs) {
if let Ok(val) = Filesize::try_from(val) {
Ok(Value::filesize(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "modulo operation overflowed".into(),
span,
help: None,
})
}
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
Ok(Value::duration(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = checked_mod_i64(*lhs, *rhs) {
Ok(Value::duration(val, span))
} else if *rhs == 0 {
Err(ShellError::DivisionByZero { span: op })
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
}
(Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) {
if i64::MIN as f64 <= val && val <= i64::MAX as f64 {
Ok(Value::duration(val as i64, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "division operation overflowed".into(),
span,
help: None,
})
}
} else {
Err(ShellError::DivisionByZero { span: op })
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Math(Math::Modulo), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
return lhs.operation(
self.span(),
Operator::Comparison(Comparison::LessThan),
op,
rhs,
);
}
if matches!(self, Value::Nothing { .. }) || matches!(rhs, Value::Nothing { .. }) {
return Ok(Value::nothing(span));
}
if !type_compatible(self.get_type(), rhs.get_type())
&& (self.get_type() != Type::Any)
&& (rhs.get_type() != Type::Any)
{
return Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
});
}
if let Some(ordering) = self.partial_cmp(rhs) {
Ok(Value::bool(matches!(ordering, Ordering::Less), span))
} else {
Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
})
}
}
pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
return lhs.operation(
self.span(),
Operator::Comparison(Comparison::LessThanOrEqual),
op,
rhs,
);
}
if matches!(self, Value::Nothing { .. }) || matches!(rhs, Value::Nothing { .. }) {
return Ok(Value::nothing(span));
}
if !type_compatible(self.get_type(), rhs.get_type())
&& (self.get_type() != Type::Any)
&& (rhs.get_type() != Type::Any)
{
return Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
});
}
self.partial_cmp(rhs)
.map(|ordering| Value::bool(matches!(ordering, Ordering::Less | Ordering::Equal), span))
.ok_or(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
})
}
pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
return lhs.operation(
self.span(),
Operator::Comparison(Comparison::GreaterThan),
op,
rhs,
);
}
if matches!(self, Value::Nothing { .. }) || matches!(rhs, Value::Nothing { .. }) {
return Ok(Value::nothing(span));
}
if !type_compatible(self.get_type(), rhs.get_type())
&& (self.get_type() != Type::Any)
&& (rhs.get_type() != Type::Any)
{
return Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
});
}
self.partial_cmp(rhs)
.map(|ordering| Value::bool(matches!(ordering, Ordering::Greater), span))
.ok_or(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
})
}
pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
return lhs.operation(
self.span(),
Operator::Comparison(Comparison::GreaterThanOrEqual),
op,
rhs,
);
}
if matches!(self, Value::Nothing { .. }) || matches!(rhs, Value::Nothing { .. }) {
return Ok(Value::nothing(span));
}
if !type_compatible(self.get_type(), rhs.get_type())
&& (self.get_type() != Type::Any)
&& (rhs.get_type() != Type::Any)
{
return Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
});
}
match self.partial_cmp(rhs) {
Some(ordering) => Ok(Value::bool(
matches!(ordering, Ordering::Greater | Ordering::Equal),
span,
)),
None => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
return lhs.operation(
self.span(),
Operator::Comparison(Comparison::Equal),
op,
rhs,
);
}
if let Some(ordering) = self.partial_cmp(rhs) {
Ok(Value::bool(matches!(ordering, Ordering::Equal), span))
} else {
match (self, rhs) {
(Value::Nothing { .. }, _) | (_, Value::Nothing { .. }) => {
Ok(Value::bool(false, span))
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
}
pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) {
return lhs.operation(
self.span(),
Operator::Comparison(Comparison::NotEqual),
op,
rhs,
);
}
if let Some(ordering) = self.partial_cmp(rhs) {
Ok(Value::bool(!matches!(ordering, Ordering::Equal), span))
} else {
match (self, rhs) {
(Value::Nothing { .. }, _) | (_, Value::Nothing { .. }) => {
Ok(Value::bool(true, span))
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
}
pub fn r#in(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(lhs, Value::Range { val: rhs, .. }) => Ok(Value::bool(rhs.contains(lhs), span)),
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::bool(rhs.contains(lhs), span))
}
(lhs, Value::List { vals: rhs, .. }) => Ok(Value::bool(rhs.contains(lhs), span)),
(Value::String { val: lhs, .. }, Value::Record { val: rhs, .. }) => {
Ok(Value::bool(rhs.contains(lhs), span))
}
(Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => {
let val = rhs.members.iter().any(|member| match (self, member) {
(Value::Int { val: lhs, .. }, PathMember::Int { val: rhs, .. }) => {
*lhs == *rhs as i64
}
(Value::String { val: lhs, .. }, PathMember::String { val: rhs, .. }) => {
lhs == rhs
}
(Value::String { .. }, PathMember::Int { .. })
| (Value::Int { .. }, PathMember::String { .. }) => false,
_ => unreachable!(
"outer match arm ensures `self` is either a `String` or `Int` variant"
),
});
Ok(Value::bool(val, span))
}
(Value::CellPath { val: lhs, .. }, Value::CellPath { val: rhs, .. }) => {
Ok(Value::bool(
rhs.members
.windows(lhs.members.len())
.any(|member_window| member_window == rhs.members),
span,
))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Comparison(Comparison::In), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn not_in(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(lhs, Value::Range { val: rhs, .. }) => Ok(Value::bool(!rhs.contains(lhs), span)),
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::bool(!rhs.contains(lhs), span))
}
(lhs, Value::List { vals: rhs, .. }) => Ok(Value::bool(!rhs.contains(lhs), span)),
(Value::String { val: lhs, .. }, Value::Record { val: rhs, .. }) => {
Ok(Value::bool(!rhs.contains(lhs), span))
}
(Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => {
let val = rhs.members.iter().any(|member| match (self, member) {
(Value::Int { val: lhs, .. }, PathMember::Int { val: rhs, .. }) => {
*lhs != *rhs as i64
}
(Value::String { val: lhs, .. }, PathMember::String { val: rhs, .. }) => {
lhs != rhs
}
(Value::String { .. }, PathMember::Int { .. })
| (Value::Int { .. }, PathMember::String { .. }) => true,
_ => unreachable!(
"outer match arm ensures `self` is either a `String` or `Int` variant"
),
});
Ok(Value::bool(val, span))
}
(Value::CellPath { val: lhs, .. }, Value::CellPath { val: rhs, .. }) => {
Ok(Value::bool(
rhs.members
.windows(lhs.members.len())
.all(|member_window| member_window != rhs.members),
span,
))
}
(Value::Custom { val: lhs, .. }, rhs) => lhs.operation(
self.span(),
Operator::Comparison(Comparison::NotIn),
op,
rhs,
),
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn regex_match(
&self,
engine_state: &EngineState,
op: Span,
rhs: &Value,
invert: bool,
span: Span,
) -> Result<Value, ShellError> {
let rhs_span = rhs.span();
match (self, rhs) {
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
let is_match = match engine_state.regex_cache.try_lock() {
Ok(mut cache) => {
if let Some(regex) = cache.get(rhs) {
regex.is_match(lhs)
} else {
let regex =
Regex::new(rhs).map_err(|e| ShellError::UnsupportedInput {
msg: format!("{e}"),
input: "value originated from here".into(),
msg_span: span,
input_span: rhs_span,
})?;
let ret = regex.is_match(lhs);
cache.put(rhs.clone(), regex);
ret
}
}
Err(_) => {
let regex = Regex::new(rhs).map_err(|e| ShellError::UnsupportedInput {
msg: format!("{e}"),
input: "value originated from here".into(),
msg_span: span,
input_span: rhs_span,
})?;
regex.is_match(lhs)
}
};
Ok(Value::bool(
if invert {
!is_match.unwrap_or(false)
} else {
is_match.unwrap_or(true)
},
span,
))
}
(Value::Custom { val: lhs, .. }, rhs) => lhs.operation(
span,
if invert {
Operator::Comparison(Comparison::NotRegexMatch)
} else {
Operator::Comparison(Comparison::RegexMatch)
},
op,
rhs,
),
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn starts_with(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::bool(lhs.starts_with(rhs), span))
}
(Value::Custom { val: lhs, .. }, rhs) => lhs.operation(
self.span(),
Operator::Comparison(Comparison::StartsWith),
op,
rhs,
),
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn ends_with(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::bool(lhs.ends_with(rhs), span))
}
(Value::Custom { val: lhs, .. }, rhs) => lhs.operation(
self.span(),
Operator::Comparison(Comparison::EndsWith),
op,
rhs,
),
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn bit_shl(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
// Currently we disallow negative operands like Rust's `Shl`
// Cheap guarding with TryInto<u32>
if let Some(val) = (*rhs).try_into().ok().and_then(|rhs| lhs.checked_shl(rhs)) {
Ok(Value::int(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "right operand to bit-shl exceeds available bits in underlying data"
.into(),
span,
help: Some(format!("Limit operand to 0 <= rhs < {}", i64::BITS)),
})
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::ShiftLeft), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn bit_shr(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
// Currently we disallow negative operands like Rust's `Shr`
// Cheap guarding with TryInto<u32>
if let Some(val) = (*rhs).try_into().ok().and_then(|rhs| lhs.checked_shr(rhs)) {
Ok(Value::int(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "right operand to bit-shr exceeds available bits in underlying data"
.into(),
span,
help: Some(format!("Limit operand to 0 <= rhs < {}", i64::BITS)),
})
}
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::ShiftRight), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn bit_or(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::int(*lhs | rhs, span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::BitOr), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn bit_xor(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::int(*lhs ^ rhs, span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::BitXor), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn bit_and(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::int(*lhs & rhs, span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::BitAnd), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn and(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => {
Ok(Value::bool(*lhs && *rhs, span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Boolean(Boolean::And), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn or(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => {
Ok(Value::bool(*lhs || *rhs, span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Boolean(Boolean::Or), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn xor(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => {
Ok(Value::bool((*lhs && !*rhs) || (!*lhs && *rhs), span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Boolean(Boolean::Xor), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
pub fn pow(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
match (self, rhs) {
(Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
if let Some(val) = lhs.checked_pow(*rhs as u32) {
Ok(Value::int(val, span))
} else {
Err(ShellError::OperatorOverflow {
msg: "pow operation overflowed".into(),
span,
help: Some("Consider using floating point values for increased range by promoting operand with 'into float'. Note: float has reduced precision!".into()),
})
}
}
(Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float((*lhs as f64).powf(*rhs), span))
}
(Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
Ok(Value::float(lhs.powf(*rhs as f64), span))
}
(Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => {
Ok(Value::float(lhs.powf(*rhs), span))
}
(Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Math(Math::Pow), op, rhs)
}
_ => Err(ShellError::OperatorMismatch {
op_span: op,
lhs_ty: self.get_type().to_string(),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
}
}
}
// TODO: The name of this function is overly broad with partial compatibility
// Should be replaced by an explicitly named helper on `Type` (take `Any` into account)
fn type_compatible(a: Type, b: Type) -> bool {
if a == b {
return true;
}
matches!((a, b), (Type::Int, Type::Float) | (Type::Float, Type::Int))
}
#[cfg(test)]
mod tests {
use super::{Record, Value};
use crate::record;
mod is_empty {
use super::*;
#[test]
fn test_string() {
let value = Value::test_string("");
assert!(value.is_empty());
}
#[test]
fn test_list() {
let list_with_no_values = Value::test_list(vec![]);
let list_with_one_empty_string = Value::test_list(vec![Value::test_string("")]);
assert!(list_with_no_values.is_empty());
assert!(!list_with_one_empty_string.is_empty());
}
#[test]
fn test_record() {
let no_columns_nor_cell_values = Value::test_record(Record::new());
let one_column_and_one_cell_value_with_empty_strings = Value::test_record(record! {
"" => Value::test_string(""),
});
let one_column_with_a_string_and_one_cell_value_with_empty_string =
Value::test_record(record! {
"column" => Value::test_string(""),
});
let one_column_with_empty_string_and_one_value_with_a_string =
Value::test_record(record! {
"" => Value::test_string("text"),
});
assert!(no_columns_nor_cell_values.is_empty());
assert!(!one_column_and_one_cell_value_with_empty_strings.is_empty());
assert!(!one_column_with_a_string_and_one_cell_value_with_empty_string.is_empty());
assert!(!one_column_with_empty_string_and_one_value_with_a_string.is_empty());
}
}
mod get_type {
use crate::Type;
use super::*;
#[test]
fn test_list() {
let list_of_ints = Value::test_list(vec![Value::test_int(0)]);
let list_of_floats = Value::test_list(vec![Value::test_float(0.0)]);
let list_of_ints_and_floats =
Value::test_list(vec![Value::test_int(0), Value::test_float(0.0)]);
let list_of_ints_and_floats_and_bools = Value::test_list(vec![
Value::test_int(0),
Value::test_float(0.0),
Value::test_bool(false),
]);
assert_eq!(list_of_ints.get_type(), Type::List(Box::new(Type::Int)));
assert_eq!(list_of_floats.get_type(), Type::List(Box::new(Type::Float)));
assert_eq!(
list_of_ints_and_floats_and_bools.get_type(),
Type::List(Box::new(Type::Any))
);
assert_eq!(
list_of_ints_and_floats.get_type(),
Type::List(Box::new(Type::Number))
);
}
}
mod into_string {
use chrono::{DateTime, FixedOffset};
use super::*;
#[test]
fn test_datetime() {
let date = DateTime::from_timestamp_millis(-123456789)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap());
let string = Value::test_date(date).to_expanded_string("", &Default::default());
// We need to cut the humanized part off for tests to work, because
// it is relative to current time.
let formatted = string.split('(').next().unwrap();
assert_eq!("Tue, 30 Dec 1969 13:42:23 +0000 ", formatted);
}
#[test]
fn test_negative_year_datetime() {
let date = DateTime::from_timestamp_millis(-72135596800000)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap());
let string = Value::test_date(date).to_expanded_string("", &Default::default());
// We need to cut the humanized part off for tests to work, because
// it is relative to current time.
let formatted = string.split(' ').next().unwrap();
assert_eq!("-0316-02-11T06:13:20+00:00", formatted);
}
}
}