From e2973d2176bec6a206c37892458ef3b09146d34b Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 19 May 2021 20:23:45 +1200 Subject: [PATCH] Add explicit block params (#3444) * Add explicit block params * Add explicit block params --- crates/nu-cli/src/completion/engine.rs | 2 +- crates/nu-cli/src/types/deduction.rs | 6 +- .../nu-command/src/commands/each/command.rs | 9 +++ crates/nu-engine/src/evaluate/evaluator.rs | 2 +- crates/nu-parser/src/parse.rs | 77 ++++++++++++++++--- crates/nu-parser/src/parse/def.rs | 1 + crates/nu-parser/src/parse/def/signature.rs | 21 ++--- crates/nu-parser/src/parse/def/tests.rs | 44 +++++------ crates/nu-parser/src/shapes.rs | 26 ++++++- crates/nu-protocol/src/hir.rs | 22 +++--- 10 files changed, 153 insertions(+), 57 deletions(-) diff --git a/crates/nu-cli/src/completion/engine.rs b/crates/nu-cli/src/completion/engine.rs index 1f76cfc3c..5000abc33 100644 --- a/crates/nu-cli/src/completion/engine.rs +++ b/crates/nu-cli/src/completion/engine.rs @@ -37,7 +37,7 @@ impl<'s> Flatten<'s> { ) .collect(), Expression::Command => vec![LocationType::Command.spanned(e.span)], - Expression::Path(path) => self.expression(&path.head), + Expression::FullColumnPath(path) => self.expression(&path.head), Expression::Variable(_, _) => vec![LocationType::Variable.spanned(e.span)], Expression::Boolean(_) diff --git a/crates/nu-cli/src/types/deduction.rs b/crates/nu-cli/src/types/deduction.rs index a67f5e76a..81379d4e4 100644 --- a/crates/nu-cli/src/types/deduction.rs +++ b/crates/nu-cli/src/types/deduction.rs @@ -296,7 +296,7 @@ fn get_shape_of_expr(expr: &SpannedExpression) -> Option { Expression::List(_) => Some(SyntaxShape::Table), Expression::Boolean(_) => Some(SyntaxShape::String), - Expression::Path(_) => Some(SyntaxShape::ColumnPath), + Expression::FullColumnPath(_) => Some(SyntaxShape::ColumnPath), Expression::FilePath(_) => Some(SyntaxShape::FilePath), Expression::Block(_) => Some(SyntaxShape::Block), Expression::ExternalCommand(_) => Some(SyntaxShape::String), @@ -539,7 +539,7 @@ impl VarSyntaxShapeDeductor { trace!("Inferring vars in block"); self.infer_shape(&b, scope)?; } - Expression::Path(path) => { + Expression::FullColumnPath(path) => { trace!("Inferring vars in path"); match &path.head.expr { //PathMember can't be var yet (?) @@ -790,7 +790,7 @@ impl VarSyntaxShapeDeductor { | Expression::Binary(_) | Expression::Range(_) | Expression::Block(_) - | Expression::Path(_) + | Expression::FullColumnPath(_) | Expression::FilePath(_) | Expression::ExternalCommand(_) | Expression::Command diff --git a/crates/nu-command/src/commands/each/command.rs b/crates/nu-command/src/commands/each/command.rs index 1a62e1507..8ab4ba491 100644 --- a/crates/nu-command/src/commands/each/command.rs +++ b/crates/nu-command/src/commands/each/command.rs @@ -55,6 +55,15 @@ impl WholeStreamCommand for Each { "echo ['bob' 'fred'] | each --numbered { echo $\"{$it.index} is {$it.item}\" }", result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]), }, + Example { + description: "Name the block variable that each uses", + example: "[1, 2, 3] | each {|x| $x + 100}", + result: Some(vec![ + UntaggedValue::int(101).into(), + UntaggedValue::int(102).into(), + UntaggedValue::int(103).into(), + ]), + }, ] } } diff --git a/crates/nu-engine/src/evaluate/evaluator.rs b/crates/nu-engine/src/evaluate/evaluator.rs index 6a5338a67..dca73e50e 100644 --- a/crates/nu-engine/src/evaluate/evaluator.rs +++ b/crates/nu-engine/src/evaluate/evaluator.rs @@ -159,7 +159,7 @@ pub fn evaluate_baseline_expr( .into_value(&tag), ) } - Expression::Path(path) => { + Expression::FullColumnPath(path) => { let value = evaluate_baseline_expr(&path.head, ctx)?; let mut item = value; diff --git a/crates/nu-parser/src/parse.rs b/crates/nu-parser/src/parse.rs index 334a5f5b1..95b6a95f3 100644 --- a/crates/nu-parser/src/parse.rs +++ b/crates/nu-parser/src/parse.rs @@ -13,12 +13,18 @@ use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPa use nu_source::{HasSpan, Span, Spanned, SpannedItem}; use num_bigint::BigInt; -use crate::lex::tokens::{LiteBlock, LiteCommand, LitePipeline}; -use crate::path::expand_path; use crate::{ lex::lexer::{lex, parse_block}, ParserScope, }; +use crate::{ + lex::{ + lexer::Token, + tokens::{LiteBlock, LiteCommand, LitePipeline, TokenContents}, + }, + parse::def::lex_split_baseline_tokens_on, +}; +use crate::{parse::def::parse_parameter, path::expand_path}; use self::{ def::{parse_definition, parse_definition_prototype}, @@ -919,20 +925,78 @@ fn parse_arg( let string: String = chars.collect(); // We haven't done much with the inner string, so let's go ahead and work with it - let (tokens, err) = lex(&string, lite_arg.span.start() + 1); + let (mut tokens, err) = lex(&string, lite_arg.span.start() + 1); if err.is_some() { return (garbage(lite_arg.span), err); } + // Check to see if we have parameters + let params = if matches!( + tokens.first(), + Some(Token { + contents: TokenContents::Pipe, + .. + }) + ) { + // We've found a parameter list + let mut param_tokens = vec![]; + let mut token_iter = tokens.into_iter().skip(1); + while let Some(token) = token_iter.next() { + if matches!( + token, + Token { + contents: TokenContents::Pipe, + .. + } + ) { + break; + } else { + param_tokens.push(token); + } + } + let split_tokens = + lex_split_baseline_tokens_on(param_tokens, &[',', ':', '?']); + + let mut i = 0; + let mut params = vec![]; + + while i < split_tokens.len() { + let (parameter, advance_by, error) = + parse_parameter(&split_tokens[i..], split_tokens[i].span); + + if error.is_some() { + return (garbage(lite_arg.span), error); + } + i += advance_by; + params.push(parameter); + } + + tokens = token_iter.collect(); + params + } else { + vec![] + }; + let (lite_block, err) = parse_block(tokens); if err.is_some() { return (garbage(lite_arg.span), err); } scope.enter_scope(); - let (classified_block, err) = classify_block(&lite_block, scope); + let (mut classified_block, err) = classify_block(&lite_block, scope); scope.exit_scope(); + if !params.is_empty() && classified_block.params.positional.is_empty() { + if let Some(classified_block) = Arc::get_mut(&mut classified_block) { + for param in params { + classified_block + .params + .positional + .push((param.pos_type, param.desc.unwrap_or_default())); + } + } + } + ( SpannedExpression::new(Expression::Block(classified_block), lite_arg.span), err, @@ -1600,16 +1664,11 @@ fn parse_call( })), error, ); - // } else if lite_cmd.parts[0].item.starts_with('(') { - // let (expr, err) = parse_simple_invocation(&lite_cmd.parts[0], scope); - // error = error.or(err); - // return (Some(ClassifiedCommand::Expr(Box::new(expr))), error); } else if lite_cmd.parts[0].item.starts_with('{') { return parse_value_call(lite_cmd, scope); } else if lite_cmd.parts[0].item.starts_with('$') || lite_cmd.parts[0].item.starts_with('\"') || lite_cmd.parts[0].item.starts_with('\'') - || lite_cmd.parts[0].item.starts_with('`') || lite_cmd.parts[0].item.starts_with('-') || lite_cmd.parts[0].item.starts_with('0') || lite_cmd.parts[0].item.starts_with('1') diff --git a/crates/nu-parser/src/parse/def.rs b/crates/nu-parser/src/parse/def.rs index c176e3874..70c27d903 100644 --- a/crates/nu-parser/src/parse/def.rs +++ b/crates/nu-parser/src/parse/def.rs @@ -16,6 +16,7 @@ use crate::lex::lexer::{lex, parse_block}; use crate::ParserScope; use self::signature::parse_signature; +pub use self::signature::{lex_split_baseline_tokens_on, parse_parameter}; mod data_structs; mod primitives; diff --git a/crates/nu-parser/src/parse/def/signature.rs b/crates/nu-parser/src/parse/def/signature.rs index 95b969ed8..e10f1b001 100644 --- a/crates/nu-parser/src/parse/def/signature.rs +++ b/crates/nu-parser/src/parse/def/signature.rs @@ -87,7 +87,7 @@ pub fn parse_signature( i += advanced_by; rest = rest_; } else { - let (parameter, advanced_by, error) = parse_parameter(&tokens[i..], signature_vec); + let (parameter, advanced_by, error) = parse_parameter(&tokens[i..], signature_vec.span); err = err.or(error); i += advanced_by; parameters.push(parameter); @@ -100,16 +100,13 @@ pub fn parse_signature( (signature, err) } -fn parse_parameter( - tokens: &[Token], - tokens_as_str: &Spanned, -) -> (Parameter, usize, Option) { +pub fn parse_parameter(tokens: &[Token], span: Span) -> (Parameter, usize, Option) { if tokens.is_empty() { //TODO fix span return ( Parameter::error(), 0, - Some(ParseError::unexpected_eof("parameter", tokens_as_str.span)), + Some(ParseError::unexpected_eof("parameter", span)), ); } @@ -145,9 +142,15 @@ fn parse_parameter( } let pos_type = if optional { - PositionalType::optional(&name.item, type_) - } else { + if name.item.starts_with('$') { + PositionalType::optional(&name.item, type_) + } else { + PositionalType::optional(&format!("${}", name.item), type_) + } + } else if name.item.starts_with('$') { PositionalType::mandatory(&name.item, type_) + } else { + PositionalType::mandatory(&format!("${}", name.item), type_) }; let parameter = Parameter::new(pos_type, comment, name.span); @@ -402,7 +405,7 @@ fn lex_split_shortflag_from_longflag(tokens: Vec) -> Vec { } //Currently the lexer does not split baselines on ',' ':' '?' //The parameter list requires this. Therefore here is a hacky method doing this. -fn lex_split_baseline_tokens_on( +pub fn lex_split_baseline_tokens_on( tokens: Vec, extra_baseline_terminal_tokens: &[char], ) -> Vec { diff --git a/crates/nu-parser/src/parse/def/tests.rs b/crates/nu-parser/src/parse/def/tests.rs index e0ba1f8ec..4fe4f0059 100644 --- a/crates/nu-parser/src/parse/def/tests.rs +++ b/crates/nu-parser/src/parse/def/tests.rs @@ -19,11 +19,11 @@ fn simple_def_with_params() { sign.positional, vec![ ( - PositionalType::Optional("param1".into(), SyntaxShape::Int), + PositionalType::Optional("$param1".into(), SyntaxShape::Int), "".into() ), ( - PositionalType::Mandatory("param2".into(), SyntaxShape::String), + PositionalType::Mandatory("$param2".into(), SyntaxShape::String), "".into() ), ] @@ -40,11 +40,11 @@ fn simple_def_with_optional_param_without_type() { sign.positional, vec![ ( - PositionalType::Optional("param1".into(), SyntaxShape::Any), + PositionalType::Optional("$param1".into(), SyntaxShape::Any), "".into() ), ( - PositionalType::Optional("param2".into(), SyntaxShape::Any), + PositionalType::Optional("$param2".into(), SyntaxShape::Any), "".into() ), ] @@ -64,11 +64,11 @@ fn simple_def_with_params_with_comment() { sign.positional, vec![ ( - PositionalType::Mandatory("param1".into(), SyntaxShape::FilePath), + PositionalType::Mandatory("$param1".into(), SyntaxShape::FilePath), "My first param".into() ), ( - PositionalType::Mandatory("param2".into(), SyntaxShape::Number), + PositionalType::Mandatory("$param2".into(), SyntaxShape::Number), "My second param".into() ), ] @@ -88,11 +88,11 @@ fn simple_def_with_params_without_type() { sign.positional, vec![ ( - PositionalType::Mandatory("param1".into(), SyntaxShape::Any), + PositionalType::Mandatory("$param1".into(), SyntaxShape::Any), "My first param".into() ), ( - PositionalType::Mandatory("param2".into(), SyntaxShape::Number), + PositionalType::Mandatory("$param2".into(), SyntaxShape::Number), "My second param".into() ), ] @@ -116,23 +116,23 @@ fn oddly_but_correct_written_params() { sign.positional, vec![ ( - PositionalType::Mandatory("param1".into(), SyntaxShape::Int), + PositionalType::Mandatory("$param1".into(), SyntaxShape::Int), "param1".into() ), ( - PositionalType::Mandatory("param2".into(), SyntaxShape::Number), + PositionalType::Mandatory("$param2".into(), SyntaxShape::Number), "My second param".into() ), ( - PositionalType::Mandatory("param4".into(), SyntaxShape::Any), + PositionalType::Mandatory("$param4".into(), SyntaxShape::Any), "".into() ), ( - PositionalType::Mandatory("param5".into(), SyntaxShape::FilePath), + PositionalType::Mandatory("$param5".into(), SyntaxShape::FilePath), "".into() ), ( - PositionalType::Mandatory("param6".into(), SyntaxShape::Any), + PositionalType::Mandatory("$param6".into(), SyntaxShape::Any), "param6".into() ), ] @@ -225,19 +225,19 @@ fn simple_def_with_params_and_flags() { sign.positional, vec![ ( - PositionalType::Mandatory("param1".into(), SyntaxShape::Any), + PositionalType::Mandatory("$param1".into(), SyntaxShape::Any), "".into() ), ( - PositionalType::Mandatory("param2".into(), SyntaxShape::Table), + PositionalType::Mandatory("$param2".into(), SyntaxShape::Table), "Param2 Doc".into() ), ( - PositionalType::Mandatory("param3".into(), SyntaxShape::Number), + PositionalType::Mandatory("$param3".into(), SyntaxShape::Number), "".into() ), ( - PositionalType::Optional("param4".into(), SyntaxShape::Table), + PositionalType::Optional("$param4".into(), SyntaxShape::Table), "Optional Param".into() ), ] @@ -262,15 +262,15 @@ fn simple_def_with_parameters_and_flags_no_delimiter() { // --flag3 # Third flag vec![ ( - PositionalType::Mandatory("param1".into(), SyntaxShape::Int), + PositionalType::Mandatory("$param1".into(), SyntaxShape::Int), "".into() ), ( - PositionalType::Mandatory("param2".into(), SyntaxShape::Any), + PositionalType::Mandatory("$param2".into(), SyntaxShape::Any), "".into() ), ( - PositionalType::Mandatory("param3".into(), SyntaxShape::Any), + PositionalType::Mandatory("$param3".into(), SyntaxShape::Any), "Param3".into() ), ] @@ -302,7 +302,7 @@ fn simple_example_signature() { assert_eq!( sign.positional, vec![( - PositionalType::Mandatory("d".into(), SyntaxShape::Int), + PositionalType::Mandatory("$d".into(), SyntaxShape::Int), "The required d parameter".into() )] ); @@ -374,7 +374,7 @@ fn simple_def_with_param_flag_and_rest() { assert_eq!( sign.positional, vec![( - PositionalType::Mandatory("d".into(), SyntaxShape::String), + PositionalType::Mandatory("$d".into(), SyntaxShape::String), "The required d parameter".into() )] ); diff --git a/crates/nu-parser/src/shapes.rs b/crates/nu-parser/src/shapes.rs index 2e514ab3f..5a8b2c8fa 100644 --- a/crates/nu-parser/src/shapes.rs +++ b/crates/nu-parser/src/shapes.rs @@ -28,7 +28,7 @@ pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec } output } - Expression::Path(exprs) => { + Expression::FullColumnPath(exprs) => { let mut output = vec![]; output.append(&mut expression_to_flat_shape(&exprs.head)); for member in exprs.tail.iter() { @@ -119,6 +119,30 @@ pub fn shapes(commands: &Block) -> Vec> { ClassifiedCommand::Expr(expr) => { output.append(&mut expression_to_flat_shape(expr)) } + ClassifiedCommand::Dynamic(call) => { + output.append(&mut expression_to_flat_shape(&call.head)); + + if let Some(positionals) = &call.positional { + for positional_arg in positionals { + output.append(&mut expression_to_flat_shape(positional_arg)); + } + } + + if let Some(named) = &call.named { + for (_, named_arg) in named.iter() { + match named_arg { + NamedValue::PresentSwitch(span) => { + output.push(FlatShape::Flag.spanned(*span)); + } + NamedValue::Value(span, expr) => { + output.push(FlatShape::Flag.spanned(*span)); + output.append(&mut expression_to_flat_shape(expr)); + } + _ => {} + } + } + } + } _ => {} } } diff --git a/crates/nu-protocol/src/hir.rs b/crates/nu-protocol/src/hir.rs index 80acaf09e..e8f7a525d 100644 --- a/crates/nu-protocol/src/hir.rs +++ b/crates/nu-protocol/src/hir.rs @@ -315,10 +315,10 @@ impl ExternalCommand { pub fn has_it_usage(&self) -> bool { self.args.iter().any(|arg| match arg { SpannedExpression { - expr: Expression::Path(path), + expr: Expression::FullColumnPath(path), .. } => { - let Path { head, .. } = &**path; + let FullColumnPath { head, .. } = &**path; matches!(head, SpannedExpression{expr: Expression::Variable(x, ..), ..} if x == "$it") } _ => false, @@ -753,7 +753,7 @@ impl PrettyDebugWithSource for SpannedExpression { ), "]", ), - Expression::Path(path) => path.pretty_debug(source), + Expression::FullColumnPath(path) => path.pretty_debug(source), Expression::FilePath(path) => { DbgDocBldr::typed("path", DbgDocBldr::primitive(path.display())) } @@ -808,7 +808,7 @@ impl PrettyDebugWithSource for SpannedExpression { ), "]", ), - Expression::Path(path) => path.pretty_debug(source), + Expression::FullColumnPath(path) => path.pretty_debug(source), Expression::FilePath(path) => { DbgDocBldr::typed("path", DbgDocBldr::primitive(path.display())) } @@ -1006,12 +1006,12 @@ impl PrettyDebugWithSource for SpannedLiteral { } #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, new, Deserialize, Serialize)] -pub struct Path { +pub struct FullColumnPath { pub head: SpannedExpression, pub tail: Vec, } -impl PrettyDebugWithSource for Path { +impl PrettyDebugWithSource for FullColumnPath { fn pretty_debug(&self, source: &str) -> DebugDocBuilder { self.head.pretty_debug(source) + DbgDocBldr::operator(".") @@ -1033,7 +1033,7 @@ pub enum Expression { Block(Arc), List(Vec), Table(Vec, Vec>), - Path(Box), + FullColumnPath(Box), FilePath(PathBuf), ExternalCommand(ExternalStringCommand), @@ -1063,7 +1063,7 @@ impl ShellTypeName for Expression { Expression::Range(..) => "range", Expression::Block(..) => "block", Expression::Invocation(..) => "command invocation", - Expression::Path(..) => "variable path", + Expression::FullColumnPath(..) => "variable path", Expression::Boolean(..) => "boolean", Expression::ExternalCommand(..) => "external", Expression::Garbage => "garbage", @@ -1129,7 +1129,7 @@ impl Expression { pub fn path(head: SpannedExpression, tail: Vec>) -> Expression { let tail = tail.into_iter().map(|t| t.into()).collect(); - Expression::Path(Box::new(Path::new(head, tail))) + Expression::FullColumnPath(Box::new(FullColumnPath::new(head, tail))) } pub fn unit(i: Spanned, unit: Spanned) -> Expression { @@ -1157,7 +1157,7 @@ impl Expression { Expression::List(list) => list.iter().any(|se| se.has_it_usage()), Expression::Invocation(block) => block.has_it_usage(), Expression::Binary(binary) => binary.left.has_it_usage() || binary.right.has_it_usage(), - Expression::Path(path) => path.head.has_it_usage(), + Expression::FullColumnPath(path) => path.head.has_it_usage(), Expression::Range(range) => { (if let Some(left) = &range.left { left.has_it_usage() @@ -1203,7 +1203,7 @@ impl Expression { output.extend(binary.left.get_free_variables(known_variables)); output.extend(binary.right.get_free_variables(known_variables)); } - Expression::Path(path) => { + Expression::FullColumnPath(path) => { output.extend(path.head.get_free_variables(known_variables)); } Expression::Range(range) => {