Switch let/let-env family to init with math expressions (#8545)

# Description

This is an experiment to see what switching the `let/let-env` family to
math expressions for initialisers would be like.

# User-Facing Changes

This would require any commands you call from `let x = <command here>`
(and similar family) to call the command in parentheses. `let x = (foo)`
to call `foo`.

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
JT 2023-03-23 09:14:10 +13:00 committed by GitHub
parent 0f4a073eaf
commit 2f8a52d256
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 68 additions and 21 deletions

View file

@ -21,7 +21,7 @@ impl Command for Const {
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
"equals sign followed by constant value",
)
.category(Category::Core)

View file

@ -20,7 +20,7 @@ impl Command for If {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("if")
.input_output_types(vec![(Type::Any, Type::Any)])
.required("cond", SyntaxShape::Expression, "condition to check")
.required("cond", SyntaxShape::MathExpression, "condition to check")
.required(
"then_block",
SyntaxShape::Block,

View file

@ -22,7 +22,7 @@ impl Command for Let {
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
"equals sign followed by value",
)
.category(Category::Core)

View file

@ -22,7 +22,7 @@ impl Command for Mut {
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
"equals sign followed by value",
)
.category(Category::Core)

View file

@ -21,7 +21,7 @@ impl Command for While {
Signature::build("while")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("cond", SyntaxShape::Expression, "condition to check")
.required("cond", SyntaxShape::MathExpression, "condition to check")
.required(
"block",
SyntaxShape::Block,

View file

@ -24,7 +24,7 @@ impl Command for LetEnv {
.required("var_name", SyntaxShape::String, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
"equals sign followed by value",
)
.category(Category::Env)

View file

@ -172,7 +172,7 @@ fn use_export_env_combined() {
"spam.nu",
r#"
alias bar = foo
export-env { let-env FOO = bar }
export-env { let-env FOO = (bar) }
def foo [] { 'foo' }
"#,
)]);

View file

@ -698,6 +698,29 @@ pub fn eval_expression_with_input(
input = eval_subexpression(engine_state, stack, block, input)?;
}
elem @ Expression {
expr: Expr::FullCellPath(full_cell_path),
..
} => match &full_cell_path.head {
Expression {
expr: Expr::Subexpression(block_id),
span,
..
} => {
let block = engine_state.get_block(*block_id);
// FIXME: protect this collect with ctrl-c
input = eval_subexpression(engine_state, stack, block, input)?;
let value = input.into_value(*span);
input = value
.follow_cell_path(&full_cell_path.tail, false)?
.into_pipeline_data()
}
_ => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
}
},
elem => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
}

View file

@ -3102,7 +3102,10 @@ pub fn parse_let_or_const(
working_set,
spans,
&mut idx,
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
&SyntaxShape::Keyword(
b"=".to_vec(),
Box::new(SyntaxShape::MathExpression),
),
expand_aliases_denylist,
);
error = error.or(err);
@ -3236,7 +3239,10 @@ pub fn parse_mut(
working_set,
spans,
&mut idx,
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
&SyntaxShape::Keyword(
b"=".to_vec(),
Box::new(SyntaxShape::MathExpression),
),
expand_aliases_denylist,
);
error = error.or(err);

View file

@ -71,7 +71,12 @@ pub fn is_math_expression_like(
return false;
}
if bytes == b"true" || bytes == b"false" || bytes == b"null" || bytes == b"not" {
if bytes == b"true"
|| bytes == b"false"
|| bytes == b"null"
|| bytes == b"not"
|| bytes == b"if"
{
return true;
}
@ -1112,12 +1117,12 @@ pub fn parse_call(
for word_span in spans[cmd_start..].iter() {
// Find the longest group of words that could form a command
if is_math_expression_like(working_set, *word_span, expand_aliases_denylist) {
let bytes = working_set.get_span_contents(*word_span);
if bytes != b"true" && bytes != b"false" && bytes != b"null" && bytes != b"not" {
break;
}
}
// if is_math_expression_like(working_set, *word_span, expand_aliases_denylist) {
// let bytes = working_set.get_span_contents(*word_span);
// if bytes != b"true" && bytes != b"false" && bytes != b"null" && bytes != b"not" {
// break;
// }
// }
name_spans.push(*word_span);
@ -4993,7 +4998,20 @@ pub fn parse_math_expression(
let first_span = working_set.get_span_contents(spans[0]);
if first_span == b"not" {
if first_span == b"if" {
// If expression
if spans.len() > 1 {
return parse_call(working_set, spans, spans[0], expand_aliases_denylist, false);
} else {
return (
garbage(spans[0]),
Some(ParseError::Expected(
"expression".into(),
Span::new(spans[0].end, spans[0].end),
)),
);
}
} else if first_span == b"not" {
if spans.len() > 1 {
let (remainder, err) = parse_math_expression(
working_set,

View file

@ -27,7 +27,7 @@ impl Command for Let {
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
"equals sign followed by value",
)
}
@ -1555,7 +1555,7 @@ mod input_types {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("if")
.required("cond", SyntaxShape::Expression, "condition to check")
.required("cond", SyntaxShape::MathExpression, "condition to check")
.required(
"then_block",
SyntaxShape::Block,

View file

@ -146,7 +146,7 @@ fn date_comparison() -> TestResult {
#[test]
fn let_sees_input() -> TestResult {
run_test(
r#"def c [] { let x = str length; $x }; "hello world" | c"#,
r#"def c [] { let x = (str length); $x }; "hello world" | c"#,
"11",
)
}

View file

@ -91,7 +91,7 @@ fn module_def_import_uses_internal_command() -> TestResult {
#[test]
fn module_env_import_uses_internal_command() -> TestResult {
run_test(
r#"module foo { def b [] { "2" }; export-env { let-env a = b } }; use foo; $env.a"#,
r#"module foo { def b [] { "2" }; export-env { let-env a = (b) } }; use foo; $env.a"#,
"2",
)
}