From 311c0e3f50215eceef461f5c9ffeaead77042d15 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 12 May 2021 13:53:57 +1200 Subject: [PATCH] Simplify string interpolation (#3401) * [DRAFT] simplify string interpolation * Fix test --- Cargo.lock | 1 - .../nu-command/src/commands/each/command.rs | 2 +- crates/nu-command/tests/commands/reduce.rs | 4 +- crates/nu-parser/src/parse.rs | 131 ++++++++---------- crates/nu-parser/src/parse/util.rs | 1 - tests/shell/pipeline/commands/internal.rs | 40 +----- 6 files changed, 62 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d89de780d1..bb69ba5805 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3710,7 +3710,6 @@ dependencies = [ name = "nu-table" version = "0.31.0" dependencies = [ - "nu-ansi-term 0.31.0", "regex 1.5.3", "unicode-width", diff --git a/crates/nu-command/src/commands/each/command.rs b/crates/nu-command/src/commands/each/command.rs index ec155a4570..24aa4d6e23 100644 --- a/crates/nu-command/src/commands/each/command.rs +++ b/crates/nu-command/src/commands/each/command.rs @@ -52,7 +52,7 @@ impl WholeStreamCommand for Each { Example { description: "Number each item and echo a message", example: - "echo ['bob' 'fred'] | each --numbered { echo `{{$it.index}} is {{$it.item}}` }", + "echo ['bob' 'fred'] | each --numbered { echo $\"{$it.index} is {$it.item}\" }", result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]), }, ] diff --git a/crates/nu-command/tests/commands/reduce.rs b/crates/nu-command/tests/commands/reduce.rs index cc753bd4da..3ff1daddb1 100644 --- a/crates/nu-command/tests/commands/reduce.rs +++ b/crates/nu-command/tests/commands/reduce.rs @@ -8,7 +8,7 @@ fn reduce_table_column() { echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]" | from json | get total - | reduce -f 20 { $it + ( math eval `{{$acc}}^1.05` )} + | reduce -f 20 { $it + (math eval $"{$acc}^1.05")} | str from -d 1 "# ) @@ -21,7 +21,7 @@ fn reduce_table_column() { r#" echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]" | from json - | reduce -f 20 { $it.total + ( math eval `{{$acc}}^1.05` )} + | reduce -f 20 { $it.total + (math eval $"{$acc}^1.05")} | str from -d 1 "# ) diff --git a/crates/nu-parser/src/parse.rs b/crates/nu-parser/src/parse.rs index 17482c54e3..52ee8559e4 100644 --- a/crates/nu-parser/src/parse.rs +++ b/crates/nu-parser/src/parse.rs @@ -467,7 +467,10 @@ fn parse_dollar_expr( scope: &dyn ParserScope, ) -> (SpannedExpression, Option) { trace!("Parsing dollar expression: {:?}", lite_arg.item); - if let (expr, None) = parse_range(lite_arg, scope) { + if lite_arg.item.starts_with("$\"") && lite_arg.item.len() > 1 && lite_arg.item.ends_with('"') { + // This is an interpolated string + parse_interpolated_string(&lite_arg, scope) + } else if let (expr, None) = parse_range(lite_arg, scope) { (expr, None) } else if let (expr, None) = parse_full_column_path(lite_arg, scope) { (expr, None) @@ -493,79 +496,60 @@ fn format(input: &str, start: usize) -> (Vec, Option) loop { let mut before = String::new(); - let mut found_start = false; - while let Some(c) = loop_input.next() { + loop { end += 1; - if c == '{' { - if let Some(x) = loop_input.peek() { - if *x == '{' { - found_start = true; - end += 1; - let _ = loop_input.next(); - break; - } + if let Some(c) = loop_input.next() { + if c == '{' { + break; } + before.push(c); + } else { + break; } - before.push(c); } if !before.is_empty() { - if found_start { - output.push(FormatCommand::Text( - before.to_string().spanned(Span::new(start, end - 2)), - )); - } else { - output.push(FormatCommand::Text(before.spanned(Span::new(start, end)))); - break; - } + output.push(FormatCommand::Text( + before.to_string().spanned(Span::new(start, end - 1)), + )); } // Look for column as we're now at one let mut column = String::new(); start = end; - let mut previous_c = ' '; let mut found_end = false; + let mut open_count = 1; while let Some(c) = loop_input.next() { end += 1; - if c == '}' && previous_c == '}' { - let _ = column.pop(); - found_end = true; - break; + if c == '{' { + open_count += 1; + } else if c == '}' { + open_count -= 1; + if open_count == 0 { + found_end = true; + break; + } } - previous_c = c; column.push(c); } if !column.is_empty() { - if found_end { - output.push(FormatCommand::Column( - column.to_string().spanned(Span::new(start, end - 2)), - )); - } else { - output.push(FormatCommand::Column( - column.to_string().spanned(Span::new(start, end)), - )); - - if error.is_none() { - error = Some(ParseError::argument_error( - input.spanned(Span::new(original_start, end)), - ArgumentError::MissingValueForName("unclosed {{ }}".to_string()), - )); - } - } - } - - if found_start && !found_end { - error = Some(ParseError::argument_error( - input.spanned(Span::new(original_start, end)), - ArgumentError::MissingValueForName("unclosed {{ }}".to_string()), + output.push(FormatCommand::Column( + column.to_string().spanned(Span::new(start, end)), )); } - if before.is_empty() && column.is_empty() { + if column.is_empty() { break; } + if !found_end { + error = Some(ParseError::argument_error( + input.spanned(Span::new(original_start, end)), + ArgumentError::MissingValueForName("unclosed { }".to_string()), + )); + } + start = end; } @@ -578,10 +562,16 @@ fn parse_interpolated_string( scope: &dyn ParserScope, ) -> (SpannedExpression, Option) { trace!("Parse_interpolated_string"); - let inner_string = trim_quotes(&lite_arg.item); + let string_len = lite_arg.item.len(); + let inner_string = lite_arg + .item + .chars() + .skip(2) + .take(string_len - 3) + .collect::(); let mut error = None; - let (format_result, err) = format(&inner_string, lite_arg.span.start() + 1); + let (format_result, err) = format(&inner_string, lite_arg.span.start() + 2); if error.is_none() { error = err; @@ -598,11 +588,18 @@ fn parse_interpolated_string( }); } FormatCommand::Column(c) => { - let (o, err) = parse_full_column_path(&c, scope); - if error.is_none() { - error = err; + let result = parse(&c, c.span.start(), scope); + match result { + (classified_block, None) => { + output.push(SpannedExpression { + expr: Expression::Invocation(classified_block), + span: c.span, + }); + } + (_, Some(err)) => { + return (garbage(c.span), Some(err)); + } } - output.push(o); } } } @@ -649,12 +646,6 @@ fn parse_external_arg( parse_dollar_expr(&lite_arg, scope) } else if lite_arg.item.starts_with('(') { parse_invocation(&lite_arg, scope) - } else if lite_arg.item.starts_with('`') - && lite_arg.item.len() > 1 - && lite_arg.item.ends_with('`') - { - // This is an interpolated string - parse_interpolated_string(&lite_arg, scope) } else { ( SpannedExpression::new(Expression::string(lite_arg.item.clone()), lite_arg.span), @@ -811,19 +802,11 @@ fn parse_arg( } } SyntaxShape::String => { - if lite_arg.item.starts_with('`') - && lite_arg.item.len() > 1 - && lite_arg.item.ends_with('`') - { - // This is an interpolated string - parse_interpolated_string(&lite_arg, scope) - } else { - let trimmed = trim_quotes(&lite_arg.item); - ( - SpannedExpression::new(Expression::string(trimmed), lite_arg.span), - None, - ) - } + let trimmed = trim_quotes(&lite_arg.item); + ( + SpannedExpression::new(Expression::string(trimmed), lite_arg.span), + None, + ) } SyntaxShape::GlobPattern => { let trimmed = trim_quotes(&lite_arg.item); diff --git a/crates/nu-parser/src/parse/util.rs b/crates/nu-parser/src/parse/util.rs index 95d28fe36b..b2c1a2d0d9 100644 --- a/crates/nu-parser/src/parse/util.rs +++ b/crates/nu-parser/src/parse/util.rs @@ -19,7 +19,6 @@ pub(crate) fn trim_quotes(input: &str) -> String { match (chars.next(), chars.next_back()) { (Some('\''), Some('\'')) => chars.collect(), (Some('"'), Some('"')) => chars.collect(), - (Some('`'), Some('`')) => chars.collect(), _ => input.to_string(), } } diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs index 2c051b573e..915440f951 100644 --- a/tests/shell/pipeline/commands/internal.rs +++ b/tests/shell/pipeline/commands/internal.rs @@ -118,55 +118,19 @@ fn string_interpolation_with_it() { let actual = nu!( cwd: ".", r#" - echo "foo" | each { echo `{{$it}}` } + echo "foo" | each { echo $"{$it}" } "# ); assert_eq!(actual.out, "foo"); } -#[test] -fn string_interpolation_with_column() { - let actual = nu!( - cwd: ".", - r#" - echo [[name]; [bob]] | each { echo `{{name}} is cool` } - "# - ); - - assert_eq!(actual.out, "bob is cool"); -} - -#[test] -fn string_interpolation_with_column2() { - let actual = nu!( - cwd: ".", - r#" - echo [[name]; [fred]] | each { echo `also {{name}} is cool` } - "# - ); - - assert_eq!(actual.out, "also fred is cool"); -} - -#[test] -fn string_interpolation_with_column3() { - let actual = nu!( - cwd: ".", - r#" - echo [[name]; [sally]] | each { echo `also {{name}}` } - "# - ); - - assert_eq!(actual.out, "also sally"); -} - #[test] fn string_interpolation_with_it_column_path() { let actual = nu!( cwd: ".", r#" - echo [[name]; [sammie]] | each { echo `{{$it.name}}` } + echo [[name]; [sammie]] | each { echo $"{$it.name}" } "# );