Skip to content

Commit dc595d1

Browse files
committed
improve fstring parser
part of: RustPython#1671
1 parent 5097e20 commit dc595d1

File tree

4 files changed

+102
-40
lines changed

4 files changed

+102
-40
lines changed

Lib/test/test_support.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,6 @@ def test_make_bad_fd(self):
315315
os.write(fd, b"foo")
316316
self.assertEqual(cm.exception.errno, errno.EBADF)
317317

318-
# TODO: RUSTPYTHON
319-
@unittest.expectedFailure
320318
def test_check_syntax_error(self):
321319
support.check_syntax_error(self, "def class", lineno=1, offset=5)
322320
with self.assertRaises(AssertionError):

compiler/codegen/src/symboltable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ impl SymbolTableError {
172172
pub fn into_codegen_error(self, source_path: String) -> CodegenError {
173173
CodegenError {
174174
error: CodegenErrorType::SyntaxError(self.error),
175-
location: self.location,
175+
location: Location::new(self.location.row(), self.location.column() + 1),
176176
source_path,
177177
}
178178
}

compiler/parser/src/error.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,45 @@ pub enum FStringErrorType {
8282
InvalidExpression(Box<ParseErrorType>),
8383
InvalidConversionFlag,
8484
EmptyExpression,
85-
MismatchedDelimiter,
85+
MismatchedDelimiter(char, char),
8686
ExpressionNestedTooDeeply,
87+
ExpressionCannotInclude(char),
88+
SingleRbrace,
89+
Unmatched(char),
90+
UnterminatedString,
8791
}
8892

8993
impl fmt::Display for FStringErrorType {
9094
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9195
match self {
92-
FStringErrorType::UnclosedLbrace => write!(f, "Unclosed '{{'"),
96+
FStringErrorType::UnclosedLbrace => write!(f, "expecting '}}'"),
9397
FStringErrorType::UnopenedRbrace => write!(f, "Unopened '}}'"),
9498
FStringErrorType::ExpectedRbrace => write!(f, "Expected '}}' after conversion flag."),
9599
FStringErrorType::InvalidExpression(error) => {
96-
write!(f, "Invalid expression: {}", error)
100+
write!(f, "{}", error)
97101
}
98-
FStringErrorType::InvalidConversionFlag => write!(f, "Invalid conversion flag"),
99-
FStringErrorType::EmptyExpression => write!(f, "Empty expression"),
100-
FStringErrorType::MismatchedDelimiter => write!(f, "Mismatched delimiter"),
102+
FStringErrorType::InvalidConversionFlag => write!(f, "invalid conversion character"),
103+
FStringErrorType::EmptyExpression => write!(f, "empty expression not allowed"),
104+
FStringErrorType::MismatchedDelimiter(first, second) => write!(
105+
f,
106+
"closing parenthesis '{}' does not match opening parenthesis '{}'",
107+
second, first
108+
),
109+
FStringErrorType::SingleRbrace => write!(f, "single '}}' is not allowed"),
110+
FStringErrorType::Unmatched(delim) => write!(f, "unmatched '{}'", delim),
101111
FStringErrorType::ExpressionNestedTooDeeply => {
102112
write!(f, "expressions nested too deeply")
103113
}
114+
FStringErrorType::UnterminatedString => {
115+
write!(f, "unterminated string")
116+
}
117+
FStringErrorType::ExpressionCannotInclude(c) => {
118+
if *c == '\\' {
119+
write!(f, "f-string expression part cannot include a backslash")
120+
} else {
121+
write!(f, "f-string expression part cannot include '{}'s", c)
122+
}
123+
}
104124
}
105125
}
106126
}
@@ -162,7 +182,7 @@ pub(crate) fn parse_error_from_lalrpop(
162182
let expected = (expected.len() == 1).then(|| expected[0].clone());
163183
ParseError {
164184
error: ParseErrorType::UnrecognizedToken(token.1, expected),
165-
location: token.0,
185+
location: Location::new(token.0.row(), token.0.column() + 1),
166186
source_path,
167187
}
168188
}

compiler/parser/src/fstring.rs

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,35 @@ impl FStringParser {
6767
Some('a') => ConversionFlag::Ascii,
6868
Some('r') => ConversionFlag::Repr,
6969
Some(_) => {
70-
return Err(InvalidConversionFlag);
70+
return Err(if expression[1..].trim().is_empty() {
71+
EmptyExpression
72+
} else {
73+
InvalidConversionFlag
74+
});
7175
}
7276
None => {
73-
return Err(ExpectedRbrace);
77+
return Err(if expression[1..].trim().is_empty() {
78+
EmptyExpression
79+
} else {
80+
UnclosedLbrace
81+
});
7482
}
7583
};
7684

7785
if let Some(&peek) = chars.peek() {
7886
if peek != '}' && peek != ':' {
79-
if expression[1..].trim().is_empty() {
80-
return Err(EmptyExpression);
87+
return Err(if expression[1..].trim().is_empty() {
88+
EmptyExpression
8189
} else {
82-
return Err(ExpectedRbrace);
83-
}
90+
UnclosedLbrace
91+
});
8492
}
85-
} else if expression[1..].trim().is_empty() {
86-
return Err(EmptyExpression);
8793
} else {
88-
return Err(ExpectedRbrace);
94+
return Err(if expression[1..].trim().is_empty() {
95+
EmptyExpression
96+
} else {
97+
UnclosedLbrace
98+
});
8999
}
90100
}
91101

