String interpolation (#1849)

* Add string interpolation

* fix coloring

* A few more fixups + tests

* merge master again
This commit is contained in:
Jonathan Turner
2020-05-19 12:27:26 -07:00
committed by GitHub
parent ae87582cb6
commit ed80933806
6 changed files with 303 additions and 13 deletions

View File

@ -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)?;

View File

@ -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);