@@ -32,6 +32,7 @@ pub enum ParsingError {
3232 UnexpectedEndOfInput ( usize ) ,
3333 Expected ( usize , & ' static str ) ,
3434 ExpectedMatchingChar ( usize , & ' static str , char , char ) ,
35+ MissingParenthesis ( usize ) ,
3536 // more specific errors
3637 InvalidCharacter ( usize , char ) ,
3738 InvalidHeader ( usize ) ,
@@ -68,6 +69,7 @@ impl ParsingError {
6869 ParsingError :: Unrecognized ( offset) => * offset,
6970 ParsingError :: Expected ( offset, _) => * offset,
7071 ParsingError :: ExpectedMatchingChar ( offset, _, _, _) => * offset,
72+ ParsingError :: MissingParenthesis ( offset) => * offset,
7173 ParsingError :: UnclosedInterpolation ( offset) => * offset,
7274 ParsingError :: InvalidHeader ( offset) => * offset,
7375 ParsingError :: InvalidCharacter ( offset, _) => * offset,
@@ -1297,6 +1299,15 @@ impl<'i> Parser<'i> {
12971299
12981300 if is_binding ( content) {
12991301 self . read_binding_expression ( )
1302+ } else if malformed_binding_pattern ( content) {
1303+ if let Some ( tilde_pos) = self
1304+ . source
1305+ . find ( '~' )
1306+ {
1307+ self . advance ( tilde_pos + 1 ) ; // Move past ~
1308+ self . trim_whitespace ( ) ;
1309+ }
1310+ return Err ( ParsingError :: MissingParenthesis ( self . offset ) ) ;
13001311 } else if is_repeat_keyword ( content) {
13011312 self . read_repeat_expression ( )
13021313 } else if is_foreach_keyword ( content) {
@@ -2022,7 +2033,20 @@ impl<'i> Parser<'i> {
20222033 if parser. peek_next_char ( ) == Some ( '~' ) {
20232034 parser. advance ( 1 ) ;
20242035 parser. trim_whitespace ( ) ;
2036+ let start_pos = parser. offset ;
20252037 let variable = parser. read_identifier ( ) ?;
2038+
2039+ // Check for malformed tuple binding (missing parentheses)
2040+ parser. trim_whitespace ( ) ;
2041+ if parser
2042+ . source
2043+ . starts_with ( ',' )
2044+ {
2045+ return Err ( ParsingError :: MissingParenthesis (
2046+ start_pos,
2047+ ) ) ;
2048+ }
2049+
20262050 content. push ( Descriptive :: Binding (
20272051 Box :: new ( Descriptive :: Application ( invocation) ) ,
20282052 vec ! [ variable] ,
@@ -2049,7 +2073,19 @@ impl<'i> Parser<'i> {
20492073 } else if parser. peek_next_char ( ) == Some ( '~' ) {
20502074 parser. advance ( 1 ) ;
20512075 parser. trim_whitespace ( ) ;
2076+ let start_pos = parser. offset ;
20522077 let variable = parser. read_identifier ( ) ?;
2078+
2079+ parser. trim_whitespace ( ) ;
2080+ if parser
2081+ . source
2082+ . starts_with ( ',' )
2083+ {
2084+ return Err ( ParsingError :: MissingParenthesis (
2085+ start_pos,
2086+ ) ) ;
2087+ }
2088+
20532089 content. push ( Descriptive :: Binding (
20542090 Box :: new ( Descriptive :: Text ( text) ) ,
20552091 vec ! [ variable] ,
@@ -2705,6 +2741,12 @@ fn is_binding(content: &str) -> bool {
27052741 re. is_match ( content)
27062742}
27072743
2744+ fn malformed_binding_pattern ( content : & str ) -> bool {
2745+ // Detect ~ identifier, identifier (missing parentheses)
2746+ let re = regex ! ( r"~\s+[a-z][a-z0-9_]*\s*,\s*[a-z]" ) ;
2747+ re. is_match ( content)
2748+ }
2749+
27082750fn is_step_dependent ( content : & str ) -> bool {
27092751 let re = regex ! ( r"^\s*\d+\.\s+" ) ;
27102752 re. is_match ( content)
0 commit comments