First (failed!) attempt at string interpolation

This commit is contained in:
Richard Feldman 2019-05-31 22:31:44 -04:00
parent 6bdff2b069
commit 7ae610ad18
3 changed files with 70 additions and 35 deletions

View file

@ -31,7 +31,7 @@ fn problem(prob: Problem) -> Evaluated {
pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated { pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
match expr { match expr {
// Primitives need no further evaluation // Primitives need no further evaluation
Error(_) | Int(_) | Str(_) | Frac(_, _) | Char(_) | Bool(_) | Closure(_, _) | Expr::EmptyRecord => Evaluated(expr), Error(_) | Int(_) | EmptyStr | Str(_) | InterpolatedStr(_, _) | Frac(_, _) | Char(_) | Bool(_) | Closure(_, _) | Expr::EmptyRecord => Evaluated(expr),
// Resolve variable names // Resolve variable names
Var(name) => match vars.get(&name) { Var(name) => match vars.get(&name) {

View file

@ -7,7 +7,9 @@ pub enum Expr {
// Literals // Literals
Int(i64), Int(i64),
Frac(i64, u64), Frac(i64, u64),
EmptyStr,
Str(String), Str(String),
InterpolatedStr(Vec<(String, Ident)>, String),
Char(char), Char(char),
Bool(bool), Bool(bool),
@ -34,7 +36,7 @@ pub enum Expr {
Error(Problem), Error(Problem),
} }
type Ident = String; pub type Ident = String;
impl fmt::Display for Expr { impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

View file

@ -441,8 +441,12 @@ pub fn string_literal<I>() -> impl Parser<Input = I, Output = Expr>
where I: Stream<Item = char, Position = IndentablePosition>, where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> I::Error: ParseError<I::Item, I::Range, I::Position>
{ {
between(char('"'), char('"'), many(string_body())) char('"').with(
.map(|str| Expr::Str(str)) choice((
char('"').with(value(Expr::EmptyStr)),
string_body()
))
)
} }
pub fn char_literal<I>() -> impl Parser<Input = I, Output = Expr> pub fn char_literal<I>() -> impl Parser<Input = I, Output = Expr>
@ -487,43 +491,72 @@ where
char('u').with(between(char('{'), char('}'), hex_code_pt)) char('u').with(between(char('{'), char('}'), hex_code_pt))
} }
fn string_body<I>() -> impl Parser<Input = I, Output = char> fn string_body<I>() -> impl Parser<Input = I, Output = Expr>
where where
I: Stream<Item = char, Position = IndentablePosition>, I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position>, I::Error: ParseError<I::Item, I::Range, I::Position>,
{ {
parser(|input: &mut I| { parser(|input: &mut I| {
let (parsed_char, consumed) = try!(any().parse_lazy(input).into()); let mut interpolated_pairs_so_far:Option<Vec<(Ident, String)>> = None;
let mut escaped = satisfy_map(|escaped_char| { let mut current_string:String = String::new();
// NOTE! When modifying this, revisit char_body too!
// Their implementations are similar but not the same.
match escaped_char {
'"' => Some('"'),
'\\' => Some('\\'),
't' => Some('\t'),
'n' => Some('\n'),
'r' => Some('\r'),
_ => None,
}
});
match parsed_char { loop {
'\\' => { let (parsed_char, consumed) = try!(any().parse_lazy(input).into());
consumed.combine(|_| {
// Try to parse basic backslash-escaped literals match parsed_char {
// e.g. \t, \n, \r '"' => {
escaped.parse_stream(input).or_else(|_| // An unescaped " means we've reached the end of the string!
// If we didn't find any of those, try \u{...} match interpolated_pairs_so_far {
unicode_code_pt().parse_stream(input) Some(pairs) => {
) return Ok((Expr::InterpolatedStr(pairs, current_string), consumed));
}) },
}, None => {
'"' => { return Ok((Expr::Str(current_string), consumed));
// We should never consume a double quote unless }
// it's preceded by a backslash };
Err(Consumed::Empty(I::Error::empty(input.position()).into())) },
}, '\\' => {
_ => Ok((parsed_char, consumed)), choice((
unicode_code_pt(),
satisfy_map(|escaped_char| {
// Try to parse basic backslash-escaped literals
// e.g. \t, \n, \r
//
// NOTE! When modifying this, revisit char_body too!
// Their implementations are similar but not the same.
match escaped_char {
'"' => Some('"'),
'\\' => Some('\\'),
't' => Some('\t'),
'n' => Some('\n'),
'r' => Some('\r'),
_ => None,
}
}),
)).parse_stream(input).map(|(escaped_char, _)| {
current_string.push(escaped_char);
}).or_else(|_|
// If we didn't find any of those, try \(...)
between(char('('), char(')'), ident())
.parse_stream(input)
.map(|(variable, _)| {
let pair = (variable, current_string.clone());
current_string = String::new();
match interpolated_pairs_so_far {
None => {
interpolated_pairs_so_far = Some(vec![pair]);
},
Some(mut pairs) => {
pairs.push(pair);
}
};
})
)?;
},
_ => ()
}
} }
}) })
} }