diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 47dc2efcde..b41f639222 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -1,5 +1,5 @@ +use crate::commands::classified::block::run_block; use crate::commands::classified::external::{MaybeTextCodec, StringOrBinary}; -use crate::commands::classified::pipeline::run_pipeline; use crate::commands::plugin::JsonRpc; use crate::commands::plugin::{PluginCommand, PluginSink}; use crate::commands::whole_stream_command; @@ -710,12 +710,12 @@ async fn process_line( debug!("=== Parsed ==="); debug!("{:#?}", result); - let pipeline = nu_parser::classify_pipeline(&result, ctx.registry()); + let classified_block = nu_parser::classify_block(&result, ctx.registry()); - debug!("{:#?}", pipeline); + debug!("{:#?}", classified_block); //println!("{:#?}", pipeline); - if let Some(failure) = pipeline.failed { + if let Some(failure) = classified_block.failed { return LineResult::Error(line.to_string(), failure.into()); } @@ -725,10 +725,13 @@ async fn process_line( // ...and it doesn't have any arguments // ...and we're in the CLI // ...then change to this directory - if cli_mode && pipeline.commands.list.len() == 1 { + if cli_mode + && classified_block.block.block.len() == 1 + && classified_block.block.block[0].list.len() == 1 + { if let ClassifiedCommand::Internal(InternalCommand { ref name, ref args, .. - }) = pipeline.commands.list[0] + }) = classified_block.block.block[0].list[0] { let internal_name = name; let name = args @@ -826,7 +829,7 @@ async fn process_line( InputStream::empty() }; - match run_pipeline(pipeline, ctx, input_stream, &Scope::empty()).await { + match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await { Ok(input) => { // Running a pipeline gives us back a stream that we can then // work through. At the top level, we just want to pull on the @@ -896,12 +899,10 @@ mod tests { #[quickcheck] fn quickcheck_parse(data: String) -> bool { - if let Ok(lite_pipeline) = nu_parser::lite_parse(&data, 0) { + if let Ok(lite_block) = nu_parser::lite_parse(&data, 0) { let context = crate::context::Context::basic().unwrap(); - let _ = nu_parser::classify_pipeline(&lite_pipeline, context.registry()); - true - } else { - false + let _ = nu_parser::classify_block(&lite_block, context.registry()); } + true } } diff --git a/crates/nu-cli/src/commands/classified/block.rs b/crates/nu-cli/src/commands/classified/block.rs new file mode 100644 index 0000000000..fb8777bc90 --- /dev/null +++ b/crates/nu-cli/src/commands/classified/block.rs @@ -0,0 +1,97 @@ +use crate::commands::classified::expr::run_expression_block; +use crate::commands::classified::external::run_external_command; +use crate::commands::classified::internal::run_internal_command; +use crate::context::Context; +use crate::prelude::*; +use crate::stream::InputStream; +use futures::stream::TryStreamExt; +use nu_errors::ShellError; +use nu_protocol::hir::{Block, ClassifiedCommand, Commands}; +use nu_protocol::{ReturnSuccess, Scope, UntaggedValue, Value}; +use std::sync::atomic::Ordering; + +pub(crate) async fn run_block( + block: &Block, + ctx: &mut Context, + mut input: InputStream, + scope: &Scope, +) -> Result { + let mut output: Result = Ok(InputStream::empty()); + for pipeline in &block.block { + match output { + Ok(inp) if inp.is_empty() => {} + Ok(inp) => { + let mut output_stream = inp.to_output_stream(); + + loop { + match output_stream.try_next().await { + Ok(Some(ReturnSuccess::Value(Value { + value: UntaggedValue::Error(e), + .. + }))) => return Err(e), + Ok(Some(_item)) => { + if ctx.ctrl_c.load(Ordering::SeqCst) { + break; + } + } + Ok(None) => break, + Err(e) => return Err(e), + } + } + if !ctx.get_errors().is_empty() { + return Ok(InputStream::empty()); + } + } + Err(e) => { + return Err(e); + } + } + output = run_pipeline(pipeline, ctx, input, scope).await; + + input = InputStream::empty(); + } + + output +} + +async fn run_pipeline( + commands: &Commands, + ctx: &mut Context, + mut input: InputStream, + scope: &Scope, +) -> Result { + let mut iter = commands.list.clone().into_iter().peekable(); + + loop { + let item: Option = iter.next(); + let next: Option<&ClassifiedCommand> = iter.peek(); + + input = match (item, next) { + (Some(ClassifiedCommand::Dynamic(_)), _) | (_, Some(ClassifiedCommand::Dynamic(_))) => { + return Err(ShellError::unimplemented("Dynamic commands")) + } + + (Some(ClassifiedCommand::Expr(expr)), _) => { + run_expression_block(*expr, ctx, input, scope)? + } + (Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()), + (_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()), + + (Some(ClassifiedCommand::Internal(left)), _) => { + run_internal_command(left, ctx, input, scope)? + } + + (Some(ClassifiedCommand::External(left)), None) => { + run_external_command(left, ctx, input, scope, true).await? + } + + (Some(ClassifiedCommand::External(left)), _) => { + run_external_command(left, ctx, input, scope, false).await? + } + + (None, _) => break, + }; + } + + Ok(input) +} diff --git a/crates/nu-cli/src/commands/classified/internal.rs b/crates/nu-cli/src/commands/classified/internal.rs index cfb3c8a493..cbe8d1ec28 100644 --- a/crates/nu-cli/src/commands/classified/internal.rs +++ b/crates/nu-cli/src/commands/classified/internal.rs @@ -120,12 +120,12 @@ pub(crate) fn run_internal_command( FilesystemShell::with_location(location, context.registry().clone()), )); } - CommandAction::AddAlias(name, args, commands) => { + CommandAction::AddAlias(name, args, block) => { context.add_commands(vec![ per_item_command(AliasCommand::new( name, args, - commands, + block, )) ]); } diff --git a/crates/nu-cli/src/commands/classified/mod.rs b/crates/nu-cli/src/commands/classified/mod.rs index 3c60d8b00a..67f25150b3 100644 --- a/crates/nu-cli/src/commands/classified/mod.rs +++ b/crates/nu-cli/src/commands/classified/mod.rs @@ -1,8 +1,8 @@ +pub(crate) mod block; mod dynamic; pub(crate) mod expr; pub(crate) mod external; pub(crate) mod internal; -pub(crate) mod pipeline; #[allow(unused_imports)] pub(crate) use dynamic::Command as DynamicCommand; diff --git a/crates/nu-cli/src/commands/each.rs b/crates/nu-cli/src/commands/each.rs index 5d9e887c01..bd621fb119 100644 --- a/crates/nu-cli/src/commands/each.rs +++ b/crates/nu-cli/src/commands/each.rs @@ -1,4 +1,4 @@ -use crate::commands::classified::pipeline::run_pipeline; +use crate::commands::classified::block::run_block; use crate::commands::PerItemCommand; use crate::context::CommandRegistry; use crate::prelude::*; @@ -6,10 +6,7 @@ use crate::prelude::*; use futures::stream::once; use nu_errors::ShellError; -use nu_protocol::{ - hir::ClassifiedPipeline, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, - Value, -}; +use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value}; pub struct Each; @@ -50,8 +47,8 @@ impl PerItemCommand for Each { let input_clone = input.clone(); let input_stream = once(async { Ok(input) }).to_input_stream(); - let result = run_pipeline( - ClassifiedPipeline::new(block.clone(), None), + let result = run_block( + block, &mut context, input_stream, &Scope::new(input_clone), diff --git a/crates/nu-cli/src/commands/run_alias.rs b/crates/nu-cli/src/commands/run_alias.rs index 3320d5cdac..3d72b7fd53 100644 --- a/crates/nu-cli/src/commands/run_alias.rs +++ b/crates/nu-cli/src/commands/run_alias.rs @@ -1,18 +1,15 @@ -use crate::commands::classified::pipeline::run_pipeline; +use crate::commands::classified::block::run_block; use crate::prelude::*; use derive_new::new; use nu_errors::ShellError; -use nu_protocol::{ - hir::ClassifiedPipeline, hir::Commands, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, - Value, -}; +use nu_protocol::{hir::Block, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, Value}; #[derive(new)] pub struct AliasCommand { name: String, args: Vec, - block: Commands, + block: Block, } impl PerItemCommand for AliasCommand { @@ -60,8 +57,8 @@ impl PerItemCommand for AliasCommand { let input_clone = Ok(input.clone()); let input_stream = futures::stream::once(async { input_clone }).boxed().to_input_stream(); - let result = run_pipeline( - ClassifiedPipeline::new(block.clone(), None), + let result = run_block( + &block, &mut context, input_stream, &scope diff --git a/crates/nu-cli/src/commands/skip_while.rs b/crates/nu-cli/src/commands/skip_while.rs index 2da7db46a8..187351e9af 100644 --- a/crates/nu-cli/src/commands/skip_while.rs +++ b/crates/nu-cli/src/commands/skip_while.rs @@ -40,25 +40,34 @@ impl WholeStreamCommand for SkipWhile { Value { value: UntaggedValue::Block(block), tag, - } => match block.list.get(0) { - Some(item) => match item { - ClassifiedCommand::Expr(expr) => expr.clone(), - _ => { - return Err(ShellError::labeled_error( - "Expected a condition", - "expected a condition", - tag, - )) - } - }, - None => { + } => { + if block.block.len() != 1 { return Err(ShellError::labeled_error( "Expected a condition", "expected a condition", tag, )); } - }, + match block.block[0].list.get(0) { + Some(item) => match item { + ClassifiedCommand::Expr(expr) => expr.clone(), + _ => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )) + } + }, + None => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + } + } Value { tag, .. } => { return Err(ShellError::labeled_error( "Expected a condition", diff --git a/crates/nu-cli/src/commands/where_.rs b/crates/nu-cli/src/commands/where_.rs index 73de03bfaa..84d6bf39f4 100644 --- a/crates/nu-cli/src/commands/where_.rs +++ b/crates/nu-cli/src/commands/where_.rs @@ -40,25 +40,34 @@ impl PerItemCommand for Where { Value { value: UntaggedValue::Block(block), tag, - } => match block.list.get(0) { - Some(item) => match item { - ClassifiedCommand::Expr(expr) => expr.clone(), - _ => { - return Err(ShellError::labeled_error( - "Expected a condition", - "expected a condition", - tag, - )) - } - }, - None => { + } => { + if block.block.len() != 1 { return Err(ShellError::labeled_error( "Expected a condition", "expected a condition", tag, )); } - }, + match block.block[0].list.get(0) { + Some(item) => match item { + ClassifiedCommand::Expr(expr) => expr.clone(), + _ => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )) + } + }, + None => { + return Err(ShellError::labeled_error( + "Expected a condition", + "expected a condition", + tag, + )); + } + } + } Value { tag, .. } => { return Err(ShellError::labeled_error( "Expected a condition", diff --git a/crates/nu-cli/src/deserializer.rs b/crates/nu-cli/src/deserializer.rs index 66406a5999..911f4bbfb9 100644 --- a/crates/nu-cli/src/deserializer.rs +++ b/crates/nu-cli/src/deserializer.rs @@ -1,7 +1,7 @@ use log::trace; use nu_errors::{CoerceInto, ShellError}; use nu_protocol::{ - hir::Commands, CallInfo, ColumnPath, Primitive, RangeInclusion, ShellTypeName, UntaggedValue, + hir::Block, CallInfo, ColumnPath, Primitive, RangeInclusion, ShellTypeName, UntaggedValue, Value, }; use nu_source::{HasSpan, Spanned, SpannedItem, Tagged, TaggedItem}; @@ -369,7 +369,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> { )) } }; - return visit::(block, name, fields, visitor); + return visit::(block, name, fields, visitor); } if name == "ColumnPath" { diff --git a/crates/nu-cli/src/shell/completer.rs b/crates/nu-cli/src/shell/completer.rs index 9c391f73d1..e96a7b97cb 100644 --- a/crates/nu-cli/src/shell/completer.rs +++ b/crates/nu-cli/src/shell/completer.rs @@ -34,14 +34,9 @@ impl NuCompleter { // See if we're a flag if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' { - if let Ok(lite_pipeline) = nu_parser::lite_parse(line, 0) { - completions = self.get_matching_arguments( - &lite_pipeline, - &line_chars, - line, - replace_pos, - pos, - ); + if let Ok(lite_block) = nu_parser::lite_parse(line, 0) { + completions = + self.get_matching_arguments(&lite_block, &line_chars, line, replace_pos, pos); } else { completions = self.file_completer.complete(line, pos, context)?.1; } @@ -96,7 +91,7 @@ impl NuCompleter { fn get_matching_arguments( &self, - lite_parse: &nu_parser::LitePipeline, + lite_block: &nu_parser::LiteBlock, line_chars: &[char], line: &str, replace_pos: usize, @@ -109,23 +104,25 @@ impl NuCompleter { let replace_string = (replace_pos..pos).map(|_| " ").collect::(); line_copy.replace_range(replace_pos..pos, &replace_string); - let result = nu_parser::classify_pipeline(&lite_parse, &self.commands); + let result = nu_parser::classify_block(&lite_block, &self.commands); - for command in result.commands.list { - if let nu_protocol::hir::ClassifiedCommand::Internal( - nu_protocol::hir::InternalCommand { args, .. }, - ) = command - { - if replace_pos >= args.span.start() && replace_pos <= args.span.end() { - if let Some(named) = args.named { - for (name, _) in named.iter() { - let full_flag = format!("--{}", name); + for pipeline in &result.block.block { + for command in &pipeline.list { + if let nu_protocol::hir::ClassifiedCommand::Internal( + nu_protocol::hir::InternalCommand { args, .. }, + ) = command + { + if replace_pos >= args.span.start() && replace_pos <= args.span.end() { + if let Some(named) = &args.named { + for (name, _) in named.iter() { + let full_flag = format!("--{}", name); - if full_flag.starts_with(&substring) { - matching_arguments.push(rustyline::completion::Pair { - display: full_flag.clone(), - replacement: full_flag, - }); + if full_flag.starts_with(&substring) { + matching_arguments.push(rustyline::completion::Pair { + display: full_flag.clone(), + replacement: full_flag, + }); + } } } } diff --git a/crates/nu-cli/src/shell/helper.rs b/crates/nu-cli/src/shell/helper.rs index f5a39d9552..e12426272e 100644 --- a/crates/nu-cli/src/shell/helper.rs +++ b/crates/nu-cli/src/shell/helper.rs @@ -61,15 +61,15 @@ impl Highlighter for Helper { } fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { - let lite_pipeline = nu_parser::lite_parse(line, 0); + let lite_block = nu_parser::lite_parse(line, 0); - match lite_pipeline { + match lite_block { Err(_) => Cow::Borrowed(line), - Ok(lp) => { + Ok(lb) => { let classified = - nu_parser::classify_pipeline(&lp, &self.context.registry().clone_box()); + nu_parser::classify_block(&lb, &self.context.registry().clone_box()); - let shapes = nu_parser::shapes(&classified.commands); + let shapes = nu_parser::shapes(&classified.block); let mut painter = Painter::new(line); for shape in shapes { diff --git a/crates/nu-cli/tests/commands/mod.rs b/crates/nu-cli/tests/commands/mod.rs index 952788f8c7..0302f3d583 100644 --- a/crates/nu-cli/tests/commands/mod.rs +++ b/crates/nu-cli/tests/commands/mod.rs @@ -30,6 +30,7 @@ mod rename; mod reverse; mod rm; mod save; +mod semicolon; mod sort_by; mod split_by; mod split_column; diff --git a/crates/nu-cli/tests/commands/semicolon.rs b/crates/nu-cli/tests/commands/semicolon.rs new file mode 100644 index 0000000000..de66ed5f1e --- /dev/null +++ b/crates/nu-cli/tests/commands/semicolon.rs @@ -0,0 +1,29 @@ +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn semicolon_allows_lhs_to_complete() { + Playground::setup("create_test_1", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), + "touch i_will_be_created_semi.txt; echo done" + ); + + let path = dirs.test().join("i_will_be_created_semi.txt"); + + assert!(path.exists()); + assert_eq!(actual, "done"); + }) +} + +#[test] +fn semicolon_lhs_error_stops_processing() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + where 1 1; echo done + "# + )); + + assert!(!actual.contains("done")); +} diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index f4fda943d2..8b789f338c 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -6,7 +6,7 @@ mod shapes; mod signature; pub use crate::files::Files; -pub use crate::lite_parse::{lite_parse, LitePipeline}; -pub use crate::parse::{classify_pipeline, garbage}; +pub use crate::lite_parse::{lite_parse, LiteBlock}; +pub use crate::parse::{classify_block, garbage}; pub use crate::shapes::shapes; pub use crate::signature::{Signature, SignatureRegistry}; diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index 4a940ecbe1..2ba14f5364 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -29,11 +29,16 @@ impl LiteCommand { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LitePipeline { pub commands: Vec, } +#[derive(Debug, Clone)] +pub struct LiteBlock { + pub block: Vec, +} + fn skip_whitespace(src: &mut Input) { while let Some((_, x)) = src.peek() { if x.is_whitespace() { @@ -58,7 +63,8 @@ fn bare(src: &mut Input, span_offset: usize) -> Result, ParseErr let mut inside_quote = false; let mut block_level = vec![]; - for (_, c) in src { + while let Some((_, c)) = src.peek() { + let c = *c; if inside_quote { if c == delimiter { inside_quote = false; @@ -84,10 +90,11 @@ fn bare(src: &mut Input, span_offset: usize) -> Result, ParseErr if let Some('(') = block_level.last() { let _ = block_level.pop(); } - } else if block_level.is_empty() && c.is_whitespace() { + } else if block_level.is_empty() && (c.is_whitespace() || c == '|' || c == ';') { break; } bare.push(c); + let _ = src.next(); } let span = Span::new( @@ -139,16 +146,14 @@ fn quoted( fn command(src: &mut Input, span_offset: usize) -> Result { let command = bare(src, span_offset)?; if command.item.is_empty() { - Err(ParseError::unexpected_eof( - "unexpected end of input", - command.span, - )) + Err(ParseError::unexpected_eof("command", command.span)) } else { Ok(LiteCommand::new(command)) } } -fn pipeline(src: &mut Input, span_offset: usize) -> Result { +fn pipeline(src: &mut Input, span_offset: usize) -> Result { + let mut block = vec![]; let mut commands = vec![]; skip_whitespace(src); @@ -167,6 +172,10 @@ fn pipeline(src: &mut Input, span_offset: usize) -> Result { + // this is the end of the command and the end of the pipeline + break; + } '|' => { let _ = src.next(); if let Some((pos, next_c)) = src.peek() { @@ -202,21 +211,35 @@ fn pipeline(src: &mut Input, span_offset: usize) -> Result Result { +pub fn lite_parse(src: &str, span_offset: usize) -> Result { pipeline(&mut src.char_indices().peekable(), span_offset) } #[test] fn lite_simple_1() -> Result<(), ParseError> { let result = lite_parse("foo", 0)?; - assert_eq!(result.commands.len(), 1); - assert_eq!(result.commands[0].name.span.start(), 0); - assert_eq!(result.commands[0].name.span.end(), 3); + assert_eq!(result.block.len(), 1); + assert_eq!(result.block[0].commands.len(), 1); + assert_eq!(result.block[0].commands[0].name.span.start(), 0); + assert_eq!(result.block[0].commands[0].name.span.end(), 3); Ok(()) } @@ -224,9 +247,10 @@ fn lite_simple_1() -> Result<(), ParseError> { #[test] fn lite_simple_offset() -> Result<(), ParseError> { let result = lite_parse("foo", 10)?; - assert_eq!(result.commands.len(), 1); - assert_eq!(result.commands[0].name.span.start(), 10); - assert_eq!(result.commands[0].name.span.end(), 13); + assert_eq!(result.block.len(), 1); + assert_eq!(result.block[0].commands.len(), 1); + assert_eq!(result.block[0].commands[0].name.span.start(), 10); + assert_eq!(result.block[0].commands[0].name.span.end(), 13); Ok(()) } diff --git a/crates/nu-parser/src/parse.rs b/crates/nu-parser/src/parse.rs index 8f9c11faab..0acbb44d6b 100644 --- a/crates/nu-parser/src/parse.rs +++ b/crates/nu-parser/src/parse.rs @@ -1,13 +1,14 @@ use std::path::Path; -use crate::lite_parse::{lite_parse, LiteCommand, LitePipeline}; +use crate::lite_parse::{lite_parse, LiteBlock, LiteCommand, LitePipeline}; use crate::path::expand_path; use crate::signature::SignatureRegistry; use log::trace; use nu_errors::{ArgumentError, ParseError}; use nu_protocol::hir::{ - self, Binary, ClassifiedCommand, ClassifiedPipeline, Commands, Expression, Flag, FlagKind, - InternalCommand, Member, NamedArguments, Operator, SpannedExpression, Unit, + self, Binary, Block, ClassifiedBlock, ClassifiedCommand, ClassifiedPipeline, Commands, + Expression, Flag, FlagKind, InternalCommand, Member, NamedArguments, Operator, + SpannedExpression, Unit, }; use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember}; use nu_source::{Span, Spanned, SpannedItem}; @@ -416,11 +417,19 @@ fn parse_arg( let mut error = None; // We haven't done much with the inner string, so let's go ahead and work with it - let lite_pipeline = match lite_parse(&string, lite_arg.span.start() + 1) { - Ok(lp) => lp, + let lite_block = match lite_parse(&string, lite_arg.span.start() + 1) { + Ok(lb) => lb, Err(e) => return (garbage(lite_arg.span), Some(e)), }; + if lite_block.block.len() != 1 { + return ( + garbage(lite_arg.span), + Some(ParseError::mismatch("table", lite_arg.clone())), + ); + } + + let lite_pipeline = lite_block.block[0].clone(); let mut output = vec![]; for lite_inner in &lite_pipeline.commands { let (arg, err) = parse_arg(SyntaxShape::Any, registry, &lite_inner.name); @@ -462,17 +471,17 @@ 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 lite_pipeline = match lite_parse(&string, lite_arg.span.start() + 1) { + let lite_block = match lite_parse(&string, lite_arg.span.start() + 1) { Ok(lp) => lp, Err(e) => return (garbage(lite_arg.span), Some(e)), }; - let classified_block = classify_pipeline(&lite_pipeline, registry); + let classified_block = classify_block(&lite_block, registry); let error = classified_block.failed; ( SpannedExpression::new( - Expression::Block(classified_block.commands), + Expression::Block(classified_block.block), lite_arg.span, ), error, @@ -586,11 +595,20 @@ fn parse_parenthesized_expression( let string: String = chars.collect(); // We haven't done much with the inner string, so let's go ahead and work with it - let mut lite_pipeline = match lite_parse(&string, lite_arg.span.start() + 1) { - Ok(lp) => lp, + let lite_block = match lite_parse(&string, lite_arg.span.start() + 1) { + Ok(lb) => lb, Err(e) => return (garbage(lite_arg.span), Some(e)), }; + if lite_block.block.len() != 1 { + return ( + garbage(lite_arg.span), + Some(ParseError::mismatch("math expression", lite_arg.clone())), + ); + } + + let mut lite_pipeline = lite_block.block[0].clone(); + let mut collection = vec![]; for lite_cmd in lite_pipeline.commands.iter_mut() { collection.push(lite_cmd.name.clone()); @@ -796,8 +814,10 @@ fn parse_positional_argument( let span = arg.span; let mut commands = hir::Commands::new(span); commands.push(ClassifiedCommand::Expr(Box::new(arg))); + let mut block = hir::Block::new(span); + block.push(commands); - let arg = SpannedExpression::new(Expression::Block(commands), span); + let arg = SpannedExpression::new(Expression::Block(block), span); idx = new_idx; if error.is_none() { @@ -971,10 +991,10 @@ fn parse_internal_command( /// Convert a lite-ly parsed pipeline into a fully classified pipeline, ready to be evaluated. /// This conversion does error-recovery, so the result is allowed to be lossy. A lossy unit is designated as garbage. /// Errors are returned as part of a side-car error rather than a Result to allow both error and lossy result simultaneously. -pub fn classify_pipeline( +fn classify_pipeline( lite_pipeline: &LitePipeline, registry: &dyn SignatureRegistry, -) -> ClassifiedPipeline { +) -> (ClassifiedPipeline, Option) { // FIXME: fake span let mut commands = Commands::new(Span::new(0, 0)); let mut error = None; @@ -1054,7 +1074,23 @@ pub fn classify_pipeline( } } - ClassifiedPipeline::new(commands, error) + (ClassifiedPipeline::new(commands), error) +} + +pub fn classify_block(lite_block: &LiteBlock, registry: &dyn SignatureRegistry) -> ClassifiedBlock { + // FIXME: fake span + let mut block = Block::new(Span::new(0, 0)); + + let mut error = None; + for lite_pipeline in &lite_block.block { + let (pipeline, err) = classify_pipeline(lite_pipeline, registry); + block.push(pipeline.commands); + if error.is_none() { + error = err; + } + } + + ClassifiedBlock::new(block, error) } /// Parse out arguments from spanned expressions diff --git a/crates/nu-parser/src/shapes.rs b/crates/nu-parser/src/shapes.rs index 5c25dea397..2b764272ae 100644 --- a/crates/nu-parser/src/shapes.rs +++ b/crates/nu-parser/src/shapes.rs @@ -62,43 +62,45 @@ pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec } /// Converts a series of commands into a vec of spanned shapes ready for color-highlighting -pub fn shapes(commands: &Commands) -> Vec> { +pub fn shapes(commands: &Block) -> Vec> { let mut output = vec![]; - for command in &commands.list { - match command { - ClassifiedCommand::Internal(internal) => { - output.append(&mut expression_to_flat_shape(&internal.args.head)); + for pipeline in &commands.block { + for command in &pipeline.list { + match command { + ClassifiedCommand::Internal(internal) => { + output.append(&mut expression_to_flat_shape(&internal.args.head)); - if let Some(positionals) = &internal.args.positional { - for positional_arg in positionals { - output.append(&mut expression_to_flat_shape(positional_arg)); + if let Some(positionals) = &internal.args.positional { + for positional_arg in positionals { + output.append(&mut expression_to_flat_shape(positional_arg)); + } } - } - if let Some(named) = &internal.args.named { - for (_, named_arg) in named.iter() { - match named_arg { - NamedValue::PresentSwitch(span) => { - output.push(FlatShape::Flag.spanned(*span)); + if let Some(named) = &internal.args.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)); + } + _ => {} } - NamedValue::Value(span, expr) => { - output.push(FlatShape::Flag.spanned(*span)); - output.append(&mut expression_to_flat_shape(expr)); - } - _ => {} } } } - } - ClassifiedCommand::External(external) => { - output.push(FlatShape::ExternalCommand.spanned(external.name_tag.span)); - for arg in external.args.iter() { - output.push(FlatShape::ExternalWord.spanned(arg.tag.span)); + ClassifiedCommand::External(external) => { + output.push(FlatShape::ExternalCommand.spanned(external.name_tag.span)); + for arg in external.args.iter() { + output.push(FlatShape::ExternalWord.spanned(arg.tag.span)); + } } + ClassifiedCommand::Expr(expr) => output.append(&mut expression_to_flat_shape(expr)), + _ => {} } - ClassifiedCommand::Expr(expr) => 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 93745828f3..4788a41433 100644 --- a/crates/nu-protocol/src/hir.rs +++ b/crates/nu-protocol/src/hir.rs @@ -44,16 +44,27 @@ impl InternalCommand { } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] -pub struct ClassifiedPipeline { - pub commands: Commands, +pub struct ClassifiedBlock { + pub block: Block, // this is not a Result to make it crystal clear that these shapes // aren't intended to be used directly with `?` pub failed: Option, } +impl ClassifiedBlock { + pub fn new(block: Block, failed: Option) -> ClassifiedBlock { + ClassifiedBlock { block, failed } + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +pub struct ClassifiedPipeline { + pub commands: Commands, +} + impl ClassifiedPipeline { - pub fn new(commands: Commands, failed: Option) -> ClassifiedPipeline { - ClassifiedPipeline { commands, failed } + pub fn new(commands: Commands) -> ClassifiedPipeline { + ClassifiedPipeline { commands } } } @@ -83,6 +94,25 @@ impl Commands { } } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +pub struct Block { + pub block: Vec, + pub span: Span, +} + +impl Block { + pub fn new(span: Span) -> Block { + Block { + block: vec![], + span, + } + } + + pub fn push(&mut self, commands: Commands) { + self.block.push(commands); + } +} + #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)] pub struct ExternalStringCommand { pub name: Spanned, @@ -766,7 +796,7 @@ pub enum Expression { Variable(Variable), Binary(Box), Range(Box), - Block(hir::Commands), + Block(hir::Block), List(Vec), Path(Box), diff --git a/crates/nu-protocol/src/return_value.rs b/crates/nu-protocol/src/return_value.rs index a3aecbcf5c..dc17f6979e 100644 --- a/crates/nu-protocol/src/return_value.rs +++ b/crates/nu-protocol/src/return_value.rs @@ -1,4 +1,4 @@ -use crate::hir::Commands; +use crate::hir::Block; use crate::value::Value; use nu_errors::ShellError; use nu_source::{b, DebugDocBuilder, PrettyDebug}; @@ -22,7 +22,7 @@ pub enum CommandAction { /// Enter the help shell, which allows exploring the help system EnterHelpShell(Value), /// Enter the help shell, which allows exploring the help system - AddAlias(String, Vec, Commands), + AddAlias(String, Vec, Block), /// Go to the previous shell in the shell ring buffer PreviousShell, /// Go to the next shell in the shell ring buffer diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 255314eecd..38b818aadc 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -37,8 +37,8 @@ pub enum UntaggedValue { /// An error value that represents an error that occurred as the values in the pipeline were built Error(ShellError), - /// A block of Nu code, eg `{ ls | get name }` - Block(hir::Commands), + /// A block of Nu code, eg `{ ls | get name ; echo "done" }` + Block(hir::Block), } impl UntaggedValue { diff --git a/crates/nu-value-ext/src/lib.rs b/crates/nu-value-ext/src/lib.rs index a6d4c88771..b401153ad1 100644 --- a/crates/nu-value-ext/src/lib.rs +++ b/crates/nu-value-ext/src/lib.rs @@ -228,7 +228,7 @@ pub fn insert_data_at_path(value: &Value, path: &str, new_value: Value) -> Optio if let UntaggedValue::Row(o) = &mut next.value { o.entries.insert( split_path[idx + 1].to_string(), - new_value.value.clone().into_value(&value.tag), + new_value.value.into_value(&value.tag), ); } return Some(new_obj.clone());