From 13515c5eb057b6056ac8cf5a0d7eedc974df6bde Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 11 Nov 2022 19:51:08 +1300 Subject: [PATCH] Limited mutable variables (#7089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .cargo/config.toml | 2 +- crates/nu-cli/src/repl.rs | 1 + crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/core_commands/mut_.rs | 117 +++++++++ .../values/nu_dataframe/between_values.rs | 57 ++--- .../values/nu_expression/custom_value.rs | 29 ++- crates/nu-command/src/default_context.rs | 1 + .../nu-command/src/strings/format/command.rs | 2 +- crates/nu-command/tests/commands/let_.rs | 12 + crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-command/tests/commands/mut_.rs | 49 ++++ crates/nu-engine/src/eval.rs | 209 ++++++++-------- crates/nu-parser/src/errors.rs | 25 ++ crates/nu-parser/src/parse_keywords.rs | 131 +++++++++- crates/nu-parser/src/parser.rs | 229 +++++++++++------- crates/nu-parser/src/type_check.rs | 108 +++++---- crates/nu-protocol/src/ast/expression.rs | 54 +++-- crates/nu-protocol/src/ast/operator.rs | 93 ++++--- crates/nu-protocol/src/engine/engine_state.rs | 29 ++- crates/nu-protocol/src/shell_error.rs | 18 ++ crates/nu-protocol/src/value/mod.rs | 71 +++--- crates/nu-protocol/src/variable.rs | 4 +- 22 files changed, 857 insertions(+), 387 deletions(-) create mode 100644 crates/nu-command/src/core_commands/mut_.rs create mode 100644 crates/nu-command/tests/commands/mut_.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 32f73a85ed..808cf30806 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,7 @@ [target.x86_64-pc-windows-msvc] # increase the default windows stack size # 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 diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index dbafd9ecae..73b3b44ea0 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -801,6 +801,7 @@ pub fn eval_hook( name.as_bytes().to_vec(), val.span()?, Type::Any, + false, ); vars.push((var_id, val)); diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 241672e65a..6c5d1048b9 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -24,6 +24,7 @@ mod ignore; mod let_; mod metadata; mod module; +mod mut_; pub(crate) mod overlay; mod use_; mod version; @@ -54,6 +55,7 @@ pub use ignore::Ignore; pub use let_::Let; pub use metadata::Metadata; pub use module::Module; +pub use mut_::Mut; pub use overlay::*; pub use use_::Use; pub use version::Version; diff --git a/crates/nu-command/src/core_commands/mut_.rs b/crates/nu-command/src/core_commands/mut_.rs new file mode 100644 index 0000000000..5c6dd5847d --- /dev/null +++ b/crates/nu-command/src/core_commands/mut_.rs @@ -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 { + 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 { + 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)); + } +} diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs index 2c74d622fc..c81d38eabc 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs @@ -1,5 +1,8 @@ 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 polars::prelude::{ BooleanType, ChunkCompare, ChunkedArray, DataType, Float64Type, Int64Type, IntoSeries, @@ -16,7 +19,7 @@ pub(super) fn between_dataframes( ) -> Result { let operation_span = span(&[left.span()?, right.span()?]); 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)), Err(e) => Err(e), }, @@ -39,25 +42,25 @@ pub(super) fn compute_between_series( ) -> Result { let operation_span = span(&[left.span()?, right.span()?]); match operator.item { - Operator::Plus => { + Operator::Math(Math::Plus) => { let mut res = lhs + rhs; let name = format!("sum_{}_{}", lhs.name(), rhs.name()); res.rename(&name); NuDataFrame::series_to_value(res, operation_span) } - Operator::Minus => { + Operator::Math(Math::Minus) => { let mut res = lhs - rhs; let name = format!("sub_{}_{}", lhs.name(), rhs.name()); res.rename(&name); NuDataFrame::series_to_value(res, operation_span) } - Operator::Multiply => { + Operator::Math(Math::Multiply) => { let mut res = lhs * rhs; let name = format!("mul_{}_{}", lhs.name(), rhs.name()); res.rename(&name); NuDataFrame::series_to_value(res, operation_span) } - Operator::Divide => { + Operator::Math(Math::Divide) => { let res = lhs.checked_div(rhs); match 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 res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; NuDataFrame::series_to_value(res, operation_span) } - Operator::NotEqual => { + Operator::Comparison(Comparison::NotEqual) => { let name = format!("neq_{}_{}", lhs.name(), rhs.name()); let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; NuDataFrame::series_to_value(res, operation_span) } - Operator::LessThan => { + Operator::Comparison(Comparison::LessThan) => { let name = format!("lt_{}_{}", lhs.name(), rhs.name()); let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; NuDataFrame::series_to_value(res, operation_span) } - Operator::LessThanOrEqual => { + Operator::Comparison(Comparison::LessThanOrEqual) => { let name = format!("lte_{}_{}", lhs.name(), rhs.name()); let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; NuDataFrame::series_to_value(res, operation_span) } - Operator::GreaterThan => { + Operator::Comparison(Comparison::GreaterThan) => { let name = format!("gt_{}_{}", lhs.name(), rhs.name()); let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; NuDataFrame::series_to_value(res, operation_span) } - Operator::GreaterThanOrEqual => { + Operator::Comparison(Comparison::GreaterThanOrEqual) => { let name = format!("gte_{}_{}", lhs.name(), rhs.name()); let res = compare_series(lhs, rhs, name.as_str(), right.span().ok(), Series::equal)?; NuDataFrame::series_to_value(res, operation_span) } - Operator::And => match lhs.dtype() { + Operator::Boolean(Boolean::And) => match lhs.dtype() { DataType::Boolean => { let lhs_cast = lhs.bool(); let rhs_cast = rhs.bool(); @@ -133,7 +136,7 @@ pub(super) fn compute_between_series( operation_span, )), }, - Operator::Or => match lhs.dtype() { + Operator::Boolean(Boolean::Or) => match lhs.dtype() { DataType::Boolean => { let lhs_cast = lhs.bool(); let rhs_cast = rhs.bool(); @@ -218,7 +221,7 @@ pub(super) fn compute_series_single_value( let lhs = lhs.as_series(lhs_span)?; match operator.item { - Operator::Plus => match &right { + Operator::Math(Math::Plus) => match &right { Value::Int { val, .. } => { compute_series_i64(&lhs, *val, >::add, lhs_span) } @@ -234,7 +237,7 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, - Operator::Minus => match &right { + Operator::Math(Math::Minus) => match &right { Value::Int { val, .. } => { compute_series_i64(&lhs, *val, >::sub, lhs_span) } @@ -249,7 +252,7 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, - Operator::Multiply => match &right { + Operator::Math(Math::Multiply) => match &right { Value::Int { val, .. } => { compute_series_i64(&lhs, *val, >::mul, lhs_span) } @@ -264,7 +267,7 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, - Operator::Divide => match &right { + Operator::Math(Math::Divide) => match &right { Value::Int { val, span } => { if *val == 0 { Err(ShellError::DivisionByZero(*span)) @@ -287,7 +290,7 @@ pub(super) fn compute_series_single_value( 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::Float { val, .. } => { compare_series_decimal(&lhs, *val, ChunkedArray::equal, lhs_span) @@ -307,7 +310,7 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, - Operator::NotEqual => match &right { + Operator::Comparison(Comparison::NotEqual) => match &right { Value::Int { val, .. } => { 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()?, }), }, - Operator::LessThan => match &right { + Operator::Comparison(Comparison::LessThan) => match &right { Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt, lhs_span), Value::Float { val, .. } => { compare_series_decimal(&lhs, *val, ChunkedArray::lt, lhs_span) @@ -344,7 +347,7 @@ pub(super) fn compute_series_single_value( 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::Float { val, .. } => { 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()?, }), }, - Operator::GreaterThan => match &right { + Operator::Comparison(Comparison::GreaterThan) => match &right { Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt, lhs_span), Value::Float { val, .. } => { compare_series_decimal(&lhs, *val, ChunkedArray::gt, lhs_span) @@ -376,7 +379,7 @@ pub(super) fn compute_series_single_value( 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::Float { val, .. } => { 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? - Operator::RegexMatch => match &right { + Operator::Comparison(Comparison::RegexMatch) => match &right { Value::String { val, .. } => contains_series_pat(&lhs, val, lhs_span), _ => Err(ShellError::OperatorMismatch { op_span: operator.span, @@ -403,7 +406,7 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, - Operator::StartsWith => match &right { + Operator::Comparison(Comparison::StartsWith) => match &right { Value::String { val, .. } => { let starts_with_pattern = format!("^{}", fancy_regex::escape(val)); contains_series_pat(&lhs, &starts_with_pattern, lhs_span) @@ -416,7 +419,7 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, - Operator::EndsWith => match &right { + Operator::Comparison(Comparison::EndsWith) => match &right { Value::String { val, .. } => { let ends_with_pattern = format!("{}$", fancy_regex::escape(val)); contains_series_pat(&lhs, &ends_with_pattern, lhs_span) diff --git a/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs b/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs index f856598e41..ee85756303 100644 --- a/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs +++ b/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs @@ -1,7 +1,10 @@ use std::ops::{Add, Div, Mul, Rem, Sub}; 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; // CustomValue implementation for NuDataFrame @@ -95,33 +98,33 @@ fn with_operator( op_span: Span, ) -> Result { match operator { - Operator::Plus => apply_arithmetic(left, right, lhs_span, Add::add), - Operator::Minus => apply_arithmetic(left, right, lhs_span, Sub::sub), - Operator::Multiply => apply_arithmetic(left, right, lhs_span, Mul::mul), - Operator::Divide => apply_arithmetic(left, right, lhs_span, Div::div), - Operator::Modulo => apply_arithmetic(left, right, lhs_span, Rem::rem), - Operator::FloorDivision => apply_arithmetic(left, right, lhs_span, Div::div), - Operator::Equal => Ok(left + Operator::Math(Math::Plus) => apply_arithmetic(left, right, lhs_span, Add::add), + Operator::Math(Math::Minus) => apply_arithmetic(left, right, lhs_span, Sub::sub), + Operator::Math(Math::Multiply) => apply_arithmetic(left, right, lhs_span, Mul::mul), + Operator::Math(Math::Divide) => apply_arithmetic(left, right, lhs_span, Div::div), + Operator::Math(Math::Modulo) => apply_arithmetic(left, right, lhs_span, Rem::rem), + Operator::Math(Math::FloorDivision) => apply_arithmetic(left, right, lhs_span, Div::div), + Operator::Comparison(Comparison::Equal) => Ok(left .clone() .apply_with_expr(right.clone(), Expr::eq) .into_value(lhs_span)), - Operator::NotEqual => Ok(left + Operator::Comparison(Comparison::NotEqual) => Ok(left .clone() .apply_with_expr(right.clone(), Expr::neq) .into_value(lhs_span)), - Operator::GreaterThan => Ok(left + Operator::Comparison(Comparison::GreaterThan) => Ok(left .clone() .apply_with_expr(right.clone(), Expr::gt) .into_value(lhs_span)), - Operator::GreaterThanOrEqual => Ok(left + Operator::Comparison(Comparison::GreaterThanOrEqual) => Ok(left .clone() .apply_with_expr(right.clone(), Expr::gt_eq) .into_value(lhs_span)), - Operator::LessThan => Ok(left + Operator::Comparison(Comparison::LessThan) => Ok(left .clone() .apply_with_expr(right.clone(), Expr::lt) .into_value(lhs_span)), - Operator::LessThanOrEqual => Ok(left + Operator::Comparison(Comparison::LessThanOrEqual) => Ok(left .clone() .apply_with_expr(right.clone(), Expr::lt_eq) .into_value(lhs_span)), diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index b361048210..08fcea8525 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -59,6 +59,7 @@ pub fn create_default_context() -> EngineState { Let, Metadata, Module, + Mut, Use, Version, }; diff --git a/crates/nu-command/src/strings/format/command.rs b/crates/nu-command/src/strings/format/command.rs index 2d88988ff4..fe960e82ac 100644 --- a/crates/nu-command/src/strings/format/command.rs +++ b/crates/nu-command/src/strings/format/command.rs @@ -45,7 +45,7 @@ impl Command for Format { let specified_pattern: Result = call.req(engine_state, stack, 0); let input_val = input.into_value(call.head); // 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()); match specified_pattern { diff --git a/crates/nu-command/tests/commands/let_.rs b/crates/nu-command/tests/commands/let_.rs index 4ee0614ad5..d2db3e5877 100644 --- a/crates/nu-command/tests/commands/let_.rs +++ b/crates/nu-command/tests/commands/let_.rs @@ -13,3 +13,15 @@ fn let_parse_error() { .err .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")); +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 103c53ab1d..b0aa208d98 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -42,6 +42,7 @@ mod math; mod merge; mod mkdir; mod move_; +mod mut_; mod n; mod network; mod nu_check; diff --git a/crates/nu-command/tests/commands/mut_.rs b/crates/nu-command/tests/commands/mut_.rs new file mode 100644 index 0000000000..b6a72e7376 --- /dev/null +++ b/crates/nu-command/tests/commands/mut_.rs @@ -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"); +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b8e54ac158..2260f17fa3 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,7 +1,7 @@ use crate::{current_dir_str, get_full_help}; use nu_path::expand_path_with; use nu_protocol::{ - ast::{Block, Call, Expr, Expression, Operator}, + ast::{Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator}, engine::{EngineState, Stack, Visibility}, Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream, PipelineData, Range, RawStream, ShellError, Span, Spanned, SyntaxShape, Unit, Value, VarId, @@ -355,131 +355,112 @@ pub fn eval_expression( } Expr::BinaryOp(lhs, op, rhs) => { let op_span = op.span; - let lhs = eval_expression(engine_state, stack, lhs)?; let op = eval_operator(op)?; match op { - Operator::And => { - if lhs.is_false() { - Ok(Value::Bool { - val: false, - span: expr.span, - }) - } else { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.and(op_span, &rhs, expr.span) + Operator::Boolean(boolean) => { + let lhs = eval_expression(engine_state, stack, lhs)?; + match boolean { + Boolean::And => { + if lhs.is_false() { + Ok(Value::Bool { + val: false, + span: 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 => { - 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::Math(math) => { + let lhs = eval_expression(engine_state, stack, lhs)?; + let rhs = eval_expression(engine_state, stack, rhs)?; + + match math { + Math::Plus => lhs.add(op_span, &rhs, expr.span), + Math::Minus => lhs.sub(op_span, &rhs, expr.span), + Math::Multiply => lhs.mul(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)?; - 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)?; - 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)?; - lhs.sub(op_span, &rhs, expr.span) - } - Operator::Multiply => { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.mul(op_span, &rhs, expr.span) - } - Operator::Divide => { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.div(op_span, &rhs, expr.span) - } - Operator::LessThan => { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.lt(op_span, &rhs, expr.span) - } - Operator::LessThanOrEqual => { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.lte(op_span, &rhs, expr.span) - } - Operator::GreaterThan => { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.gt(op_span, &rhs, expr.span) - } - Operator::GreaterThanOrEqual => { - let rhs = eval_expression(engine_state, stack, rhs)?; - lhs.gte(op_span, &rhs, expr.span) - } - Operator::Equal => { - 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) + + match &lhs.expr { + Expr::Var(var_id) | Expr::VarDecl(var_id) => { + let var_info = engine_state.get_var(*var_id); + if var_info.mutable { + stack.vars.insert(*var_id, rhs); + Ok(Value::nothing(lhs.span)) + } else { + Err(ShellError::AssignmentRequiresMutableVar(lhs.span)) + } + } + Expr::FullCellPath(cell_path) => match &cell_path.head.expr { + Expr::Var(var_id) | Expr::VarDecl(var_id) => { + let var_info = engine_state.get_var(*var_id); + if var_info.mutable { + let mut lhs = + eval_expression(engine_state, stack, &cell_path.head)?; + lhs.update_data_at_cell_path(&cell_path.tail, rhs)?; + stack.vars.insert(*var_id, lhs); + Ok(Value::nothing(cell_path.head.span)) + } else { + Err(ShellError::AssignmentRequiresMutableVar(lhs.span)) + } + } + _ => Err(ShellError::AssignmentRequiresVar(lhs.span)), + }, + _ => Err(ShellError::AssignmentRequiresVar(lhs.span)), + } } } } diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 5002b690b4..9abe911625 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -56,6 +56,10 @@ pub enum ParseError { Type, ), + #[error("Capture of mutable variable.")] + #[diagnostic(code(nu::parser::expected_keyword), url(docsrs))] + CaptureOfMutableVar(#[label("capture of mutable variable")] Span), + #[error("Expected keyword.")] #[diagnostic(code(nu::parser::expected_keyword), url(docsrs))] ExpectedKeyword(String, #[label("expected {0}")] Span), @@ -88,6 +92,16 @@ pub enum ParseError { )] 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.")] #[diagnostic( code(nu::parser::let_builtin_var), @@ -96,6 +110,14 @@ pub enum ParseError { )] 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")] #[diagnostic(code(nu::parser::incorrect_value), url(docsrs), help("{2}"))] IncorrectValue(String, #[label("unexpected {0}")] Span, String), @@ -343,7 +365,10 @@ impl ParseError { ParseError::UnexpectedKeyword(_, s) => *s, ParseError::BuiltinCommandInPipeline(_, s) => *s, ParseError::LetInPipeline(_, _, s) => *s, + ParseError::MutInPipeline(_, _, s) => *s, ParseError::LetBuiltinVar(_, s) => *s, + ParseError::MutBuiltinVar(_, s) => *s, + ParseError::CaptureOfMutableVar(s) => *s, ParseError::IncorrectValue(_, s, _) => *s, ParseError::MultipleRestParams(s) => *s, ParseError::VariableNotFound(s) => *s, diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 57d31ebf19..65b6e4e8af 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -2781,8 +2781,12 @@ pub fn parse_let( } let mut idx = 0; - let (lvalue, err) = - parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx); + let (lvalue, err) = parse_var_with_opt_type( + working_set, + &spans[1..(span.0)], + &mut idx, + false, + ); error = error.or(err); 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) { + 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( working_set: &mut StateWorkingSet, spans: &[Span], diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index c67b07e017..7f5ef20aaa 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,15 +1,16 @@ use crate::{ lex, lite_parse, lite_parse::LiteCommand, + parse_mut, type_check::{math_result_type, type_compatible}, LiteBlock, ParseError, Token, TokenContents, }; use nu_protocol::{ ast::{ - Argument, Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern, - ImportPatternHead, ImportPatternMember, Operator, PathMember, Pipeline, RangeInclusion, - RangeOperator, + Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, + FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator, + PathMember, Pipeline, RangeInclusion, RangeOperator, }, engine::StateWorkingSet, span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, @@ -633,7 +634,7 @@ pub fn parse_multispan_value( SyntaxShape::VarWithOptType => { 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); (arg, error) @@ -2919,6 +2920,7 @@ pub fn parse_var_with_opt_type( working_set: &mut StateWorkingSet, spans: &[Span], spans_idx: &mut usize, + mutable: bool, ) -> (Expression, Option) { 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 { @@ -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 { expr: Expr::VarDecl(id), @@ -3005,6 +3007,7 @@ pub fn parse_var_with_opt_type( var_name, span(&spans[*spans_idx..*spans_idx + 1]), Type::Any, + mutable, ); ( @@ -3044,7 +3047,7 @@ pub fn parse_row_condition( spans: &[Span], expand_aliases_denylist: &[usize], ) -> (Expression, Option) { - 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) = parse_math_expression(working_set, spans, Some(var_id), expand_aliases_denylist); let span = span(spans); @@ -3232,7 +3235,7 @@ pub fn parse_signature_helper( } 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 { args.push(Arg::Flag(Flag { @@ -3282,8 +3285,12 @@ pub fn parse_signature_helper( }) } - let var_id = - working_set.add_variable(variable_name, span, Type::Any); + let var_id = working_set.add_variable( + variable_name, + span, + Type::Any, + false, + ); if chars.len() == 1 { args.push(Arg::Flag(Flag { @@ -3327,7 +3334,7 @@ pub fn parse_signature_helper( } 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 { 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 args.push(Arg::Positional( @@ -3420,7 +3428,7 @@ pub fn parse_signature_helper( } 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 { desc: String::new(), @@ -3443,7 +3451,7 @@ pub fn parse_signature_helper( } 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 args.push(Arg::Positional( @@ -4411,33 +4419,34 @@ pub fn parse_operator( let contents = working_set.get_span_contents(span); let operator = match contents { - b"==" => Operator::Equal, - b"!=" => Operator::NotEqual, - b"<" => Operator::LessThan, - b"<=" => Operator::LessThanOrEqual, - b">" => Operator::GreaterThan, - b">=" => Operator::GreaterThanOrEqual, - b"=~" => Operator::RegexMatch, - b"!~" => Operator::NotRegexMatch, - b"+" => Operator::Plus, - b"++" => Operator::Append, - b"-" => Operator::Minus, - b"*" => Operator::Multiply, - b"/" => Operator::Divide, - b"//" => Operator::FloorDivision, - b"in" => Operator::In, - b"not-in" => Operator::NotIn, - b"mod" => Operator::Modulo, - b"bit-or" => Operator::BitOr, - b"bit-xor" => Operator::BitXor, - b"bit-and" => Operator::BitAnd, - b"bit-shl" => Operator::ShiftLeft, - b"bit-shr" => Operator::ShiftRight, - b"starts-with" => Operator::StartsWith, - b"ends-with" => Operator::EndsWith, - b"&&" | b"and" => Operator::And, - b"||" | b"or" => Operator::Or, - b"**" => Operator::Pow, + b"=" => Operator::Assignment(Assignment::Assign), + b"==" => Operator::Comparison(Comparison::Equal), + b"!=" => Operator::Comparison(Comparison::NotEqual), + b"<" => Operator::Comparison(Comparison::LessThan), + b"<=" => Operator::Comparison(Comparison::LessThanOrEqual), + b">" => Operator::Comparison(Comparison::GreaterThan), + b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual), + b"=~" => Operator::Comparison(Comparison::RegexMatch), + b"!~" => Operator::Comparison(Comparison::NotRegexMatch), + b"+" => Operator::Math(Math::Plus), + b"++" => Operator::Math(Math::Append), + b"-" => Operator::Math(Math::Minus), + b"*" => Operator::Math(Math::Multiply), + b"/" => Operator::Math(Math::Divide), + b"//" => Operator::Math(Math::FloorDivision), + b"in" => Operator::Comparison(Comparison::In), + b"not-in" => Operator::Comparison(Comparison::NotIn), + b"mod" => Operator::Math(Math::Modulo), + b"bit-or" => Operator::Bits(Bits::BitOr), + b"bit-xor" => Operator::Bits(Bits::BitXor), + b"bit-and" => Operator::Bits(Bits::BitAnd), + b"bit-shl" => Operator::Bits(Bits::ShiftLeft), + b"bit-shr" => Operator::Bits(Bits::ShiftRight), + b"starts-with" => Operator::Comparison(Comparison::StartsWith), + b"ends-with" => Operator::Comparison(Comparison::EndsWith), + b"&&" | b"and" => Operator::Boolean(Boolean::And), + b"||" | b"or" => Operator::Boolean(Boolean::Or), + b"**" => Operator::Math(Math::Pow), _ => { return ( garbage(span), @@ -4764,6 +4773,28 @@ pub fn parse_expression( 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" => ( parse_call( 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"extern" => parse_extern(working_set, lite_command, 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" => { let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist); (Pipeline::from_vec(vec![expr]), err) @@ -5229,8 +5261,8 @@ pub fn discover_captures_in_closure( working_set: &StateWorkingSet, block: &Block, seen: &mut Vec, - seen_blocks: &mut HashMap>, -) -> Vec { + seen_blocks: &mut HashMap>, +) -> Result, ParseError> { let mut output = vec![]; for flag in &block.signature.named { @@ -5256,57 +5288,71 @@ pub fn discover_captures_in_closure( } 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 + Ok(output) } fn discover_captures_in_pipeline( working_set: &StateWorkingSet, pipeline: &Pipeline, seen: &mut Vec, - seen_blocks: &mut HashMap>, -) -> Vec { + seen_blocks: &mut HashMap>, +) -> Result, ParseError> { let mut output = vec![]; 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 + Ok(output) } +// Closes over captured variables pub fn discover_captures_in_expr( working_set: &StateWorkingSet, expr: &Expression, seen: &mut Vec, - seen_blocks: &mut HashMap>, -) -> Vec { - let mut output = vec![]; + seen_blocks: &mut HashMap>, +) -> Result, ParseError> { + let mut output: Vec<(VarId, Span)> = vec![]; match &expr.expr { Expr::BinaryOp(lhs, _, rhs) => { - 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 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)?; output.extend(&lhs_result); output.extend(&rhs_result); } 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); } Expr::Closure(block_id) => { let block = working_set.get_block(*block_id); let results = { 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()); - for var_id in results.into_iter() { + for (var_id, span) in results.into_iter() { 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? let results = { 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()); - for var_id in results.into_iter() { + for (var_id, span) in results.into_iter() { 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 => { let block = working_set.get_block(block_id); if !block.captures.is_empty() { - output.extend(&block.captures); + output.extend(block.captures.iter().map(|var_id| (*var_id, call.head))); } else { let mut seen = vec![]; seen_blocks.insert(block_id, output.clone()); @@ -5346,7 +5392,7 @@ pub fn discover_captures_in_expr( block, &mut seen, seen_blocks, - ); + )?; output.extend(&result); seen_blocks.insert(block_id, result); } @@ -5356,24 +5402,24 @@ pub fn discover_captures_in_expr( for named in call.named_iter() { 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); } } 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); } } Expr::CellPath(_) => {} Expr::DateTime(_) => {} 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); 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); } } @@ -5381,7 +5427,8 @@ pub fn discover_captures_in_expr( Expr::Directory(_) => {} Expr::Float(_) => {} 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); } Expr::ImportPattern(_) => {} @@ -5391,27 +5438,27 @@ pub fn discover_captures_in_expr( Expr::GlobPattern(_) => {} Expr::Int(_) => {} 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); } Expr::List(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); } } Expr::Operator(_) => {} Expr::Range(expr1, expr2, expr3, _) => { 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); } 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); } 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); } } @@ -5422,13 +5469,13 @@ pub fn discover_captures_in_expr( field_name, seen, seen_blocks, - )); + )?); output.extend(&discover_captures_in_expr( working_set, field_value, seen, seen_blocks, - )); + )?); } } Expr::Signature(sig) => { @@ -5457,7 +5504,7 @@ pub fn discover_captures_in_expr( Expr::String(_) => {} Expr::StringInterpolation(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); } } @@ -5465,41 +5512,41 @@ pub fn discover_captures_in_expr( let block = working_set.get_block(*block_id); let results = { 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()); - for var_id in results.into_iter() { + for (var_id, span) in results.into_iter() { if !seen.contains(&var_id) { - output.push(var_id) + output.push((var_id, span)) } } } Expr::Table(headers, values) => { 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); } for row in values { 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); } } } 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); } Expr::Var(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) => { seen.push(*var_id); } } - output + Ok(output) } 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 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 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) { let captures = 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(); if !captures.is_empty() && block_captures_empty { let block = working_set.get_block_mut(block_id); - block.captures = captures; + block.captures = captures.into_iter().map(|(var_id, _)| var_id).collect(); } } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index c8cdd7e27f..0daf0fa93c 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,6 +1,6 @@ use crate::ParseError; use nu_protocol::{ - ast::{Expr, Expression, Operator}, + ast::{Assignment, Bits, Boolean, Comparison, Expr, Expression, Math, Operator}, engine::StateWorkingSet, Type, }; @@ -26,7 +26,7 @@ pub fn math_result_type( //println!("checking: {:?} {:?} {:?}", lhs, op, rhs); match &op.expr { 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::Float, Type::Int) => (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)) => { if a == b { (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::Float, Type::Int) => (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::Float, Type::Int) => (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::Float, Type::Int) => (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::Float, Type::Int) => (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::Float, Type::Int) => (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) { - (Type::Bool, Type::Bool) => (Type::Bool, None), + Operator::Boolean(Boolean::And) | Operator::Boolean(Boolean::Or) => { + 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(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::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 - // definitions. As soon as that syntax is added this should be removed - (a, b) if a == b => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) + // 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 + (a, b) if a == b => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Any, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + 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::Float, Type::Int) => (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::Float, Type::Int) => (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::Float, Type::Int) => (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::Float, Type::Int) => (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(a.to_string()), 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(a.to_string()), 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::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::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::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::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), (Type::Int | Type::Float, Type::Range) => (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), (Type::Int | Type::Float, Type::Range) => (Type::Bool, None), (Type::String, Type::String) => (Type::Bool, None), @@ -527,11 +532,11 @@ pub fn math_result_type( ) } }, - Operator::ShiftLeft - | Operator::ShiftRight - | Operator::BitOr - | Operator::BitXor - | Operator::BitAnd => match (&lhs.ty, &rhs.ty) { + Operator::Bits(Bits::ShiftLeft) + | Operator::Bits(Bits::ShiftRight) + | Operator::Bits(Bits::BitOr) + | Operator::Bits(Bits::BitXor) + | Operator::Bits(Bits::BitAnd) => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, 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); diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index f783972910..5e30d3a17b 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use super::{Expr, Operator}; +use super::Expr; use crate::ast::ImportPattern; use crate::DeclId; use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID}; @@ -26,34 +26,36 @@ impl Expression { pub fn precedence(&self) -> usize { match &self.expr { Expr::Operator(operator) => { + use super::operator::*; // Higher precedence binds tighter match operator { - Operator::Pow => 100, - Operator::Multiply - | Operator::Divide - | Operator::Modulo - | Operator::FloorDivision => 95, - Operator::Plus | Operator::Minus => 90, - Operator::ShiftLeft | Operator::ShiftRight => 85, - Operator::NotRegexMatch - | Operator::RegexMatch - | Operator::StartsWith - | Operator::EndsWith - | Operator::LessThan - | Operator::LessThanOrEqual - | Operator::GreaterThan - | Operator::GreaterThanOrEqual - | Operator::Equal - | Operator::NotEqual - | Operator::In - | Operator::NotIn - | Operator::Append => 80, - Operator::BitAnd => 75, - Operator::BitXor => 70, - Operator::BitOr => 60, - Operator::And => 50, - Operator::Or => 40, + Operator::Math(Math::Pow) => 100, + Operator::Math(Math::Multiply) + | Operator::Math(Math::Divide) + | Operator::Math(Math::Modulo) + | Operator::Math(Math::FloorDivision) => 95, + Operator::Math(Math::Plus) | Operator::Math(Math::Minus) => 90, + Operator::Bits(Bits::ShiftLeft) | Operator::Bits(Bits::ShiftRight) => 85, + Operator::Comparison(Comparison::NotRegexMatch) + | Operator::Comparison(Comparison::RegexMatch) + | Operator::Comparison(Comparison::StartsWith) + | Operator::Comparison(Comparison::EndsWith) + | Operator::Comparison(Comparison::LessThan) + | Operator::Comparison(Comparison::LessThanOrEqual) + | Operator::Comparison(Comparison::GreaterThan) + | Operator::Comparison(Comparison::GreaterThanOrEqual) + | Operator::Comparison(Comparison::Equal) + | Operator::Comparison(Comparison::NotEqual) + | Operator::Comparison(Comparison::In) + | Operator::Comparison(Comparison::NotIn) + | Operator::Math(Math::Append) => 80, + Operator::Bits(Bits::BitAnd) => 75, + Operator::Bits(Bits::BitXor) => 70, + Operator::Bits(Bits::BitOr) => 60, + Operator::Boolean(Boolean::And) => 50, + Operator::Boolean(Boolean::Or) => 40, + Operator::Assignment(Assignment::Assign) => 10, } } _ => 0, diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 2c29c1dbb5..644e7364fa 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Display; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Operator { +pub enum Comparison { Equal, NotEqual, LessThan, @@ -13,20 +13,32 @@ pub enum Operator { GreaterThanOrEqual, RegexMatch, NotRegexMatch, + In, + NotIn, + StartsWith, + EndsWith, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Math { Plus, Append, Minus, Multiply, Divide, - In, - NotIn, Modulo, FloorDivision, + Pow, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Boolean { And, Or, - Pow, - StartsWith, - EndsWith, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Bits { BitOr, BitXor, BitAnd, @@ -34,36 +46,51 @@ pub enum Operator { 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Operator::Equal => write!(f, "=="), - Operator::NotEqual => write!(f, "!="), - Operator::LessThan => write!(f, "<"), - Operator::GreaterThan => write!(f, ">"), - Operator::RegexMatch => write!(f, "=~"), - Operator::NotRegexMatch => write!(f, "!~"), - Operator::Plus => write!(f, "+"), - Operator::Append => write!(f, "++"), - Operator::Minus => write!(f, "-"), - Operator::Multiply => write!(f, "*"), - Operator::Divide => write!(f, "/"), - Operator::In => write!(f, "in"), - Operator::NotIn => write!(f, "not-in"), - Operator::Modulo => write!(f, "mod"), - Operator::FloorDivision => write!(f, "fdiv"), - Operator::And => write!(f, "&&"), - Operator::Or => write!(f, "||"), - Operator::Pow => write!(f, "**"), - Operator::BitOr => write!(f, "bit-or"), - Operator::BitXor => write!(f, "bit-xor"), - Operator::BitAnd => write!(f, "bit-and"), - Operator::ShiftLeft => write!(f, "bit-shl"), - Operator::ShiftRight => write!(f, "bit-shr"), - Operator::LessThanOrEqual => write!(f, "<="), - Operator::GreaterThanOrEqual => write!(f, ">="), - Operator::StartsWith => write!(f, "starts-with"), - Operator::EndsWith => write!(f, "ends-with"), + Operator::Assignment(Assignment::Assign) => write!(f, "="), + Operator::Comparison(Comparison::Equal) => write!(f, "=="), + Operator::Comparison(Comparison::NotEqual) => write!(f, "!="), + Operator::Comparison(Comparison::LessThan) => write!(f, "<"), + Operator::Comparison(Comparison::GreaterThan) => write!(f, ">"), + Operator::Comparison(Comparison::RegexMatch) => write!(f, "=~"), + Operator::Comparison(Comparison::NotRegexMatch) => write!(f, "!~"), + Operator::Comparison(Comparison::LessThanOrEqual) => write!(f, "<="), + Operator::Comparison(Comparison::GreaterThanOrEqual) => write!(f, ">="), + Operator::Comparison(Comparison::StartsWith) => write!(f, "starts-with"), + Operator::Comparison(Comparison::EndsWith) => write!(f, "ends-with"), + Operator::Comparison(Comparison::In) => write!(f, "in"), + Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"), + Operator::Math(Math::Plus) => write!(f, "+"), + Operator::Math(Math::Append) => write!(f, "++"), + Operator::Math(Math::Minus) => write!(f, "-"), + Operator::Math(Math::Multiply) => write!(f, "*"), + Operator::Math(Math::Divide) => write!(f, "/"), + Operator::Math(Math::Modulo) => write!(f, "mod"), + Operator::Math(Math::FloorDivision) => write!(f, "fdiv"), + Operator::Math(Math::Pow) => write!(f, "**"), + Operator::Boolean(Boolean::And) => write!(f, "&&"), + Operator::Boolean(Boolean::Or) => write!(f, "||"), + Operator::Bits(Bits::BitOr) => write!(f, "bit-or"), + Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"), + Operator::Bits(Bits::BitAnd) => write!(f, "bit-and"), + Operator::Bits(Bits::ShiftLeft) => write!(f, "bit-shl"), + Operator::Bits(Bits::ShiftRight) => write!(f, "bit-shr"), } } } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 7a5ceae8b9..0d59963e26 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -105,11 +105,11 @@ impl EngineState { files: vec![], file_contents: vec![], vars: vec![ - Variable::new(Span::new(0, 0), Type::Any), - Variable::new(Span::new(0, 0), Type::Any), - Variable::new(Span::new(0, 0), Type::Any), - Variable::new(Span::new(0, 0), Type::Any), - 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, false), + Variable::new(Span::new(0, 0), Type::Any, false), + Variable::new(Span::new(0, 0), Type::Any, false), + Variable::new(Span::new(0, 0), Type::Any, false), ], decls: vec![], aliases: vec![], @@ -1571,7 +1571,13 @@ impl<'a> StateWorkingSet<'a> { None } - pub fn add_variable(&mut self, mut name: Vec, span: Span, ty: Type) -> VarId { + pub fn add_variable( + &mut self, + mut name: Vec, + span: Span, + ty: Type, + mutable: bool, + ) -> VarId { let next_id = self.next_var_id(); // correct name if necessary @@ -1581,7 +1587,7 @@ impl<'a> StateWorkingSet<'a> { 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 } @@ -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)] pub fn get_decl(&self, decl_id: DeclId) -> &Box { let num_permanent_decls = self.permanent_state.num_decls(); diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index a77850da87..018aa91c71 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -83,6 +83,24 @@ pub enum ShellError { #[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))] 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. /// /// ## Resolution diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 5b5c8a519a..cb879b87e9 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -5,8 +5,8 @@ mod range; mod stream; mod unit; -use crate::ast::Operator; -use crate::ast::{CellPath, PathMember}; +use crate::ast::{Bits, Boolean, CellPath, Comparison, PathMember}; +use crate::ast::{Math, Operator}; use crate::ShellError; use crate::{did_you_mean, BlockId, Config, Span, Spanned, Type, VarId}; use byte_unit::ByteUnit; @@ -1734,7 +1734,7 @@ impl Value { } (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 { @@ -1841,7 +1841,7 @@ impl Value { } (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 { @@ -1927,7 +1927,7 @@ impl Value { }) } (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 { @@ -2064,7 +2064,7 @@ impl Value { } } (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 { @@ -2210,7 +2210,7 @@ impl Value { } } (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 { @@ -2225,7 +2225,7 @@ impl Value { pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result { 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()) @@ -2252,7 +2252,12 @@ impl Value { pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result { 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()) @@ -2279,7 +2284,12 @@ impl Value { pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result { 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()) @@ -2306,7 +2316,12 @@ impl Value { pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result { 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()) @@ -2333,7 +2348,7 @@ impl Value { pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result { 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) { @@ -2358,7 +2373,7 @@ impl Value { pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result { 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) { @@ -2426,7 +2441,7 @@ impl Value { }) } (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 { op_span: op, @@ -2483,7 +2498,7 @@ impl Value { }) } (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 { op_span: op, @@ -2528,9 +2543,9 @@ impl Value { (Value::CustomValue { val: lhs, span }, rhs) => lhs.operation( *span, if invert { - Operator::NotRegexMatch + Operator::Comparison(Comparison::NotRegexMatch) } else { - Operator::RegexMatch + Operator::Comparison(Comparison::RegexMatch) }, op, rhs, @@ -2552,7 +2567,7 @@ impl Value { span, }), (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 { op_span: op, @@ -2571,7 +2586,7 @@ impl Value { span, }), (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 { op_span: op, @@ -2590,7 +2605,7 @@ impl Value { val: *lhs << 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 { op_span: op, @@ -2609,7 +2624,7 @@ impl Value { val: *lhs >> 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 { op_span: op, @@ -2628,7 +2643,7 @@ impl Value { val: *lhs | 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 { op_span: op, @@ -2647,7 +2662,7 @@ impl Value { val: *lhs ^ 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 { op_span: op, @@ -2666,7 +2681,7 @@ impl Value { val: *lhs & 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 { op_span: op, @@ -2721,7 +2736,7 @@ impl Value { } } (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 { @@ -2741,7 +2756,7 @@ impl Value { span, }), (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 { op_span: op, @@ -2760,7 +2775,7 @@ impl Value { span, }), (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 { op_span: op, @@ -2797,7 +2812,7 @@ impl Value { span, }), (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 { diff --git a/crates/nu-protocol/src/variable.rs b/crates/nu-protocol/src/variable.rs index f6acaf6808..563a24248c 100644 --- a/crates/nu-protocol/src/variable.rs +++ b/crates/nu-protocol/src/variable.rs @@ -4,13 +4,15 @@ use crate::{Span, Type}; pub struct Variable { pub declaration_span: Span, pub ty: Type, + pub mutable: bool, } impl Variable { - pub fn new(declaration_span: Span, ty: Type) -> Variable { + pub fn new(declaration_span: Span, ty: Type, mutable: bool) -> Variable { Self { declaration_span, ty, + mutable, } } }