Limited mutable variables (#7089)

This adds support for (limited) mutable variables. Mutable variables are created with mut much the same way immutable variables are made with let.

Mutable variables allow mutation via the assignment operator (=).

❯ mut x = 100
❯ $x = 200
❯ print $x
200

Mutable variables are limited in that they're only tended to be used in the local code block. Trying to capture a local variable will result in an error:

❯ mut x = 123; {|| $x }
Error: nu::parser::expected_keyword (link)

  × Capture of mutable variable.

The intent of this limitation is to reduce some of the issues with mutable variables in general: namely they make code that's harder to reason about. By reducing the scope that a mutable variable can be used it, we can help create local reasoning about them.

Mutation can occur with fields as well, as in this case:

❯ mut y = {abc: 123}
❯ $y.abc = 456
❯ $y

On a historical note: mutable variables are something that we resisted for quite a long time, leaning as much as we could on the functional style of pipelines and dataflow. That said, we've watched folks struggle to work with reduce as an approximation for patterns that would be trivial to express with local mutation. With that in mind, we're leaning towards the happy path.
This commit is contained in:
JT 2022-11-11 19:51:08 +13:00 committed by GitHub
parent 58d960d914
commit 13515c5eb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 857 additions and 387 deletions

View File

@ -1,7 +1,7 @@
[target.x86_64-pc-windows-msvc] [target.x86_64-pc-windows-msvc]
# increase the default windows stack size # increase the default windows stack size
# statically link the CRT so users don't have to install it # statically link the CRT so users don't have to install it
rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-static"] rustflags = ["-C", "link-args=-stack:2147483648", "-C", "target-feature=+crt-static"]
# keeping this but commentting out in case we need them in the future # keeping this but commentting out in case we need them in the future

View File

@ -801,6 +801,7 @@ pub fn eval_hook(
name.as_bytes().to_vec(), name.as_bytes().to_vec(),
val.span()?, val.span()?,
Type::Any, Type::Any,
false,
); );
vars.push((var_id, val)); vars.push((var_id, val));

View File

@ -24,6 +24,7 @@ mod ignore;
mod let_; mod let_;
mod metadata; mod metadata;
mod module; mod module;
mod mut_;
pub(crate) mod overlay; pub(crate) mod overlay;
mod use_; mod use_;
mod version; mod version;
@ -54,6 +55,7 @@ pub use ignore::Ignore;
pub use let_::Let; pub use let_::Let;
pub use metadata::Metadata; pub use metadata::Metadata;
pub use module::Module; pub use module::Module;
pub use mut_::Mut;
pub use overlay::*; pub use overlay::*;
pub use use_::Use; pub use use_::Use;
pub use version::Version; pub use version::Version;

View File

@ -0,0 +1,117 @@
use nu_engine::eval_expression_with_input;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};
#[derive(Clone)]
pub struct Mut;
impl Command for Mut {
fn name(&self) -> &str {
"mut"
}
fn usage(&self) -> &str {
"Create a mutable variable and give it a value."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("mut")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn search_terms(&self) -> Vec<&str> {
vec!["set", "mutable"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let var_id = call
.positional_nth(0)
.expect("checked through parser")
.as_var()
.expect("internal error: missing variable");
let keyword_expr = call
.positional_nth(1)
.expect("checked through parser")
.as_keyword()
.expect("internal error: missing keyword");
let rhs = eval_expression_with_input(
engine_state,
stack,
keyword_expr,
input,
call.redirect_stdout,
call.redirect_stderr,
)?
.0;
//println!("Adding: {:?} to {}", rhs, var_id);
stack.add_var(var_id, rhs.into_value(call.head));
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Set a mutable variable to a value, then update it",
example: "mut x = 10; $x = 12",
result: None,
},
Example {
description: "Set a mutable variable to the result of an expression",
example: "mut x = 10 + 100",
result: None,
},
Example {
description: "Set a mutable variable based on the condition",
example: "mut x = if false { -1 } else { 1 }",
result: None,
},
]
}
}
#[cfg(test)]
mod test {
use nu_protocol::engine::CommandType;
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Mut {})
}
#[test]
fn test_command_type() {
assert!(matches!(Mut.command_type(), CommandType::Keyword));
}
}

View File

