diff --git a/snapshots/pattern_terminators.txt b/snapshots/pattern_terminators.txt new file mode 100644 index 0000000000..d9496d5ac0 --- /dev/null +++ b/snapshots/pattern_terminators.txt @@ -0,0 +1,577 @@ +@ ProgramNode (location: (1,0)-(21,13)) +├── flags: ∅ +├── locals: [:b, :x, :y] +└── statements: + @ StatementsNode (location: (1,0)-(21,13)) + ├── flags: ∅ + └── body: (length: 11) + ├── @ WhileNode (location: (1,0)-(1,22)) + │ ├── flags: newline + │ ├── keyword_loc: (1,0)-(1,5) = "while" + │ ├── do_keyword_loc: (1,14)-(1,16) = "do" + │ ├── closing_loc: (1,19)-(1,22) = "end" + │ ├── predicate: + │ │ @ MatchPredicateNode (location: (1,6)-(1,13)) + │ │ ├── flags: ∅ + │ │ ├── value: + │ │ │ @ CallNode (location: (1,6)-(1,7)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (1,6)-(1,7) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ ArrayPatternNode (location: (1,11)-(1,13)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── requireds: (length: 1) + │ │ │ │ └── @ LocalVariableTargetNode (location: (1,11)-(1,12)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── rest: + │ │ │ │ @ ImplicitRestNode (location: (1,12)-(1,13)) + │ │ │ │ └── flags: ∅ + │ │ │ ├── posts: (length: 0) + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (1,8)-(1,10) = "in" + │ └── statements: + │ @ StatementsNode (location: (1,17)-(1,18)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ IntegerNode (location: (1,17)-(1,18)) + │ ├── flags: newline, static_literal, decimal + │ └── value: 1 + ├── @ UntilNode (location: (3,0)-(3,22)) + │ ├── flags: newline + │ ├── keyword_loc: (3,0)-(3,5) = "until" + │ ├── do_keyword_loc: (3,14)-(3,16) = "do" + │ ├── closing_loc: (3,19)-(3,22) = "end" + │ ├── predicate: + │ │ @ MatchPredicateNode (location: (3,6)-(3,13)) + │ │ ├── flags: ∅ + │ │ ├── value: + │ │ │ @ CallNode (location: (3,6)-(3,7)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (3,6)-(3,7) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ ArrayPatternNode (location: (3,11)-(3,13)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── requireds: (length: 1) + │ │ │ │ └── @ LocalVariableTargetNode (location: (3,11)-(3,12)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── rest: + │ │ │ │ @ ImplicitRestNode (location: (3,12)-(3,13)) + │ │ │ │ └── flags: ∅ + │ │ │ ├── posts: (length: 0) + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (3,8)-(3,10) = "in" + │ └── statements: + │ @ StatementsNode (location: (3,17)-(3,18)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ IntegerNode (location: (3,17)-(3,18)) + │ ├── flags: newline, static_literal, decimal + │ └── value: 1 + ├── @ ForNode (location: (5,0)-(5,25)) + │ ├── flags: newline + │ ├── index: + │ │ @ LocalVariableTargetNode (location: (5,4)-(5,5)) + │ │ ├── flags: ∅ + │ │ ├── name: :x + │ │ └── depth: 0 + │ ├── collection: + │ │ @ MatchPredicateNode (location: (5,9)-(5,16)) + │ │ ├── flags: ∅ + │ │ ├── value: + │ │ │ @ CallNode (location: (5,9)-(5,10)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (5,9)-(5,10) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ ArrayPatternNode (location: (5,14)-(5,16)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── requireds: (length: 1) + │ │ │ │ └── @ LocalVariableTargetNode (location: (5,14)-(5,15)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 0 + │ │ │ ├── rest: + │ │ │ │ @ ImplicitRestNode (location: (5,15)-(5,16)) + │ │ │ │ └── flags: ∅ + │ │ │ ├── posts: (length: 0) + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (5,11)-(5,13) = "in" + │ ├── statements: + │ │ @ StatementsNode (location: (5,20)-(5,21)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ IntegerNode (location: (5,20)-(5,21)) + │ │ ├── flags: newline, static_literal, decimal + │ │ └── value: 1 + │ ├── for_keyword_loc: (5,0)-(5,3) = "for" + │ ├── in_keyword_loc: (5,6)-(5,8) = "in" + │ ├── do_keyword_loc: (5,17)-(5,19) = "do" + │ └── end_keyword_loc: (5,22)-(5,25) = "end" + ├── @ CallNode (location: (7,0)-(7,19)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :loop + │ ├── message_loc: (7,0)-(7,4) = "loop" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: + │ @ BlockNode (location: (7,5)-(7,19)) + │ ├── flags: ∅ + │ ├── locals: [] + │ ├── parameters: ∅ + │ ├── body: + │ │ @ StatementsNode (location: (7,8)-(7,15)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ MatchPredicateNode (location: (7,8)-(7,15)) + │ │ ├── flags: newline + │ │ ├── value: + │ │ │ @ CallNode (location: (7,8)-(7,9)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (7,8)-(7,9) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ ArrayPatternNode (location: (7,13)-(7,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── requireds: (length: 1) + │ │ │ │ └── @ LocalVariableTargetNode (location: (7,13)-(7,14)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :b + │ │ │ │ └── depth: 1 + │ │ │ ├── rest: + │ │ │ │ @ ImplicitRestNode (location: (7,14)-(7,15)) + │ │ │ │ └── flags: ∅ + │ │ │ ├── posts: (length: 0) + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (7,10)-(7,12) = "in" + │ ├── opening_loc: (7,5)-(7,7) = "do" + │ └── closing_loc: (7,16)-(7,19) = "end" + ├── @ InterpolatedStringNode (location: (9,0)-(9,12)) + │ ├── flags: newline + │ ├── opening_loc: (9,0)-(9,1) = "\"" + │ ├── parts: (length: 1) + │ │ └── @ EmbeddedStatementsNode (location: (9,1)-(9,11)) + │ │ ├── flags: ∅ + │ │ ├── opening_loc: (9,1)-(9,3) = "\#{" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (9,3)-(9,10)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ MatchPredicateNode (location: (9,3)-(9,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── value: + │ │ │ │ @ CallNode (location: (9,3)-(9,4)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :a + │ │ │ │ ├── message_loc: (9,3)-(9,4) = "a" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── pattern: + │ │ │ │ @ ArrayPatternNode (location: (9,8)-(9,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── constant: ∅ + │ │ │ │ ├── requireds: (length: 1) + │ │ │ │ │ └── @ LocalVariableTargetNode (location: (9,8)-(9,9)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :b + │ │ │ │ │ └── depth: 0 + │ │ │ │ ├── rest: + │ │ │ │ │ @ ImplicitRestNode (location: (9,9)-(9,10)) + │ │ │ │ │ └── flags: ∅ + │ │ │ │ ├── posts: (length: 0) + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ └── closing_loc: ∅ + │ │ │ └── operator_loc: (9,5)-(9,7) = "in" + │ │ └── closing_loc: (9,10)-(9,11) = "}" + │ └── closing_loc: (9,11)-(9,12) = "\"" + ├── @ WhileNode (location: (11,0)-(11,22)) + │ ├── flags: newline + │ ├── keyword_loc: (11,0)-(11,5) = "while" + │ ├── do_keyword_loc: (11,14)-(11,16) = "do" + │ ├── closing_loc: (11,19)-(11,22) = "end" + │ ├── predicate: + │ │ @ MatchPredicateNode (location: (11,6)-(11,13)) + │ │ ├── flags: ∅ + │ │ ├── value: + │ │ │ @ CallNode (location: (11,6)-(11,7)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (11,6)-(11,7) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ HashPatternNode (location: (11,11)-(11,13)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── elements: (length: 1) + │ │ │ │ └── @ AssocNode (location: (11,11)-(11,13)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── key: + │ │ │ │ │ @ SymbolNode (location: (11,11)-(11,13)) + │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ ├── value_loc: (11,11)-(11,12) = "x" + │ │ │ │ │ ├── closing_loc: (11,12)-(11,13) = ":" + │ │ │ │ │ └── unescaped: "x" + │ │ │ │ ├── value: + │ │ │ │ │ @ ImplicitNode (location: (11,11)-(11,12)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ └── value: + │ │ │ │ │ @ LocalVariableTargetNode (location: (11,11)-(11,12)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :x + │ │ │ │ │ └── depth: 0 + │ │ │ │ └── operator_loc: ∅ + │ │ │ ├── rest: ∅ + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (11,8)-(11,10) = "in" + │ └── statements: + │ @ StatementsNode (location: (11,17)-(11,18)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ IntegerNode (location: (11,17)-(11,18)) + │ ├── flags: newline, static_literal, decimal + │ └── value: 1 + ├── @ WhileNode (location: (13,0)-(13,25)) + │ ├── flags: newline + │ ├── keyword_loc: (13,0)-(13,5) = "while" + │ ├── do_keyword_loc: (13,17)-(13,19) = "do" + │ ├── closing_loc: (13,22)-(13,25) = "end" + │ ├── predicate: + │ │ @ MatchPredicateNode (location: (13,6)-(13,15)) + │ │ ├── flags: ∅ + │ │ ├── value: + │ │ │ @ CallNode (location: (13,6)-(13,7)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (13,6)-(13,7) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ HashPatternNode (location: (13,11)-(13,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── elements: (length: 1) + │ │ │ │ └── @ AssocNode (location: (13,11)-(13,15)) + │ │ │ │ ├── flags: static_literal + │ │ │ │ ├── key: + │ │ │ │ │ @ SymbolNode (location: (13,11)-(13,13)) + │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ ├── value_loc: (13,11)-(13,12) = "x" + │ │ │ │ │ ├── closing_loc: (13,12)-(13,13) = ":" + │ │ │ │ │ └── unescaped: "x" + │ │ │ │ ├── value: + │ │ │ │ │ @ IntegerNode (location: (13,14)-(13,15)) + │ │ │ │ │ ├── flags: static_literal, decimal + │ │ │ │ │ └── value: 1 + │ │ │ │ └── operator_loc: ∅ + │ │ │ ├── rest: ∅ + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (13,8)-(13,10) = "in" + │ └── statements: + │ @ StatementsNode (location: (13,20)-(13,21)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ IntegerNode (location: (13,20)-(13,21)) + │ ├── flags: newline, static_literal, decimal + │ └── value: 1 + ├── @ WhileNode (location: (15,0)-(15,26)) + │ ├── flags: newline + │ ├── keyword_loc: (15,0)-(15,5) = "while" + │ ├── do_keyword_loc: (15,18)-(15,20) = "do" + │ ├── closing_loc: (15,23)-(15,26) = "end" + │ ├── predicate: + │ │ @ MatchPredicateNode (location: (15,6)-(15,17)) + │ │ ├── flags: ∅ + │ │ ├── value: + │ │ │ @ CallNode (location: (15,6)-(15,7)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (15,6)-(15,7) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ HashPatternNode (location: (15,11)-(15,17)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── elements: (length: 2) + │ │ │ │ ├── @ AssocNode (location: (15,11)-(15,13)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (15,11)-(15,13)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── value_loc: (15,11)-(15,12) = "x" + │ │ │ │ │ │ ├── closing_loc: (15,12)-(15,13) = ":" + │ │ │ │ │ │ └── unescaped: "x" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (15,11)-(15,12)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (15,11)-(15,12)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :x + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ └── @ AssocNode (location: (15,15)-(15,17)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── key: + │ │ │ │ │ @ SymbolNode (location: (15,15)-(15,17)) + │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ ├── value_loc: (15,15)-(15,16) = "y" + │ │ │ │ │ ├── closing_loc: (15,16)-(15,17) = ":" + │ │ │ │ │ └── unescaped: "y" + │ │ │ │ ├── value: + │ │ │ │ │ @ ImplicitNode (location: (15,15)-(15,16)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ └── value: + │ │ │ │ │ @ LocalVariableTargetNode (location: (15,15)-(15,16)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :y + │ │ │ │ │ └── depth: 0 + │ │ │ │ └── operator_loc: ∅ + │ │ │ ├── rest: ∅ + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (15,8)-(15,10) = "in" + │ └── statements: + │ @ StatementsNode (location: (15,21)-(15,22)) + │ ├── flags: ∅ + │ └── body: (length: 1) + │ └── @ IntegerNode (location: (15,21)-(15,22)) + │ ├── flags: newline, static_literal, decimal + │ └── value: 1 + ├── @ CallNode (location: (17,0)-(17,19)) + │ ├── flags: newline, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :loop + │ ├── message_loc: (17,0)-(17,4) = "loop" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ ├── equal_loc: ∅ + │ └── block: + │ @ BlockNode (location: (17,5)-(17,19)) + │ ├── flags: ∅ + │ ├── locals: [] + │ ├── parameters: ∅ + │ ├── body: + │ │ @ StatementsNode (location: (17,8)-(17,15)) + │ │ ├── flags: ∅ + │ │ └── body: (length: 1) + │ │ └── @ MatchPredicateNode (location: (17,8)-(17,15)) + │ │ ├── flags: newline + │ │ ├── value: + │ │ │ @ CallNode (location: (17,8)-(17,9)) + │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ ├── receiver: ∅ + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :a + │ │ │ ├── message_loc: (17,8)-(17,9) = "a" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ ├── equal_loc: ∅ + │ │ │ └── block: ∅ + │ │ ├── pattern: + │ │ │ @ HashPatternNode (location: (17,13)-(17,15)) + │ │ │ ├── flags: ∅ + │ │ │ ├── constant: ∅ + │ │ │ ├── elements: (length: 1) + │ │ │ │ └── @ AssocNode (location: (17,13)-(17,15)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── key: + │ │ │ │ │ @ SymbolNode (location: (17,13)-(17,15)) + │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ ├── value_loc: (17,13)-(17,14) = "x" + │ │ │ │ │ ├── closing_loc: (17,14)-(17,15) = ":" + │ │ │ │ │ └── unescaped: "x" + │ │ │ │ ├── value: + │ │ │ │ │ @ ImplicitNode (location: (17,13)-(17,14)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ └── value: + │ │ │ │ │ @ LocalVariableTargetNode (location: (17,13)-(17,14)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── name: :x + │ │ │ │ │ └── depth: 1 + │ │ │ │ └── operator_loc: ∅ + │ │ │ ├── rest: ∅ + │ │ │ ├── opening_loc: ∅ + │ │ │ └── closing_loc: ∅ + │ │ └── operator_loc: (17,10)-(17,12) = "in" + │ ├── opening_loc: (17,5)-(17,7) = "do" + │ └── closing_loc: (17,16)-(17,19) = "end" + ├── @ InterpolatedStringNode (location: (19,0)-(19,12)) + │ ├── flags: newline + │ ├── opening_loc: (19,0)-(19,1) = "\"" + │ ├── parts: (length: 1) + │ │ └── @ EmbeddedStatementsNode (location: (19,1)-(19,11)) + │ │ ├── flags: ∅ + │ │ ├── opening_loc: (19,1)-(19,3) = "\#{" + │ │ ├── statements: + │ │ │ @ StatementsNode (location: (19,3)-(19,10)) + │ │ │ ├── flags: ∅ + │ │ │ └── body: (length: 1) + │ │ │ └── @ MatchPredicateNode (location: (19,3)-(19,10)) + │ │ │ ├── flags: ∅ + │ │ │ ├── value: + │ │ │ │ @ CallNode (location: (19,3)-(19,4)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :a + │ │ │ │ ├── message_loc: (19,3)-(19,4) = "a" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ ├── equal_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── pattern: + │ │ │ │ @ HashPatternNode (location: (19,8)-(19,10)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── constant: ∅ + │ │ │ │ ├── elements: (length: 1) + │ │ │ │ │ └── @ AssocNode (location: (19,8)-(19,10)) + │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── key: + │ │ │ │ │ │ @ SymbolNode (location: (19,8)-(19,10)) + │ │ │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ │ │ ├── value_loc: (19,8)-(19,9) = "x" + │ │ │ │ │ │ ├── closing_loc: (19,9)-(19,10) = ":" + │ │ │ │ │ │ └── unescaped: "x" + │ │ │ │ │ ├── value: + │ │ │ │ │ │ @ ImplicitNode (location: (19,8)-(19,9)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ └── value: + │ │ │ │ │ │ @ LocalVariableTargetNode (location: (19,8)-(19,9)) + │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ ├── name: :x + │ │ │ │ │ │ └── depth: 0 + │ │ │ │ │ └── operator_loc: ∅ + │ │ │ │ ├── rest: ∅ + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ └── closing_loc: ∅ + │ │ │ └── operator_loc: (19,5)-(19,7) = "in" + │ │ └── closing_loc: (19,10)-(19,11) = "}" + │ └── closing_loc: (19,11)-(19,12) = "\"" + └── @ AndNode (location: (21,0)-(21,13)) + ├── flags: newline + ├── left: + │ @ MatchPredicateNode (location: (21,0)-(21,7)) + │ ├── flags: ∅ + │ ├── value: + │ │ @ CallNode (location: (21,0)-(21,1)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :a + │ │ ├── message_loc: (21,0)-(21,1) = "a" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ ├── equal_loc: ∅ + │ │ └── block: ∅ + │ ├── pattern: + │ │ @ HashPatternNode (location: (21,5)-(21,7)) + │ │ ├── flags: ∅ + │ │ ├── constant: ∅ + │ │ ├── elements: (length: 1) + │ │ │ └── @ AssocNode (location: (21,5)-(21,7)) + │ │ │ ├── flags: ∅ + │ │ │ ├── key: + │ │ │ │ @ SymbolNode (location: (21,5)-(21,7)) + │ │ │ │ ├── flags: static_literal, forced_us_ascii_encoding + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── value_loc: (21,5)-(21,6) = "x" + │ │ │ │ ├── closing_loc: (21,6)-(21,7) = ":" + │ │ │ │ └── unescaped: "x" + │ │ │ ├── value: + │ │ │ │ @ ImplicitNode (location: (21,5)-(21,6)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── value: + │ │ │ │ @ LocalVariableTargetNode (location: (21,5)-(21,6)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── name: :x + │ │ │ │ └── depth: 0 + │ │ │ └── operator_loc: ∅ + │ │ ├── rest: ∅ + │ │ ├── opening_loc: ∅ + │ │ └── closing_loc: ∅ + │ └── operator_loc: (21,2)-(21,4) = "in" + ├── right: + │ @ IntegerNode (location: (21,12)-(21,13)) + │ ├── flags: static_literal, decimal + │ └── value: 1 + └── operator_loc: (21,8)-(21,11) = "and" diff --git a/src/prism.c b/src/prism.c index 19af4f4a25..311291aff5 100644 --- a/src/prism.c +++ b/src/prism.c @@ -12710,14 +12710,6 @@ match6(const pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, return match1(parser, type1) || match1(parser, type2) || match1(parser, type3) || match1(parser, type4) || match1(parser, type5) || match1(parser, type6); } -/** - * Returns true if the current token is any of the seven given types. - */ -static PRISM_INLINE bool -match7(const pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_token_type_t type3, pm_token_type_t type4, pm_token_type_t type5, pm_token_type_t type6, pm_token_type_t type7) { - return match1(parser, type1) || match1(parser, type2) || match1(parser, type3) || match1(parser, type4) || match1(parser, type5) || match1(parser, type6) || match1(parser, type7); -} - /** * Returns true if the current token is any of the eight given types. */ @@ -12930,6 +12922,31 @@ token_begins_expression_p(pm_token_type_t type) { } } +/** + * Returns true if the given token can begin a pattern element. This is the + * set of tokens that can begin an expression plus the tokens that begin + * pattern-only constructs — `*` (rest patterns), `**` (keyword rest + * patterns), and `^` (pin patterns) — which are binary operator tokens in + * expression contexts and therefore excluded from token_begins_expression_p. + * + * When a token fails this predicate at a decision point, the pattern ends + * there and the token is left for the enclosing context to accept or reject. + * This mirrors the grammar, whose pattern reductions (`p_top_expr_body: + * p_expr ','`, `p_kw: p_kw_label`, `p_kwargs: p_kwarg ','`) fire by default + * on any token that cannot start a pattern. Tokens that pass this predicate + * but are invalid in the specific context (e.g. `**` in an array pattern) + * are rejected by the pattern parser itself with a more targeted diagnostic. + */ +static PRISM_INLINE bool +token_begins_pattern_p(pm_token_type_t type) { + return ( + token_begins_expression_p(type) || + type == PM_TOKEN_USTAR || + type == PM_TOKEN_USTAR_STAR || + type == PM_TOKEN_CARET + ); +} + /** * Parse an expression with the given binding power that may be optionally * prefixed by the * operator. @@ -17097,7 +17114,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node pm_node_t *value; - if (match8(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) { + /* + * The label has an implicit value when the next token cannot + * begin a pattern, mirroring the grammar's `p_kw: p_kw_label` + * reduction. + */ + if (!token_begins_pattern_p(parser->current.type)) { if (PM_NODE_TYPE_P(first_node, PM_SYMBOL_NODE)) { value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) first_node); } else { @@ -17132,8 +17154,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node // If there are any other assocs, then we'll parse them now. while (accept1(parser, PM_TOKEN_COMMA)) { - // Here we need to break to support trailing commas. - if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) { + /* + * A trailing comma ends the pattern when the next token cannot begin + * another element, mirroring the grammar's `p_kwargs: p_kwarg ','` + * reduction. + */ + if (!token_begins_pattern_p(parser->current.type)) { // Trailing commas are not allowed to follow a rest pattern. if (rest != NULL) { pm_parser_err_token(parser, &parser->current, PM_ERR_PATTERN_EXPRESSION_AFTER_REST); @@ -17174,7 +17200,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node parse_pattern_hash_key(parser, &keys, key); pm_node_t *value = NULL; - if (match8(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) { + /* + * The label has an implicit value when the next token cannot + * begin a pattern, mirroring the grammar's `p_kw: p_kw_label` + * reduction. + */ + if (!token_begins_pattern_p(parser->current.type)) { if (PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)) { value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) key); } else { @@ -17674,15 +17705,12 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag // Gather up all of the patterns into the list. while (accept1(parser, PM_TOKEN_COMMA)) { - // Break early here in case we have a trailing comma. The newline and - // EOF terminators cover a one-line match (`x => a,`) or a `case`/`in` - // clause (`in a,\n ...`); a newline is only lexed as a token here - // when `pattern_matching_newlines` is set, so this does not affect - // patterns nested in brackets or parentheses. - if ( - match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR) || - match2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_EOF) - ) { + /* + * A trailing comma ends the pattern when the next token cannot + * begin another pattern element, leaving the token for the + * enclosing context to accept or reject. + */ + if (!token_begins_pattern_p(parser->current.type)) { // A trailing comma forms an implicit rest pattern (`[a,]` is // `[a, *]`). If a rest pattern has already been parsed, then // this is a second rest, which is not allowed (e.g. `[a, *b,]` diff --git a/test/prism/errors/dynamic_label_pattern.txt b/test/prism/errors/dynamic_label_pattern.txt index b8d1012e45..2e0fb05879 100644 --- a/test/prism/errors/dynamic_label_pattern.txt +++ b/test/prism/errors/dynamic_label_pattern.txt @@ -1,3 +1,4 @@ :a => 'a': | 1 - ^ expected a pattern expression after the key + ^ unexpected '|', expecting end-of-input + ^ unexpected '|', ignoring it diff --git a/test/prism/fixtures/pattern_terminators.txt b/test/prism/fixtures/pattern_terminators.txt new file mode 100644 index 0000000000..6224622208 --- /dev/null +++ b/test/prism/fixtures/pattern_terminators.txt @@ -0,0 +1,21 @@ +while a in b, do 1 end + +until a in b, do 1 end + +for x in a in b, do 1 end + +loop do a in b, end + +"#{a in b,}" + +while a in x: do 1 end + +while a in x: 1, do 1 end + +while a in x:, y: do 1 end + +loop do a in x: end + +"#{a in x:}" + +a in x: and 1