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("const_name", SyntaxShape::VarWithOptType, "constant name")
.required( .required(
"initial_value", "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", "equals sign followed by constant value",
) )
.category(Category::Core) .category(Category::Core)

View file

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

View file

@ -22,7 +22,7 @@ impl Command for Let {
.required("var_name", SyntaxShape::VarWithOptType, "variable name") .required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required( .required(
"initial_value", "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", "equals sign followed by value",
) )
.category(Category::Core) .category(Category::Core)

View file

@ -22,7 +22,7 @@ impl Command for Mut {
.required("var_name", SyntaxShape::VarWithOptType, "variable name") .required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required( .required(
"initial_value", "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", "equals sign followed by value",
) )
.category(Category::Core) .category(Category::Core)

View file

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

View file

@ -24,7 +24,7 @@ impl Command for LetEnv {
.required("var_name", SyntaxShape::String, "variable name") .required("var_name", SyntaxShape::String, "variable name")
.required( .required(
"initial_value", "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", "equals sign followed by value",
) )
.category(Category::Env) .category(Category::Env)

View file

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

View file

@ -698,6 +698,29 @@ pub fn eval_expression_with_input(
input = eval_subexpression(engine_state, stack, block, 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 => { elem => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data(); input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
} }

View file

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

View file

@ -71,7 +71,12 @@ pub fn is_math_expression_like(
return false; 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; return true;
} }
@ -1112,12 +1117,12 @@ pub fn parse_call(
for word_span in spans[cmd_start..].iter() { for word_span in spans[cmd_start..].iter() {
// Find the longest group of words that could form a command // Find the longest group of words that could form a command
if is_math_expression_like(working_set, *word_span, expand_aliases_denylist) { // if is_math_expression_like(working_set, *word_span, expand_aliases_denylist) {
let bytes = working_set.get_span_contents(*word_span); // let bytes = working_set.get_span_contents(*word_span);
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" {
break; // break;
} // }
} // }
name_spans.push(*word_span); name_spans.push(*word_span);
@ -4993,7 +4998,20 @@ pub fn parse_math_expression(
let first_span = working_set.get_span_contents(spans[0]); 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 { if spans.len() > 1 {
let (remainder, err) = parse_math_expression( let (remainder, err) = parse_math_expression(
working_set, working_set,

View file

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

View file

@ -146,7 +146,7 @@ fn date_comparison() -> TestResult {
#[test] #[test]
fn let_sees_input() -> TestResult { fn let_sees_input() -> TestResult {
run_test( 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", "11",
) )
} }

View file

@ -91,7 +91,7 @@ fn module_def_import_uses_internal_command() -> TestResult {
#[test] #[test]
fn module_env_import_uses_internal_command() -> TestResult { fn module_env_import_uses_internal_command() -> TestResult {
run_test( 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", "2",
) )
} }