Skip to content

Commit

Permalink
improve fstring parser
Browse files Browse the repository at this point in the history
  • Loading branch information
dvermd authored and youknowone committed Oct 26, 2022
1 parent f2bfb78 commit 0383694
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 37 deletions.
7 changes: 6 additions & 1 deletion Lib/test/test_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def setUp(self):
def tearDown(self):
self._warnings_manager.__exit__(None, None, None)


# TODO: RUSTPYTHON
@unittest.expectedFailure
def test1(self):
prog_text_1 = """\
def wrong1():
Expand All @@ -27,6 +28,8 @@ def wrong1():
"""
check_syntax_error(self, prog_text_1, lineno=4, offset=5)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test2(self):
prog_text_2 = """\
def wrong2():
Expand All @@ -35,6 +38,8 @@ def wrong2():
"""
check_syntax_error(self, prog_text_2, lineno=3, offset=5)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test3(self):
prog_text_3 = """\
def wrong3():
Expand Down
32 changes: 26 additions & 6 deletions compiler/parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,45 @@ pub enum FStringErrorType {
InvalidExpression(Box<ParseErrorType>),
InvalidConversionFlag,
EmptyExpression,
MismatchedDelimiter,
MismatchedDelimiter(char, char),
ExpressionNestedTooDeeply,
ExpressionCannotInclude(char),
SingleRbrace,
Unmatched(char),
UnterminatedString,
}

impl fmt::Display for FStringErrorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FStringErrorType::UnclosedLbrace => write!(f, "Unclosed '{{'"),
FStringErrorType::UnclosedLbrace => write!(f, "expecting '}}'"),
FStringErrorType::UnopenedRbrace => write!(f, "Unopened '}}'"),
FStringErrorType::ExpectedRbrace => write!(f, "Expected '}}' after conversion flag."),
FStringErrorType::InvalidExpression(error) => {
write!(f, "Invalid expression: {}", error)
write!(f, "{}", error)
}
FStringErrorType::InvalidConversionFlag => write!(f, "Invalid conversion flag"),
FStringErrorType::EmptyExpression => write!(f, "Empty expression"),
FStringErrorType::MismatchedDelimiter => write!(f, "Mismatched delimiter"),
FStringErrorType::InvalidConversionFlag => write!(f, "invalid conversion character"),
FStringErrorType::EmptyExpression => write!(f, "empty expression not allowed"),
FStringErrorType::MismatchedDelimiter(first, second) => write!(
f,
"closing parenthesis '{}' does not match opening parenthesis '{}'",
second, first
),
FStringErrorType::SingleRbrace => write!(f, "single '}}' is not allowed"),
FStringErrorType::Unmatched(delim) => write!(f, "unmatched '{}'", delim),
FStringErrorType::ExpressionNestedTooDeeply => {
write!(f, "expressions nested too deeply")
}
FStringErrorType::UnterminatedString => {
write!(f, "unterminated string")
}
FStringErrorType::ExpressionCannotInclude(c) => {
if *c == '\\' {
write!(f, "f-string expression part cannot include a backslash")
} else {
write!(f, "f-string expression part cannot include '{}'s", c)
}
}
}
}
}
Expand Down
106 changes: 76 additions & 30 deletions compiler/parser/src/fstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,35 @@ impl FStringParser {
Some('a') => ConversionFlag::Ascii,
Some('r') => ConversionFlag::Repr,
Some(_) => {
return Err(InvalidConversionFlag);
return Err(if expression[1..].trim().is_empty() {
EmptyExpression
} else {
InvalidConversionFlag
});
}
None => {
return Err(ExpectedRbrace);
return Err(if expression[1..].trim().is_empty() {
EmptyExpression
} else {
UnclosedLbrace
});
}
};

if let Some(&peek) = chars.peek() {
if peek != '}' && peek != ':' {
if expression[1..].trim().is_empty() {
return Err(EmptyExpression);
return Err(if expression[1..].trim().is_empty() {
EmptyExpression
} else {
return Err(ExpectedRbrace);
}
UnclosedLbrace
});
}
} else if expression[1..].trim().is_empty() {
return Err(EmptyExpression);
} else {
return Err(ExpectedRbrace);
return Err(if expression[1..].trim().is_empty() {
EmptyExpression
} else {
UnclosedLbrace
});
}
}

Expand All @@ -108,22 +118,42 @@ impl FStringParser {
delims.push(ch);
}
')' => {
if delims.pop() != Some('(') {
return Err(MismatchedDelimiter);
let last_delim = delims.pop();
match last_delim {
Some('(') => {
expression.push(ch);
}
Some(c) => {
return Err(MismatchedDelimiter(c, ')'));
}
None => {
return Err(Unmatched(')'));
}
}
expression.push(ch);
}
']' => {
if delims.pop() != Some('[') {
return Err(MismatchedDelimiter);
let last_delim = delims.pop();
match last_delim {
Some('[') => {
expression.push(ch);
}
Some(c) => {
return Err(MismatchedDelimiter(c, ']'));
}
None => {
return Err(Unmatched(']'));
}
}
expression.push(ch);
}
'}' if !delims.is_empty() => {
if delims.pop() != Some('{') {
return Err(MismatchedDelimiter);
let last_delim = delims.pop();
match last_delim {
Some('{') => {
expression.push(ch);
}
Some(c) => return Err(MismatchedDelimiter(c, '}')),
None => {}
}
expression.push(ch);
}
'}' => {
if expression[1..].trim().is_empty() {
Expand Down Expand Up @@ -171,26 +201,36 @@ impl FStringParser {
}
'"' | '\'' => {
expression.push(ch);
let mut string_ended = false;
for next in &mut chars {
expression.push(next);
if next == ch {
string_ended = true;
break;
}
}
if !string_ended {
return Err(UnterminatedString);
}
}
' ' if self_documenting => {
trailing_seq.push(ch);
}
'\\' => return Err(ExpressionCannotInclude('\\')),
_ => {
if self_documenting {
return Err(ExpectedRbrace);
return Err(UnclosedLbrace);
}

expression.push(ch);
}
}
}
Err(UnclosedLbrace)
Err(if expression[1..].trim().is_empty() {
EmptyExpression
} else {
UnclosedLbrace
})
}

fn parse_spec<'a>(
Expand Down Expand Up @@ -245,16 +285,22 @@ impl FStringParser {

let mut content = String::new();
let mut values = vec![];
// let mut brace_opened = false;

while let Some(&ch) = chars.peek() {
match ch {
'{' => {
chars.next();
if nested == 0 {
if let Some('{') = chars.peek() {
chars.next();
content.push('{');
continue;
match chars.peek() {
Some('{') => {
chars.next();
content.push('{');
// brace_opened = true;
continue;
}
None => return Err(UnclosedLbrace),
_ => {}
}
}
if !content.is_empty() {
Expand All @@ -278,7 +324,7 @@ impl FStringParser {
chars.next();
content.push('}');
} else {
return Err(UnopenedRbrace);
return Err(SingleRbrace);
}
}
_ => {
Expand Down Expand Up @@ -385,9 +431,9 @@ mod tests {

#[test]
fn test_parse_invalid_fstring() {
assert_eq!(parse_fstring("{5!a"), Err(ExpectedRbrace));
assert_eq!(parse_fstring("{5!a1}"), Err(ExpectedRbrace));
assert_eq!(parse_fstring("{5!"), Err(ExpectedRbrace));
assert_eq!(parse_fstring("{5!a"), Err(UnclosedLbrace));
assert_eq!(parse_fstring("{5!a1}"), Err(UnclosedLbrace));
assert_eq!(parse_fstring("{5!"), Err(UnclosedLbrace));
assert_eq!(parse_fstring("abc{!a 'cat'}"), Err(EmptyExpression));
assert_eq!(parse_fstring("{!a"), Err(EmptyExpression));
assert_eq!(parse_fstring("{ !a}"), Err(EmptyExpression));
Expand All @@ -397,8 +443,8 @@ mod tests {

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

assert_eq!(parse_fstring("{a:b}}"), Err(UnopenedRbrace));
assert_eq!(parse_fstring("}"), Err(UnopenedRbrace));
assert_eq!(parse_fstring("{a:b}}"), Err(SingleRbrace));
assert_eq!(parse_fstring("}"), Err(SingleRbrace));
assert_eq!(parse_fstring("{a:{b}"), Err(UnclosedLbrace));
assert_eq!(parse_fstring("{"), Err(UnclosedLbrace));

Expand Down

0 comments on commit 0383694

Please sign in to comment.