@ -1,5 +1,8 @@
use super::{operations::Axis, NuDataFrame}; use super::{operations::Axis, NuDataFrame};
use nu_protocol::{ast::Operator, span, ShellError, Span, Spanned, Value}; use nu_protocol::{
ast::{Boolean, Comparison, Math, Operator},
span, ShellError, Span, Spanned, Value,
};
use num::Zero; use num::Zero;
use polars::prelude::{ use polars::prelude::{
BooleanType, ChunkCompare, ChunkedArray, DataType, Float64Type, Int64Type, IntoSeries, BooleanType, ChunkCompare, ChunkedArray, DataType, Float64Type, Int64Type, IntoSeries,
@ -16,7 +19,7 @@ pub(super) fn between_dataframes(
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let operation_span = span(&[left.span()?, right.span()?]); let operation_span = span(&[left.span()?, right.span()?]);
match operator.item { match operator.item {
Operator::Plus => match lhs.append_df(rhs, Axis::Row, operation_span) { Operator::Math(Math::Plus) => match lhs.append_df(rhs, Axis::Row, operation_span) {
Ok(df) => Ok(df.into_value(operation_span)), Ok(df) => Ok(df.into_value(operation_span)),
Err(e) => Err(e), Err(e) => Err(e),
}, },
@ -39,25 +42,25 @@ pub(super) fn compute_between_series(
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let operation_span = span(&[left.span()?, right.span()?]); let operation_span = span(&[left.span()?, right.span()?]);
match operator.item { match operator.item {
Operator::Plus => { Operator::Math(Math::Plus) => {
let mut res = lhs + rhs; let mut res = lhs + rhs;
let name = format!("sum_{}_{}", lhs.name(), rhs.name()); let name = format!("sum_{}_{}", lhs.name(), rhs.name());
res.rename(&name); res.rename(&name);
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::Minus => { Operator::Math(Math::Minus) => {
let mut res = lhs - rhs; let mut res = lhs - rhs;
let name = format!("sub_{}_{}", lhs.name(), rhs.name()); let name = format!("sub_{}_{}", lhs.name(), rhs.name());
res.rename(&name); res.rename(&name);
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::Multiply => { Operator::Math(Math::Multiply) => {
let mut res = lhs * rhs; let mut res = lhs * rhs;
let name = format!("mul_{}_{}", lhs.name(), rhs.name()); let name = format!("mul_{}_{}", lhs.name(), rhs.name());
res.rename(&name); res.rename(&name);
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::Divide => { Operator::Math(Math::Divide) => {
let res = lhs.checked_div(rhs); let res = lhs.checked_div(rhs);
match res { match res {
Ok(mut res) => { Ok(mut res) => {
@ -74,37 +77,37 @@ pub(super) fn compute_between_series(
)), )),
} }
} }
Operator::Equal => { Operator::Comparison(Comparison::Equal) => {
let name = format!("eq_{}_{}", lhs.name(), rhs.name()); let name = format!("eq_{}_{}", lhs.name(), rhs.name());
let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?;
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::NotEqual => { Operator::Comparison(Comparison::NotEqual) => {
let name = format!("neq_{}_{}", lhs.name(), rhs.name()); let name = format!("neq_{}_{}", lhs.name(), rhs.name());
let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?;
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::LessThan => { Operator::Comparison(Comparison::LessThan) => {
let name = format!("lt_{}_{}", lhs.name(), rhs.name()); let name = format!("lt_{}_{}", lhs.name(), rhs.name());
let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?;
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::LessThanOrEqual => { Operator::Comparison(Comparison::LessThanOrEqual) => {
let name = format!("lte_{}_{}", lhs.name(), rhs.name()); let name = format!("lte_{}_{}", lhs.name(), rhs.name());
let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?;
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::GreaterThan => { Operator::Comparison(Comparison::GreaterThan) => {
let name = format!("gt_{}_{}", lhs.name(), rhs.name()); let name = format!("gt_{}_{}", lhs.name(), rhs.name());
let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?;
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::GreaterThanOrEqual => { Operator::Comparison(Comparison::GreaterThanOrEqual) => {
let name = format!("gte_{}_{}", lhs.name(), rhs.name()); let name = format!("gte_{}_{}", lhs.name(), rhs.name());
let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?;
NuDataFrame::series_to_value(res, operation_span) NuDataFrame::series_to_value(res, operation_span)
} }
Operator::And => match lhs.dtype() { Operator::Boolean(Boolean::And) => match lhs.dtype() {
DataType::Boolean => { DataType::Boolean => {
let lhs_cast = lhs.bool(); let lhs_cast = lhs.bool();
let rhs_cast = rhs.bool(); let rhs_cast = rhs.bool();
@ -133,7 +136,7 @@ pub(super) fn compute_between_series(
operation_span, operation_span,
)), )),
}, },
Operator::Or => match lhs.dtype() { Operator::Boolean(Boolean::Or) => match lhs.dtype() {
DataType::Boolean => { DataType::Boolean => {
let lhs_cast = lhs.bool(); let lhs_cast = lhs.bool();
let rhs_cast = rhs.bool(); let rhs_cast = rhs.bool();
@ -218,7 +221,7 @@ pub(super) fn compute_series_single_value(
let lhs = lhs.as_series(lhs_span)?; let lhs = lhs.as_series(lhs_span)?;
match operator.item { match operator.item {
Operator::Plus => match &right { Operator::Math(Math::Plus) => match &right {
Value::Int { val, .. } => { Value::Int { val, .. } => {
compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::add, lhs_span) compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::add, lhs_span)
} }
@ -234,7 +237,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::Minus => match &right { Operator::Math(Math::Minus) => match &right {
Value::Int { val, .. } => { Value::Int { val, .. } => {
compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::sub, lhs_span) compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::sub, lhs_span)
} }
@ -249,7 +252,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::Multiply => match &right { Operator::Math(Math::Multiply) => match &right {
Value::Int { val, .. } => { Value::Int { val, .. } => {
compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::mul, lhs_span) compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::mul, lhs_span)
} }
@ -264,7 +267,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::Divide => match &right { Operator::Math(Math::Divide) => match &right {
Value::Int { val, span } => { Value::Int { val, span } => {
if *val == 0 { if *val == 0 {
Err(ShellError::DivisionByZero(*span)) Err(ShellError::DivisionByZero(*span))
@ -287,7 +290,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::Equal => match &right { Operator::Comparison(Comparison::Equal) => match &right {
Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::equal, lhs_span), Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::equal, lhs_span),
Value::Float { val, .. } => { Value::Float { val, .. } => {
compare_series_decimal(&lhs, *val, ChunkedArray::equal, lhs_span) compare_series_decimal(&lhs, *val, ChunkedArray::equal, lhs_span)
@ -307,7 +310,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::NotEqual => match &right { Operator::Comparison(Comparison::NotEqual) => match &right {
Value::Int { val, .. } => { Value::Int { val, .. } => {
compare_series_i64(&lhs, *val, ChunkedArray::not_equal, lhs_span) compare_series_i64(&lhs, *val, ChunkedArray::not_equal, lhs_span)
} }
@ -328,7 +331,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::LessThan => match &right { Operator::Comparison(Comparison::LessThan) => match &right {
Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt, lhs_span), Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt, lhs_span),
Value::Float { val, .. } => { Value::Float { val, .. } => {
compare_series_decimal(&lhs, *val, ChunkedArray::lt, lhs_span) compare_series_decimal(&lhs, *val, ChunkedArray::lt, lhs_span)
@ -344,7 +347,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::LessThanOrEqual => match &right { Operator::Comparison(Comparison::LessThanOrEqual) => match &right {
Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt_eq, lhs_span), Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt_eq, lhs_span),
Value::Float { val, .. } => { Value::Float { val, .. } => {
compare_series_decimal(&lhs, *val, ChunkedArray::lt_eq, lhs_span) compare_series_decimal(&lhs, *val, ChunkedArray::lt_eq, lhs_span)
@ -360,7 +363,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::GreaterThan => match &right { Operator::Comparison(Comparison::GreaterThan) => match &right {
Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt, lhs_span), Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt, lhs_span),
Value::Float { val, .. } => { Value::Float { val, .. } => {
compare_series_decimal(&lhs, *val, ChunkedArray::gt, lhs_span) compare_series_decimal(&lhs, *val, ChunkedArray::gt, lhs_span)
@ -376,7 +379,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::GreaterThanOrEqual => match &right { Operator::Comparison(Comparison::GreaterThanOrEqual) => match &right {
Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt_eq, lhs_span), Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt_eq, lhs_span),
Value::Float { val, .. } => { Value::Float { val, .. } => {
compare_series_decimal(&lhs, *val, ChunkedArray::gt_eq, lhs_span) compare_series_decimal(&lhs, *val, ChunkedArray::gt_eq, lhs_span)
@ -393,7 +396,7 @@ pub(super) fn compute_series_single_value(
}), }),
}, },
// TODO: update this to do a regex match instead of a simple contains? // TODO: update this to do a regex match instead of a simple contains?
Operator::RegexMatch => match &right { Operator::Comparison(Comparison::RegexMatch) => match &right {
Value::String { val, .. } => contains_series_pat(&lhs, val, lhs_span), Value::String { val, .. } => contains_series_pat(&lhs, val, lhs_span),
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: operator.span, op_span: operator.span,
@ -403,7 +406,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::StartsWith => match &right { Operator::Comparison(Comparison::StartsWith) => match &right {
Value::String { val, .. } => { Value::String { val, .. } => {
let starts_with_pattern = format!("^{}", fancy_regex::escape(val)); let starts_with_pattern = format!("^{}", fancy_regex::escape(val));
contains_series_pat(&lhs, &starts_with_pattern, lhs_span) contains_series_pat(&lhs, &starts_with_pattern, lhs_span)
@ -416,7 +419,7 @@ pub(super) fn compute_series_single_value(
rhs_span: right.span()?, rhs_span: right.span()?,
}), }),
}, },
Operator::EndsWith => match &right { Operator::Comparison(Comparison::EndsWith) => match &right {
Value::String { val, .. } => { Value::String { val, .. } => {
let ends_with_pattern = format!("{}$", fancy_regex::escape(val)); let ends_with_pattern = format!("{}$", fancy_regex::escape(val));
contains_series_pat(&lhs, &ends_with_pattern, lhs_span) contains_series_pat(&lhs, &ends_with_pattern, lhs_span)

View File

@ -1,7 +1,10 @@
use std::ops::{Add, Div, Mul, Rem, Sub}; use std::ops::{Add, Div, Mul, Rem, Sub};
use super::NuExpression; use super::NuExpression;
use nu_protocol::{ast::Operator, CustomValue, ShellError, Span, Type, Value}; use nu_protocol::{
ast::{Comparison, Math, Operator},
CustomValue, ShellError, Span, Type, Value,
};
use polars::prelude::Expr; use polars::prelude::Expr;
// CustomValue implementation for NuDataFrame // CustomValue implementation for NuDataFrame
@ -95,33 +98,33 @@ fn with_operator(
op_span: Span, op_span: Span,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
match operator { match operator {
Operator::Plus => apply_arithmetic(left, right, lhs_span, Add::add), Operator::Math(Math::Plus) => apply_arithmetic(left, right, lhs_span, Add::add),
Operator::Minus => apply_arithmetic(left, right, lhs_span, Sub::sub), Operator::Math(Math::Minus) => apply_arithmetic(left, right, lhs_span, Sub::sub),
Operator::Multiply => apply_arithmetic(left, right, lhs_span, Mul::mul), Operator::Math(Math::Multiply) => apply_arithmetic(left, right, lhs_span, Mul::mul),
Operator::Divide => apply_arithmetic(left, right, lhs_span, Div::div), Operator::Math(Math::Divide) => apply_arithmetic(left, right, lhs_span, Div::div),
Operator::Modulo => apply_arithmetic(left, right, lhs_span, Rem::rem), Operator::Math(Math::Modulo) => apply_arithmetic(left, right, lhs_span, Rem::rem),
Operator::FloorDivision => apply_arithmetic(left, right, lhs_span, Div::div), Operator::Math(Math::FloorDivision) => apply_arithmetic(left, right, lhs_span, Div::div),
Operator::Equal => Ok(left Operator::Comparison(Comparison::Equal) => Ok(left
.clone() .clone()
.apply_with_expr(right.clone(), Expr::eq) .apply_with_expr(right.clone(), Expr::eq)
.into_value(lhs_span)), .into_value(lhs_span)),
Operator::NotEqual => Ok(left Operator::Comparison(Comparison::NotEqual) => Ok(left
.clone() .clone()
.apply_with_expr(right.clone(), Expr::neq) .apply_with_expr(right.clone(), Expr::neq)
.into_value(lhs_span)), .into_value(lhs_span)),
Operator::GreaterThan => Ok(left Operator::Comparison(Comparison::GreaterThan) => Ok(left
.clone() .clone()
.apply_with_expr(right.clone(), Expr::gt) .apply_with_expr(right.clone(), Expr::gt)
.into_value(lhs_span)), .into_value(lhs_span)),
Operator::GreaterThanOrEqual => Ok(left Operator::Comparison(Comparison::GreaterThanOrEqual) => Ok(left
.clone() .clone()
.apply_with_expr(right.clone(), Expr::gt_eq) .apply_with_expr(right.clone(), Expr::gt_eq)
.into_value(lhs_span)), .into_value(lhs_span)),
Operator::LessThan => Ok(left Operator::Comparison(Comparison::LessThan) => Ok(left
.clone() .clone()
.apply_with_expr(right.clone(), Expr::lt) .apply_with_expr(right.clone(), Expr::lt)
.into_value(lhs_span)), .into_value(lhs_span)),
Operator::LessThanOrEqual => Ok(left Operator::Comparison(Comparison::LessThanOrEqual) => Ok(left
.clone() .clone()
.apply_with_expr(right.clone(), Expr::lt_eq) .apply_with_expr(right.clone(), Expr::lt_eq)
.into_value(lhs_span)), .into_value(lhs_span)),

View File

@ -59,6 +59,7 @@ pub fn create_default_context() -> EngineState {
Let, Let,
Metadata, Metadata,
Module, Module,
Mut,
Use, Use,
Version, Version,
}; };

View File

@ -45,7 +45,7 @@ impl Command for Format {
let specified_pattern: Result<Value, ShellError> = call.req(engine_state, stack, 0); let specified_pattern: Result<Value, ShellError> = call.req(engine_state, stack, 0);
let input_val = input.into_value(call.head); let input_val = input.into_value(call.head);
// add '$it' variable to support format like this: $it.column1.column2. // add '$it' variable to support format like this: $it.column1.column2.
let it_id = working_set.add_variable(b"$it".to_vec(), call.head, Type::Any); let it_id = working_set.add_variable(b"$it".to_vec(), call.head, Type::Any, false);
stack.add_var(it_id, input_val.clone()); stack.add_var(it_id, input_val.clone());
match specified_pattern { match specified_pattern {

View File

@ -13,3 +13,15 @@ fn let_parse_error() {
.err .err
.contains("'in' is the name of a builtin Nushell variable")); .contains("'in' is the name of a builtin Nushell variable"));
} }
#[test]
fn let_doesnt_mutate() {
let actual = nu!(
cwd: ".", pipeline(
r#"
let i = 3; $i = 4
"#
));
assert!(actual.err.contains("immutable"));
}

View File

@ -42,6 +42,7 @@ mod math;
mod merge; mod merge;
mod mkdir; mod mkdir;
mod move_; mod move_;
mod mut_;
mod n; mod n;
mod network; mod network;
mod nu_check; mod nu_check;

View File

@ -0,0 +1,49 @@
use nu_test_support::{nu, pipeline};
#[test]
fn mut_variable() {
let actual = nu!(
cwd: ".", pipeline(
r#"
mut x = 3; $x = $x + 1; $x
"#
));
assert_eq!(actual.out, "4");
}
#[test]
fn mut_variable_in_loop() {
let actual = nu!(
cwd: ".", pipeline(
r#"
mut x = 1; for i in 1..10 { $x = $x + $i}; $x
"#
));
assert_eq!(actual.out, "56");
}
#[test]
fn capture_of_mutable_var() {
let actual = nu!(
cwd: ".", pipeline(
r#"
mut x = 123; {|| $x }
"#
));
assert!(actual.err.contains("capture of mutable variable"));
}
#[test]
fn mut_a_field() {
let actual = nu!(
cwd: ".", pipeline(
r#"
mut y = {abc: 123}; $y.abc = 456; $y.abc
"#
));
assert_eq!(actual.out, "456");
}

View File

@ -1,7 +1,7 @@
use crate::{current_dir_str, get_full_help}; use crate::{current_dir_str, get_full_help};
use nu_path::expand_path_with; use nu_path::expand_path_with;
use nu_protocol::{ use nu_protocol::{
ast::{Block, Call, Expr, Expression, Operator}, ast::{Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator},
engine::{EngineState, Stack, Visibility}, engine::{EngineState, Stack, Visibility},
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream, Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
PipelineData, Range, RawStream, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId, PipelineData, Range, RawStream, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId,
@ -355,131 +355,112 @@ pub fn eval_expression(
} }
Expr::BinaryOp(lhs, op, rhs) => { Expr::BinaryOp(lhs, op, rhs) => {
let op_span = op.span; let op_span = op.span;
let lhs = eval_expression(engine_state, stack, lhs)?;
let op = eval_operator(op)?; let op = eval_operator(op)?;
match op { match op {
Operator::And => { Operator::Boolean(boolean) => {
if lhs.is_false() { let lhs = eval_expression(engine_state, stack, lhs)?;
Ok(Value::Bool { match boolean {
val: false, Boolean::And => {
span: expr.span, if lhs.is_false() {
}) Ok(Value::Bool {
} else { val: false,
let rhs = eval_expression(engine_state, stack, rhs)?; span: expr.span,
lhs.and(op_span, &rhs, expr.span) })
} else {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.and(op_span, &rhs, expr.span)
}
}
Boolean::Or => {
if lhs.is_true() {
Ok(Value::Bool {
val: true,
span: expr.span,
})
} else {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.or(op_span, &rhs, expr.span)
}
}
} }
} }
Operator::Or => { Operator::Math(math) => {
if lhs.is_true() { let lhs = eval_expression(engine_state, stack, lhs)?;
Ok(Value::Bool { let rhs = eval_expression(engine_state, stack, rhs)?;
val: true,
span: expr.span, match math {
}) Math::Plus => lhs.add(op_span, &rhs, expr.span),
} else { Math::Minus => lhs.sub(op_span, &rhs, expr.span),
let rhs = eval_expression(engine_state, stack, rhs)?; Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
lhs.or(op_span, &rhs, expr.span) Math::Divide => lhs.div(op_span, &rhs, expr.span),
Math::Append => lhs.append(op_span, &rhs, expr.span),
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
} }
} }
Operator::Plus => { Operator::Comparison(comparison) => {
let lhs = eval_expression(engine_state, stack, lhs)?;
let rhs = eval_expression(engine_state, stack, rhs)?; let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.add(op_span, &rhs, expr.span) match comparison {
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
Comparison::RegexMatch => lhs.regex_match(op_span, &rhs, false, expr.span),
Comparison::NotRegexMatch => {
lhs.regex_match(op_span, &rhs, true, expr.span)
}
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
}
} }
Operator::Append => { Operator::Bits(bits) => {
let lhs = eval_expression(engine_state, stack, lhs)?;
let rhs = eval_expression(engine_state, stack, rhs)?; let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.append(op_span, &rhs, expr.span) match bits {
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
}
} }
Operator::Minus => { Operator::Assignment(Assignment::Assign) => {
let rhs = eval_expression(engine_state, stack, rhs)?; let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.sub(op_span, &rhs, expr.span)
} match &lhs.expr {
Operator::Multiply => { Expr::Var(var_id) | Expr::VarDecl(var_id) => {
let rhs = eval_expression(engine_state, stack, rhs)?; let var_info = engine_state.get_var(*var_id);
lhs.mul(op_span, &rhs, expr.span) if var_info.mutable {
} stack.vars.insert(*var_id, rhs);
Operator::Divide => { Ok(Value::nothing(lhs.span))
let rhs = eval_expression(engine_state, stack, rhs)?; } else {
lhs.div(op_span, &rhs, expr.span) Err(ShellError::AssignmentRequiresMutableVar(lhs.span))
} }
Operator::LessThan => { }
let rhs = eval_expression(engine_state, stack, rhs)?; Expr::FullCellPath(cell_path) => match &cell_path.head.expr {
lhs.lt(op_span, &rhs, expr.span) Expr::Var(var_id) | Expr::VarDecl(var_id) => {
} let var_info = engine_state.get_var(*var_id);
Operator::LessThanOrEqual => { if var_info.mutable {
let rhs = eval_expression(engine_state, stack, rhs)?; let mut lhs =
lhs.lte(op_span, &rhs, expr.span) eval_expression(engine_state, stack, &cell_path.head)?;
} lhs.update_data_at_cell_path(&cell_path.tail, rhs)?;
Operator::GreaterThan => { stack.vars.insert(*var_id, lhs);
let rhs = eval_expression(engine_state, stack, rhs)?; Ok(Value::nothing(cell_path.head.span))
lhs.gt(op_span, &rhs, expr.span) } else {
} Err(ShellError::AssignmentRequiresMutableVar(lhs.span))
Operator::GreaterThanOrEqual => { }
let rhs = eval_expression(engine_state, stack, rhs)?; }
lhs.gte(op_span, &rhs, expr.span) _ => Err(ShellError::AssignmentRequiresVar(lhs.span)),
} },
Operator::Equal => { _ => Err(ShellError::AssignmentRequiresVar(lhs.span)),
let rhs = eval_expression(engine_state, stack, rhs)?; }
lhs.eq(op_span, &rhs, expr.span)
}
Operator::NotEqual => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.ne(op_span, &rhs, expr.span)
}
Operator::In => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.r#in(op_span, &rhs, expr.span)
}
Operator::NotIn => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.not_in(op_span, &rhs, expr.span)
}
Operator::RegexMatch => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.regex_match(op_span, &rhs, false, expr.span)
}
Operator::NotRegexMatch => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.regex_match(op_span, &rhs, true, expr.span)
}
Operator::Modulo => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.modulo(op_span, &rhs, expr.span)
}
Operator::FloorDivision => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.floor_div(op_span, &rhs, expr.span)
}
Operator::Pow => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.pow(op_span, &rhs, expr.span)
}
Operator::StartsWith => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.starts_with(op_span, &rhs, expr.span)
}
Operator::EndsWith => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.ends_with(op_span, &rhs, expr.span)
}
Operator::BitOr => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_or(op_span, &rhs, expr.span)
}
Operator::BitXor => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_xor(op_span, &rhs, expr.span)
}
Operator::BitAnd => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_and(op_span, &rhs, expr.span)
}
Operator::ShiftRight => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_shr(op_span, &rhs, expr.span)
}
Operator::ShiftLeft => {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.bit_shl(op_span, &rhs, expr.span)
} }
} }
} }

View File

@ -56,6 +56,10 @@ pub enum ParseError {
Type, Type,
), ),
#[error("Capture of mutable variable.")]
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
CaptureOfMutableVar(#[label("capture of mutable variable")] Span),
#[error("Expected keyword.")] #[error("Expected keyword.")]
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))] #[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
ExpectedKeyword(String, #[label("expected {0}")] Span), ExpectedKeyword(String, #[label("expected {0}")] Span),
@ -88,6 +92,16 @@ pub enum ParseError {
)] )]
LetInPipeline(String, String, #[label("let in pipeline")] Span), LetInPipeline(String, String, #[label("let in pipeline")] Span),
#[error("Mut statement used in pipeline.")]
#[diagnostic(
code(nu::parser::unexpected_keyword),
url(docsrs),
help(
"Assigning '{0}' to '{1}' does not produce a value to be piped. If the pipeline result is meant to be assigned to '{1}', use 'mut {1} = ({0} | ...)'."
)
)]
MutInPipeline(String, String, #[label("let in pipeline")] Span),
#[error("Let used with builtin variable name.")] #[error("Let used with builtin variable name.")]
#[diagnostic( #[diagnostic(
code(nu::parser::let_builtin_var), code(nu::parser::let_builtin_var),
@ -96,6 +110,14 @@ pub enum ParseError {
)] )]
LetBuiltinVar(String, #[label("already a builtin variable")] Span), LetBuiltinVar(String, #[label("already a builtin variable")] Span),
#[error("Mut used with builtin variable name.")]
#[diagnostic(
code(nu::parser::let_builtin_var),
url(docsrs),
help("'{0}' is the name of a builtin Nushell variable. `mut` cannot assign to it.")
)]
MutBuiltinVar(String, #[label("already a builtin variable")] Span),
#[error("Incorrect value")] #[error("Incorrect value")]
#[diagnostic(code(nu::parser::incorrect_value), url(docsrs), help("{2}"))] #[diagnostic(code(nu::parser::incorrect_value), url(docsrs), help("{2}"))]
IncorrectValue(String, #[label("unexpected {0}")] Span, String), IncorrectValue(String, #[label("unexpected {0}")] Span, String),
@ -343,7 +365,10 @@ impl ParseError {
ParseError::UnexpectedKeyword(_, s) => *s, ParseError::UnexpectedKeyword(_, s) => *s,
ParseError::BuiltinCommandInPipeline(_, s) => *s, ParseError::BuiltinCommandInPipeline(_, s) => *s,
ParseError::LetInPipeline(_, _, s) => *s, ParseError::LetInPipeline(_, _, s) => *s,
ParseError::MutInPipeline(_, _, s) => *s,
ParseError::LetBuiltinVar(_, s) => *s, ParseError::LetBuiltinVar(_, s) => *s,
ParseError::MutBuiltinVar(_, s) => *s,
ParseError::CaptureOfMutableVar(s) => *s,
ParseError::IncorrectValue(_, s, _) => *s, ParseError::IncorrectValue(_, s, _) => *s,
ParseError::MultipleRestParams(s) => *s, ParseError::MultipleRestParams(s) => *s,
ParseError::VariableNotFound(s) => *s, ParseError::VariableNotFound(s) => *s,

View File

@ -2781,8 +2781,12 @@ pub fn parse_let(
} }
let mut idx = 0; let mut idx = 0;
let (lvalue, err) = let (lvalue, err) = parse_var_with_opt_type(
parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx); working_set,
&spans[1..(span.0)],
&mut idx,
false,
);
error = error.or(err); error = error.or(err);
let var_name = let var_name =
@ -2858,6 +2862,129 @@ pub fn parse_let(
) )
} }
pub fn parse_mut(
working_set: &mut StateWorkingSet,
spans: &[Span],
expand_aliases_denylist: &[usize],
) -> (Pipeline, Option<ParseError>) {
let name = working_set.get_span_contents(spans[0]);
if name == b"mut" {
if let Some((span, err)) = check_name(working_set, spans) {
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
}
if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Any) {
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
if spans.len() >= 4 {
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
// so that the var-id created by the variable isn't visible in the expression that init it
for span in spans.iter().enumerate() {
let item = working_set.get_span_contents(*span.1);
if item == b"=" && spans.len() > (span.0 + 1) {
let mut error = None;
let mut idx = span.0;
let (rvalue, err) = parse_multispan_value(
working_set,
spans,
&mut idx,
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
expand_aliases_denylist,
);
error = error.or(err);
if idx < (spans.len() - 1) {
error = error.or(Some(ParseError::ExtraPositional(
call_signature,
spans[idx + 1],
)));
}
let mut idx = 0;
let (lvalue, err) = parse_var_with_opt_type(
working_set,
&spans[1..(span.0)],
&mut idx,
true,
);
error = error.or(err);
let var_name =
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
.to_string();
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
error =
error.or(Some(ParseError::MutBuiltinVar(var_name, lvalue.span)));
}
let var_id = lvalue.as_var();
let rhs_type = rvalue.ty.clone();
if let Some(var_id) = var_id {
working_set.set_variable_type(var_id, rhs_type);
}
let call = Box::new(Call {
decl_id,
head: spans[0],
arguments: vec![
Argument::Positional(lvalue),
Argument::Positional(rvalue),
],
redirect_stdout: true,
redirect_stderr: false,
});
return (
Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call),
span: nu_protocol::span(spans),
ty: Type::Any,
custom_completion: None,
}]),
error,
);
}
}
}
let ParsedInternalCall {
call,
error: err,
output,
} = parse_internal_call(
working_set,
spans[0],
&spans[1..],
decl_id,
expand_aliases_denylist,
);
return (
Pipeline {
expressions: vec![Expression {
expr: Expr::Call(call),
span: nu_protocol::span(spans),
ty: output,
custom_completion: None,
}],
},
err,
);
}
}
(
garbage_pipeline(spans),
Some(ParseError::UnknownState(
"internal error: mut statement unparseable".into(),
span(spans),
)),
)
}
pub fn parse_source( pub fn parse_source(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
spans: &[Span], spans: &[Span],

View File

@ -1,15 +1,16 @@
use crate::{ use crate::{
lex, lite_parse, lex, lite_parse,
lite_parse::LiteCommand, lite_parse::LiteCommand,
parse_mut,
type_check::{math_result_type, type_compatible}, type_check::{math_result_type, type_compatible},
LiteBlock, ParseError, Token, TokenContents, LiteBlock, ParseError, Token, TokenContents,
}; };
use nu_protocol::{ use nu_protocol::{
ast::{ ast::{
Argument, Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern, Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
ImportPatternHead, ImportPatternMember, Operator, PathMember, Pipeline, RangeInclusion, FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator,
RangeOperator, PathMember, Pipeline, RangeInclusion, RangeOperator,
}, },
engine::StateWorkingSet, engine::StateWorkingSet,
span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId,
@ -633,7 +634,7 @@ pub fn parse_multispan_value(
SyntaxShape::VarWithOptType => { SyntaxShape::VarWithOptType => {
trace!("parsing: var with opt type"); trace!("parsing: var with opt type");
let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx); let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx, false);
error = error.or(err); error = error.or(err);
(arg, error) (arg, error)
@ -2919,6 +2920,7 @@ pub fn parse_var_with_opt_type(
working_set: &mut StateWorkingSet, working_set: &mut StateWorkingSet,
spans: &[Span], spans: &[Span],
spans_idx: &mut usize, spans_idx: &mut usize,
mutable: bool,
) -> (Expression, Option<ParseError>) { ) -> (Expression, Option<ParseError>) {
let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec(); let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec();
@ -2953,7 +2955,7 @@ pub fn parse_var_with_opt_type(
); );
} }
let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone()); let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone(), mutable);
( (
Expression { Expression {
@ -2977,7 +2979,7 @@ pub fn parse_var_with_opt_type(
); );
} }
let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any); let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any, mutable);
( (
Expression { Expression {
expr: Expr::VarDecl(id), expr: Expr::VarDecl(id),
@ -3005,6 +3007,7 @@ pub fn parse_var_with_opt_type(
var_name, var_name,
span(&spans[*spans_idx..*spans_idx + 1]), span(&spans[*spans_idx..*spans_idx + 1]),
Type::Any, Type::Any,
mutable,
); );
( (
@ -3044,7 +3047,7 @@ pub fn parse_row_condition(
spans: &[Span], spans: &[Span],
expand_aliases_denylist: &[usize], expand_aliases_denylist: &[usize],
) -> (Expression, Option<ParseError>) { ) -> (Expression, Option<ParseError>) {
let var_id = working_set.add_variable(b"$it".to_vec(), span(spans), Type::Any); let var_id = working_set.add_variable(b"$it".to_vec(), span(spans), Type::Any, false);
let (expression, err) = let (expression, err) =
parse_math_expression(working_set, spans, Some(var_id), expand_aliases_denylist); parse_math_expression(working_set, spans, Some(var_id), expand_aliases_denylist);
let span = span(spans); let span = span(spans);
@ -3232,7 +3235,7 @@ pub fn parse_signature_helper(
} }
let var_id = let var_id =
working_set.add_variable(variable_name, span, Type::Any); working_set.add_variable(variable_name, span, Type::Any, false);
if flags.len() == 1 { if flags.len() == 1 {
args.push(Arg::Flag(Flag { args.push(Arg::Flag(Flag {
@ -3282,8 +3285,12 @@ pub fn parse_signature_helper(
}) })
} }
let var_id = let var_id = working_set.add_variable(
working_set.add_variable(variable_name, span, Type::Any); variable_name,
span,
Type::Any,
false,
);
if chars.len() == 1 { if chars.len() == 1 {
args.push(Arg::Flag(Flag { args.push(Arg::Flag(Flag {
@ -3327,7 +3334,7 @@ pub fn parse_signature_helper(
} }
let var_id = let var_id =
working_set.add_variable(variable_name, span, Type::Any); working_set.add_variable(variable_name, span, Type::Any, false);
args.push(Arg::Flag(Flag { args.push(Arg::Flag(Flag {
arg: None, arg: None,
@ -3394,7 +3401,8 @@ pub fn parse_signature_helper(
}) })
} }
let var_id = working_set.add_variable(contents, span, Type::Any); let var_id =
working_set.add_variable(contents, span, Type::Any, false);
// Positional arg, optional // Positional arg, optional
args.push(Arg::Positional( args.push(Arg::Positional(
@ -3420,7 +3428,7 @@ pub fn parse_signature_helper(
} }
let var_id = let var_id =
working_set.add_variable(contents_vec, span, Type::Any); working_set.add_variable(contents_vec, span, Type::Any, false);
args.push(Arg::RestPositional(PositionalArg { args.push(Arg::RestPositional(PositionalArg {
desc: String::new(), desc: String::new(),
@ -3443,7 +3451,7 @@ pub fn parse_signature_helper(
} }
let var_id = let var_id =
working_set.add_variable(contents_vec, span, Type::Any); working_set.add_variable(contents_vec, span, Type::Any, false);
// Positional arg, required // Positional arg, required
args.push(Arg::Positional( args.push(Arg::Positional(
@ -4411,33 +4419,34 @@ pub fn parse_operator(
let contents = working_set.get_span_contents(span); let contents = working_set.get_span_contents(span);
let operator = match contents { let operator = match contents {
b"==" => Operator::Equal, b"=" => Operator::Assignment(Assignment::Assign),
b"!=" => Operator::NotEqual, b"==" => Operator::Comparison(Comparison::Equal),
b"<" => Operator::LessThan, b"!=" => Operator::Comparison(Comparison::NotEqual),
b"<=" => Operator::LessThanOrEqual, b"<" => Operator::Comparison(Comparison::LessThan),
b">" => Operator::GreaterThan, b"<=" => Operator::Comparison(Comparison::LessThanOrEqual),
b">=" => Operator::GreaterThanOrEqual, b">" => Operator::Comparison(Comparison::GreaterThan),
b"=~" => Operator::RegexMatch, b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual),
b"!~" => Operator::NotRegexMatch, b"=~" => Operator::Comparison(Comparison::RegexMatch),
b"+" => Operator::Plus, b"!~" => Operator::Comparison(Comparison::NotRegexMatch),
b"++" => Operator::Append, b"+" => Operator::Math(Math::Plus),
b"-" => Operator::Minus, b"++" => Operator::Math(Math::Append),
b"*" => Operator::Multiply, b"-" => Operator::Math(Math::Minus),
b"/" => Operator::Divide, b"*" => Operator::Math(Math::Multiply),
b"//" => Operator::FloorDivision, b"/" => Operator::Math(Math::Divide),
b"in" => Operator::In, b"//" => Operator::Math(Math::FloorDivision),
b"not-in" => Operator::NotIn, b"in" => Operator::Comparison(Comparison::In),
b"mod" => Operator::Modulo, b"not-in" => Operator::Comparison(Comparison::NotIn),
b"bit-or" => Operator::BitOr, b"mod" => Operator::Math(Math::Modulo),
b"bit-xor" => Operator::BitXor, b"bit-or" => Operator::Bits(Bits::BitOr),
b"bit-and" => Operator::BitAnd, b"bit-xor" => Operator::Bits(Bits::BitXor),
b"bit-shl" => Operator::ShiftLeft, b"bit-and" => Operator::Bits(Bits::BitAnd),
b"bit-shr" => Operator::ShiftRight, b"bit-shl" => Operator::Bits(Bits::ShiftLeft),
b"starts-with" => Operator::StartsWith, b"bit-shr" => Operator::Bits(Bits::ShiftRight),
b"ends-with" => Operator::EndsWith, b"starts-with" => Operator::Comparison(Comparison::StartsWith),
b"&&" | b"and" => Operator::And, b"ends-with" => Operator::Comparison(Comparison::EndsWith),
b"||" | b"or" => Operator::Or, b"&&" | b"and" => Operator::Boolean(Boolean::And),
b"**" => Operator::Pow, b"||" | b"or" => Operator::Boolean(Boolean::Or),
b"**" => Operator::Math(Math::Pow),
_ => { _ => {
return ( return (
garbage(span), garbage(span),
@ -4764,6 +4773,28 @@ pub fn parse_expression(
spans[0], spans[0],
)), )),
), ),
b"mut" => (
parse_call(
working_set,
&spans[pos..],
spans[0],
expand_aliases_denylist,
)
.0,
Some(ParseError::MutInPipeline(
String::from_utf8_lossy(match spans.len() {
1 | 2 | 3 => b"value",
_ => working_set.get_span_contents(spans[3]),
})
.to_string(),
String::from_utf8_lossy(match spans.len() {
1 => b"variable",
_ => working_set.get_span_contents(spans[1]),
})
.to_string(),
spans[0],
)),
),
b"alias" => ( b"alias" => (
parse_call( parse_call(
working_set, working_set,
@ -4977,6 +5008,7 @@ pub fn parse_builtin_commands(
b"def" | b"def-env" => parse_def(working_set, lite_command, expand_aliases_denylist), b"def" | b"def-env" => parse_def(working_set, lite_command, expand_aliases_denylist),
b"extern" => parse_extern(working_set, lite_command, expand_aliases_denylist), b"extern" => parse_extern(working_set, lite_command, expand_aliases_denylist),
b"let" => parse_let(working_set, &lite_command.parts, expand_aliases_denylist), b"let" => parse_let(working_set, &lite_command.parts, expand_aliases_denylist),
b"mut" => parse_mut(working_set, &lite_command.parts, expand_aliases_denylist),
b"for" => { b"for" => {
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist); let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
(Pipeline::from_vec(vec![expr]), err) (Pipeline::from_vec(vec![expr]), err)
@ -5229,8 +5261,8 @@ pub fn discover_captures_in_closure(
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
block: &Block, block: &Block,
seen: &mut Vec<VarId>, seen: &mut Vec<VarId>,
seen_blocks: &mut HashMap<BlockId, Vec<VarId>>, seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Vec<VarId> { ) -> Result<Vec<(VarId, Span)>, ParseError> {
let mut output = vec![]; let mut output = vec![];
for flag in &block.signature.named { for flag in &block.signature.named {
@ -5256,57 +5288,71 @@ pub fn discover_captures_in_closure(
} }
for pipeline in &block.pipelines { for pipeline in &block.pipelines {
let result = discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks); let result = discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
output Ok(output)
} }
fn discover_captures_in_pipeline( fn discover_captures_in_pipeline(
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
pipeline: &Pipeline, pipeline: &Pipeline,
seen: &mut Vec<VarId>, seen: &mut Vec<VarId>,
seen_blocks: &mut HashMap<BlockId, Vec<VarId>>, seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Vec<VarId> { ) -> Result<Vec<(VarId, Span)>, ParseError> {
let mut output = vec![]; let mut output = vec![];
for expr in &pipeline.expressions { for expr in &pipeline.expressions {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
output Ok(output)
} }
// Closes over captured variables
pub fn discover_captures_in_expr( pub fn discover_captures_in_expr(
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
expr: &Expression, expr: &Expression,
seen: &mut Vec<VarId>, seen: &mut Vec<VarId>,
seen_blocks: &mut HashMap<BlockId, Vec<VarId>>, seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
) -> Vec<VarId> { ) -> Result<Vec<(VarId, Span)>, ParseError> {
let mut output = vec![]; let mut output: Vec<(VarId, Span)> = vec![];
match &expr.expr { match &expr.expr {
Expr::BinaryOp(lhs, _, rhs) => { Expr::BinaryOp(lhs, _, rhs) => {
let lhs_result = discover_captures_in_expr(working_set, lhs, seen, seen_blocks); let lhs_result = discover_captures_in_expr(working_set, lhs, seen, seen_blocks)?;
let rhs_result = discover_captures_in_expr(working_set, rhs, seen, seen_blocks); let rhs_result = discover_captures_in_expr(working_set, rhs, seen, seen_blocks)?;
output.extend(&lhs_result); output.extend(&lhs_result);
output.extend(&rhs_result); output.extend(&rhs_result);
} }
Expr::UnaryNot(expr) => { Expr::UnaryNot(expr) => {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
Expr::Closure(block_id) => { Expr::Closure(block_id) => {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
let results = { let results = {
let mut seen = vec![]; let mut seen = vec![];
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks) let results =
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?;
for (var_id, span) in results.iter() {
if !seen.contains(var_id) {
if let Some(variable) = working_set.get_variable_if_possible(*var_id) {
if variable.mutable {
return Err(ParseError::CaptureOfMutableVar(*span));
}
}
}
}
results
}; };
seen_blocks.insert(*block_id, results.clone()); seen_blocks.insert(*block_id, results.clone());
for var_id in results.into_iter() { for (var_id, span) in results.into_iter() {
if !seen.contains(&var_id) { if !seen.contains(&var_id) {
output.push(var_id) output.push((var_id, span))
} }
} }
} }
@ -5315,12 +5361,12 @@ pub fn discover_captures_in_expr(
// FIXME: is this correct? // FIXME: is this correct?
let results = { let results = {
let mut seen = vec![]; let mut seen = vec![];
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks) discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?
}; };
seen_blocks.insert(*block_id, results.clone()); seen_blocks.insert(*block_id, results.clone());
for var_id in results.into_iter() { for (var_id, span) in results.into_iter() {
if !seen.contains(&var_id) { if !seen.contains(&var_id) {
output.push(var_id) output.push((var_id, span))
} }
} }
} }
@ -5336,7 +5382,7 @@ pub fn discover_captures_in_expr(
None => { None => {
let block = working_set.get_block(block_id); let block = working_set.get_block(block_id);
if !block.captures.is_empty() { if !block.captures.is_empty() {
output.extend(&block.captures); output.extend(block.captures.iter().map(|var_id| (*var_id, call.head)));
} else { } else {
let mut seen = vec![]; let mut seen = vec![];
seen_blocks.insert(block_id, output.clone()); seen_blocks.insert(block_id, output.clone());
@ -5346,7 +5392,7 @@ pub fn discover_captures_in_expr(
block, block,
&mut seen, &mut seen,
seen_blocks, seen_blocks,
); )?;
output.extend(&result); output.extend(&result);
seen_blocks.insert(block_id, result); seen_blocks.insert(block_id, result);
} }
@ -5356,24 +5402,24 @@ pub fn discover_captures_in_expr(
for named in call.named_iter() { for named in call.named_iter() {
if let Some(arg) = &named.2 { if let Some(arg) = &named.2 {
let result = discover_captures_in_expr(working_set, arg, seen, seen_blocks); let result = discover_captures_in_expr(working_set, arg, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
for positional in call.positional_iter() { for positional in call.positional_iter() {
let result = discover_captures_in_expr(working_set, positional, seen, seen_blocks); let result = discover_captures_in_expr(working_set, positional, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
Expr::CellPath(_) => {} Expr::CellPath(_) => {}
Expr::DateTime(_) => {} Expr::DateTime(_) => {}
Expr::ExternalCall(head, exprs) => { Expr::ExternalCall(head, exprs) => {
let result = discover_captures_in_expr(working_set, head, seen, seen_blocks); let result = discover_captures_in_expr(working_set, head, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
for expr in exprs { for expr in exprs {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
@ -5381,7 +5427,8 @@ pub fn discover_captures_in_expr(
Expr::Directory(_) => {} Expr::Directory(_) => {}
Expr::Float(_) => {} Expr::Float(_) => {}
Expr::FullCellPath(cell_path) => { Expr::FullCellPath(cell_path) => {
let result = discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks); let result =
discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
Expr::ImportPattern(_) => {} Expr::ImportPattern(_) => {}
@ -5391,27 +5438,27 @@ pub fn discover_captures_in_expr(
Expr::GlobPattern(_) => {} Expr::GlobPattern(_) => {}
Expr::Int(_) => {} Expr::Int(_) => {}
Expr::Keyword(_, _, expr) => { Expr::Keyword(_, _, expr) => {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
Expr::List(exprs) => { Expr::List(exprs) => {
for expr in exprs { for expr in exprs {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
Expr::Operator(_) => {} Expr::Operator(_) => {}
Expr::Range(expr1, expr2, expr3, _) => { Expr::Range(expr1, expr2, expr3, _) => {
if let Some(expr) = expr1 { if let Some(expr) = expr1 {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
if let Some(expr) = expr2 { if let Some(expr) = expr2 {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
if let Some(expr) = expr3 { if let Some(expr) = expr3 {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
@ -5422,13 +5469,13 @@ pub fn discover_captures_in_expr(
field_name, field_name,
seen, seen,
seen_blocks, seen_blocks,
)); )?);
output.extend(&discover_captures_in_expr( output.extend(&discover_captures_in_expr(
working_set, working_set,
field_value, field_value,
seen, seen,
seen_blocks, seen_blocks,
)); )?);
} }
} }
Expr::Signature(sig) => { Expr::Signature(sig) => {
@ -5457,7 +5504,7 @@ pub fn discover_captures_in_expr(
Expr::String(_) => {} Expr::String(_) => {}
Expr::StringInterpolation(exprs) => { Expr::StringInterpolation(exprs) => {
for expr in exprs { for expr in exprs {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
@ -5465,41 +5512,41 @@ pub fn discover_captures_in_expr(
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
let results = { let results = {
let mut seen = vec![]; let mut seen = vec![];
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks) discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?
}; };
seen_blocks.insert(*block_id, results.clone()); seen_blocks.insert(*block_id, results.clone());
for var_id in results.into_iter() { for (var_id, span) in results.into_iter() {
if !seen.contains(&var_id) { if !seen.contains(&var_id) {
output.push(var_id) output.push((var_id, span))
} }
} }
} }
Expr::Table(headers, values) => { Expr::Table(headers, values) => {
for header in headers { for header in headers {
let result = discover_captures_in_expr(working_set, header, seen, seen_blocks); let result = discover_captures_in_expr(working_set, header, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
for row in values { for row in values {
for cell in row { for cell in row {
let result = discover_captures_in_expr(working_set, cell, seen, seen_blocks); let result = discover_captures_in_expr(working_set, cell, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
} }
} }
Expr::ValueWithUnit(expr, _) => { Expr::ValueWithUnit(expr, _) => {
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
output.extend(&result); output.extend(&result);
} }
Expr::Var(var_id) => { Expr::Var(var_id) => {
if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) { if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) {
output.push(*var_id); output.push((*var_id, expr.span));
} }
} }
Expr::VarDecl(var_id) => { Expr::VarDecl(var_id) => {
seen.push(*var_id); seen.push(*var_id);
} }
} }
output Ok(output)
} }
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression { fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
@ -5604,7 +5651,10 @@ pub fn parse(
let mut seen_blocks = HashMap::new(); let mut seen_blocks = HashMap::new();
let captures = discover_captures_in_closure(working_set, &output, &mut seen, &mut seen_blocks); let captures = discover_captures_in_closure(working_set, &output, &mut seen, &mut seen_blocks);
output.captures = captures; match captures {
Ok(captures) => output.captures = captures.into_iter().map(|(var_id, _)| var_id).collect(),
Err(err) => error = Some(err),
}
// Also check other blocks that might have been imported // Also check other blocks that might have been imported
for (block_idx, block) in working_set.delta.blocks.iter().enumerate() { for (block_idx, block) in working_set.delta.blocks.iter().enumerate() {
@ -5613,7 +5663,12 @@ pub fn parse(
if !seen_blocks.contains_key(&block_id) { if !seen_blocks.contains_key(&block_id) {
let captures = let captures =
discover_captures_in_closure(working_set, block, &mut seen, &mut seen_blocks); discover_captures_in_closure(working_set, block, &mut seen, &mut seen_blocks);
seen_blocks.insert(block_id, captures); match captures {
Ok(captures) => {
seen_blocks.insert(block_id, captures);
}
Err(err) => error = Some(err),
}
} }
} }
@ -5626,7 +5681,7 @@ pub fn parse(
let block_captures_empty = block.captures.is_empty(); let block_captures_empty = block.captures.is_empty();
if !captures.is_empty() && block_captures_empty { if !captures.is_empty() && block_captures_empty {
let block = working_set.get_block_mut(block_id); let block = working_set.get_block_mut(block_id);
block.captures = captures; block.captures = captures.into_iter().map(|(var_id, _)| var_id).collect();
} }
} }

View File

@ -1,6 +1,6 @@
use crate::ParseError; use crate::ParseError;
use nu_protocol::{ use nu_protocol::{
ast::{Expr, Expression, Operator}, ast::{Assignment, Bits, Boolean, Comparison, Expr, Expression, Math, Operator},
engine::StateWorkingSet, engine::StateWorkingSet,
Type, Type,
}; };
@ -26,7 +26,7 @@ pub fn math_result_type(
//println!("checking: {:?} {:?} {:?}", lhs, op, rhs); //println!("checking: {:?} {:?} {:?}", lhs, op, rhs);
match &op.expr { match &op.expr {
Expr::Operator(operator) => match operator { Expr::Operator(operator) => match operator {
Operator::Plus => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::Plus) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None), (Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None),
@ -69,7 +69,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Append => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::Append) => match (&lhs.ty, &rhs.ty) {
(Type::List(a), Type::List(b)) => { (Type::List(a), Type::List(b)) => {
if a == b { if a == b {
(Type::List(a.clone()), None) (Type::List(a.clone()), None)
@ -98,7 +98,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Minus => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::Minus) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None), (Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None),
@ -126,7 +126,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Multiply => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::Multiply) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None), (Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None),
@ -159,7 +159,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Pow => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::Pow) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None), (Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None),
@ -184,7 +184,8 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Divide | Operator::Modulo => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::Divide) | Operator::Math(Math::Modulo) => match (&lhs.ty, &rhs.ty)
{
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Float, None), (Type::Float, Type::Int) => (Type::Float, None),
(Type::Int, Type::Float) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None),
@ -215,7 +216,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::FloorDivision => match (&lhs.ty, &rhs.ty) { Operator::Math(Math::FloorDivision) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Float, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Int, None),
(Type::Int, Type::Float) => (Type::Int, None), (Type::Int, Type::Float) => (Type::Int, None),
@ -243,33 +244,37 @@ pub fn math_result_type(
) )
} }
}, },
Operator::And | Operator::Or => match (&lhs.ty, &rhs.ty) { Operator::Boolean(Boolean::And) | Operator::Boolean(Boolean::Or) => {
(Type::Bool, Type::Bool) => (Type::Bool, None), match (&lhs.ty, &rhs.ty) {
(Type::Bool, Type::Bool) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None), (Type::Custom(a), Type::Custom(b)) if a == b => {
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None), (Type::Custom(a.to_string()), None)
}
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
(Type::Any, _) => (Type::Any, None), (Type::Any, _) => (Type::Any, None),
(_, Type::Any) => (Type::Any, None), (_, Type::Any) => (Type::Any, None),
// FIX ME. This is added because there is no type output for custom function // FIX ME. This is added because there is no type output for custom function
// definitions. As soon as that syntax is added this should be removed // definitions. As soon as that syntax is added this should be removed
(a, b) if a == b => (Type::Bool, None), (a, b) if a == b => (Type::Bool, None),
_ => { _ => {
*op = Expression::garbage(op.span); *op = Expression::garbage(op.span);
( (
Type::Any, Type::Any,
Some(ParseError::UnsupportedOperation( Some(ParseError::UnsupportedOperation(
op.span, op.span,
lhs.span, lhs.span,
lhs.ty.clone(), lhs.ty.clone(),
rhs.span, rhs.span,
rhs.ty.clone(), rhs.ty.clone(),
)), )),
) )
}
} }
}, }
Operator::LessThan => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::LessThan) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None), (Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None), (Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None),
@ -296,7 +301,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::LessThanOrEqual => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::LessThanOrEqual) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None), (Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None), (Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None),
@ -323,7 +328,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::GreaterThan => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::GreaterThan) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None), (Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None), (Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None),
@ -350,7 +355,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::GreaterThanOrEqual => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::GreaterThanOrEqual) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Bool, None), (Type::Int, Type::Int) => (Type::Bool, None),
(Type::Float, Type::Int) => (Type::Bool, None), (Type::Float, Type::Int) => (Type::Bool, None),
(Type::Int, Type::Float) => (Type::Bool, None), (Type::Int, Type::Float) => (Type::Bool, None),
@ -377,19 +382,19 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Equal => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::Equal) => match (&lhs.ty, &rhs.ty) {
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None), (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None), (Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
_ => (Type::Bool, None), _ => (Type::Bool, None),
}, },
Operator::NotEqual => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::NotEqual) => match (&lhs.ty, &rhs.ty) {
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None), (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.to_string()), None),
(Type::Custom(a), _) => (Type::Custom(a.to_string()), None), (Type::Custom(a), _) => (Type::Custom(a.to_string()), None),
_ => (Type::Bool, None), _ => (Type::Bool, None),
}, },
Operator::RegexMatch => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::RegexMatch) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None), (Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None), (_, Type::Any) => (Type::Bool, None),
@ -411,7 +416,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::NotRegexMatch => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::NotRegexMatch) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None), (Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None), (_, Type::Any) => (Type::Bool, None),
@ -433,7 +438,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::StartsWith => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::StartsWith) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None), (Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None), (_, Type::Any) => (Type::Bool, None),
@ -455,7 +460,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::EndsWith => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::EndsWith) => match (&lhs.ty, &rhs.ty) {
(Type::String, Type::String) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None),
(Type::Any, _) => (Type::Bool, None), (Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None), (_, Type::Any) => (Type::Bool, None),
@ -477,7 +482,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::In => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::In) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None), (t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None), (Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None),
@ -502,7 +507,7 @@ pub fn math_result_type(
) )
} }
}, },
Operator::NotIn => match (&lhs.ty, &rhs.ty) { Operator::Comparison(Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None), (t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float, Type::Range) => (Type::Bool, None), (Type::Int | Type::Float, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None),
@ -527,11 +532,11 @@ pub fn math_result_type(
) )
} }
}, },
Operator::ShiftLeft Operator::Bits(Bits::ShiftLeft)
| Operator::ShiftRight | Operator::Bits(Bits::ShiftRight)
| Operator::BitOr | Operator::Bits(Bits::BitOr)
| Operator::BitXor | Operator::Bits(Bits::BitXor)
| Operator::BitAnd => match (&lhs.ty, &rhs.ty) { | Operator::Bits(Bits::BitAnd) => match (&lhs.ty, &rhs.ty) {
(Type::Int, Type::Int) => (Type::Int, None), (Type::Int, Type::Int) => (Type::Int, None),
(Type::Any, _) => (Type::Any, None), (Type::Any, _) => (Type::Any, None),
@ -550,6 +555,15 @@ pub fn math_result_type(
) )
} }
}, },
Operator::Assignment(Assignment::Assign) => match (&lhs.ty, &rhs.ty) {
(x, y) if x == y => (Type::Nothing, None),
(Type::Any, _) => (Type::Nothing, None),
(_, Type::Any) => (Type::Nothing, None),
(x, y) => (
Type::Nothing,
Some(ParseError::Mismatch(x.to_string(), y.to_string(), rhs.span)),
),
},
}, },
_ => { _ => {
*op = Expression::garbage(op.span); *op = Expression::garbage(op.span);

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{Expr, Operator}; use super::Expr;
use crate::ast::ImportPattern; use crate::ast::ImportPattern;
use crate::DeclId; use crate::DeclId;
use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID}; use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID};
@ -26,34 +26,36 @@ impl Expression {
pub fn precedence(&self) -> usize { pub fn precedence(&self) -> usize {
match &self.expr { match &self.expr {
Expr::Operator(operator) => { Expr::Operator(operator) => {
use super::operator::*;
// Higher precedence binds tighter // Higher precedence binds tighter
match operator { match operator {
Operator::Pow => 100, Operator::Math(Math::Pow) => 100,
Operator::Multiply Operator::Math(Math::Multiply)
| Operator::Divide | Operator::Math(Math::Divide)
| Operator::Modulo | Operator::Math(Math::Modulo)
| Operator::FloorDivision => 95, | Operator::Math(Math::FloorDivision) => 95,
Operator::Plus | Operator::Minus => 90, Operator::Math(Math::Plus) | Operator::Math(Math::Minus) => 90,
Operator::ShiftLeft | Operator::ShiftRight => 85, Operator::Bits(Bits::ShiftLeft) | Operator::Bits(Bits::ShiftRight) => 85,
Operator::NotRegexMatch Operator::Comparison(Comparison::NotRegexMatch)
| Operator::RegexMatch | Operator::Comparison(Comparison::RegexMatch)
| Operator::StartsWith | Operator::Comparison(Comparison::StartsWith)
| Operator::EndsWith | Operator::Comparison(Comparison::EndsWith)
| Operator::LessThan | Operator::Comparison(Comparison::LessThan)
| Operator::LessThanOrEqual | Operator::Comparison(Comparison::LessThanOrEqual)
| Operator::GreaterThan | Operator::Comparison(Comparison::GreaterThan)
| Operator::GreaterThanOrEqual | Operator::Comparison(Comparison::GreaterThanOrEqual)
| Operator::Equal | Operator::Comparison(Comparison::Equal)
| Operator::NotEqual | Operator::Comparison(Comparison::NotEqual)
| Operator::In | Operator::Comparison(Comparison::In)
| Operator::NotIn | Operator::Comparison(Comparison::NotIn)
| Operator::Append => 80, | Operator::Math(Math::Append) => 80,
Operator::BitAnd => 75, Operator::Bits(Bits::BitAnd) => 75,
Operator::BitXor => 70, Operator::Bits(Bits::BitXor) => 70,
Operator::BitOr => 60, Operator::Bits(Bits::BitOr) => 60,
Operator::And => 50, Operator::Boolean(Boolean::And) => 50,
Operator::Or => 40, Operator::Boolean(Boolean::Or) => 40,
Operator::Assignment(Assignment::Assign) => 10,
} }
} }
_ => 0, _ => 0,

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Operator { pub enum Comparison {
Equal, Equal,
NotEqual, NotEqual,
LessThan, LessThan,
@ -13,20 +13,32 @@ pub enum Operator {
GreaterThanOrEqual, GreaterThanOrEqual,
RegexMatch, RegexMatch,
NotRegexMatch, NotRegexMatch,
In,
NotIn,
StartsWith,
EndsWith,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Math {
Plus, Plus,
Append, Append,
Minus, Minus,
Multiply, Multiply,
Divide, Divide,
In,
NotIn,
Modulo, Modulo,
FloorDivision, FloorDivision,
Pow,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Boolean {
And, And,
Or, Or,
Pow, }
StartsWith,
EndsWith, #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Bits {
BitOr, BitOr,
BitXor, BitXor,
BitAnd, BitAnd,
@ -34,36 +46,51 @@ pub enum Operator {
ShiftRight, ShiftRight,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Assignment {
Assign,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Operator {
Comparison(Comparison),
Math(Math),
Boolean(Boolean),
Bits(Bits),
Assignment(Assignment),
}
impl Display for Operator { impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Operator::Equal => write!(f, "=="), Operator::Assignment(Assignment::Assign) => write!(f, "="),
Operator::NotEqual => write!(f, "!="), Operator::Comparison(Comparison::Equal) => write!(f, "=="),
Operator::LessThan => write!(f, "<"), Operator::Comparison(Comparison::NotEqual) => write!(f, "!="),
Operator::GreaterThan => write!(f, ">"), Operator::Comparison(Comparison::LessThan) => write!(f, "<"),
Operator::RegexMatch => write!(f, "=~"), Operator::Comparison(Comparison::GreaterThan) => write!(f, ">"),
Operator::NotRegexMatch => write!(f, "!~"), Operator::Comparison(Comparison::RegexMatch) => write!(f, "=~"),
Operator::Plus => write!(f, "+"), Operator::Comparison(Comparison::NotRegexMatch) => write!(f, "!~"),
Operator::Append => write!(f, "++"), Operator::Comparison(Comparison::LessThanOrEqual) => write!(f, "<="),
Operator::Minus => write!(f, "-"), Operator::Comparison(Comparison::GreaterThanOrEqual) => write!(f, ">="),
Operator::Multiply => write!(f, "*"), Operator::Comparison(Comparison::StartsWith) => write!(f, "starts-with"),
Operator::Divide => write!(f, "/"), Operator::Comparison(Comparison::EndsWith) => write!(f, "ends-with"),
Operator::In => write!(f, "in"), Operator::Comparison(Comparison::In) => write!(f, "in"),
Operator::NotIn => write!(f, "not-in"), Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"),
Operator::Modulo => write!(f, "mod"), Operator::Math(Math::Plus) => write!(f, "+"),
Operator::FloorDivision => write!(f, "fdiv"), Operator::Math(Math::Append) => write!(f, "++"),
Operator::And => write!(f, "&&"), Operator::Math(Math::Minus) => write!(f, "-"),
Operator::Or => write!(f, "||"), Operator::Math(Math::Multiply) => write!(f, "*"),
Operator::Pow => write!(f, "**"), Operator::Math(Math::Divide) => write!(f, "/"),
Operator::BitOr => write!(f, "bit-or"), Operator::Math(Math::Modulo) => write!(f, "mod"),
Operator::BitXor => write!(f, "bit-xor"), Operator::Math(Math::FloorDivision) => write!(f, "fdiv"),
Operator::BitAnd => write!(f, "bit-and"), Operator::Math(Math::Pow) => write!(f, "**"),
Operator::ShiftLeft => write!(f, "bit-shl"), Operator::Boolean(Boolean::And) => write!(f, "&&"),
Operator::ShiftRight => write!(f, "bit-shr"), Operator::Boolean(Boolean::Or) => write!(f, "||"),
Operator::LessThanOrEqual => write!(f, "<="), Operator::Bits(Bits::BitOr) => write!(f, "bit-or"),
Operator::GreaterThanOrEqual => write!(f, ">="), Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"),
Operator::StartsWith => write!(f, "starts-with"), Operator::Bits(Bits::BitAnd) => write!(f, "bit-and"),
Operator::EndsWith => write!(f, "ends-with"), Operator::Bits(Bits::ShiftLeft) => write!(f, "bit-shl"),
Operator::Bits(Bits::ShiftRight) => write!(f, "bit-shr"),
} }
} }
} }

View File

@ -105,11 +105,11 @@ impl EngineState {
files: vec![], files: vec![],
file_contents: vec![], file_contents: vec![],
vars: vec![ vars: vec![
Variable::new(Span::new(0, 0), Type::Any), Variable::new(Span::new(0, 0), Type::Any, false),
Variable::new(Span::new(0, 0), Type::Any), Variable::new(Span::new(0, 0), Type::Any, false),
Variable::new(Span::new(0, 0), Type::Any), Variable::new(Span::new(0, 0), Type::Any, false),
Variable::new(Span::new(0, 0), Type::Any), Variable::new(Span::new(0, 0), Type::Any, false),
Variable::new(Span::new(0, 0), Type::Any), Variable::new(Span::new(0, 0), Type::Any, false),
], ],
decls: vec![], decls: vec![],
aliases: vec![], aliases: vec![],
@ -1571,7 +1571,13 @@ impl<'a> StateWorkingSet<'a> {
None None
} }
pub fn add_variable(&mut self, mut name: Vec<u8>, span: Span, ty: Type) -> VarId { pub fn add_variable(
&mut self,
mut name: Vec<u8>,
span: Span,
ty: Type,
mutable: bool,
) -> VarId {
let next_id = self.next_var_id(); let next_id = self.next_var_id();
// correct name if necessary // correct name if necessary
@ -1581,7 +1587,7 @@ impl<'a> StateWorkingSet<'a> {
self.last_overlay_mut().vars.insert(name, next_id); self.last_overlay_mut().vars.insert(name, next_id);
self.delta.vars.push(Variable::new(span, ty)); self.delta.vars.push(Variable::new(span, ty, mutable));
next_id next_id
} }
@ -1643,6 +1649,15 @@ impl<'a> StateWorkingSet<'a> {
} }
} }
pub fn get_variable_if_possible(&self, var_id: VarId) -> Option<&Variable> {
let num_permanent_vars = self.permanent_state.num_vars();
if var_id < num_permanent_vars {
Some(self.permanent_state.get_var(var_id))
} else {
self.delta.vars.get(var_id - num_permanent_vars)
}
}
#[allow(clippy::borrowed_box)] #[allow(clippy::borrowed_box)]
pub fn get_decl(&self, decl_id: DeclId) -> &Box<dyn Command> { pub fn get_decl(&self, decl_id: DeclId) -> &Box<dyn Command> {
let num_permanent_decls = self.permanent_state.num_decls(); let num_permanent_decls = self.permanent_state.num_decls();

View File

@ -83,6 +83,24 @@ pub enum ShellError {
#[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))] #[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))]
UnsupportedOperator(Operator, #[label = "unsupported operator"] Span), UnsupportedOperator(Operator, #[label = "unsupported operator"] Span),
/// This value cannot be used with this operator.
///
/// ## Resolution
///
/// Assignment requires that you assign to a variable or variable cell path.
#[error("Assignment operations require a variable.")]
#[diagnostic(code(nu::shell::assignment_requires_variable), url(docsrs))]
AssignmentRequiresVar(#[label = "needs to be a variable"] Span),
/// This value cannot be used with this operator.
///
/// ## Resolution
///
/// Assignment requires that you assign to a mutable variable or cell path.
#[error("Assignment to an immutable variable.")]
#[diagnostic(code(nu::shell::assignment_requires_mutable_variable), url(docsrs))]
AssignmentRequiresMutableVar(#[label = "needs to be a mutable variable"] Span),
/// An operator was not recognized during evaluation. /// An operator was not recognized during evaluation.
/// ///
/// ## Resolution /// ## Resolution

View File

@ -5,8 +5,8 @@ mod range;
mod stream; mod stream;
mod unit; mod unit;
use crate::ast::Operator; use crate::ast::{Bits, Boolean, CellPath, Comparison, PathMember};
use crate::ast::{CellPath, PathMember}; use crate::ast::{Math, Operator};
use crate::ShellError; use crate::ShellError;
use crate::{did_you_mean, BlockId, Config, Span, Spanned, Type, VarId}; use crate::{did_you_mean, BlockId, Config, Span, Spanned, Type, VarId};
use byte_unit::ByteUnit; use byte_unit::ByteUnit;
@ -1734,7 +1734,7 @@ impl Value {
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Plus, op, rhs) lhs.operation(*span, Operator::Math(Math::Plus), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -1841,7 +1841,7 @@ impl Value {
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Minus, op, rhs) lhs.operation(*span, Operator::Math(Math::Minus), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -1927,7 +1927,7 @@ impl Value {
}) })
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Multiply, op, rhs) lhs.operation(*span, Operator::Math(Math::Multiply), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -2064,7 +2064,7 @@ impl Value {
} }
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Divide, op, rhs) lhs.operation(*span, Operator::Math(Math::Divide), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -2210,7 +2210,7 @@ impl Value {
} }
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Divide, op, rhs) lhs.operation(*span, Operator::Math(Math::Divide), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -2225,7 +2225,7 @@ impl Value {
pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::LessThan, op, rhs); return lhs.operation(*span, Operator::Comparison(Comparison::LessThan), op, rhs);
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type())
@ -2252,7 +2252,12 @@ impl Value {
pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::LessThanOrEqual, op, rhs); return lhs.operation(
*span,
Operator::Comparison(Comparison::LessThanOrEqual),
op,
rhs,
);
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type())
@ -2279,7 +2284,12 @@ impl Value {
pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::GreaterThan, op, rhs); return lhs.operation(
*span,
Operator::Comparison(Comparison::GreaterThan),
op,
rhs,
);
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type())
@ -2306,7 +2316,12 @@ impl Value {
pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::GreaterThanOrEqual, op, rhs); return lhs.operation(
*span,
Operator::Comparison(Comparison::GreaterThanOrEqual),
op,
rhs,
);
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type())
@ -2333,7 +2348,7 @@ impl Value {
pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::Equal, op, rhs); return lhs.operation(*span, Operator::Comparison(Comparison::Equal), op, rhs);
} }
match self.partial_cmp(rhs) { match self.partial_cmp(rhs) {
@ -2358,7 +2373,7 @@ impl Value {
pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
return lhs.operation(*span, Operator::NotEqual, op, rhs); return lhs.operation(*span, Operator::Comparison(Comparison::NotEqual), op, rhs);
} }
match self.partial_cmp(rhs) { match self.partial_cmp(rhs) {
@ -2426,7 +2441,7 @@ impl Value {
}) })
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::In, op, rhs) lhs.operation(*span, Operator::Comparison(Comparison::In), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2483,7 +2498,7 @@ impl Value {
}) })
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::NotIn, op, rhs) lhs.operation(*span, Operator::Comparison(Comparison::NotIn), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2528,9 +2543,9 @@ impl Value {
(Value::CustomValue { val: lhs, span }, rhs) => lhs.operation( (Value::CustomValue { val: lhs, span }, rhs) => lhs.operation(
*span, *span,
if invert { if invert {
Operator::NotRegexMatch Operator::Comparison(Comparison::NotRegexMatch)
} else { } else {
Operator::RegexMatch Operator::Comparison(Comparison::RegexMatch)
}, },
op, op,
rhs, rhs,
@ -2552,7 +2567,7 @@ impl Value {
span, span,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::StartsWith, op, rhs) lhs.operation(*span, Operator::Comparison(Comparison::StartsWith), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2571,7 +2586,7 @@ impl Value {
span, span,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::EndsWith, op, rhs) lhs.operation(*span, Operator::Comparison(Comparison::EndsWith), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2590,7 +2605,7 @@ impl Value {
val: *lhs << rhs, val: *lhs << rhs,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::ShiftLeft, op, rhs) lhs.operation(*span, Operator::Bits(Bits::ShiftLeft), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2609,7 +2624,7 @@ impl Value {
val: *lhs >> rhs, val: *lhs >> rhs,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::ShiftRight, op, rhs) lhs.operation(*span, Operator::Bits(Bits::ShiftRight), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2628,7 +2643,7 @@ impl Value {
val: *lhs | rhs, val: *lhs | rhs,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::BitOr, op, rhs) lhs.operation(*span, Operator::Bits(Bits::BitOr), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2647,7 +2662,7 @@ impl Value {
val: *lhs ^ rhs, val: *lhs ^ rhs,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::BitXor, op, rhs) lhs.operation(*span, Operator::Bits(Bits::BitXor), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2666,7 +2681,7 @@ impl Value {
val: *lhs & rhs, val: *lhs & rhs,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::BitAnd, op, rhs) lhs.operation(*span, Operator::Bits(Bits::BitAnd), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2721,7 +2736,7 @@ impl Value {
} }
} }
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Modulo, op, rhs) lhs.operation(*span, Operator::Math(Math::Modulo), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
@ -2741,7 +2756,7 @@ impl Value {
span, span,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::And, op, rhs) lhs.operation(*span, Operator::Boolean(Boolean::And), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2760,7 +2775,7 @@ impl Value {
span, span,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Or, op, rhs) lhs.operation(*span, Operator::Boolean(Boolean::Or), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
@ -2797,7 +2812,7 @@ impl Value {
span, span,
}), }),
(Value::CustomValue { val: lhs, span }, rhs) => { (Value::CustomValue { val: lhs, span }, rhs) => {
lhs.operation(*span, Operator::Pow, op, rhs) lhs.operation(*span, Operator::Math(Math::Pow), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {

View File

@ -4,13 +4,15 @@ use crate::{Span, Type};
pub struct Variable { pub struct Variable {
pub declaration_span: Span, pub declaration_span: Span,
pub ty: Type, pub ty: Type,
pub mutable: bool,
} }
impl Variable { impl Variable {
pub fn new(declaration_span: Span, ty: Type) -> Variable { pub fn new(declaration_span: Span, ty: Type, mutable: bool) -> Variable {
Self { Self {
declaration_span, declaration_span,
ty, ty,
mutable,
} }
} }
} }