Skip to content

Commit bcf9b27

Browse files
authored
Allow escaping braces in fstrings (#725)
2 parents 9671930 + 6fb761e commit bcf9b27

File tree

4 files changed

+47
-5
lines changed

4 files changed

+47
-5
lines changed

nemo/src/parser/ast/expression/complex/fstring.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ pub enum FormatStringElement<'a> {
1717
String(Token<'a>),
1818
/// Expression
1919
Expression(Expression<'a>),
20+
/// Escaped start marker
21+
EscapedStart,
22+
/// Escaped end marker
23+
EscapedEnd,
2024
}
2125

2226
/// A string which may include sub expressions
@@ -48,6 +52,12 @@ impl<'a> FormatString<'a> {
4852
fn parse_element(input: ParserInput<'a>) -> ParserResult<'a, FormatStringElement<'a>> {
4953
alt((
5054
map(Token::fstring, FormatStringElement::String),
55+
map(Token::fstring_escape_start, |_| {
56+
FormatStringElement::EscapedStart
57+
}),
58+
map(Token::fstring_escape_end, |_| {
59+
FormatStringElement::EscapedEnd
60+
}),
5161
map(Self::parse_expression, FormatStringElement::Expression),
5262
))(input)
5363
}
@@ -58,6 +68,12 @@ impl<'a> FormatString<'a> {
5868
) -> ParserResult<'a, FormatStringElement<'a>> {
5969
alt((
6070
map(Token::multiline_fstring, FormatStringElement::String),
71+
map(Token::fstring_escape_start, |_| {
72+
FormatStringElement::EscapedStart
73+
}),
74+
map(Token::fstring_escape_end, |_| {
75+
FormatStringElement::EscapedEnd
76+
}),
6177
map(Self::parse_expression, FormatStringElement::Expression),
6278
))(input)
6379
}
@@ -73,6 +89,8 @@ impl<'a> ProgramAST<'a> for FormatString<'a> {
7389
match element {
7490
FormatStringElement::String(_token) => {}
7591
FormatStringElement::Expression(expression) => result.push(expression),
92+
&FormatStringElement::EscapedStart => {}
93+
&FormatStringElement::EscapedEnd => {}
7694
}
7795
}
7896

@@ -125,6 +143,7 @@ impl<'a> ProgramAST<'a> for FormatString<'a> {
125143
#[cfg(test)]
126144
mod test {
127145
use nom::combinator::all_consuming;
146+
use test_log::test;
128147

129148
use crate::parser::{
130149
ParserState,
@@ -141,6 +160,10 @@ mod test {
141160
(r#"f"{?x + 1}""#, 1),
142161
(r#"f"result: {?x + 1}""#, 2),
143162
(r#"f"{?x} + {?y} = {?x + ?y}""#, 5),
163+
(r#"f"{{""#, 1),
164+
(r#"f"}}""#, 1),
165+
(r#"f"{{}}""#, 2),
166+
(r#"f"}}{{""#, 2),
144167
];
145168

146169
for (input, expected) in test {
@@ -150,6 +173,11 @@ mod test {
150173
assert!(result.is_ok());
151174

152175
let result = result.unwrap().1;
176+
177+
log::debug!(
178+
"{input:?}: expected {expected}; got {:?}",
179+
result.elements().collect::<Vec<_>>()
180+
);
153181
assert_eq!(expected, result.elements().count());
154182
}
155183
}

nemo/src/parser/ast/token.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ pub enum TokenKind {
180180
/// Format string expression close
181181
#[assoc(name = format_string::EXPRESSION_END)]
182182
FormatStringExpressionEnd,
183+
/// Format string escaped start marker
184+
#[assoc(name = format_string::ESCAPE_START)]
185+
FormatStringEscapeStart,
186+
/// Format string escaped end marker
187+
#[assoc(name = format_string::ESCAPE_END)]
188+
FormatStringEscapeEnd,
183189
/// Blank node prefix
184190
#[assoc(name = "_:")]
185191
BlankNodePrefix,
@@ -816,6 +822,8 @@ impl<'a> Token<'a> {
816822
string_token!(triple_quote, TokenKind::TripleQuote);
817823
string_token!(fstring_open, TokenKind::FormatStringOpen);
818824
string_token!(fstring_close, TokenKind::FormatStringClose);
825+
string_token!(fstring_escape_start, TokenKind::FormatStringEscapeStart);
826+
string_token!(fstring_escape_end, TokenKind::FormatStringEscapeEnd);
819827
string_token!(fstring_multiline_open, TokenKind::FormatStringMultilineOpen);
820828
string_token!(
821829
fstring_multiline_close,

nemo/src/rule_model/translation/complex/fstring.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
newtype_wrapper,
3-
parser::ast::{self},
3+
parser::ast::{self, expression::complex::fstring::FormatStringElement},
44
rule_model::{
55
components::term::{
66
Term,
@@ -9,6 +9,7 @@ use crate::{
99
origin::Origin,
1010
translation::TranslationComponent,
1111
},
12+
syntax::expression::format_string::{EXPRESSION_END, EXPRESSION_START},
1213
};
1314

1415
pub(crate) struct FormatStringLiteral(Operation);
@@ -25,15 +26,15 @@ impl TranslationComponent for FormatStringLiteral {
2526

2627
for element in format_string.elements() {
2728
let term = match element {
28-
ast::expression::complex::fstring::FormatStringElement::String(token) => {
29-
Term::from(token.to_string())
30-
}
31-
ast::expression::complex::fstring::FormatStringElement::Expression(expression) => {
29+
FormatStringElement::String(token) => Term::from(token.to_string()),
30+
FormatStringElement::Expression(expression) => {
3231
let inner_term = Term::build_component(translation, expression)?;
3332
let string_conversion =
3433
Operation::new(OperationKind::LexicalValue, vec![inner_term]);
3534
Term::from(string_conversion)
3635
}
36+
FormatStringElement::EscapedStart => Term::from(EXPRESSION_START),
37+
FormatStringElement::EscapedEnd => Term::from(EXPRESSION_END),
3738
};
3839

3940
subterms.push(term);

nemo/src/syntax.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ pub mod expression {
147147
pub const EXPRESSION_START: &str = "{";
148148
/// Marker of the end of an expression
149149
pub const EXPRESSION_END: &str = "}";
150+
151+
/// Escaped version of the start marker
152+
pub const ESCAPE_START: &str = "{{";
153+
/// Escaped version of the end marker
154+
pub const ESCAPE_END: &str = "}}";
150155
}
151156
}
152157

0 commit comments

Comments
 (0)