mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 08:45:45 +02:00
String interpolation (#1849)
* Add string interpolation * fix coloring * A few more fixups + tests * merge master again
This commit is contained in:
@ -69,7 +69,7 @@ fn bare(src: &mut Input, span_offset: usize) -> Result<Spanned<String>, ParseErr
|
||||
if c == delimiter {
|
||||
inside_quote = false;
|
||||
}
|
||||
} else if c == '\'' || c == '"' {
|
||||
} else if c == '\'' || c == '"' || c == '`' {
|
||||
inside_quote = true;
|
||||
delimiter = c;
|
||||
} else if c == '[' {
|
||||
@ -154,12 +154,6 @@ fn pipeline(src: &mut Input, span_offset: usize) -> Result<LiteBlock, ParseError
|
||||
break;
|
||||
}
|
||||
}
|
||||
// '"' | '\'' => {
|
||||
// let c = *c;
|
||||
// // quoted string
|
||||
// let arg = quoted(src, c, span_offset)?;
|
||||
// cmd.args.push(arg);
|
||||
// }
|
||||
_ => {
|
||||
// basic argument
|
||||
let arg = bare(src, span_offset)?;
|
||||
|
@ -29,7 +29,7 @@ fn parse_simple_column_path(lite_arg: &Spanned<String>) -> (SpannedExpression, O
|
||||
if c == delimiter {
|
||||
inside_delimiter = false;
|
||||
}
|
||||
} else if c == '\'' || c == '"' {
|
||||
} else if c == '\'' || c == '"' || c == '`' {
|
||||
inside_delimiter = true;
|
||||
delimiter = c;
|
||||
} else if c == '.' {
|
||||
@ -228,6 +228,7 @@ 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(),
|
||||
}
|
||||
}
|
||||
@ -352,6 +353,165 @@ fn parse_unit(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseErr
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FormatCommand {
|
||||
Text(Spanned<String>),
|
||||
Column(Spanned<String>),
|
||||
}
|
||||
|
||||
fn format(input: &str, start: usize) -> (Vec<FormatCommand>, Option<ParseError>) {
|
||||
let original_start = start;
|
||||
let mut output = vec![];
|
||||
let mut error = None;
|
||||
|
||||
let mut loop_input = input.chars().peekable();
|
||||
let mut start = start;
|
||||
let mut end = start;
|
||||
loop {
|
||||
let mut before = String::new();
|
||||
|
||||
let mut found_start = false;
|
||||
while let Some(c) = loop_input.next() {
|
||||
end += 1;
|
||||
if c == '{' {
|
||||
if let Some(x) = loop_input.peek() {
|
||||
if *x == '{' {
|
||||
found_start = true;
|
||||
end += 1;
|
||||
let _ = loop_input.next();
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
while let Some(c) = loop_input.next() {
|
||||
end += 1;
|
||||
if c == '}' && previous_c == '}' {
|
||||
let _ = column.pop();
|
||||
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()),
|
||||
));
|
||||
}
|
||||
|
||||
if before.is_empty() && column.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
start = end;
|
||||
}
|
||||
|
||||
(output, error)
|
||||
}
|
||||
|
||||
/// Parses an interpolated string, one that has expressions inside of it
|
||||
fn parse_interpolated_string(
|
||||
registry: &dyn SignatureRegistry,
|
||||
lite_arg: &Spanned<String>,
|
||||
) -> (SpannedExpression, Option<ParseError>) {
|
||||
let inner_string = trim_quotes(&lite_arg.item);
|
||||
let mut error = None;
|
||||
|
||||
let (format_result, err) = format(&inner_string, lite_arg.span.start() + 1);
|
||||
|
||||
if error.is_none() {
|
||||
error = err;
|
||||
}
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for f in format_result {
|
||||
match f {
|
||||
FormatCommand::Text(t) => {
|
||||
output.push(SpannedExpression {
|
||||
expr: Expression::Literal(hir::Literal::String(t.item)),
|
||||
span: t.span,
|
||||
});
|
||||
}
|
||||
FormatCommand::Column(c) => {
|
||||
let (o, err) = parse_full_column_path(&c, registry);
|
||||
if error.is_none() {
|
||||
error = err;
|
||||
}
|
||||
output.push(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let block = vec![Commands {
|
||||
span: lite_arg.span,
|
||||
list: vec![ClassifiedCommand::Internal(InternalCommand {
|
||||
name: "build-string".to_owned(),
|
||||
name_span: lite_arg.span,
|
||||
args: hir::Call {
|
||||
head: Box::new(SpannedExpression {
|
||||
expr: Expression::Synthetic(hir::Synthetic::String("build-string".to_owned())),
|
||||
span: lite_arg.span,
|
||||
}),
|
||||
is_last: false,
|
||||
named: None,
|
||||
positional: Some(output),
|
||||
span: lite_arg.span,
|
||||
},
|
||||
})],
|
||||
}];
|
||||
|
||||
let call = SpannedExpression {
|
||||
expr: Expression::Invocation(Block {
|
||||
block,
|
||||
span: lite_arg.span,
|
||||
}),
|
||||
span: lite_arg.span,
|
||||
};
|
||||
|
||||
(call, error)
|
||||
}
|
||||
|
||||
/// Parses the given argument using the shape as a guide for how to correctly parse the argument
|
||||
fn parse_arg(
|
||||
expected_type: SyntaxShape,
|
||||
@ -395,11 +555,19 @@ fn parse_arg(
|
||||
}
|
||||
}
|
||||
SyntaxShape::String => {
|
||||
let trimmed = trim_quotes(&lite_arg.item);
|
||||
(
|
||||
SpannedExpression::new(Expression::string(trimmed), lite_arg.span),
|
||||
None,
|
||||
)
|
||||
if lite_arg.item.starts_with('`')
|
||||
&& lite_arg.item.len() > 1
|
||||
&& lite_arg.item.ends_with('`')
|
||||
{
|
||||
// This is an interpolated string
|
||||
parse_interpolated_string(registry, &lite_arg)
|
||||
} else {
|
||||
let trimmed = trim_quotes(&lite_arg.item);
|
||||
(
|
||||
SpannedExpression::new(Expression::string(trimmed), lite_arg.span),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
SyntaxShape::Pattern => {
|
||||
let trimmed = trim_quotes(&lite_arg.item);
|
||||
|
Reference in New Issue
Block a user