@@ -108,22 +118,42 @@ impl FStringParser {
108118
delims.push(ch);
109119
}
110120
')' => {
111-
if delims.pop() != Some('(') {
112-
return Err(MismatchedDelimiter);
121+
let last_delim = delims.pop();
122+
match last_delim {
123+
Some('(') => {
124+
expression.push(ch);
125+
}
126+
Some(c) => {
127+
return Err(MismatchedDelimiter(c, ')'));
128+
}
129+
None => {
130+
return Err(Unmatched(')'));
131+
}
113132
}
114-
expression.push(ch);
115133
}
116134
']' => {
117-
if delims.pop() != Some('[') {
118-
return Err(MismatchedDelimiter);
135+
let last_delim = delims.pop();
136+
match last_delim {
137+
Some('[') => {
138+
expression.push(ch);
139+
}
140+
Some(c) => {
141+
return Err(MismatchedDelimiter(c, ']'));
142+
}
143+
None => {
144+
return Err(Unmatched(']'));
145+
}
119146
}
120-
expression.push(ch);
121147
}
122148
'}' if !delims.is_empty() => {
123-
if delims.pop() != Some('{') {
124-
return Err(MismatchedDelimiter);
149+
let last_delim = delims.pop();
150+
match last_delim {
151+
Some('{') => {
152+
expression.push(ch);
153+
}
154+
Some(c) => return Err(MismatchedDelimiter(c, '}')),
155+
None => {}
125156
}
126-
expression.push(ch);
127157
}
128158
'}' => {
129159
if expression[1..].trim().is_empty() {
@@ -171,26 +201,36 @@ impl FStringParser {
171201
}
172202
'"' | '\'' => {
173203
expression.push(ch);
204+
let mut string_ended = false;
174205
for next in &mut chars {
175206
expression.push(next);
176207
if next == ch {
208+
string_ended = true;
177209
break;
178210
}
179211
}
212+
if !string_ended {
213+
return Err(UnterminatedString);
214+
}
180215
}
181216
' ' if self_documenting => {
182217
trailing_seq.push(ch);
183218
}
219+
'\\' => return Err(ExpressionCannotInclude('\\')),
184220
_ => {
185221
if self_documenting {
186-
return Err(ExpectedRbrace);
222+
return Err(UnclosedLbrace);
187223
}
188224

189225
expression.push(ch);
190226
}
191227
}
192228
}
193-
Err(UnclosedLbrace)
229+
Err(if expression[1..].trim().is_empty() {
230+
EmptyExpression
231+
} else {
232+
UnclosedLbrace
233+
})
194234
}
195235

196236
fn parse_spec<'a>(
@@ -251,10 +291,14 @@ impl FStringParser {
251291
'{' => {
252292
chars.next();
253293
if nested == 0 {
254-
if let Some('{') = chars.peek() {
255-
chars.next();
256-
content.push('{');
257-
continue;
294+
match chars.peek() {
295+
Some('{') => {
296+
chars.next();
297+
content.push('{');
298+
continue;
299+
}
300+
None => return Err(UnclosedLbrace),
301+
_ => {}
258302
}
259303
}
260304
if !content.is_empty() {
@@ -278,7 +322,7 @@ impl FStringParser {
278322
chars.next();
279323
content.push('}');
280324
} else {
281-
return Err(UnopenedRbrace);
325+
return Err(SingleRbrace);
282326
}
283327
}
284328
_ => {
@@ -385,9 +429,9 @@ mod tests {
385429

386430
#[test]
387431
fn test_parse_invalid_fstring() {
388-
assert_eq!(parse_fstring("{5!a"), Err(ExpectedRbrace));
389-
assert_eq!(parse_fstring("{5!a1}"), Err(ExpectedRbrace));
390-
assert_eq!(parse_fstring("{5!"), Err(ExpectedRbrace));
432+
assert_eq!(parse_fstring("{5!a"), Err(UnclosedLbrace));
433+
assert_eq!(parse_fstring("{5!a1}"), Err(UnclosedLbrace));
434+
assert_eq!(parse_fstring("{5!"), Err(UnclosedLbrace));
391435
assert_eq!(parse_fstring("abc{!a 'cat'}"), Err(EmptyExpression));
392436
assert_eq!(parse_fstring("{!a"), Err(EmptyExpression));
393437
assert_eq!(parse_fstring("{ !a}"), Err(EmptyExpression));
@@ -397,8 +441,8 @@ mod tests {
397441

398442
assert_eq!(parse_fstring("{a:{a:{b}}}"), Err(ExpressionNestedTooDeeply));
399443

400-
assert_eq!(parse_fstring("{a:b}}"), Err(UnopenedRbrace));
401-
assert_eq!(parse_fstring("}"), Err(UnopenedRbrace));
444+
assert_eq!(parse_fstring("{a:b}}"), Err(SingleRbrace));
445+
assert_eq!(parse_fstring("}"), Err(SingleRbrace));
402446
assert_eq!(parse_fstring("{a:{b}"), Err(UnclosedLbrace));
403447
assert_eq!(parse_fstring("{"), Err(UnclosedLbrace));
404448

0 commit comments

Comments
 (0)