Skip to content

Commit 5968c4d

Browse files
convert f-string parser to state machine
1 parent 7364866 commit 5968c4d

File tree

2 files changed

+96
-57
lines changed

2 files changed

+96
-57
lines changed

parser/src/parser.rs

Lines changed: 93 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
extern crate lalrpop_util;
22

33
use std::iter;
4-
use std::mem;
54

65
use super::ast;
76
use super::error::ParseError;
@@ -86,74 +85,111 @@ impl From<FStringError>
8685
}
8786
}
8887

88+
enum ParseState {
89+
Text { content: String },
90+
FormattedValue { expression: String, depth: usize },
91+
}
92+
8993
pub fn parse_fstring(source: &str) -> Result<ast::StringGroup, FStringError> {
94+
use self::ParseState::*;
95+
9096
let mut values = vec![];
91-
let mut start = 0;
92-
let mut depth = 0;
93-
let mut escaped = false;
94-
let mut content = String::new();
95-
96-
let mut chars = source.char_indices().peekable();
97-
while let Some((pos, ch)) = chars.next() {
98-
match ch {
99-
'{' | '}' if escaped => {
100-
if depth == 0 {
97+
let mut state = ParseState::Text {
98+
content: String::new(),
99+
};
100+
101+
let mut chars = source.chars().peekable();
102+
while let Some(ch) = chars.next() {
103+
state = match state {
104+
Text { mut content } => match ch {
105+
'{' => {
106+
if let Some('{') = chars.peek() {
107+
chars.next();
108+
content.push('{');
109+
Text { content }
110+
} else {
111+
if !content.is_empty() {
112+
values.push(ast::StringGroup::Constant { value: content });
113+
}
114+
115+
FormattedValue {
116+
expression: String::new(),
117+
depth: 0,
118+
}
119+
}
120+
}
121+
'}' => {
122+
if let Some('}') = chars.peek() {
123+
chars.next();
124+
content.push('}');
125+
Text { content }
126+
} else {
127+
return Err(FStringError::UnopenedRbrace);
128+
}
129+
}
130+
_ => {
101131
content.push(ch);
132+
Text { content }
102133
}
103-
escaped = false;
104-
}
105-
'{' => {
106-
if let Some((_, '{')) = chars.peek() {
107-
escaped = true;
108-
continue;
134+
},
135+
136+
FormattedValue {
137+
mut expression,
138+
depth,
139+
} => match ch {
140+
'{' => {
141+
if let Some('{') = chars.peek() {
142+
expression.push_str("{{");
143+
chars.next();
144+
FormattedValue { expression, depth }
145+
} else {
146+
expression.push('{');
147+
FormattedValue {
148+
expression,
149+
depth: depth + 1,
150+
}
151+
}
109152
}
110-
111-
if depth == 0 {
112-
if !content.is_empty() {
113-
values.push(ast::StringGroup::Constant {
114-
value: mem::replace(&mut content, String::new()),
153+
'}' => {
154+
if let Some('}') = chars.peek() {
155+
expression.push_str("}}");
156+
chars.next();
157+
FormattedValue { expression, depth }
158+
} else if depth > 0 {
159+
expression.push('}');
160+
FormattedValue {
161+
expression,
162+
depth: depth - 1,
163+
}
164+
} else {
165+
values.push(ast::StringGroup::FormattedValue {
166+
value: Box::new(match parse_expression(dbg!(expression.trim())) {
167+
Ok(expr) => expr,
168+
Err(_) => return Err(FStringError::InvalidExpression),
169+
}),
115170
});
171+
Text {
172+
content: String::new(),
173+
}
116174
}
117-
118-
start = pos + 1;
119-
}
120-
121-
depth += 1;
122-
}
123-
'}' => {
124-
if let Some((_, '}')) = chars.peek() {
125-
escaped = true;
126-
continue;
127175
}
128-
129-
if depth == 0 {
130-
return Err(FStringError::UnopenedRbrace);
176+
_ => {
177+
expression.push(ch);
178+
FormattedValue { expression, depth }
131179
}
180+
},
181+
};
182+
}
132183

133-
depth -= 1;
134-
if depth == 0 {
135-
values.push(ast::StringGroup::FormattedValue {
136-
value: Box::new(match parse_expression(source[start..pos].trim()) {
137-
Ok(expr) => expr,
138-
Err(_) => return Err(FStringError::InvalidExpression),
139-
}),
140-
});
141-
}
142-
}
143-
ch => {
144-
if depth == 0 {
145-
content.push(ch);
146-
}
184+
match state {
185+
Text { content } => {
186+
if !content.is_empty() {
187+
values.push(ast::StringGroup::Constant { value: content })
147188
}
148189
}
149-
}
150-
151-
if depth != 0 {
152-
return Err(FStringError::UnclosedLbrace);
153-
}
154-
155-
if !content.is_empty() {
156-
values.push(ast::StringGroup::Constant { value: content })
190+
FormattedValue { .. } => {
191+
return Err(FStringError::UnclosedLbrace);
192+
}
157193
}
158194

159195
Ok(match values.len() {

tests/snippets/fstrings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@
1111
assert f'{f"{{"}' == '{'
1212
assert f'{f"}}"}' == '}'
1313
assert f'{foo}' f"{foo}" 'foo' == 'barbarfoo'
14+
assert f'{"!:"}' == '!:'
15+
#assert f"{1 != 2}" == 'True'
16+
assert fr'x={4*10}\n' == 'x=40\\n'

0 commit comments

Comments
 (0)