Add $(...) string interpolation syntax

This commit is contained in:
Richard Feldman 2024-01-06 08:34:58 -05:00
parent a3c062d845
commit 25be487977
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
2 changed files with 68 additions and 0 deletions

View file

@ -141,6 +141,7 @@ pub enum EscapedChar {
SingleQuote, // \'
Backslash, // \\
CarriageReturn, // \r
Dollar, // \$
}
impl EscapedChar {
@ -155,6 +156,7 @@ impl EscapedChar {
CarriageReturn => 'r',
Tab => 't',
Newline => 'n',
Dollar => '$',
}
}
@ -168,6 +170,7 @@ impl EscapedChar {
CarriageReturn => '\r',
Tab => '\t',
Newline => '\n',
Dollar => '$',
}
}
}

View file

@ -349,6 +349,68 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
return Err((MadeProgress, EString::EndlessSingleLine(start_state.pos())));
}
}
b'$' => {
// This is for the byte we're about to parse.
segment_parsed_bytes += 1;
// iff the '$' is followed by '(', this is string interpolation.
if let Some(b'(') = bytes.next() {
// We're about to begin string interpolation!
//
// End the previous segment so we can begin a new one.
// Retroactively end it right before the `$` char we parsed.
// (We can't use end_segment! here because it ends it right after
// the just-parsed character, which here would be '(' rather than '$')
// Don't push anything if the string would be empty.
if segment_parsed_bytes > 2 {
// exclude the 2 chars we just parsed, namely '$' and '('
let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 2)];
match std::str::from_utf8(string_bytes) {
Ok(string) => {
state.advance_mut(string.len());
segments.push(StrSegment::Plaintext(string));
}
Err(_) => {
return Err((
MadeProgress,
EString::Space(BadInputError::BadUtf8, state.pos()),
));
}
}
}
// Advance past the `$(`
state.advance_mut(2);
let original_byte_count = state.bytes().len();
// Parse an arbitrary expression, followed by ')'
let (_progress, loc_expr, new_state) = skip_second!(
specialize_ref(
EString::Format,
loc(allocated(reset_min_indent(expr::expr_help())))
),
word1(b')', EString::FormatEnd)
)
.parse(arena, state, min_indent)?;
// Advance the iterator past the expr we just parsed.
for _ in 0..(original_byte_count - new_state.bytes().len()) {
bytes.next();
}
segments.push(StrSegment::Interpolated(loc_expr));
// Reset the segment
segment_parsed_bytes = 0;
state = new_state;
}
// If the '$' wasn't followed by '(', then this wasn't interpolation,
// and we don't need to do anything special.
}
b'\\' => {
// We're about to begin an escaped segment of some sort!
//
@ -437,6 +499,9 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
Some(b'n') => {
escaped_char!(EscapedChar::Newline);
}
Some(b'$') => {
escaped_char!(EscapedChar::Dollar);
}
_ => {
// Invalid escape! A backslash must be followed
// by either an open paren or else one of the