mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
First (failed!) attempt at string interpolation
This commit is contained in:
parent
6bdff2b069
commit
7ae610ad18
3 changed files with 70 additions and 35 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
73
src/parse.rs
73
src/parse.rs
|
@ -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,14 +491,37 @@ 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 mut interpolated_pairs_so_far:Option<Vec<(Ident, String)>> = None;
|
||||||
|
let mut current_string:String = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
let (parsed_char, consumed) = try!(any().parse_lazy(input).into());
|
let (parsed_char, consumed) = try!(any().parse_lazy(input).into());
|
||||||
let mut escaped = satisfy_map(|escaped_char| {
|
|
||||||
|
match parsed_char {
|
||||||
|
'"' => {
|
||||||
|
// An unescaped " means we've reached the end of the string!
|
||||||
|
match interpolated_pairs_so_far {
|
||||||
|
Some(pairs) => {
|
||||||
|
return Ok((Expr::InterpolatedStr(pairs, current_string), consumed));
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Ok((Expr::Str(current_string), 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!
|
// NOTE! When modifying this, revisit char_body too!
|
||||||
// Their implementations are similar but not the same.
|
// Their implementations are similar but not the same.
|
||||||
match escaped_char {
|
match escaped_char {
|
||||||
|
@ -505,25 +532,31 @@ where
|
||||||
'r' => Some('\r'),
|
'r' => Some('\r'),
|
||||||
_ => None,
|
_ => 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());
|
||||||
|
|
||||||
match parsed_char {
|
current_string = String::new();
|
||||||
'\\' => {
|
|
||||||
consumed.combine(|_| {
|
match interpolated_pairs_so_far {
|
||||||
// Try to parse basic backslash-escaped literals
|
None => {
|
||||||
// e.g. \t, \n, \r
|
interpolated_pairs_so_far = Some(vec![pair]);
|
||||||
escaped.parse_stream(input).or_else(|_|
|
},
|
||||||
// If we didn't find any of those, try \u{...}
|
Some(mut pairs) => {
|
||||||
unicode_code_pt().parse_stream(input)
|
pairs.push(pair);
|
||||||
)
|
}
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
)?;
|
||||||
},
|
},
|
||||||
'"' => {
|
_ => ()
|
||||||
// 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)),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue