Created
March 8, 2026 17:32
-
-
Save kddnewton/5cefcd5ab1e5d361ddc9f8a791a71305 to your computer and use it in GitHub Desktop.
Parser compat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # Exhaustive cross-parser test: verifies prism and parse.y agree on syntax validity. | |
| # This file is auto-consolidated from multiple test rounds. | |
| cases = [] | |
| # 1a. fcall command_args: foo bar | |
| cases += [ | |
| ['foo bar in a', :err], # in requires arg | |
| ['foo bar and 1', :ok], # and works on expr | |
| ['foo bar if true', :ok], # modifier works on stmt | |
| ['foo bar + 1', :ok], # + binds inside command args | |
| ] | |
| # 1b. primary.call_op command_args: obj.foo bar | |
| cases += [ | |
| ['obj.foo bar in a', :err], | |
| ['obj.foo bar and 1', :ok], | |
| ['obj.foo bar if true', :ok], | |
| ] | |
| # 1c. primary::op command_args: Foo::bar baz | |
| cases += [ | |
| ['Foo::bar baz in a', :err], | |
| ['Foo::bar baz and 1', :ok], | |
| ] | |
| # 1d. super command_args | |
| cases += [ | |
| ['super bar in a', :err], | |
| ['super bar and 1', :ok], | |
| ['super bar if true', :ok], | |
| ['super(bar) in a', :ok], # with parens = primary, not command | |
| ] | |
| # 1e. yield command_args (inside method) | |
| cases += [ | |
| ['def f; yield bar in a; end', :err], | |
| ['def f; yield bar and 1; end', :ok], | |
| ['def f; yield bar if true; end', :ok], | |
| ['def f; yield(bar) in a; end', :ok], # with parens = primary | |
| ] | |
| # 1f. return call_args | |
| cases += [ | |
| ['return 1 and 2', :err], # void value expression | |
| ['return 1 or 2', :err], | |
| ['return 1 in [1]', :err], | |
| ['return 1 if true', :ok], | |
| ['return 1 + 2', :ok], # + binds inside call_args | |
| ['return 1 rescue nil', :ok], | |
| ['return foo bar and 1', :err], | |
| ['return foo bar if true', :ok], | |
| ] | |
| # 1g. break call_args | |
| cases += [ | |
| ['loop { break 1 and 2 }', :err], | |
| ['loop { break 1 or 2 }', :err], | |
| ['loop { break 1 if true }', :ok], | |
| ['loop { break 1 + 2 }', :ok], | |
| ['loop { break foo bar and 1 }', :err], | |
| ] | |
| # 1h. next call_args | |
| cases += [ | |
| ['loop { next 1 and 2 }', :err], | |
| ['loop { next 1 or 2 }', :err], | |
| ['loop { next 1 if true }', :ok], | |
| ['loop { next foo bar and 1 }', :err], | |
| ] | |
| # 1i. ! command_call | |
| cases += [ | |
| ['!foo bar in a', :err, '/tmp/_test_bang_in.rb'], | |
| ['!foo bar and 1', :ok, '/tmp/_test_bang_and.rb'], | |
| ['!foo bar if true', :ok, '/tmp/_test_bang_if.rb'], | |
| ['!foo in a', :ok, '/tmp/_test_bangfoo_in.rb'], # !foo is arg, not command | |
| ] | |
| # 1j. not command_call | |
| cases += [ | |
| ['not foo bar in a', :err], | |
| ['not foo bar and 1', :ok], | |
| ['not foo bar if true', :ok], | |
| ['not 1 in a', :ok], # not(1 in a), 1 is arg | |
| ['not not foo bar in a', :err], | |
| ] | |
| # 1k. block_command: foo bar do...end | |
| cases += [ | |
| ['foo bar do end in a', :err], | |
| ['foo bar do end and 1', :ok], | |
| ['foo bar do end if true', :ok], | |
| ['foo bar do end.d', :ok], # block_call chaining | |
| ['foo bar do end::d', :ok], # block_call chaining | |
| ['foo bar do end.d e', :ok], # block_command chaining | |
| ['foo bar do end + 1', :err], # arg-level blocked | |
| ] | |
| # 1l. foo bar { } (brace block on command) | |
| cases += [ | |
| ['foo bar { } in a', :err], | |
| ['foo bar { } and 1', :ok], | |
| ] | |
| # 1m. return/break/next without args | |
| cases += [ | |
| ['return and 1', :err], | |
| ['return or 1', :err], | |
| ['return if true', :ok], | |
| ['loop { break and 1 }', :err], | |
| ['loop { next or 1 }', :err], | |
| ] | |
| # 2a. alias | |
| cases += [ | |
| ['alias a b and 1', :err], | |
| ['alias a b if true', :ok], | |
| ] | |
| # 2b. undef | |
| cases += [ | |
| ['undef a and 1', :err], | |
| ['undef a if true', :ok], | |
| ] | |
| # 2c. END {} | |
| cases += [ | |
| ['END {} and 1', :err], | |
| ['END {} if true', :ok], | |
| ] | |
| # 2d. BEGIN {} | |
| cases += [ | |
| ['BEGIN {} and 1', :err], | |
| ['BEGIN {} if true', :err], | |
| ] | |
| # 2e. command_asgn: lhs = command_call | |
| cases += [ | |
| ['a = foo bar and 1', :err], | |
| ['a = foo bar or 1', :err], | |
| ['a = foo bar in a', :err], | |
| ['a = foo bar if true', :ok], | |
| ['a = foo bar + 1', :ok], # + inside command args | |
| ['a = foo bar rescue nil', :ok], | |
| ] | |
| # 2f. op_asgn with command RHS | |
| cases += [ | |
| ['a += foo bar and 1', :err], | |
| ['a ||= foo bar and 1', :err], | |
| ['a &&= foo bar or 1', :err], | |
| ] | |
| # 2g. Various LHS types with command assignment | |
| cases += [ | |
| ['@a = foo bar and 1', :err], | |
| ['$a = foo bar and 1', :err], | |
| ['A = foo bar and 1', :err], | |
| ['A::B = foo bar and 1', :err], | |
| ['a.b = foo bar and 1', :err], | |
| ['a[0] = foo bar and 1', :err], | |
| ] | |
| # 2h. Assignment with super/yield command RHS | |
| cases += [ | |
| ['a = super bar and 1', :err], | |
| ['a = super bar in a', :err], | |
| ['def f; a = yield bar and 1; end', :err], | |
| ['def f; a = yield bar in a; end', :err], | |
| ] | |
| # 2i. Chained assignment with command RHS | |
| cases += [ | |
| ['a = b = foo bar and 1', :err], | |
| ] | |
| # 2j. Endless method with command body (command_asgn) | |
| cases += [ | |
| ['def f = foo bar and 1', :err], | |
| ['def f = foo bar or 1', :err], | |
| ['def f = foo bar in a', :err], | |
| ['def f = foo bar if true', :ok], | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = 1 and 2', :ok], # non-command body = expr, not stmt | |
| ['def f = 1 or 2', :ok], | |
| ['def self.f = foo bar and 1', :err], | |
| ['def obj.f = foo bar or 1', :err], | |
| ['def f = super bar and 1', :err], | |
| ['def f = super bar in a', :err], | |
| ] | |
| # 2k. Multi-write | |
| cases += [ | |
| ['a, b = 1, 2 and 3', :err], | |
| ['a, b = 1, 2 if true', :ok], | |
| ] | |
| # 2l. Implicit array writes | |
| cases += [ | |
| ['a = 1, 2 and 3', :err], | |
| ['a = 1, 2 if true', :ok], | |
| ] | |
| # ============================================================ | |
| # 3. EXPR productions that ARE also ARG (should accept all operators) | |
| # ============================================================ | |
| cases += [ | |
| ['1 in a', :ok], | |
| ['1 + 2 in a', :ok], | |
| ['foo() in a', :ok], | |
| ['foo in a', :ok], # foo without args = primary = arg | |
| ['1 and 2', :ok], | |
| ['foo and bar', :ok], | |
| ] | |
| # ============================================================ | |
| # 4. MODIFIER CHAINING | |
| # ============================================================ | |
| cases += [ | |
| ['return 1 if true if false', :ok], | |
| ['alias a b if true and 1', :ok], # (alias a b if true) and 1 - modifier makes it stmt, then and connects stmts | |
| ['a = foo bar if true and 1', :ok], # same | |
| ] | |
| # ============================================================ | |
| # 5. MISC EDGE CASES | |
| # ============================================================ | |
| cases += [ | |
| ['private def f = foo bar and 1', :ok], # private wraps the def; and applies to private | |
| ['a = return 1', :err], # void value | |
| ['true ? foo bar : baz', :err], # ternary expects arg, not command | |
| ['foo bar, baz and 1', :ok], # multi-arg command | |
| ['a = def f = foo bar', :ok], # assignment of endless def (non-command, no and/or) | |
| ] | |
| # ============================================================ | |
| # A. TERNARY OPERATOR with various productions | |
| # ============================================================ | |
| cases += [ | |
| ['true ? foo bar : baz', :err], # ternary expects arg, command not allowed | |
| ['true ? foo(bar) : baz', :ok], | |
| ['true ? 1 : foo bar', :err], # command in false branch | |
| ['true ? 1 : foo(bar)', :ok], | |
| ['true ? foo bar : foo baz', :err], | |
| ] | |
| # ============================================================ | |
| # B. RANGE OPERATORS with commands | |
| # ============================================================ | |
| cases += [ | |
| ['1..foo bar', :err], # range RHS can't be command | |
| ['foo bar..1', :ok], # parsed as foo(bar..1) | |
| ['1..2', :ok], | |
| ['1...foo bar', :err], | |
| ['(foo bar)..1', :ok], # parens make it primary | |
| ] | |
| # ============================================================ | |
| # C. SPLAT/DOUBLE-SPLAT with commands | |
| # ============================================================ | |
| cases += [ | |
| ['foo *bar baz', :err], # splat of command | |
| ['foo **bar baz', :err], # double-splat of command | |
| ['[*foo bar]', :err], # splat in array | |
| ] | |
| # ============================================================ | |
| # D. CONDITIONAL ASSIGNMENT with commands | |
| # ============================================================ | |
| cases += [ | |
| ['a ||= foo bar and 1', :err], | |
| ['a &&= foo bar and 1', :err], | |
| ['a ||= foo bar if true', :ok], | |
| ['a &&= foo bar if true', :ok], | |
| ['a ||= foo bar rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # E. NESTED ENDLESS DEF | |
| # ============================================================ | |
| cases += [ | |
| ['def f = def g = foo bar', :err], # nested command_asgn def can't be def body | |
| ['def f = def g = foo bar and 1', :err], # inner is command_asgn | |
| ['def f = 1; end', :err], # endless def can't have semicolon+end | |
| ['def f = def g = 1', :ok], # inner is arg-level, fine | |
| ] | |
| # ============================================================ | |
| # F. LAMBDA with commands | |
| # ============================================================ | |
| cases += [ | |
| ['-> { foo bar and 1 }', :ok], # inside block, it's a statement | |
| ['-> { foo bar } and 1', :ok], # lambda is primary, and works | |
| ['-> { foo bar } + 1', :ok], # lambda is primary | |
| ] | |
| # ============================================================ | |
| # G. PATTERN MATCHING operators | |
| # ============================================================ | |
| cases += [ | |
| ['1 in a', :ok], | |
| ['1 => a', :ok], | |
| ['foo bar in a', :err], # command can't be followed by in | |
| ['foo bar => a', :ok], # parsed as foo(bar => a) hash arg | |
| ['foo(bar) in a', :ok], # with parens = primary | |
| ['foo(bar) => a', :ok], | |
| ] | |
| # ============================================================ | |
| # H. DEFINED? with commands | |
| # ============================================================ | |
| cases += [ | |
| ['defined? foo bar', :err], # defined? doesn't take command args | |
| ['defined? foo bar and 1', :err], | |
| ['defined?(foo) and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # I. NOT/! edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['!foo(bar) in a', :ok], # !foo(bar) is arg, in works | |
| ['not foo(bar) in a', :ok], # not(foo(bar)) is arg | |
| ['!!foo bar', :err], # !! on command | |
| ['not not 1', :ok], | |
| ['not not foo bar', :ok], # not(not(foo bar)) - command is arg of inner not | |
| ] | |
| # ============================================================ | |
| # J. ASSIGNMENT targets with command RHS | |
| # ============================================================ | |
| cases += [ | |
| ['a, b = foo bar', :ok], # multi-assign with command RHS | |
| ['a, b = foo bar and 1', :err], # multi-assign command, then and | |
| ['*a = foo bar', :ok], # splat LHS | |
| ['a.b, c = foo bar', :ok], | |
| ['a[0] = foo bar and 1', :err], | |
| ] | |
| # ============================================================ | |
| # K. BLOCK COMMANDS - more edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end.baz quux', :ok], # block_command chaining | |
| ['foo bar do end.baz quux and 1', :ok], # chained block_command then and | |
| ['foo bar do end.baz', :ok], # block_call | |
| ['foo bar do end::baz', :ok], # block_call with :: | |
| ['foo bar do end + 1', :err], # block_command can't use + | |
| ['foo bar do end * 1', :err], # block_command can't use * | |
| ['foo bar do end == 1', :err], # block_command can't use == | |
| ['foo bar do end[0]', :err], # block_command can't use [] | |
| ['foo bar do end in a', :err], # block_command can't use in | |
| ['foo bar do end => a', :err], # block_command can't use => | |
| ] | |
| # ============================================================ | |
| # L. BRACE BLOCK on commands | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar { } + 1', :ok], # brace block attaches to bar; + is in arg | |
| ['foo bar { } and 1', :ok], # brace block command, then and | |
| ['foo bar { } if true', :ok], # modifier | |
| ['foo bar { } in a', :err], # can't use in | |
| ['foo bar { } => a', :ok], # parsed as foo(bar({}) => a) hash arg | |
| ] | |
| # ============================================================ | |
| # M. SUPER/YIELD edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['super', :ok], # zsuper (no args) | |
| ['super and 1', :ok], # zsuper is primary, and works | |
| ['super + 1', :ok], # zsuper is primary, + works | |
| ['super in a', :ok], # zsuper is primary | |
| ['super(bar) and 1', :ok], # with parens = primary | |
| ['super(bar) + 1', :ok], | |
| ['super bar and 1', :ok], # command super, and works (expr level) | |
| ['super bar + 1', :ok], # + inside command args | |
| ['super bar in a', :err], # command super can't be followed by in | |
| ['super bar => a', :ok], # parsed as super(bar => a) hash arg | |
| ['def f; yield; end', :ok], | |
| ['def f; yield and 1; end', :ok], # yield (no args) is primary | |
| ['def f; yield + 1; end', :ok], # yield is primary (unary +?) | |
| ['def f; yield(bar) and 1; end', :ok], | |
| ['def f; yield bar and 1; end', :ok], # yield with args, and works (yield is value expr) | |
| ['def f; yield bar in a; end', :err], # command yield can't be followed by in | |
| ['def f; yield bar => a; end', :ok], # parsed as yield(bar => a) hash arg | |
| ] | |
| # ============================================================ | |
| # N. RESCUE MODIFIER edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar rescue nil', :ok], | |
| ['foo bar rescue nil and 1', :ok], # (foo bar rescue nil) and 1 | |
| ['foo bar rescue nil in a', :ok], # rescue value includes `nil in a` | |
| ['a = foo bar rescue nil', :ok], | |
| ['a = foo bar rescue nil and 1', :ok], | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = foo bar rescue nil and 1', :err], # endless def command body rescue, then and | |
| ] | |
| # ============================================================ | |
| # O. POSTFIX IF/UNLESS/WHILE/UNTIL chaining | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar if true if false', :ok], # double modifier | |
| ['foo bar unless true', :ok], | |
| ['foo bar while true', :ok], | |
| ['foo bar until true', :ok], | |
| ['return 1 unless true', :ok], | |
| ['return 1 while true', :ok], | |
| ] | |
| # ============================================================ | |
| # P. COMPARISON and EQUALITY with commands | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar == 1', :ok], # == inside command args | |
| ['foo bar != 1', :ok], | |
| ['foo bar <=> 1', :ok], | |
| ['foo bar < 1', :ok], | |
| ['foo bar > 1', :ok], | |
| ] | |
| # ============================================================ | |
| # Q. ARRAY/HASH LITERALS with commands | |
| # ============================================================ | |
| cases += [ | |
| ['[foo bar]', :err], # command inside array not allowed | |
| ['[foo bar, 1]', :err], | |
| ['{a: foo bar}', :err], # command in hash value not allowed | |
| ] | |
| # ============================================================ | |
| # R. STRING INTERPOLATION with commands | |
| # ============================================================ | |
| cases += [ | |
| ['"#{foo bar}"', :ok], # command inside interpolation | |
| ['"#{foo bar and 1}"', :ok], | |
| ] | |
| # ============================================================ | |
| # S. MULTIPLE ASSIGNMENT (implicit array) edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['a = 1, 2', :ok], # implicit array | |
| ['a = 1, 2 and 3', :err], # implicit array then and | |
| ['a = 1, 2 if true', :ok], # implicit array with modifier | |
| ['a = 1, foo bar', :err], # command in implicit array not allowed | |
| ['a = 1, foo bar and 1', :err], # implicit array then and | |
| ] | |
| # ============================================================ | |
| # T. ENDLESS DEF with various body types | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1', :ok], | |
| ['def f = 1 + 2', :ok], | |
| ['def f = 1 and 2', :ok], # non-command body, and is fine | |
| ['def f = 1 or 2', :ok], | |
| ['def f = 1 in [1]', :ok], # pattern match in def body | |
| ['def f = foo bar', :ok], # command body | |
| ['def f = foo bar + 1', :ok], # + inside command args | |
| ['def f = foo bar if true', :ok], # modifier on endless def | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = foo bar and 1', :err], # command body then and | |
| ['def f = foo bar or 1', :err], | |
| ['def f = foo bar in a', :err], # command body then in | |
| ['def f = super bar and 1', :err], | |
| ['def f = super bar in a', :err], | |
| ['def self.f = foo bar and 1', :err], | |
| ['def self.f = foo bar or 1', :err], | |
| ] | |
| # ============================================================ | |
| # U. PIPE OPERATORS | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar | 1', :ok], # | inside command args | |
| ['foo bar & 1', :ok], # & inside command args | |
| ['foo bar ^ 1', :ok], # ^ inside command args | |
| ] | |
| # ============================================================ | |
| # V. ASSIGNMENT OPERATORS with various LHS | |
| # ============================================================ | |
| cases += [ | |
| ['A::B += foo bar and 1', :err], | |
| ['a.b += foo bar and 1', :err], | |
| ['a.b ||= foo bar or 1', :err], | |
| ['a[0] += foo bar and 1', :err], | |
| ['A::B = foo bar if true', :ok], | |
| ['a.b = foo bar if true', :ok], | |
| ] | |
| # ============================================================ | |
| # W. PROC/BLOCK PASS with commands | |
| # ============================================================ | |
| cases += [ | |
| ['foo &bar baz', :err], # block pass of command? | |
| ['foo(&bar)', :ok], | |
| ] | |
| # ============================================================ | |
| # X. CONDITIONAL (ternary) in assignment | |
| # ============================================================ | |
| cases += [ | |
| ['a = true ? 1 : 2', :ok], | |
| ['a = true ? foo bar : 1', :err], # command in ternary | |
| ['a = true ? 1 : foo bar', :err], # command in ternary false | |
| ] | |
| # ============================================================ | |
| # Y. FLIP-FLOP and other rare operators | |
| # ============================================================ | |
| cases += [ | |
| ['if foo bar..baz; end', :ok], # flip-flop with command parsed as foo(bar..baz) | |
| ] | |
| # ============================================================ | |
| # Z. COMMAND IN WHEN/CASE | |
| # ============================================================ | |
| cases += [ | |
| ['case foo bar; when 1; end', :ok], # command as case expression | |
| ['case 1; when foo bar; end', :err], # command not allowed as when value | |
| ] | |
| # ============================================================ | |
| # A. ENDLESS DEF edge cases we haven't tested | |
| # ============================================================ | |
| cases += [ | |
| # Nested endless def - non-command body is fine | |
| ['def f = def g = 1', :ok], | |
| ['def f = def g = 1 + 2', :ok], | |
| ['def f = def g = 1 and 2', :ok], # inner body is arg, outer is arg | |
| # Triple nesting | |
| ['def f = def g = def h = foo bar', :err], # inner-most has command body | |
| ['def f = def g = def h = 1', :ok], | |
| # Endless def with op-asgn body | |
| ['def f = a = 1', :ok], | |
| ['def f = a = foo bar', :err], # command_asgn as body | |
| # Endless def with various command forms | |
| ['def f = super bar', :ok], | |
| ['def f = super bar and 1', :err], | |
| ['def f = super(bar)', :ok], | |
| ['def f = super(bar) and 1', :ok], | |
| # self. endless def | |
| ['def self.f = foo bar', :ok], | |
| ['def self.f = foo bar rescue nil', :ok], | |
| ['def self.f = foo bar rescue nil and 1', :err], | |
| ['def self.f = def g = foo bar', :err], | |
| ] | |
| # ============================================================ | |
| # B. ASSIGNMENT + COMMAND interactions | |
| # ============================================================ | |
| cases += [ | |
| # Various LHS with command RHS followed by operators | |
| ['a = foo bar', :ok], | |
| ['a = foo bar and 1', :err], | |
| ['a = foo bar or 1', :err], | |
| ['a = foo bar if true', :ok], | |
| ['a = foo bar rescue nil', :ok], | |
| # Op-assignment | |
| ['a += foo bar', :ok], | |
| ['a += foo bar and 1', :err], | |
| ['a -= foo bar or 1', :err], | |
| ['a *= foo bar if true', :ok], | |
| ['a /= foo bar rescue nil', :ok], | |
| # Attribute assignment | |
| ['a.b = foo bar', :ok], | |
| ['a.b = foo bar and 1', :err], | |
| ['a::b = foo bar and 1', :err], | |
| ['a[0] = foo bar', :ok], | |
| ['a[0] = foo bar and 1', :err], | |
| # Attribute op-assignment | |
| ['a.b += foo bar', :ok], | |
| ['a.b += foo bar and 1', :err], | |
| ['a.b ||= foo bar', :ok], | |
| ['a.b ||= foo bar and 1', :err], | |
| ['a.b &&= foo bar or 1', :err], | |
| # Constant assignment | |
| ['A = foo bar', :ok], | |
| ['A = foo bar and 1', :err], | |
| ['A::B = foo bar', :ok], | |
| ['A::B = foo bar and 1', :err], | |
| # Instance/class variable assignment | |
| ['@a = foo bar and 1', :err], | |
| ['@@a = foo bar and 1', :err], | |
| ['$a = foo bar and 1', :err], | |
| ] | |
| # ============================================================ | |
| # C. CHAINED ASSIGNMENTS with commands | |
| # ============================================================ | |
| cases += [ | |
| ['a = b = foo bar', :ok], | |
| ['a = b = foo bar and 1', :err], | |
| ['a = b = c = foo bar and 1', :err], | |
| ['a = b = 1', :ok], | |
| ['a = b = 1 and 2', :ok], # non-command, and binds to outer | |
| ] | |
| # ============================================================ | |
| # D. RESCUE MODIFIER in different contexts | |
| # ============================================================ | |
| cases += [ | |
| # Statement level rescue | |
| ['foo bar rescue nil', :ok], | |
| ['foo bar rescue nil and 1', :ok], | |
| ['foo bar rescue nil or 1', :ok], | |
| # Assignment rescue | |
| ['a = foo bar rescue nil', :ok], | |
| ['a = foo bar rescue nil and 1', :ok], | |
| ['a = 1 rescue nil', :ok], | |
| ['a = 1 rescue nil and 1', :ok], | |
| # Endless def rescue | |
| ['def f = 1 rescue nil', :ok], | |
| ['def f = 1 rescue nil and 1', :ok], # body is non-command | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = foo bar rescue nil and 1', :err], # body is command | |
| ['def f = foo bar rescue nil or 1', :err], # body is command | |
| # Rescue with pattern match | |
| ['1 rescue nil in a', :ok], # rescue value includes pattern | |
| ['1 rescue nil => a', :ok], | |
| ] | |
| # ============================================================ | |
| # E. COMMAND CALLS with do blocks - chaining | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end', :ok], | |
| ['foo bar do end.baz', :ok], | |
| ['foo bar do end::baz', :ok], | |
| ['foo bar do end.baz quux', :ok], | |
| ['foo bar do end.baz quux and 1', :ok], | |
| ['foo bar do end.baz quux or 1', :ok], | |
| ['foo bar do end.baz quux if true', :ok], | |
| ['foo bar do end + 1', :err], | |
| ['foo bar do end - 1', :err], | |
| ['foo bar do end * 1', :err], | |
| ['foo bar do end[0]', :err], | |
| ['foo bar do end in a', :err], | |
| ] | |
| # ============================================================ | |
| # F. RETURN/BREAK/NEXT with various operators | |
| # ============================================================ | |
| cases += [ | |
| ['return', :ok], | |
| ['return 1', :ok], | |
| ['return 1 if true', :ok], | |
| ['return 1 unless true', :ok], | |
| ['return 1 while true', :ok], | |
| ['return 1 until true', :ok], | |
| ['return 1 rescue nil', :ok], | |
| ['return 1 and 2', :err], | |
| ['return 1 or 2', :err], | |
| ['return 1 + 2', :ok], # + binds inside return args | |
| ['return 1 in [1]', :err], | |
| ['return if true', :ok], | |
| ['return and 1', :err], | |
| ['loop { break }', :ok], | |
| ['loop { break 1 }', :ok], | |
| ['loop { break 1 if true }', :ok], | |
| ['loop { break 1 and 2 }', :err], | |
| ['loop { break 1 or 2 }', :err], | |
| ['loop { break if true }', :ok], | |
| ['loop { next }', :ok], | |
| ['loop { next 1 }', :ok], | |
| ['loop { next 1 if true }', :ok], | |
| ['loop { next 1 and 2 }', :err], | |
| ['loop { next 1 or 2 }', :err], | |
| ] | |
| # ============================================================ | |
| # G. NOT/! with various arguments | |
| # ============================================================ | |
| cases += [ | |
| ['!1', :ok], | |
| ['!foo bar', :ok], # not(command) | |
| ['!foo bar and 1', :ok], # (not(command)) and 1 | |
| ['!foo bar in a', :err], # not(command) in a → err | |
| ['!foo bar if true', :ok], | |
| ['not 1', :ok], | |
| ['not foo bar', :ok], | |
| ['not foo bar and 1', :ok], | |
| ['not foo bar in a', :err], | |
| ['not foo bar if true', :ok], | |
| ['!!1', :ok], | |
| ['not not 1', :ok], | |
| ] | |
| # ============================================================ | |
| # H. YIELD as value expression | |
| # ============================================================ | |
| cases += [ | |
| ['def f; yield; end', :ok], | |
| ['def f; yield 1; end', :ok], | |
| ['def f; yield 1 and 2; end', :ok], # yield is value expr, unlike return | |
| ['def f; yield 1 or 2; end', :ok], | |
| ['def f; yield 1 if true; end', :ok], | |
| ['def f; yield foo bar; end', :ok], | |
| ['def f; yield foo bar and 1; end', :ok], # yield(command) and 1 | |
| ['def f; yield foo bar in a; end', :err], # command yield can't use in | |
| ['def f; a = yield 1; end', :ok], | |
| ['def f; a = yield foo bar; end', :ok], | |
| ['def f; a = yield foo bar and 1; end', :err], # assignment + command yield | |
| ] | |
| # ============================================================ | |
| # I. SUPER edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['super', :ok], | |
| ['super()', :ok], | |
| ['super(1)', :ok], | |
| ['super 1', :ok], | |
| ['super 1 and 2', :ok], # super is value expr | |
| ['super 1 or 2', :ok], | |
| ['super foo bar', :ok], # super(command) | |
| ['super foo bar and 1', :ok], # super(command) and 1 | |
| ['super foo bar in a', :err], # command super can't use in | |
| ['a = super foo bar', :ok], | |
| ['a = super foo bar and 1', :err], # assignment + command super | |
| ] | |
| # ============================================================ | |
| # J. ALIAS/UNDEF edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['alias a b', :ok], | |
| ['alias a b if true', :ok], | |
| ['alias a b and 1', :err], | |
| ['alias a b or 1', :err], | |
| ['alias a b + 1', :err], | |
| ['undef a', :ok], | |
| ['undef a if true', :ok], | |
| ['undef a and 1', :err], | |
| ['undef a, b', :ok], | |
| ['undef a, b if true', :ok], | |
| ['undef a, b and 1', :err], | |
| ] | |
| # ============================================================ | |
| # K. BEGIN/END blocks | |
| # ============================================================ | |
| cases += [ | |
| ['BEGIN { 1 }', :ok], | |
| ['BEGIN { 1 } if true', :err], # BEGIN can't have modifiers | |
| ['BEGIN { 1 } and 1', :err], | |
| ['END { 1 }', :ok], | |
| ['END { 1 } if true', :ok], # END can have modifiers | |
| ['END { 1 } and 1', :err], | |
| ] | |
| # ============================================================ | |
| # L. MULTI-WRITE and implicit array | |
| # ============================================================ | |
| cases += [ | |
| ['a, b = 1, 2', :ok], | |
| ['a, b = 1, 2 if true', :ok], | |
| ['a, b = 1, 2 and 3', :err], | |
| ['a = 1, 2', :ok], # implicit array | |
| ['a = 1, 2 if true', :ok], | |
| ['a = 1, 2 and 3', :err], | |
| ] | |
| # ============================================================ | |
| # M. CONDITIONAL OPERATOR (ternary) edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['true ? 1 : 2', :ok], | |
| ['true ? 1 : 2 and 3', :ok], # ternary result is arg | |
| ['a = true ? 1 : 2', :ok], | |
| ['true ? foo bar : 1', :err], # command in ternary | |
| ['true ? 1 : foo bar', :err], | |
| ['foo bar ? 1 : 2', :ok], # command as condition? parsed as foo(bar ? 1 : 2) | |
| ] | |
| # ============================================================ | |
| # N. PATTERN MATCHING edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['1 in a', :ok], | |
| ['1 => a', :ok], | |
| ['1 + 2 in a', :ok], | |
| ['1 + 2 => a', :ok], | |
| ['foo() in a', :ok], | |
| ['foo() => a', :ok], | |
| ['foo in a', :ok], # foo (no args) is primary | |
| ['(foo bar) in a', :ok], # parens make it primary | |
| ['(foo bar) => a', :ok], | |
| ] | |
| # ============================================================ | |
| # O. MIXED modifier + composition | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar if true and 1', :ok], # (foo bar if true) and 1 | |
| ['a = foo bar if true and 1', :ok], # (a = foo bar if true) and 1 | |
| ['alias a b if true and 1', :ok], # (alias a b if true) and 1 | |
| ['return 1 if true and 1', :ok], # (return 1 if true) and 1 | |
| ['undef a if true or 1', :ok], # (undef a if true) or 1 | |
| ] | |
| # ============================================================ | |
| # P. DEFINED? edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['defined? foo', :ok], | |
| ['defined?(foo)', :ok], | |
| ['defined? foo and 1', :ok], # defined?(foo) and 1 | |
| ['defined? foo bar', :err], # defined? can't take command args | |
| ['defined? 1 + 2', :ok], # defined?(1 + 2) | |
| ] | |
| # ============================================================ | |
| # Q. OPERATOR-ASSIGNMENT with yield/super | |
| # ============================================================ | |
| cases += [ | |
| ['a = super bar', :ok], | |
| ['a = super bar and 1', :err], | |
| ['a += super bar', :ok], | |
| ['a += super bar and 1', :err], | |
| ['a ||= super bar', :ok], | |
| ['a ||= super bar and 1', :err], | |
| ['def f; a = yield bar; end', :ok], | |
| ['def f; a = yield bar and 1; end', :err], | |
| ['def f; a += yield bar; end', :ok], | |
| ['def f; a += yield bar and 1; end',:err], | |
| ] | |
| # ============================================================ | |
| # R. PRIVATE/PUBLIC wrapping endless def | |
| # ============================================================ | |
| cases += [ | |
| ['private def f = foo bar', :ok], | |
| ['private def f = foo bar and 1', :ok], # and applies to private | |
| ['private def f = foo bar or 1', :ok], | |
| ['private def f = foo bar if true', :ok], | |
| ['private def f = 1 and 2', :ok], | |
| ] | |
| # ============================================================ | |
| # A. ENDLESS DEF inside various contexts | |
| # ============================================================ | |
| cases += [ | |
| # Endless def as argument to method call | |
| ['foo(def f = 1)', :ok], | |
| ['foo(def f = foo bar)', :ok], | |
| ['foo(def f = foo bar and 1)', :err], # def with command body is command_asgn | |
| # Endless def in array literal | |
| ['[def f = 1]', :ok], | |
| ['[def f = foo bar]', :err], # command in array | |
| # Endless def in ternary | |
| ['true ? def f = 1 : 2', :ok], | |
| ['true ? def f = foo bar : 2', :err], # command in ternary | |
| # Endless def after assignment with parens | |
| ['a = (def f = foo bar)', :ok], # parens make it ok | |
| ['a = (def f = foo bar and 1)', :err], # def with command body is command_asgn | |
| # Endless def in hash | |
| ['{a: def f = 1}', :ok], | |
| ['{a: def f = foo bar}', :err], # command in hash value | |
| # Endless def in interpolation | |
| ['"#{def f = foo bar}"', :ok], # interpolation is stmt context | |
| ['"#{def f = foo bar and 1}"', :err], # def with command body is command_asgn | |
| ] | |
| # ============================================================ | |
| # B. COMMAND in when/case/in clauses | |
| # ============================================================ | |
| cases += [ | |
| ['case foo bar; when 1; end', :ok], # command as case expr | |
| ['case 1; when foo bar; end', :err], # command as when arg | |
| ['case 1; when 1; foo bar; end', :ok], # command in when body | |
| ['case 1; in a; foo bar; end', :ok], # command in pattern body | |
| ['case 1; in a if foo bar; end', :ok], # command in guard clause is OK | |
| ] | |
| # ============================================================ | |
| # C. COMMAND in if/unless/while/until conditions | |
| # ============================================================ | |
| cases += [ | |
| ['if foo bar; end', :ok], # command as condition | |
| ['unless foo bar; end', :ok], | |
| ['while foo bar; end', :ok], | |
| ['until foo bar; end', :ok], | |
| ['if foo bar and 1; end', :ok], # command then and in condition | |
| ['if foo bar or 1; end', :ok], | |
| ] | |
| # ============================================================ | |
| # D. COMMAND in for loop | |
| # ============================================================ | |
| cases += [ | |
| ['for a in foo bar; end', :ok], # command as enumerable | |
| ] | |
| # ============================================================ | |
| # E. COMMAND with &. (safe navigation) | |
| # ============================================================ | |
| cases += [ | |
| ['obj&.foo bar', :ok], # safe nav command | |
| ['obj&.foo bar and 1', :ok], # command then and | |
| ['obj&.foo bar in a', :err], # command can't use in | |
| ['obj&.foo bar if true', :ok], | |
| ] | |
| # ============================================================ | |
| # F. COMMAND with :: method call | |
| # ============================================================ | |
| cases += [ | |
| ['Foo::bar baz', :ok], | |
| ['Foo::bar baz and 1', :ok], | |
| ['Foo::bar baz in a', :err], | |
| ['Foo::bar baz if true', :ok], | |
| ['a = Foo::bar baz', :ok], | |
| ['a = Foo::bar baz and 1', :err], | |
| ] | |
| # ============================================================ | |
| # G. MULTIPLE do blocks / brace blocks | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end.baz do end', :ok], # chained do blocks | |
| ['foo bar do end.baz do end and 1', :ok], | |
| ['foo bar do end.baz do end + 1', :err], # block_call can't use + | |
| ['foo bar do end.baz do end in a', :err], # block_call can't use in | |
| ['foo bar { }.baz { }', :ok], # chained brace blocks | |
| ] | |
| # ============================================================ | |
| # H. SPLAT in assignment with commands | |
| # ============================================================ | |
| cases += [ | |
| ['*a = foo bar', :ok], | |
| ['*a = foo bar and 1', :err], | |
| ['a, *b = foo bar', :ok], | |
| ['a, *b = foo bar and 1', :err], | |
| ['*a, b = 1, 2 and 3', :err], | |
| ] | |
| # ============================================================ | |
| # I. CONDITIONAL ASSIGNMENT edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['a.b ||= foo bar', :ok], | |
| ['a.b ||= foo bar and 1', :err], | |
| ['a[0] ||= foo bar', :ok], | |
| ['a[0] ||= foo bar and 1', :err], | |
| ['A::B ||= foo bar', :ok], | |
| ['A::B ||= foo bar and 1', :err], | |
| ['@a ||= foo bar', :ok], | |
| ['@a ||= foo bar and 1', :err], | |
| ['$a &&= foo bar', :ok], | |
| ['$a &&= foo bar and 1', :err], | |
| ['@@a ||= foo bar or 1', :err], | |
| ] | |
| # ============================================================ | |
| # J. RETURN/BREAK/NEXT with command args | |
| # ============================================================ | |
| cases += [ | |
| ['return foo bar', :ok], | |
| ['return foo bar if true', :ok], | |
| ['return foo bar and 1', :err], # void value | |
| ['loop { break foo bar }', :ok], | |
| ['loop { break foo bar if true }', :ok], | |
| ['loop { break foo bar and 1 }', :err], | |
| ['loop { next foo bar }', :ok], | |
| ['loop { next foo bar if true }', :ok], | |
| ['loop { next foo bar and 1 }', :err], | |
| ] | |
| # ============================================================ | |
| # K. RESCUE modifier + rescue modifier (chained) | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue 2 rescue 3', :ok], # chained rescue | |
| ['foo bar rescue nil rescue nil', :ok], | |
| ['a = foo bar rescue nil rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # L. ENDLESS METHOD as rescue value | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue def f = 1', :ok], | |
| ['1 rescue def f = foo bar', :ok], # rescue value is def | |
| ['a = 1 rescue def f = foo bar', :err], # command_asgn def can't be rescue value | |
| ] | |
| # ============================================================ | |
| # M. COMMAND in begin/ensure/else | |
| # ============================================================ | |
| cases += [ | |
| ['begin; foo bar; rescue; end', :ok], | |
| ['begin; rescue; foo bar; end', :ok], | |
| ['begin; ensure; foo bar; end', :ok], | |
| ['begin; foo bar; end and 1', :ok], # begin is primary | |
| ['begin; foo bar; end + 1', :ok], | |
| ] | |
| # ============================================================ | |
| # N. HEREDOC interactions | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar, <<~HEREDOC | |
| hello | |
| HEREDOC', :ok], | |
| ] | |
| # ============================================================ | |
| # O. PROC/LAMBDA with command bodies | |
| # ============================================================ | |
| cases += [ | |
| ['proc { foo bar }', :ok], | |
| ['proc { foo bar and 1 }', :ok], # inside block is stmt | |
| ['lambda { foo bar }', :ok], | |
| ['-> { foo bar and 1 }', :ok], | |
| ['-> (a) { foo bar }', :ok], | |
| ] | |
| # ============================================================ | |
| # P. TERNARY deeply nested | |
| # ============================================================ | |
| cases += [ | |
| ['a ? b ? 1 : 2 : 3', :ok], | |
| ['a ? 1 : b ? 2 : 3', :ok], | |
| ['true ? (foo bar) : 1', :ok], # parens around command ok | |
| ['true ? 1 : (foo bar)', :ok], | |
| ] | |
| # ============================================================ | |
| # Q. OPERATOR METHODS that look like commands | |
| # ============================================================ | |
| cases += [ | |
| ['a + b', :ok], | |
| ['a.+(b)', :ok], | |
| ['a.+ b', :ok], # operator as command-style call | |
| ['a.+ b and 1', :ok], # command then and | |
| ['a.+ b in c', :err], # command can't use in | |
| ] | |
| # ============================================================ | |
| # R. MULTIPLE ARGUMENTS to commands | |
| # ============================================================ | |
| cases += [ | |
| ['foo a, b', :ok], | |
| ['foo a, b and 1', :ok], # and applies to foo | |
| ['foo a, b or 1', :ok], | |
| ['foo a, b if true', :ok], | |
| ['foo a, b in c', :err], # command can't use in | |
| ] | |
| # ============================================================ | |
| # S. COMMAND followed by newline then operator | |
| # ============================================================ | |
| cases += [ | |
| # These should be separate statements (newline terminates) | |
| ["foo bar\nand 1", :ok], # separate stmts | |
| ["foo bar\nor 1", :ok], | |
| ["foo bar\nin a", :err], # `in` not valid as stmt start | |
| ] | |
| # ============================================================ | |
| # T. BLOCK PASS as argument | |
| # ============================================================ | |
| cases += [ | |
| ['foo(&method(:bar))', :ok], | |
| ['foo &method(:bar)', :ok], | |
| ['foo bar, &baz', :ok], | |
| ['foo bar, &baz and 1', :ok], # and applies to foo | |
| ['foo bar, &baz in a', :err], # command can't use in | |
| ] | |
| # ============================================================ | |
| # U. COMMAND in conditional assignment target | |
| # ============================================================ | |
| cases += [ | |
| ['a = foo bar ? 1 : 2', :ok], # foo(bar ? 1 : 2) | |
| ['a = foo(bar) ? 1 : 2', :ok], | |
| ] | |
| # ============================================================ | |
| # V. ENDLESS DEF with rescue and pattern match | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 rescue nil in a', :ok], # rescue(nil in a)? | |
| ['def f = 1 rescue nil => a', :ok], | |
| ['def f = foo bar rescue nil in a', :err], # command body, rescue value can't include in | |
| ] | |
| # ============================================================ | |
| # W. BLOCK COMMAND with safe navigation chaining | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end&.baz', :ok], | |
| ['foo bar do end&.baz quux', :ok], | |
| ['foo bar do end&.baz quux and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # X. COMMAND inside method body (non-endless) | |
| # ============================================================ | |
| cases += [ | |
| ['def f; foo bar; end', :ok], | |
| ['def f; foo bar and 1; end', :ok], # inside method body is stmt | |
| ['def f; return foo bar; end', :ok], | |
| ['def f; return foo bar and 1; end', :err], # void value | |
| ] | |
| # ============================================================ | |
| # Y. CLASS/MODULE body with commands | |
| # ============================================================ | |
| cases += [ | |
| ['class A; foo bar; end', :ok], | |
| ['class A; foo bar and 1; end', :ok], | |
| ['module A; foo bar; end', :ok], | |
| ['module A; foo bar and 1; end', :ok], | |
| ] | |
| # ============================================================ | |
| # Z. COMMAND with star/double-star args | |
| # ============================================================ | |
| cases += [ | |
| ['foo *a', :ok], | |
| ['foo **a', :ok], | |
| ['foo *a and 1', :ok], # command with splat | |
| ['foo **a and 1', :ok], | |
| ['foo *a, **b', :ok], | |
| ] | |
| # ============================================================ | |
| # A. BLOCK CALL chains with safe navigation (&.) | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end&.baz + 1', :err], # block_call can't use + | |
| ['foo bar do end&.baz in a', :err], # block_call can't use in | |
| ['foo bar do end&.baz and 1', :ok], | |
| ['foo bar do end&.baz { } + 1', :err], # block_call with brace block can't use + | |
| ['foo bar do end&.baz { } and 1', :ok], | |
| ['foo bar do end&.baz do end + 1', :err], | |
| ['foo bar do end&.baz do end and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # B. NESTED block call chains (deeper chaining) | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end.baz.quux', :ok], | |
| ['foo bar do end.baz.quux waldo', :ok], | |
| ['foo bar do end.baz.quux + 1', :err], | |
| ['foo bar do end.baz.quux and 1', :ok], | |
| ['foo bar do end.baz.quux { } + 1', :err], | |
| ['foo bar do end.baz.quux do end + 1', :err], | |
| ] | |
| # ============================================================ | |
| # C. COMMAND with blocks and => (hash rocket as arg vs pattern) | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar => a', :ok], # hash rocket arg | |
| ['foo bar do end => a', :err], # block_command can't use => | |
| ['foo bar { } => a', :ok], # parsed as foo(bar({}) => a) | |
| ['foo(bar) => a', :ok], # pattern match | |
| ] | |
| # ============================================================ | |
| # D. ENDLESS DEF with block calls in body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = foo bar do end', :err], # do block makes it command | |
| ['def f = foo bar do end and 1', :err], # command body, and blocked | |
| ['def f = foo(bar) do end', :ok], # paren call with block | |
| ['def f = foo(bar) do end and 1', :ok], # paren call = primary, not command | |
| ] | |
| # ============================================================ | |
| # E. COMMAND + rescue + various operators | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar rescue nil + 1', :ok], # rescue(nil + 1) | |
| ['foo bar rescue nil if true', :ok], # (foo bar rescue nil) if true | |
| ['foo bar rescue nil in a', :ok], # rescue(nil in a) | |
| ['foo bar rescue nil => a', :ok], # rescue(nil => a) | |
| ['foo bar rescue nil and 1', :ok], # (foo bar rescue nil) and 1 | |
| ['foo bar rescue foo baz', :ok], # rescue value is another command? No, rescue arg is arg-level | |
| ] | |
| # ============================================================ | |
| # F. ASSIGNMENT + rescue + command | |
| # ============================================================ | |
| cases += [ | |
| ['a = foo bar rescue nil + 1', :ok], | |
| ['a = foo bar rescue nil in a', :ok], # rescue(nil in a) | |
| ['a = foo bar rescue nil and 1', :ok], # (a = foo bar rescue nil) and 1 | |
| ['a = 1 rescue foo bar', :err], # rescue value must be arg, command isn't | |
| ] | |
| # ============================================================ | |
| # G. ENDLESS DEF + rescue + operators | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 rescue nil + 1', :ok], | |
| ['def f = 1 rescue nil in a', :ok], # rescue(nil in a)? No, endless def rescue is arg-level | |
| ['def f = 1 rescue nil and 1', :ok], # body is non-command, and applies to def | |
| ['def f = 1 rescue nil => a', :ok], | |
| ['def f = foo bar rescue nil + 1', :ok], | |
| ['def f = foo bar rescue nil in a', :err], # endless_command rescue arg — in not allowed | |
| ['def f = foo bar rescue nil and 1', :err], # command body | |
| ['def f = foo bar rescue nil => a', :err], # endless_command rescue arg — => not allowed | |
| ] | |
| # ============================================================ | |
| # H. DOUBLE RESCUE modifier | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue 2 rescue 3', :ok], | |
| ['foo bar rescue 1 rescue 2', :ok], | |
| ['a = foo bar rescue 1 rescue 2', :ok], | |
| ['def f = 1 rescue 2 rescue 3', :ok], | |
| ['def f = foo bar rescue 1 rescue 2', :ok], | |
| ] | |
| # ============================================================ | |
| # I. UNLESS/WHILE/UNTIL modifier with commands | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar unless false', :ok], | |
| ['foo bar while false', :ok], | |
| ['foo bar until true', :ok], | |
| ['a = foo bar unless false', :ok], | |
| ['a = foo bar while false', :ok], | |
| ['a = foo bar until true', :ok], | |
| ] | |
| # ============================================================ | |
| # J. DOUBLE MODIFIER (if/unless chaining) | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar if true unless false', :ok], | |
| ['foo bar unless true if false', :ok], | |
| ['a = foo bar if true unless false', :ok], | |
| ['a = foo bar if true and 1', :ok], # (a = foo bar if true) and 1 | |
| ['a = foo bar unless true or 1', :ok], | |
| ] | |
| # ============================================================ | |
| # K. TERNARY with various arg-level expressions | |
| # ============================================================ | |
| cases += [ | |
| ['true ? 1 + 2 : 3', :ok], | |
| ['true ? 1 : 2 + 3', :ok], | |
| ['true ? a = 1 : 2', :ok], # assignment in ternary branch ok | |
| ['true ? 1 : a = 2', :ok], # assignment in ternary branch ok | |
| ['true ? def f = 1 : 2', :ok], # def in ternary ok | |
| ['a = true ? 1 : 2 if true', :ok], | |
| ] | |
| # ============================================================ | |
| # L. PATTERN MATCH (case/in) with commands | |
| # ============================================================ | |
| cases += [ | |
| ['case foo bar; in a; end', :ok], # command as case expr for pattern | |
| ['case 1; in a if true; end', :ok], | |
| ['case 1; in a unless true; end', :ok], | |
| ['case 1; in ^a; end', :err], # pin needs parens: ^(a) | |
| ['case 1; in [1, 2]; end', :ok], | |
| ] | |
| # ============================================================ | |
| # M. COMMAND with various argument types | |
| # ============================================================ | |
| cases += [ | |
| ['foo a: 1', :ok], # keyword arg | |
| ['foo a: 1, b: 2', :ok], | |
| ['foo a: 1 and 1', :ok], # command with keyword, then and | |
| ['foo a: 1 in b', :err], # command can't use in | |
| ['foo(*a, **b, &c)', :ok], # paren call with splats | |
| ['foo *a, **b, &c', :ok], # command with splats | |
| ['foo *a, **b, &c and 1', :ok], # command then and | |
| ] | |
| # ============================================================ | |
| # N. POSTFIX RESCUE as arg-level expression | |
| # ============================================================ | |
| cases += [ | |
| ['[1 rescue 2]', :err], # rescue not allowed in array | |
| ['{a: 1 rescue 2}', :err], # rescue not allowed in hash value | |
| ['true ? 1 rescue 2 : 3', :err], # rescue not allowed in ternary | |
| ['true ? 1 : 2 rescue 3', :ok], | |
| ] | |
| # ============================================================ | |
| # O. ASSIGNMENT as expression value | |
| # ============================================================ | |
| cases += [ | |
| ['a = b = 1', :ok], | |
| ['a = b = 1 and 2', :ok], # (a = b = 1) and 2 | |
| ['a = b = 1 if true', :ok], | |
| ['a = (b = 1) and 2', :ok], | |
| ['a = (b = foo bar) and 2', :ok], # parens make inner assignment primary | |
| ] | |
| # ============================================================ | |
| # P. DEFINED? edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['defined? 1', :ok], | |
| ['defined? a', :ok], | |
| ['defined? a.b', :ok], | |
| ['defined? a + 1', :ok], | |
| ['defined? a and 1', :ok], | |
| ['defined? a in b', :ok], # defined?(a in b) | |
| ['a = defined? 1', :ok], | |
| ['a = defined? 1 and 2', :ok], | |
| ] | |
| # ============================================================ | |
| # Q. NOT with various expressions | |
| # ============================================================ | |
| cases += [ | |
| ['not 1', :ok], | |
| ['not 1 and 2', :ok], | |
| ['not 1 or 2', :ok], | |
| ['not 1 in a', :ok], # not(1 in a) | |
| ['not 1 if true', :ok], | |
| ['not foo bar', :ok], | |
| ['not foo bar and 1', :ok], # (not(foo bar)) and 1 | |
| ['not foo bar if true', :ok], | |
| ['a = not 1', :err], # not can't be RHS of assignment | |
| ['a = !1', :ok], | |
| ] | |
| # ============================================================ | |
| # R. COMMAND in string interpolation contexts | |
| # ============================================================ | |
| cases += [ | |
| ['"#{foo bar}"', :ok], | |
| ['"#{foo bar and 1}"', :ok], | |
| ['"#{a = foo bar}"', :ok], | |
| ['"#{a = foo bar and 1}"', :err], # command_asgn in interp | |
| ['`#{foo bar}`', :ok], # backtick interpolation | |
| ['/#{foo bar}/', :ok], # regex interpolation | |
| ] | |
| # ============================================================ | |
| # S. YIELD without args edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['def f; yield; end', :ok], | |
| ['def f; yield + 1; end', :ok], # yield() + 1 | |
| ['def f; yield and 1; end', :ok], | |
| ['def f; yield in a; end', :ok], # yield() in a | |
| ['def f; yield if true; end', :ok], | |
| ['def f; a = yield; end', :ok], | |
| ['def f; a = yield and 1; end', :ok], # (a = yield) and 1 | |
| ] | |
| # ============================================================ | |
| # T. SUPER without args edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['super', :ok], | |
| ['super + 1', :ok], | |
| ['super and 1', :ok], | |
| ['super in a', :ok], # super() in a | |
| ['super if true', :ok], | |
| ['a = super', :ok], | |
| ['a = super and 1', :ok], | |
| ['a = super + 1', :ok], | |
| ] | |
| # ============================================================ | |
| # U. ENDLESS DEF edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['def f = (foo bar)', :ok], # parens make it ok | |
| ['def f = (foo bar) and 1', :ok], # parens = primary | |
| ['def f = (foo bar) in a', :ok], | |
| ['def f = begin; foo bar; end', :ok], # begin/end is primary | |
| ['def f = begin; foo bar; end and 1', :ok], | |
| ['def f = if true; 1; end', :ok], | |
| ['def f = if true; 1; end and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # V. BLOCK PASS edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['foo(&bar)', :ok], | |
| ['foo &bar', :ok], | |
| ['foo(&bar) and 1', :ok], # paren call, primary | |
| ['foo &bar and 1', :ok], # command with block pass | |
| ['foo &bar if true', :ok], | |
| ['foo &bar in a', :err], # command can't use in | |
| ] | |
| # ============================================================ | |
| # W. PROC/LAMBDA as values | |
| # ============================================================ | |
| cases += [ | |
| ['proc { 1 } and 1', :ok], # primary | |
| ['proc { 1 } + 1', :ok], | |
| ['proc { 1 } in a', :ok], | |
| ['lambda { 1 } and 1', :ok], | |
| ['-> { 1 } and 1', :ok], | |
| ['-> { 1 } + 1', :ok], | |
| ['-> { 1 } in a', :ok], | |
| ] | |
| # ============================================================ | |
| # X. MULTIPLE RETURN/BREAK/NEXT values | |
| # ============================================================ | |
| cases += [ | |
| ['return 1, 2', :ok], | |
| ['return 1, 2 if true', :ok], | |
| ['return 1, 2 and 3', :err], | |
| ['loop { break 1, 2 }', :ok], | |
| ['loop { break 1, 2 if true }', :ok], | |
| ['loop { break 1, 2 and 3 }', :err], | |
| ['loop { next 1, 2 }', :ok], | |
| ['loop { next 1, 2 if true }', :ok], | |
| ['loop { next 1, 2 and 3 }', :err], | |
| ] | |
| # ============================================================ | |
| # Y. COMMAND followed by do/brace blocks on chained calls | |
| # ============================================================ | |
| cases += [ | |
| ['foo(bar).baz do end', :ok], | |
| ['foo(bar).baz do end + 1', :ok], # paren call, so baz do end is method_call, + ok | |
| ['foo(bar).baz do end and 1', :ok], | |
| ['foo(bar).baz { }', :ok], | |
| ['foo(bar).baz { } + 1', :ok], | |
| ['foo(bar).baz { } and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # Z. MISC EDGE CASES | |
| # ============================================================ | |
| cases += [ | |
| ['a = 1 if true and 1', :ok], # (a = 1 if true) and 1 | |
| ['a = 1 unless true or 1', :ok], | |
| ['a += 1 if true and 1', :ok], | |
| ['a ||= 1 if true and 1', :ok], | |
| ['a.b = 1 if true and 1', :ok], | |
| ['a[0] = 1 if true and 1', :ok], | |
| ['A::B = 1 if true and 1', :ok], | |
| ['@a = 1 if true and 1', :ok], | |
| ['$a = 1 if true and 1', :ok], | |
| ['@@a = 1 if true and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # A. BLOCK CALL with :: chaining | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end::baz quux', :ok], | |
| ['foo bar do end::baz quux and 1', :ok], | |
| ['foo bar do end::baz + 1', :err], | |
| ['foo bar do end::baz in a', :err], | |
| ['foo bar do end::baz do end', :ok], | |
| ['foo bar do end::baz do end + 1', :err], | |
| ['foo bar do end::baz do end and 1', :ok], | |
| ['foo bar do end::baz { }', :ok], | |
| ['foo bar do end::baz { } + 1', :err], | |
| ['foo bar do end::baz { } and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # B. COMMAND with do block inside assignment | |
| # ============================================================ | |
| cases += [ | |
| ['a = foo bar do end', :ok], | |
| ['a = foo bar do end and 1', :err], # block call in assignment, and blocked | |
| ['a = foo bar do end + 1', :err], # block call in assignment, + blocked | |
| ['a = foo bar do end.baz', :ok], | |
| ['a = foo bar do end.baz quux', :ok], | |
| ['a = foo bar do end.baz + 1', :err], # block call chain in assignment, + blocked | |
| ] | |
| # ============================================================ | |
| # C. NESTED TERNARY with assignments | |
| # ============================================================ | |
| cases += [ | |
| ['a ? b = 1 : c = 2', :ok], | |
| ['a ? b ? c : d : e', :ok], | |
| ['a ? b : c ? d : e', :ok], | |
| ['a ? 1 : 2 if true', :ok], | |
| ['a ? 1 : 2 and 3', :ok], | |
| ['a = a ? 1 : 2 and 3', :ok], | |
| ] | |
| # ============================================================ | |
| # D. COMMAND with proc/lambda args | |
| # ============================================================ | |
| cases += [ | |
| ['foo -> { 1 }', :ok], | |
| ['foo -> { 1 } and 1', :ok], # command then and | |
| ['foo -> { 1 } in a', :err], # command can't use in | |
| ['foo proc { 1 }', :ok], | |
| ['foo lambda { 1 }', :ok], | |
| ] | |
| # ============================================================ | |
| # E. ATTR WRITER assignment edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['a.b = 1', :ok], | |
| ['a.b = 1 and 2', :ok], # (a.b = 1) and 2 | |
| ['a.b = 1 if true', :ok], | |
| ['a.b = 1 in [1]', :ok], # (a.b = 1) in [1] | |
| ['a::b = 1', :ok], | |
| ['a::b = 1 and 2', :ok], | |
| ['a::b = 1 in [1]', :ok], | |
| ['a[0] = 1', :ok], | |
| ['a[0] = 1 and 2', :ok], | |
| ['a[0] = 1 in [1]', :ok], | |
| ] | |
| # ============================================================ | |
| # F. OP-ASGN followed by operators | |
| # ============================================================ | |
| cases += [ | |
| ['a += 1 and 2', :ok], | |
| ['a += 1 or 2', :ok], | |
| ['a += 1 if true', :ok], | |
| ['a += 1 in [1]', :ok], | |
| ['a.b += 1 and 2', :ok], | |
| ['a.b += 1 in [1]', :ok], | |
| ['a[0] += 1 and 2', :ok], | |
| ['a[0] += 1 in [1]', :ok], | |
| ] | |
| # ============================================================ | |
| # G. ENDLESS DEF as value in various positions | |
| # ============================================================ | |
| cases += [ | |
| ['a = def f = 1', :ok], | |
| ['a = def f = 1 and 2', :ok], # and applies to a = ... | |
| ['a = def f = foo bar', :ok], # def with command body, as assignment value | |
| ['a = def f = foo bar and 1', :err], # command body def, then and → err because def is command_asgn | |
| ['[def f = 1]', :ok], | |
| ['[def f = 1, 2]', :ok], | |
| ['{a: def f = 1}', :ok], | |
| ] | |
| # ============================================================ | |
| # H. MULTIPLE assignment with splat patterns | |
| # ============================================================ | |
| cases += [ | |
| ['a, = 1', :ok], | |
| ['a, = 1 if true', :ok], | |
| ['a, = 1 and 2', :err], # multi-write | |
| ['*, a = 1, 2', :ok], | |
| ['*, a = 1, 2 if true', :ok], | |
| ['*, a = 1, 2 and 3', :err], | |
| ] | |
| # ============================================================ | |
| # I. COMMAND with regex/string literal args | |
| # ============================================================ | |
| cases += [ | |
| ['foo /bar/', :ok], # regex arg | |
| ['foo "bar"', :ok], # string arg | |
| ['foo :bar', :ok], # symbol arg | |
| ['foo [1, 2]', :ok], # array arg | |
| ['foo({a: 1})', :ok], # hash arg in parens | |
| ] | |
| # ============================================================ | |
| # J. HEREDOC with command | |
| # ============================================================ | |
| cases += [ | |
| ["foo <<~H\nhello\nH", :ok], | |
| ["foo bar, <<~H\nhello\nH", :ok], | |
| ["a = foo <<~H\nhello\nH", :ok], | |
| ] | |
| # ============================================================ | |
| # K. RAISE/FAIL as commands | |
| # ============================================================ | |
| cases += [ | |
| ['raise "error"', :ok], | |
| ['raise "error" if true', :ok], | |
| ['raise "error" and 1', :ok], # raise is just a method call | |
| ['raise RuntimeError, "msg"', :ok], | |
| ['a = raise "error"', :ok], # raise is just a method | |
| ['a = raise "error" and 1', :err], # command_asgn | |
| ] | |
| # ============================================================ | |
| # L. SEND with operator methods | |
| # ============================================================ | |
| cases += [ | |
| ['a.== b', :ok], | |
| ['a.!= b', :ok], # != is a method name | |
| ['a.<< b', :ok], | |
| ['a.>> b', :ok], | |
| ['a.<=> b', :ok], | |
| ['a.[] b', :ok], | |
| ['a.[]= b, c', :ok], | |
| ] | |
| # ============================================================ | |
| # M. SAFE NAV with various forms | |
| # ============================================================ | |
| cases += [ | |
| ['a&.b', :ok], | |
| ['a&.b c', :ok], | |
| ['a&.b c and 1', :ok], | |
| ['a&.b c in d', :err], # command can't use in | |
| ['a&.b(c)', :ok], | |
| ['a&.b(c) and 1', :ok], | |
| ['a&.b(c) in d', :ok], # paren call is primary | |
| ] | |
| # ============================================================ | |
| # N. CLASS/MODULE/DEF bodies | |
| # ============================================================ | |
| cases += [ | |
| ['class A; end and 1', :ok], # class is primary | |
| ['class A; end + 1', :ok], | |
| ['class A; end in a', :ok], | |
| ['module A; end and 1', :ok], | |
| ['def f; end and 1', :ok], | |
| ['def f; end + 1', :ok], | |
| ['def f; end in a', :ok], | |
| ] | |
| # ============================================================ | |
| # O. BEGIN/END blocks | |
| # ============================================================ | |
| cases += [ | |
| ['begin; end', :ok], | |
| ['begin; end and 1', :ok], | |
| ['begin; end + 1', :ok], | |
| ['begin; end in a', :ok], | |
| ['begin; end if true', :ok], | |
| ['begin; 1; rescue; 2; end', :ok], | |
| ['begin; 1; rescue; 2; end and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # P. IF/UNLESS/WHILE/UNTIL as expressions | |
| # ============================================================ | |
| cases += [ | |
| ['if true; 1; end and 2', :ok], | |
| ['if true; 1; end + 2', :ok], | |
| ['if true; 1; end in a', :ok], | |
| ['unless true; 1; end and 2', :ok], | |
| ['while true; break; end and 2', :ok], | |
| ['until true; break; end and 2', :ok], | |
| ['for a in [1]; end and 2', :ok], | |
| ] | |
| # ============================================================ | |
| # Q. ARRAY/HASH with various elements | |
| # ============================================================ | |
| cases += [ | |
| ['[1, 2, 3]', :ok], | |
| ['[*a]', :ok], | |
| ['[**a]', :ok], | |
| ['{a: 1, b: 2}', :ok], | |
| ['{**a}', :ok], | |
| ['[foo(bar)]', :ok], | |
| ['{a: foo(bar)}', :ok], | |
| ] | |
| # ============================================================ | |
| # R. COMMAND with chained method calls | |
| # ============================================================ | |
| cases += [ | |
| ['foo(bar).baz quux', :ok], | |
| ['foo(bar).baz quux and 1', :ok], # command then and | |
| ['foo(bar).baz quux in a', :err], # command can't use in | |
| ['foo(bar)::baz quux', :ok], | |
| ['foo(bar)::baz quux and 1', :ok], | |
| ['foo(bar)::baz quux in a', :err], | |
| ] | |
| # ============================================================ | |
| # S. SPECIAL VARIABLES | |
| # ============================================================ | |
| cases += [ | |
| ['$~ in a', :ok], | |
| ['$_ in a', :ok], | |
| ['$1 in a', :ok], | |
| ['__FILE__ in a', :ok], | |
| ['__LINE__ in a', :ok], | |
| ['__ENCODING__ in a', :ok], | |
| ] | |
| # ============================================================ | |
| # T. ASSIGNMENT + TERNARY edge cases | |
| # ============================================================ | |
| cases += [ | |
| ['a = b ? c : d', :ok], | |
| ['a = b ? c = 1 : d = 2', :ok], | |
| ['a.b = c ? 1 : 2', :ok], | |
| ['a[0] = b ? 1 : 2', :ok], | |
| ['a += b ? 1 : 2', :ok], | |
| ] | |
| # ============================================================ | |
| # U. COMPLEX NESTED EXPRESSIONS | |
| # ============================================================ | |
| cases += [ | |
| ['a = 1 and b = 2', :ok], | |
| ['a = 1 or b = 2', :ok], | |
| ['a = 1 and b = 2 and c = 3', :ok], | |
| ['not a = 1', :ok], # not(a = 1) | |
| ['!a = 1', :ok], # (!a) = 1 → actually ok in parse.y | |
| ] | |
| # ============================================================ | |
| # A. LABELS in various contexts | |
| # ============================================================ | |
| cases += [ | |
| # Labels in method calls (hash-style args) | |
| ['foo(a: 1)', :ok], | |
| ['foo(a: 1, b: 2)', :ok], | |
| ['foo a: 1', :ok], | |
| ['foo a: 1, b: 2', :ok], | |
| ['foo a: 1, b: 2 and 1', :ok], # command then and | |
| ['foo a: 1, b: 2 in c', :err], # command can't use in | |
| # Labels in hash literals | |
| ['{a: 1}', :ok], | |
| ['{a: 1, b: 2}', :ok], | |
| ['{a: foo bar}', :err], # command in hash value | |
| ['{a: 1, b: foo bar}', :err], | |
| # Labels in array of hashes | |
| ['[{a: 1}]', :ok], | |
| ['[{a: 1}, {b: 2}]', :ok], | |
| ] | |
| # ============================================================ | |
| # B. MODIFIER RESCUE - basic forms | |
| # ============================================================ | |
| cases += [ | |
| # Statement-level rescue | |
| ['1 rescue 2', :ok], | |
| ['foo rescue nil', :ok], | |
| ['foo bar rescue nil', :ok], | |
| ['foo(bar) rescue nil', :ok], | |
| # Rescue value types | |
| ['1 rescue 2 + 3', :ok], # rescue(2 + 3) | |
| ['1 rescue 2 * 3', :ok], | |
| ['1 rescue a ? b : c', :ok], # rescue(a ? b : c) | |
| ['1 rescue 2..3', :ok], # rescue(2..3) | |
| ['1 rescue !a', :ok], | |
| ['1 rescue not a', :ok], # not IS allowed as rescue value | |
| ['1 rescue defined? a', :ok], | |
| ] | |
| # ============================================================ | |
| # C. MODIFIER RESCUE + pattern match | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue nil in a', :ok], # rescue(nil in a) | |
| ['1 rescue nil => a', :ok], # rescue(nil => a) | |
| ['1 rescue 1 + 2 in a', :ok], # rescue((1+2) in a) | |
| ['1 rescue 1 + 2 => a', :ok], | |
| # Rescue whose value is a pattern match — the whole thing is stmt | |
| ['1 rescue nil in a and 1', :ok], # (1 rescue (nil in a)) and 1 | |
| ['1 rescue nil => a and 1', :err], # rescue(nil => a) is stmt, can't and | |
| ['1 rescue nil in a + 1', :err], # rescue(nil in a) is stmt, can't + 1 | |
| ] | |
| # ============================================================ | |
| # D. MODIFIER RESCUE in assignment | |
| # ============================================================ | |
| cases += [ | |
| ['a = 1 rescue 2', :ok], | |
| ['a = 1 rescue nil', :ok], | |
| ['a = foo bar rescue nil', :ok], | |
| ['a = foo(bar) rescue nil', :ok], | |
| ['a += 1 rescue nil', :ok], | |
| ['a ||= 1 rescue nil', :ok], | |
| ['a.b = 1 rescue nil', :ok], | |
| ['a[0] = 1 rescue nil', :ok], | |
| ['A::B = 1 rescue nil', :ok], | |
| # Assignment rescue followed by operators | |
| ['a = 1 rescue nil and 1', :ok], # (a = 1 rescue nil) and 1 | |
| ['a = 1 rescue nil or 1', :ok], | |
| ['a = 1 rescue nil if true', :ok], | |
| ['a = 1 rescue nil in [1]', :ok], # a = (1 rescue (nil in [1])) | |
| # Command assignment rescue | |
| ['a = foo bar rescue nil', :ok], | |
| ['a = foo bar rescue nil and 1', :ok], # (a = foo bar rescue nil) and 1 | |
| ['a = foo bar rescue nil + 1', :ok], # a = foo(bar) rescue (nil + 1) | |
| ] | |
| # ============================================================ | |
| # E. MODIFIER RESCUE in endless def | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 rescue nil', :ok], | |
| ['def f = 1 rescue 2', :ok], | |
| ['def f = 1 rescue nil and 1', :ok], # (def f = (1 rescue nil)) and 1 | |
| ['def f = 1 rescue nil or 1', :ok], | |
| ['def f = 1 rescue nil if true', :ok], | |
| # Endless def with command body + rescue | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = foo bar rescue nil and 1', :err], # command body def | |
| ['def f = foo bar rescue nil or 1', :err], | |
| ['def f = foo bar rescue nil if true', :ok], # modifier on def | |
| # Endless def rescue value constraints | |
| ['def f = 1 rescue nil in a', :ok], # non-command body, rescue(nil in a) — but wait | |
| ['def f = 1 rescue nil => a', :ok], | |
| ['def f = foo bar rescue nil in a', :err], # command body, rescue arg excludes in | |
| ['def f = foo bar rescue nil => a', :err], # command body, rescue arg excludes => | |
| ['def f = foo bar rescue 1 + 2', :ok], # rescue value is arg-level expression | |
| ] | |
| # ============================================================ | |
| # F. RESCUE VALUE edge cases | |
| # ============================================================ | |
| cases += [ | |
| # What can be a rescue value (arg-level) | |
| ['1 rescue -> { 1 }', :ok], | |
| ['1 rescue [1, 2]', :ok], | |
| ['1 rescue {a: 1}', :ok], | |
| ['1 rescue "hello"', :ok], | |
| ['1 rescue :hello', :ok], | |
| ['1 rescue /regex/', :ok], | |
| ['1 rescue self', :ok], | |
| ['1 rescue true', :ok], | |
| ['1 rescue false', :ok], | |
| ['1 rescue nil', :ok], | |
| ['1 rescue __FILE__', :ok], | |
| ] | |
| # ============================================================ | |
| # G. RESCUE + RESCUE chaining | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue 2 rescue 3', :ok], | |
| ['1 rescue 2 rescue 3 rescue 4', :ok], | |
| ['foo bar rescue 1 rescue 2', :ok], | |
| ['a = 1 rescue 2 rescue 3', :ok], | |
| ['def f = 1 rescue 2 rescue 3', :ok], | |
| ['def f = foo bar rescue 1 rescue 2', :ok], | |
| ] | |
| # ============================================================ | |
| # H. LABEL as symbol in various positions | |
| # ============================================================ | |
| cases += [ | |
| # Symbols that look like labels | |
| ['foo(:a)', :ok], | |
| ['foo :a', :ok], | |
| ['foo :a, :b', :ok], | |
| ['{:a => 1}', :ok], | |
| ['{"a": 1}', :ok], # string label | |
| # Label-like in ternary | |
| ['a ? b: 1', :ok], # parsed as ternary a ? b : 1 | |
| ['a ? b : 1', :ok], # proper ternary | |
| ] | |
| # ============================================================ | |
| # I. RESCUE in block/lambda | |
| # ============================================================ | |
| cases += [ | |
| ['proc { 1 rescue 2 }', :ok], | |
| ['proc { foo bar rescue nil }', :ok], | |
| ['-> { 1 rescue 2 }', :ok], | |
| ['-> { foo bar rescue nil }', :ok], | |
| ['[1].map { |x| x rescue nil }', :ok], | |
| ] | |
| # ============================================================ | |
| # J. RESCUE + ASSIGNMENT interactions | |
| # ============================================================ | |
| cases += [ | |
| # Rescue value cannot be a command | |
| ['a = 1 rescue foo bar', :err], # rescue in assignment: value must be arg | |
| ['1 rescue foo bar', :ok], # rescue value can be command | |
| # But rescue expression (LHS) can be a command | |
| ['foo bar rescue nil', :ok], | |
| # Rescue in op-assignment | |
| ['a += 1 rescue nil', :ok], | |
| ['a += foo bar rescue nil', :ok], | |
| ['a ||= foo bar rescue nil', :ok], | |
| ['a &&= foo bar rescue nil', :ok], | |
| ['a.b += 1 rescue nil', :ok], | |
| ['a[0] += 1 rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # K. MODIFIER RESCUE with do blocks | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end rescue nil', :ok], # block_command rescue | |
| ['a = foo bar do end rescue nil', :ok], # assignment of block command rescue | |
| ['foo bar do end rescue nil and 1', :ok], # (block_command rescue nil) and 1 | |
| ['foo bar do end rescue nil + 1', :ok], # rescue(nil + 1) | |
| ] | |
| # ============================================================ | |
| # L. RESCUE value with assignment | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue a = 1', :ok], # rescue(a = 1)? or rescue is stmt-level | |
| ['1 rescue a += 1', :ok], # op-asgn allowed as rescue value | |
| ] | |
| # ============================================================ | |
| # M. LABEL-LIKE tokens in various positions | |
| # ============================================================ | |
| cases += [ | |
| # Keyword arguments in commands | |
| ['foo a: 1', :ok], | |
| ['foo a: 1, b: 2', :ok], | |
| ['foo a: foo bar', :err], # command as keyword value in hash | |
| ['foo(a: foo bar)', :err], # command as keyword value in parens | |
| ['foo(a: foo(bar))', :ok], # paren call ok | |
| # Double-splat with keyword | |
| ['foo(**a)', :ok], | |
| ['foo **a', :ok], | |
| ['foo **a, b: 1', :ok], | |
| ] | |
| # ============================================================ | |
| # N. RESCUE + TERNARY | |
| # ============================================================ | |
| cases += [ | |
| ['a ? 1 : 2 rescue 3', :ok], # (a ? 1 : 2) rescue 3? or a ? 1 : (2 rescue 3)? | |
| ['(1 rescue 2) ? 3 : 4', :ok], | |
| ['1 rescue 2 ? 3 : 4', :ok], # rescue(2 ? 3 : 4) | |
| ] | |
| # ============================================================ | |
| # O. RESCUE + NOT | |
| # ============================================================ | |
| cases += [ | |
| ['!1 rescue nil', :ok], | |
| ['!foo bar rescue nil', :ok], # !(foo bar) rescue nil | |
| ['not 1 rescue nil', :ok], | |
| ['not foo bar rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # P. RESCUE + DEFINED? | |
| # ============================================================ | |
| cases += [ | |
| ['defined? 1 rescue nil', :ok], # defined?(1 rescue nil)? or (defined? 1) rescue nil? | |
| ['defined?(1) rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # Q. RESCUE in conditional | |
| # ============================================================ | |
| cases += [ | |
| ['if 1 rescue nil; end', :err], # rescue not allowed in condition | |
| ['unless 1 rescue nil; end', :err], | |
| ['while 1 rescue nil; break; end', :err], | |
| ['until 1 rescue nil; break; end', :err], | |
| ] | |
| # ============================================================ | |
| # R. LABEL in pattern matching | |
| # ============================================================ | |
| cases += [ | |
| ['case 1; in a:; end', :ok], # label pattern ok | |
| ['case {a: 1}; in {a: 1}; end', :ok], # hash pattern | |
| ['case {a: 1}; in {a:}; end', :ok], # shorthand hash pattern | |
| ['case [1]; in [1]; end', :ok], | |
| ] | |
| # ============================================================ | |
| # S. RESCUE in class/module body | |
| # ============================================================ | |
| cases += [ | |
| ['class A; 1 rescue nil; end', :ok], | |
| ['module A; 1 rescue nil; end', :ok], | |
| ['class A; foo bar rescue nil; end', :ok], | |
| ] | |
| # ============================================================ | |
| # T. RESCUE modifier + method def rescue clause | |
| # ============================================================ | |
| cases += [ | |
| ['def f; 1 rescue nil; end', :ok], # modifier rescue in method | |
| ['def f; 1 rescue nil; rescue; end', :ok], # modifier rescue then rescue clause | |
| ['def f; foo bar rescue nil; end', :ok], | |
| ] | |
| # ============================================================ | |
| # U. RESCUE + YIELD/SUPER | |
| # ============================================================ | |
| cases += [ | |
| ['def f; yield rescue nil; end', :ok], | |
| ['def f; yield 1 rescue nil; end', :ok], | |
| ['def f; yield foo bar rescue nil; end', :ok], | |
| ['super rescue nil', :ok], | |
| ['super 1 rescue nil', :ok], | |
| ['super foo bar rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # V. RESCUE + RETURN/BREAK/NEXT | |
| # ============================================================ | |
| cases += [ | |
| ['return 1 rescue nil', :ok], | |
| ['return foo bar rescue nil', :ok], | |
| ['loop { break 1 rescue nil }', :ok], | |
| ['loop { next 1 rescue nil }', :ok], | |
| ] | |
| # ============================================================ | |
| # W. MODIFIER RESCUE as method argument | |
| # ============================================================ | |
| cases += [ | |
| ['foo(1 rescue 2)', :err], # rescue not allowed in paren args | |
| ['[1 rescue 2]', :err], # rescue in array — not allowed | |
| ['{a: 1 rescue 2}', :err], # rescue in hash value — not allowed | |
| ] | |
| # ============================================================ | |
| # X. LABEL + RESCUE combinations | |
| # ============================================================ | |
| cases += [ | |
| ['foo a: 1 rescue nil', :ok], # command with keyword arg, rescue | |
| ['foo a: 1 rescue nil and 1', :ok], | |
| ['foo a: 1, b: 2 rescue nil', :ok], | |
| ['a = foo a: 1 rescue nil', :ok], | |
| ['a = foo a: 1 rescue nil and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # Y. ENDLESS DEF + LABEL args | |
| # ============================================================ | |
| cases += [ | |
| ['def f = foo a: 1', :ok], | |
| ['def f = foo a: 1, b: 2', :ok], | |
| ['def f = foo a: 1 rescue nil', :ok], | |
| ['def f = foo a: 1 and 1', :err], # command body | |
| ['def f = foo a: 1 or 1', :err], | |
| ] | |
| # ============================================================ | |
| # Z. RESCUE + SPLAT/DOUBLE-SPLAT | |
| # ============================================================ | |
| cases += [ | |
| ['foo *a rescue nil', :ok], | |
| ['foo **a rescue nil', :ok], | |
| ['a = foo *a rescue nil', :ok], | |
| ['a = foo **a rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # A. RESCUE VALUE context differences (stmt vs assignment) | |
| # ============================================================ | |
| cases += [ | |
| # At statement level, rescue value is `stmt` (allows commands) | |
| ['1 rescue foo bar', :ok], | |
| ['1 rescue foo bar and 1', :ok], # (1 rescue (foo bar)) and 1 | |
| ['1 rescue foo bar or 1', :ok], | |
| ['1 rescue foo bar if true', :ok], # (1 rescue (foo bar)) if true? or 1 rescue (foo(bar if true))? | |
| # In assignment, rescue value is `arg` (no commands) | |
| ['a = 1 rescue foo bar', :err], | |
| ['a += 1 rescue foo bar', :err], | |
| ['a.b = 1 rescue foo bar', :err], | |
| ['a[0] = 1 rescue foo bar', :err], | |
| ['A::B = 1 rescue foo bar', :err], | |
| # Assignment rescue value with operators (arg-level) | |
| ['a = 1 rescue 2 + 3', :ok], | |
| ['a = 1 rescue 2 * 3', :ok], | |
| ['a = 1 rescue 2 == 3', :ok], | |
| ['a = 1 rescue a ? b : c', :ok], | |
| ['a = 1 rescue !a', :ok], | |
| ['a = 1 rescue defined? a', :ok], | |
| ] | |
| # ============================================================ | |
| # B. RESCUE in endless def - value context | |
| # ============================================================ | |
| cases += [ | |
| # Non-command body: rescue value is arg-level | |
| ['def f = 1 rescue 2 + 3', :ok], | |
| ['def f = 1 rescue 2 * 3', :ok], | |
| ['def f = 1 rescue a ? b : c', :ok], | |
| # Command body: rescue value is arg-level (MATCH+1 excludes in/=>) | |
| ['def f = foo bar rescue 2 + 3', :ok], | |
| ['def f = foo bar rescue 2 * 3', :ok], | |
| ['def f = foo bar rescue a ? b : c', :ok], | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = foo bar rescue nil in a', :err], # in not allowed in endless_command rescue | |
| ['def f = foo bar rescue nil => a', :err], # => not allowed either | |
| ['def f = foo bar rescue nil and 1', :err], # and not allowed | |
| ['def f = foo bar rescue nil or 1', :err], # or not allowed | |
| # Non-command body: rescue value allows in/=> | |
| ['def f = 1 rescue nil in a', :ok], | |
| ['def f = 1 rescue nil => a', :ok], | |
| ] | |
| # ============================================================ | |
| # C. RESCUE modifier on different statement types | |
| # ============================================================ | |
| cases += [ | |
| ['alias a b rescue nil', :ok], # alias can have rescue modifier | |
| ['undef a rescue nil', :ok], | |
| ['return 1 rescue nil', :ok], | |
| ['loop { break 1 rescue nil }', :ok], | |
| ['loop { next 1 rescue nil }', :ok], | |
| ['END { 1 } rescue nil', :ok], # END can have rescue | |
| ] | |
| # ============================================================ | |
| # D. LABEL in method definitions | |
| # ============================================================ | |
| cases += [ | |
| ['def f(a:) end', :ok], | |
| ['def f(a:, b:) end', :ok], | |
| ['def f(a: 1) end', :ok], | |
| ['def f(a: 1, b: 2) end', :ok], | |
| ['def f(**a) end', :ok], | |
| ['def f(a:, **b) end', :ok], | |
| ] | |
| # ============================================================ | |
| # E. LABEL in lambda/proc params | |
| # ============================================================ | |
| cases += [ | |
| ['-> (a:) { 1 }', :ok], | |
| ['-> (a: 1) { 1 }', :ok], | |
| ['-> (a:, b:) { 1 }', :ok], | |
| ['proc { |a:| 1 }', :ok], | |
| ['proc { |a: 1| 1 }', :ok], | |
| ] | |
| # ============================================================ | |
| # F. RESCUE MODIFIER vs RESCUE clause disambiguation | |
| # ============================================================ | |
| cases += [ | |
| # In method body | |
| ['def f; 1 rescue nil; end', :ok], # modifier rescue | |
| ['def f; 1; rescue; nil; end', :ok], # rescue clause | |
| ['def f; 1 rescue nil; rescue; end', :ok], # both | |
| # In begin block | |
| ['begin; 1 rescue nil; end', :ok], # modifier rescue | |
| ['begin; 1; rescue; nil; end', :ok], # rescue clause | |
| ['begin; 1 rescue nil; rescue; end', :ok], # both | |
| # In class body | |
| ['class A; 1 rescue nil; end', :ok], | |
| ] | |
| # ============================================================ | |
| # G. RESCUE + BLOCK interactions | |
| # ============================================================ | |
| cases += [ | |
| # Rescue modifier on block result | |
| ['foo { 1 } rescue nil', :ok], | |
| ['foo do 1 end rescue nil', :ok], | |
| ['foo bar { 1 } rescue nil', :ok], # command with brace block | |
| ['foo bar do 1 end rescue nil', :ok], # block_command | |
| # Rescue value with block | |
| ['1 rescue foo { 1 }', :ok], | |
| ['1 rescue foo do 1 end', :ok], # do block as rescue value ok at stmt level | |
| ] | |
| # ============================================================ | |
| # H. RESCUE + STRING INTERPOLATION | |
| # ============================================================ | |
| cases += [ | |
| ['"#{1 rescue 2}"', :ok], | |
| ['"#{foo bar rescue nil}"', :ok], | |
| ['"#{a = 1 rescue nil}"', :ok], | |
| ] | |
| # ============================================================ | |
| # I. RESCUE modifier + THEN keyword | |
| # ============================================================ | |
| cases += [ | |
| ['if true then 1 rescue nil end', :ok], | |
| ['unless true then 1 rescue nil end', :ok], | |
| ['case 1; when 1 then 1 rescue nil; end', :ok], | |
| ] | |
| # ============================================================ | |
| # J. LABEL-LIKE in string interpolation | |
| # ============================================================ | |
| cases += [ | |
| ['"#{a: 1}"', :err], # label syntax in interpolation | |
| ['"#{foo a: 1}"', :ok], # command with keyword arg | |
| ['"#{foo a: 1, b: 2}"', :ok], | |
| ] | |
| # ============================================================ | |
| # K. CHAINED RESCUE + LABEL | |
| # ============================================================ | |
| cases += [ | |
| ['foo a: 1 rescue nil', :ok], | |
| ['foo a: 1, b: 2 rescue nil', :ok], | |
| ['foo a: 1 rescue nil rescue nil', :ok], | |
| ['a = foo a: 1 rescue nil', :ok], | |
| ['def f = foo a: 1 rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # L. RESCUE in pattern match context | |
| # ============================================================ | |
| cases += [ | |
| ['case 1; in a; 1 rescue nil; end', :ok], # rescue in pattern body | |
| ['case 1; in a if true; 1 rescue nil; end', :ok], | |
| ] | |
| # ============================================================ | |
| # M. RESCUE + MULTIPLE ASSIGNMENT | |
| # ============================================================ | |
| cases += [ | |
| ['a, b = 1 rescue nil', :ok], # multi-write with rescue | |
| ['a, b = 1, 2 rescue nil', :ok], # multi-write with rescue | |
| ['a = 1, 2 rescue nil', :ok], # implicit array with rescue | |
| ] | |
| # ============================================================ | |
| # N. RESCUE + SPLAT assignment | |
| # ============================================================ | |
| cases += [ | |
| ['*a = 1 rescue nil', :ok], # splat multi-write with rescue | |
| ['a, *b = 1, 2 rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # O. MODIFIER RESCUE then MODIFIER IF | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue nil if true', :ok], # (1 rescue nil) if true | |
| ['foo bar rescue nil if true', :ok], | |
| ['a = 1 rescue nil if true', :ok], | |
| ['a = foo bar rescue nil if true', :ok], | |
| ['def f = 1 rescue nil if true', :ok], # modifier on def | |
| ['def f = foo bar rescue nil if true', :ok], | |
| ] | |
| # ============================================================ | |
| # P. RESCUE + AND/OR chaining | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue nil and 2 rescue nil', :ok], # ((1 rescue nil) and 2) rescue nil? | |
| ['1 rescue nil or 2 rescue nil', :ok], | |
| ['1 rescue nil and 2 or 3', :ok], | |
| ] | |
| # ============================================================ | |
| # Q. LABEL resolution ambiguity | |
| # ============================================================ | |
| cases += [ | |
| # Method call with label arg vs ternary | |
| ['foo a: b ? 1 : 2', :ok], # foo(a: (b ? 1 : 2)) | |
| ['foo a: b', :ok], # foo(a: b) | |
| ['foo a: b, c: d', :ok], | |
| # Label in ternary result | |
| ['true ? {a: 1} : 2', :ok], | |
| ['true ? 1 : {a: 1}', :ok], | |
| ] | |
| # ============================================================ | |
| # R. RESCUE + DEFINED? | |
| # ============================================================ | |
| cases += [ | |
| ['defined? 1 rescue nil', :ok], # defined?(1) rescue nil? or defined?(1 rescue nil)? | |
| ['defined?(1 rescue nil)', :err], # rescue not allowed in parens | |
| ['defined? foo bar rescue nil', :err], # defined? can't take command args | |
| ] | |
| # ============================================================ | |
| # S. RESCUE + NOT combinations | |
| # ============================================================ | |
| cases += [ | |
| ['not 1 rescue nil', :ok], # not(1) rescue nil? or not(1 rescue nil)? | |
| ['not foo bar rescue nil', :ok], | |
| ['!1 rescue nil', :ok], | |
| ['!foo bar rescue nil', :ok], | |
| ['not 1 rescue nil and 1', :ok], # (not(1) rescue nil) and 1 | |
| ['!foo bar rescue nil and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # T. ATTR WRITER + RESCUE | |
| # ============================================================ | |
| cases += [ | |
| ['a.b = 1 rescue nil', :ok], | |
| ['a.b = 1 rescue nil and 1', :ok], | |
| ['a.b = 1 rescue nil if true', :ok], | |
| ['a::b = 1 rescue nil', :ok], | |
| ['a[0] = 1 rescue nil', :ok], | |
| ['a.b += 1 rescue nil', :ok], | |
| ['a.b ||= 1 rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # U. BLOCK CALL + RESCUE | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar do end rescue nil', :ok], | |
| ['foo bar do end rescue nil and 1', :ok], | |
| ['foo bar do end rescue nil if true', :ok], | |
| ['foo bar do end.baz rescue nil', :ok], | |
| ['foo bar do end.baz quux rescue nil', :ok], | |
| ['a = foo bar do end rescue nil', :ok], | |
| ['a = foo bar do end rescue nil and 1', :ok], | |
| ] | |
| # ============================================================ | |
| # V. ENDLESS DEF + BLOCK + RESCUE | |
| # ============================================================ | |
| cases += [ | |
| ['def f = foo(bar) do end rescue nil', :ok], | |
| ['def f = foo(bar) do end', :ok], | |
| ['def f = foo(bar) { }', :ok], | |
| ['def f = foo(bar) { } rescue nil', :ok], | |
| ] | |
| # ============================================================ | |
| # W. RESCUE + RANGE operators | |
| # ============================================================ | |
| cases += [ | |
| ['1 rescue 2..3', :ok], | |
| ['1 rescue 2...3', :ok], | |
| ['a = 1 rescue 2..3', :ok], | |
| ['def f = 1 rescue 2..3', :ok], | |
| ] | |
| # ============================================================ | |
| # X. HASH ROCKET (=>) as pattern vs hash arg | |
| # ============================================================ | |
| cases += [ | |
| ['foo bar => a', :ok], # foo(bar => a) hash arg | |
| ['foo(bar) => a', :ok], # pattern match | |
| ['foo(bar => a)', :ok], # hash arg in parens | |
| ['1 => a', :ok], # pattern match | |
| ['1 + 2 => a', :ok], # pattern match on expression | |
| ['a = 1 => b', :ok], # a = (1 => b) pattern match | |
| ] | |
| # ============================================================ | |
| # Y. MODIFIER RESCUE in for loop | |
| # ============================================================ | |
| cases += [ | |
| ['for a in [1]; 1 rescue nil; end', :ok], | |
| ['for a in [1] rescue nil; end', :err], # rescue on for | |
| ] | |
| # ============================================================ | |
| # Z. DEEPLY NESTED RESCUE + DEF + COMMAND | |
| # ============================================================ | |
| cases += [ | |
| ['def f = def g = 1 rescue nil', :ok], | |
| ['def f = def g = 1 rescue 2 rescue 3', :ok], | |
| ['def f = def g = foo bar', :err], # nested def with command body | |
| ['def f = def g = foo bar rescue nil', :err], # nested def with command body | |
| ['def f = (def g = foo bar)', :ok], # parens make it ok | |
| ['def f = (def g = foo bar rescue nil)', :ok], | |
| ] | |
| # ============================================================ | |
| # A. ENDLESS DEF basic body types | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1', :ok], | |
| ['def f = nil', :ok], | |
| ['def f = true', :ok], | |
| ['def f = "hello"', :ok], | |
| ['def f = :hello', :ok], | |
| ['def f = [1, 2]', :ok], | |
| ['def f = {a: 1}', :ok], | |
| ['def f = -> { 1 }', :ok], | |
| ['def f = self', :ok], | |
| ['def f = __FILE__', :ok], | |
| ['def f = __LINE__', :ok], | |
| ] | |
| # ============================================================ | |
| # B. ENDLESS DEF with operator bodies | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 + 2', :ok], | |
| ['def f = 1 * 2', :ok], | |
| ['def f = 1 == 2', :ok], | |
| ['def f = 1 <=> 2', :ok], | |
| ['def f = 1 .. 2', :ok], | |
| ['def f = 1 ... 2', :ok], | |
| ['def f = !true', :ok], | |
| ['def f = ~1', :ok], | |
| ['def f = -1', :ok], | |
| ['def f = +1', :ok], | |
| ['def f = a ? b : c', :ok], | |
| ['def f = a && b', :ok], | |
| ['def f = a || b', :ok], | |
| ['def f = defined? a', :ok], | |
| ] | |
| # ============================================================ | |
| # C. ENDLESS DEF with and/or (non-command body) | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 and 2', :ok], | |
| ['def f = 1 or 2', :ok], | |
| ['def f = 1 and 2 and 3', :ok], | |
| ['def f = 1 or 2 or 3', :ok], | |
| ['def f = 1 and 2 or 3', :ok], | |
| ['def f = true and false or nil', :ok], | |
| ] | |
| # ============================================================ | |
| # D. ENDLESS DEF with command body + all operators | |
| # ============================================================ | |
| cases += [ | |
| # Command body — only modifiers allowed after def | |
| ['def f = foo bar', :ok], | |
| ['def f = foo bar and 1', :err], | |
| ['def f = foo bar or 1', :err], | |
| ['def f = foo bar if true', :ok], | |
| ['def f = foo bar unless true', :ok], | |
| ['def f = foo bar while true', :ok], | |
| ['def f = foo bar until true', :ok], | |
| ['def f = foo bar rescue nil', :ok], | |
| ['def f = foo bar in a', :err], | |
| ['def f = foo bar => a', :ok], # foo(bar => a) hash arg | |
| # Operators inside command args are fine | |
| ['def f = foo bar + 1', :ok], | |
| ['def f = foo bar * 1', :ok], | |
| ['def f = foo bar == 1', :ok], | |
| ['def f = foo bar, baz', :ok], | |
| ['def f = foo bar, baz and 1', :err], # still command body | |
| ] | |
| # ============================================================ | |
| # E. ENDLESS DEF with receiver | |
| # ============================================================ | |
| cases += [ | |
| ['def self.f = 1', :ok], | |
| ['def self.f = 1 and 2', :ok], | |
| ['def self.f = foo bar', :ok], | |
| ['def self.f = foo bar and 1', :err], | |
| ['def self.f = foo bar or 1', :err], | |
| ['def self.f = foo bar if true', :ok], | |
| ['def self.f = foo bar rescue nil', :ok], | |
| ['def self.f = foo bar rescue nil and 1', :err], | |
| ['def obj.f = 1', :ok], | |
| ['def obj.f = foo bar', :ok], | |
| ['def obj.f = foo bar and 1', :err], | |
| ['def obj.f = foo bar or 1', :err], | |
| ] | |
| # ============================================================ | |
| # F. ENDLESS DEF with parameters | |
| # ============================================================ | |
| cases += [ | |
| ['def f(a) = 1', :ok], | |
| ['def f(a) = a + 1', :ok], | |
| ['def f(a) = foo a', :ok], | |
| ['def f(a) = foo a and 1', :err], | |
| ['def f(a, b) = foo a, b', :ok], | |
| ['def f(a, b) = foo a, b and 1', :err], | |
| ['def f(*a) = foo *a', :ok], | |
| ['def f(**a) = foo **a', :ok], | |
| ['def f(&a) = foo &a', :ok], | |
| ['def f(a:) = foo a: a', :ok], | |
| ['def f(a: 1) = a', :ok], | |
| ] | |
| # ============================================================ | |
| # G. ENDLESS DEF with super/yield body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = super', :ok], | |
| ['def f = super()', :ok], | |
| ['def f = super(1)', :ok], | |
| ['def f = super 1', :ok], # command super | |
| ['def f = super 1 and 2', :err], # command super body | |
| ['def f = super 1 or 2', :err], | |
| ['def f = super bar', :ok], | |
| ['def f = super bar and 1', :err], | |
| ['def f = super bar if true', :ok], | |
| ['def f = super bar rescue nil', :ok], | |
| ['def f = super(bar) and 1', :ok], # paren super = non-command body | |
| ['def f = yield', :ok], | |
| ['def f = yield()', :ok], | |
| ['def f = yield(1)', :ok], | |
| ['def f = yield 1', :ok], # command yield | |
| ['def f = yield 1 and 2', :err], # command yield body | |
| ['def f = yield 1 or 2', :err], | |
| ['def f = yield bar', :ok], | |
| ['def f = yield bar and 1', :err], | |
| ['def f = yield bar if true', :ok], | |
| ['def f = yield bar rescue nil', :ok], | |
| ['def f = yield(bar) and 1', :ok], # paren yield = non-command body | |
| ] | |
| # ============================================================ | |
| # H. ENDLESS DEF with not/! body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = !foo bar', :err], # ! on command in def body → ERR | |
| ['def f = !foo bar and 1', :err], | |
| ['def f = !foo bar if true', :err], | |
| ['def f = not foo bar', :ok], | |
| ['def f = not foo bar and 1', :err], | |
| ['def f = not foo bar if true', :ok], | |
| ['def f = !1', :ok], # non-command | |
| ['def f = !1 and 2', :ok], | |
| ['def f = not 1', :ok], | |
| ['def f = not 1 and 2', :ok], | |
| ] | |
| # ============================================================ | |
| # I. ENDLESS DEF with assignment body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = a = 1', :ok], | |
| ['def f = a = 1 and 2', :ok], # (def f = (a = 1)) and 2 | |
| ['def f = a = foo bar', :err], # command_asgn as body | |
| ['def f = a = foo bar and 1', :err], | |
| ['def f = a = foo bar if true', :err], # command_asgn as body | |
| ['def f = a += 1', :ok], | |
| ['def f = a += foo bar', :err], # command op_asgn body | |
| ['def f = a += foo bar and 1', :err], | |
| ['def f = a ||= foo bar', :err], | |
| ['def f = a &&= foo bar', :err], | |
| ['def f = @a = foo bar', :err], | |
| ['def f = @@a = foo bar', :err], | |
| ['def f = $a = foo bar', :err], | |
| ['def f = A = foo bar', :err], | |
| ['def f = A::B = foo bar', :err], | |
| ['def f = a.b = foo bar', :err], | |
| ['def f = a[0] = foo bar', :err], | |
| ] | |
| # ============================================================ | |
| # J. ENDLESS DEF with block body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = foo(bar) { }', :ok], | |
| ['def f = foo(bar) do end', :ok], | |
| ['def f = foo(bar) { } and 1', :ok], # paren call = non-command | |
| ['def f = foo(bar) do end and 1', :ok], | |
| ['def f = foo { }', :ok], # foo with brace block, no args | |
| ['def f = foo do end', :ok], # foo with do block, no args | |
| ['def f = foo { } and 1', :ok], | |
| ['def f = foo do end and 1', :ok], | |
| ['def f = foo bar { }', :ok], # command with brace block | |
| ['def f = foo bar { } and 1', :err], # command body | |
| ['def f = foo bar do end', :err], # command with do block — parse error | |
| ['def f = foo bar do end and 1', :err], | |
| ] | |
| # ============================================================ | |
| # K. NESTED ENDLESS DEF | |
| # ============================================================ | |
| cases += [ | |
| ['def f = def g = 1', :ok], | |
| ['def f = def g = 1 + 2', :ok], | |
| ['def f = def g = 1 and 2', :ok], # (def f = (def g = 1)) and 2? or def f = (def g = (1 and 2))? | |
| ['def f = def g = foo bar', :err], # inner def has command body | |
| ['def f = def g = foo bar and 1', :err], | |
| ['def f = def g = foo bar rescue nil', :err], | |
| ['def f = def g = 1 rescue nil', :ok], | |
| ['def f = def g = a = foo bar', :err], # inner def has command_asgn body | |
| # Triple nesting | |
| ['def f = def g = def h = 1', :ok], | |
| ['def f = def g = def h = foo bar', :err], | |
| ['def f = def g = def h = 1 and 2', :ok], | |
| # Nested with receiver | |
| ['def f = def self.g = 1', :ok], | |
| ['def f = def self.g = foo bar', :err], | |
| ['def self.f = def g = 1', :ok], | |
| ['def self.f = def g = foo bar', :err], | |
| ['def self.f = def self.g = foo bar', :err], | |
| ] | |
| # ============================================================ | |
| # L. ENDLESS DEF as value in expressions | |
| # ============================================================ | |
| cases += [ | |
| ['a = def f = 1', :ok], | |
| ['a = def f = 1 and 2', :ok], | |
| ['a = def f = foo bar', :ok], # assignment value is def | |
| ['a = def f = foo bar and 1', :err], # command body def in assignment | |
| ['a = def f = foo bar if true', :ok], # modifier on def, assigned | |
| ['[def f = 1]', :ok], | |
| ['[def f = foo bar]', :err], # command body def in array | |
| ['{a: def f = 1}', :ok], | |
| ['{a: def f = foo bar}', :err], # command body def in hash | |
| ['true ? def f = 1 : 2', :ok], | |
| ['true ? def f = foo bar : 2', :err], # command body def in ternary | |
| ['foo(def f = 1)', :ok], | |
| ['foo(def f = foo bar)', :ok], # command body def as arg — ok in parse.y | |
| ] | |
| # ============================================================ | |
| # M. ENDLESS DEF with rescue + various value types | |
| # ============================================================ | |
| cases += [ | |
| # Non-command body rescue | |
| ['def f = 1 rescue nil', :ok], | |
| ['def f = 1 rescue 2 + 3', :ok], | |
| ['def f = 1 rescue a ? b : c', :ok], | |
| ['def f = 1 rescue !a', :ok], | |
| ['def f = 1 rescue [1, 2]', :ok], | |
| ['def f = 1 rescue {a: 1}', :ok], | |
| ['def f = 1 rescue -> { 1 }', :ok], | |
| ['def f = 1 rescue nil in a', :ok], # non-command, rescue(nil in a) ok | |
| ['def f = 1 rescue nil => a', :ok], | |
| # Command body rescue — arg-level only, excludes in/=> | |
| ['def f = foo bar rescue 2 + 3', :ok], | |
| ['def f = foo bar rescue a ? b : c', :ok], | |
| ['def f = foo bar rescue !a', :ok], | |
| ['def f = foo bar rescue nil in a', :err], | |
| ['def f = foo bar rescue nil => a', :err], | |
| ['def f = foo bar rescue nil and 1', :err], | |
| ['def f = foo bar rescue nil or 1', :err], | |
| ] | |
| # ============================================================ | |
| # N. ENDLESS DEF with pattern match body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 in a', :ok], # pattern match as body | |
| ['def f = 1 => a', :ok], | |
| ['def f = 1 + 2 in a', :ok], | |
| ['def f = 1 + 2 => a', :ok], | |
| ['def f = (foo bar) in a', :ok], # parens around command | |
| ['def f = (foo bar) => a', :ok], | |
| ] | |
| # ============================================================ | |
| # O. ENDLESS DEF modifier chaining | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 if true', :ok], | |
| ['def f = 1 unless true', :ok], | |
| ['def f = 1 while true', :ok], | |
| ['def f = 1 until true', :ok], | |
| ['def f = 1 if true if false', :ok], # double modifier | |
| ['def f = 1 if true and 1', :ok], # (def ... if true) and 1 | |
| ['def f = foo bar if true and 1', :ok], # (def ... if true) and 1 | |
| ['def f = foo bar if true or 1', :ok], | |
| ] | |
| # ============================================================ | |
| # P. ENDLESS DEF with rescue + modifier | |
| # ============================================================ | |
| cases += [ | |
| ['def f = 1 rescue nil if true', :ok], | |
| ['def f = 1 rescue nil unless true', :ok], | |
| ['def f = foo bar rescue nil if true', :ok], | |
| ['def f = foo bar rescue nil unless true', :ok], | |
| ['def f = 1 rescue nil and 1', :ok], # non-command body | |
| ['def f = foo bar rescue nil and 1', :err], # command body | |
| ] | |
| # ============================================================ | |
| # Q. ENDLESS DEF with chained calls body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = foo.bar', :ok], | |
| ['def f = foo.bar baz', :ok], # command body | |
| ['def f = foo.bar baz and 1', :err], | |
| ['def f = foo.bar(baz)', :ok], # paren call | |
| ['def f = foo.bar(baz) and 1', :ok], # non-command | |
| ['def f = foo::bar baz', :ok], | |
| ['def f = foo::bar baz and 1', :err], | |
| ['def f = foo&.bar baz', :ok], | |
| ['def f = foo&.bar baz and 1', :err], | |
| ] | |
| # ============================================================ | |
| # R. PRIVATE/PUBLIC wrapping endless def | |
| # ============================================================ | |
| cases += [ | |
| ['private def f = 1', :ok], | |
| ['private def f = 1 and 2', :ok], # and applies to private | |
| ['private def f = foo bar', :ok], | |
| ['private def f = foo bar and 1', :ok], # and applies to private | |
| ['private def f = foo bar or 1', :ok], | |
| ['private def f = foo bar if true', :ok], | |
| ['public def f = foo bar', :ok], | |
| ['protected def f = foo bar', :ok], | |
| # Private with do block | |
| ['private def f = foo(bar) do end', :ok], | |
| ['private def f = foo bar do end', :ok], # do block attaches to private, not def body | |
| # Nested private def | |
| ['private def f = def g = foo bar', :err], # inner command body | |
| ['private def f = def g = 1', :ok], | |
| ['private def f = def g = 1 and 2', :ok], | |
| ] | |
| # ============================================================ | |
| # S. ENDLESS DEF in various statement positions | |
| # ============================================================ | |
| cases += [ | |
| # In if body | |
| ['if true; def f = foo bar; end', :ok], | |
| ['if true; def f = foo bar and 1; end', :err], # command body def, and is rejected | |
| # In while body | |
| ['while true; def f = 1; break; end', :ok], | |
| # In block body | |
| ['proc { def f = foo bar }', :ok], | |
| ['proc { def f = foo bar and 1 }', :err], # command body def, and is rejected | |
| # After semicolon | |
| ['1; def f = foo bar', :ok], | |
| ['1; def f = foo bar and 1', :err], # command body def, and is rejected | |
| ] | |
| # ============================================================ | |
| # T. ENDLESS DEF error recovery | |
| # ============================================================ | |
| cases += [ | |
| ['def f = ;', :err], # no body | |
| ['def f =', :err], # no body, EOF | |
| ['def f = end', :err], # end is not expression | |
| ] | |
| # ============================================================ | |
| # U. ENDLESS DEF with string/regex interpolation body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = "hello #{1}"', :ok], | |
| ['def f = /hello #{1}/', :ok], | |
| ['def f = `hello #{1}`', :ok], | |
| ['def f = "hello" + "world"', :ok], | |
| ['def f = "hello #{foo bar}"', :ok], # command in interpolation ok | |
| ] | |
| # ============================================================ | |
| # V. ENDLESS DEF + BEGIN/END | |
| # ============================================================ | |
| cases += [ | |
| ['def f = begin; 1; end', :ok], | |
| ['def f = begin; 1; rescue; 2; end', :ok], | |
| ['def f = begin; 1; end and 2', :ok], # begin/end is primary | |
| ['def f = begin; foo bar; end', :ok], | |
| ['def f = begin; foo bar; end and 2', :ok], # begin/end is primary | |
| ] | |
| # ============================================================ | |
| # W. ENDLESS DEF + conditional expression body | |
| # ============================================================ | |
| cases += [ | |
| ['def f = if true; 1; end', :ok], | |
| ['def f = if true; 1; else; 2; end', :ok], | |
| ['def f = unless true; 1; end', :ok], | |
| ['def f = case a; when 1; 2; end', :ok], | |
| ['def f = case a; in 1; 2; end', :ok], | |
| ['def f = if true; 1; end and 2', :ok], # conditional is primary | |
| ] | |
| # ============================================================ | |
| # X. ENDLESS DEF with heredoc body | |
| # ============================================================ | |
| cases += [ | |
| ["def f = <<~H\nhello\nH", :ok], | |
| ["def f = foo <<~H\nhello\nH", :ok], | |
| ] | |
| mismatches = [] | |
| cases.each_with_index do |(input, expected, file), i| | |
| file ||= "/tmp/_test_case.rb" | |
| File.write(file, input) | |
| py = `./miniruby --parser=parse.y -c #{file} 2>&1` | |
| pr = `./miniruby --parser=prism -c #{file} 2>&1` | |
| py_ok = py.include?("Syntax OK") | |
| pr_ok = pr.include?("Syntax OK") | |
| if py_ok != pr_ok | |
| mismatches << [i, input, "parse.y=#{py_ok ? "OK" : "ERR"}", "prism=#{pr_ok ? "OK" : "ERR"}", "expected=#{expected}"] | |
| end | |
| if (expected == :ok) != py_ok | |
| mismatches << [i, input, "parse.y=#{py_ok ? "OK" : "ERR"}", "expected=#{expected}", "EXPECTATION WRONG"] | |
| end | |
| end | |
| if mismatches.empty? | |
| puts "ALL #{cases.size} CASES MATCH (both parsers agree and match expectations)" | |
| else | |
| puts "#{mismatches.size} ISSUES:" | |
| mismatches.each do |fields| | |
| puts " ##{fields[0]}: #{fields[1..-1].join(", ")}" | |
| end | |
| exit 1 | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # frozen_string_literal: true | |
| # Generative cross-parser test: systematically generates Ruby snippets from | |
| # parse.y grammar productions and verifies prism and parse.y agree on whether | |
| # each snippet is valid syntax. | |
| # | |
| # Usage: ./miniruby test/prism/generative_parser_test.rb | |
| MINIRUBY = "./miniruby" | |
| TMPFILE = "/tmp/_gen_test_case.rb" | |
| # ============================================================ | |
| # Building blocks: concrete syntax fragments for each grammar | |
| # category. Each fragment is tagged with its grammar level. | |
| # ============================================================ | |
| # primary: literals, variables, paren expressions, blocks, etc. | |
| PRIMARIES = %w[ | |
| 1 nil true false self __FILE__ __LINE__ | |
| ].freeze | |
| PRIMARY_EXPRS = [ | |
| '1', 'nil', 'true', 'false', 'self', '__FILE__', '__LINE__', | |
| '"hello"', ':sym', '[1, 2]', '{a: 1}', '-> { 1 }', | |
| '(1)', '(foo bar)', 'begin; 1; end', | |
| 'if true; 1; end', 'unless true; 1; end', | |
| 'case 1; when 1; 2; end', | |
| 'def f; end', 'class A; end', 'module A; end', | |
| 'foo()', 'foo(1)', 'foo(bar)', | |
| 'a.b', 'a.b()', 'a.b(1)', | |
| 'a::b', 'A::B', | |
| 'super()', 'super(1)', | |
| 'foo(&bar)', 'foo(*a)', 'foo(**a)', | |
| ].freeze | |
| # arg-level expressions (things that can appear as method arguments) | |
| ARG_EXPRS = PRIMARY_EXPRS + [ | |
| '1 + 2', '1 * 2', '1 == 2', '1 <=> 2', '1 && 2', '1 || 2', | |
| '1..2', '1...2', '-1', '+1', '~1', '!1', | |
| 'a ? b : c', 'defined? a', | |
| '1 + 2 * 3', '(1 + 2) * 3', | |
| 'a = 1', 'a += 1', 'a ||= 1', | |
| ] | |
| # command-level expressions (method calls without parens that have args) | |
| COMMANDS = [ | |
| 'foo bar', | |
| 'obj.foo bar', | |
| 'Foo::bar baz', | |
| 'foo bar, baz', | |
| 'foo a: 1', | |
| 'foo a: 1, b: 2', | |
| 'foo *a', | |
| 'foo **a', | |
| 'foo &bar', | |
| 'foo bar, &baz', | |
| ] | |
| # command forms with super/yield | |
| COMMAND_SUPER_YIELD = [ | |
| 'super bar', | |
| 'super bar, baz', | |
| # yield must be inside method, handled separately | |
| ] | |
| # return/break/next (void value) | |
| VOID_COMMANDS = [ | |
| 'return', | |
| 'return 1', | |
| 'return foo bar', | |
| 'return 1, 2', | |
| ] | |
| VOID_COMMANDS_IN_LOOP = [ | |
| 'break', | |
| 'break 1', | |
| 'break foo bar', | |
| 'next', | |
| 'next 1', | |
| 'next foo bar', | |
| ] | |
| # block_command forms | |
| BLOCK_COMMANDS = [ | |
| 'foo bar do end', | |
| 'foo bar do end.baz', | |
| 'foo bar do end::baz', | |
| 'foo bar do end&.baz', | |
| 'foo bar do end.baz quux', | |
| 'foo bar do end.baz do end', | |
| 'foo bar do end.baz { }', | |
| ] | |
| # Operators to append (with their grammar level expectations) | |
| COMPOSITION_OPS = ['and 1', 'or 1'] | |
| MODIFIER_OPS = ['if true', 'unless true', 'while true', 'until true'] | |
| RESCUE_MOD = ['rescue nil'] | |
| MATCH_OPS = ['in [1]', '=> a'] | |
| ARITH_OPS = ['+ 1', '* 1', '== 1', '< 1'] | |
| CALL_OPS = ['.to_s', '::to_s', '&.to_s', '[0]'] | |
| # Assignment LHS forms | |
| ASSIGN_LHS = [ | |
| 'a', '@a', '@@a', '$a', 'A', 'A::B', | |
| 'a.b', 'a[0]', | |
| ] | |
| OP_ASSIGN_OPS = ['+=', '-=', '*=', '||=', '&&='] | |
| # ============================================================ | |
| # Test generation | |
| # ============================================================ | |
| cases = [] | |
| # ------------------------------------ | |
| # 1. Every command form × every trailing operator class | |
| # ------------------------------------ | |
| (COMMANDS + COMMAND_SUPER_YIELD).each do |cmd| | |
| # Commands can be followed by composition and modifiers | |
| COMPOSITION_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{cmd} #{op}" } | |
| # Commands cannot be followed by match or arithmetic | |
| MATCH_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| ARITH_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 2. Every block_command form × every trailing operator class | |
| # ------------------------------------ | |
| BLOCK_COMMANDS.each do |cmd| | |
| COMPOSITION_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{cmd} #{op}" } | |
| MATCH_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| ARITH_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| CALL_OPS.each { |op| cases << "#{cmd}#{op}" } | |
| end | |
| # ------------------------------------ | |
| # 3. Assignment + every command/block form × trailing operators | |
| # ------------------------------------ | |
| ASSIGN_LHS.each do |lhs| | |
| # Simple assignment with commands | |
| COMMANDS.each do |cmd| | |
| cases << "#{lhs} = #{cmd}" | |
| COMPOSITION_OPS.each { |op| cases << "#{lhs} = #{cmd} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{lhs} = #{cmd} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{lhs} = #{cmd} #{op}" } | |
| end | |
| # Simple assignment with block commands | |
| BLOCK_COMMANDS[0..2].each do |cmd| | |
| cases << "#{lhs} = #{cmd}" | |
| COMPOSITION_OPS.each { |op| cases << "#{lhs} = #{cmd} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{lhs} = #{cmd} #{op}" } | |
| ARITH_OPS.each { |op| cases << "#{lhs} = #{cmd} #{op}" } | |
| end | |
| # Assignment with primary expressions (should allow and/or) | |
| cases << "#{lhs} = 1 and 2" | |
| cases << "#{lhs} = 1 or 2" | |
| cases << "#{lhs} = 1 in [1]" | |
| cases << "#{lhs} = 1 => a" | |
| end | |
| # Op-assignment with commands | |
| %w[a @a $a A a.b a[0]].each do |lhs| | |
| OP_ASSIGN_OPS.each do |op| | |
| COMMANDS[0..2].each do |cmd| | |
| cases << "#{lhs} #{op} #{cmd}" | |
| cases << "#{lhs} #{op} #{cmd} and 1" | |
| cases << "#{lhs} #{op} #{cmd} if true" | |
| cases << "#{lhs} #{op} #{cmd} rescue nil" | |
| end | |
| end | |
| end | |
| # ------------------------------------ | |
| # 4. Endless def × every body type × trailing operators | |
| # ------------------------------------ | |
| endless_bodies_ok = [ | |
| '1', '1 + 2', 'a ? b : c', '!1', 'not 1', 'defined? a', | |
| '[1, 2]', '{a: 1}', '-> { 1 }', 'begin; 1; end', | |
| 'if true; 1; end', 'super(1)', 'foo(bar)', | |
| 'a = 1', 'a += 1', | |
| ] | |
| endless_bodies_command = [ | |
| 'foo bar', 'obj.foo bar', 'Foo::bar baz', | |
| 'super bar', 'yield bar', | |
| 'foo a: 1', 'foo *a', 'foo **a', | |
| 'not foo bar', | |
| ] | |
| endless_bodies_command_asgn = [ | |
| 'a = foo bar', 'a += foo bar', 'a ||= foo bar', | |
| '@a = foo bar', '$a = foo bar', | |
| ] | |
| # Non-command bodies: def is an expression, can be followed by and/or/in/etc. | |
| ['def f', 'def self.f', 'def f(a)'].each do |defn| | |
| endless_bodies_ok.each do |body| | |
| cases << "#{defn} = #{body}" | |
| COMPOSITION_OPS.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| end | |
| # Command bodies: def is stmt (command_asgn), only modifiers allowed | |
| endless_bodies_command.each do |body| | |
| cases << "#{defn} = #{body}" | |
| COMPOSITION_OPS.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| end | |
| # Command-assignment bodies: also stmt | |
| endless_bodies_command_asgn.each do |body| | |
| cases << "#{defn} = #{body}" | |
| COMPOSITION_OPS.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{defn} = #{body} #{op}" } | |
| end | |
| end | |
| # ------------------------------------ | |
| # 5. Nested endless defs | |
| # ------------------------------------ | |
| [ | |
| 'def f = def g = 1', | |
| 'def f = def g = foo bar', | |
| 'def f = def g = 1 and 2', | |
| 'def f = def g = foo bar and 1', | |
| 'def f = def g = 1 rescue nil', | |
| 'def f = def g = foo bar rescue nil', | |
| 'def f = def g = def h = 1', | |
| 'def f = def g = def h = foo bar', | |
| 'def self.f = def g = 1', | |
| 'def self.f = def g = foo bar', | |
| 'def f = def self.g = foo bar', | |
| ].each { |c| cases << c } | |
| # ------------------------------------ | |
| # 6. Rescue modifier interactions | |
| # ------------------------------------ | |
| # Statement-level rescue | |
| ['1', 'foo bar', 'foo(bar)', 'a = 1', 'a = foo bar'].each do |expr| | |
| cases << "#{expr} rescue nil" | |
| cases << "#{expr} rescue nil and 1" | |
| cases << "#{expr} rescue nil or 1" | |
| cases << "#{expr} rescue nil if true" | |
| cases << "#{expr} rescue 1 + 2" | |
| end | |
| # Endless def rescue | |
| ['1', 'foo bar'].each do |body| | |
| cases << "def f = #{body} rescue nil" | |
| cases << "def f = #{body} rescue nil and 1" | |
| cases << "def f = #{body} rescue nil or 1" | |
| cases << "def f = #{body} rescue nil in a" | |
| cases << "def f = #{body} rescue nil => a" | |
| cases << "def f = #{body} rescue 1 + 2" | |
| end | |
| # Chained rescue | |
| cases << '1 rescue 2 rescue 3' | |
| cases << 'foo bar rescue 1 rescue 2' | |
| cases << 'def f = 1 rescue 2 rescue 3' | |
| cases << 'def f = foo bar rescue 1 rescue 2' | |
| # ------------------------------------ | |
| # 7. return/break/next × operators | |
| # ------------------------------------ | |
| VOID_COMMANDS.each do |cmd| | |
| COMPOSITION_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{cmd} #{op}" } | |
| end | |
| VOID_COMMANDS_IN_LOOP.each do |cmd| | |
| COMPOSITION_OPS.each { |op| cases << "loop { #{cmd} #{op} }" } | |
| MODIFIER_OPS.each { |op| cases << "loop { #{cmd} #{op} }" } | |
| RESCUE_MOD.each { |op| cases << "loop { #{cmd} #{op} }" } | |
| end | |
| # ------------------------------------ | |
| # 8. yield (inside method) × operators | |
| # ------------------------------------ | |
| ['yield', 'yield 1', 'yield bar', 'yield(1)', 'yield foo bar'].each do |cmd| | |
| COMPOSITION_OPS.each { |op| cases << "def _; #{cmd} #{op}; end" } | |
| MODIFIER_OPS.each { |op| cases << "def _; #{cmd} #{op}; end" } | |
| RESCUE_MOD.each { |op| cases << "def _; #{cmd} #{op}; end" } | |
| MATCH_OPS.each { |op| cases << "def _; #{cmd} #{op}; end" } | |
| end | |
| # ------------------------------------ | |
| # 9. not/! × various args × operators | |
| # ------------------------------------ | |
| ['not 1', 'not foo bar', '!1', '!foo bar', 'not not 1'].each do |expr| | |
| cases << expr | |
| COMPOSITION_OPS.each { |op| cases << "#{expr} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{expr} #{op}" } | |
| MATCH_OPS.each { |op| cases << "#{expr} #{op}" } | |
| end | |
| # not in def body | |
| ['not 1', 'not foo bar', '!1', '!foo bar'].each do |expr| | |
| cases << "def f = #{expr}" | |
| COMPOSITION_OPS.each { |op| cases << "def f = #{expr} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "def f = #{expr} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 10. alias/undef × operators | |
| # ------------------------------------ | |
| ['alias a b', 'undef a'].each do |stmt| | |
| COMPOSITION_OPS.each { |op| cases << "#{stmt} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{stmt} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{stmt} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 11. BEGIN/END blocks × operators | |
| # ------------------------------------ | |
| ['BEGIN { 1 }', 'END { 1 }'].each do |blk| | |
| COMPOSITION_OPS.each { |op| cases << "#{blk} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{blk} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{blk} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 12. Multi-write / implicit array × operators | |
| # ------------------------------------ | |
| ['a, b = 1, 2', 'a = 1, 2', '*a = 1', 'a, *b = 1, 2'].each do |stmt| | |
| COMPOSITION_OPS.each { |op| cases << "#{stmt} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{stmt} #{op}" } | |
| RESCUE_MOD.each { |op| cases << "#{stmt} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 13. Ternary with various branch types | |
| # ------------------------------------ | |
| ['1', 'foo(bar)', 'a = 1', 'def f = 1'].each do |val| | |
| cases << "true ? #{val} : 2" | |
| cases << "true ? 1 : #{val}" | |
| end | |
| ['foo bar', 'return 1', 'alias a b'].each do |val| | |
| cases << "true ? #{val} : 2" | |
| cases << "true ? 1 : #{val}" | |
| end | |
| # ------------------------------------ | |
| # 14. Pattern match (in/=>) with various LHS | |
| # ------------------------------------ | |
| ['1', '1 + 2', 'foo()', 'foo', '(foo bar)', '!1', 'a = 1'].each do |expr| | |
| cases << "#{expr} in a" | |
| cases << "#{expr} => a" | |
| end | |
| # Commands can't use in/=> | |
| ['foo bar', 'super bar'].each do |cmd| | |
| cases << "#{cmd} in a" | |
| cases << "#{cmd} => a" | |
| end | |
| # ------------------------------------ | |
| # 15. defined? edge cases | |
| # ------------------------------------ | |
| ['1', 'a', 'a + 1', 'a.b'].each do |arg| | |
| cases << "defined? #{arg}" | |
| cases << "defined? #{arg} and 1" | |
| end | |
| cases << 'defined? foo bar' | |
| cases << 'defined?(foo) and 1' | |
| # ------------------------------------ | |
| # 16. Array/Hash with various element types | |
| # ------------------------------------ | |
| ['1', 'foo(bar)', 'a = 1', '1 + 2', 'true ? 1 : 2'].each do |val| | |
| cases << "[#{val}]" | |
| cases << "{a: #{val}}" | |
| end | |
| ['foo bar', 'not 1', '1 rescue 2'].each do |val| | |
| cases << "[#{val}]" | |
| cases << "{a: #{val}}" | |
| end | |
| # ------------------------------------ | |
| # 17. String interpolation with various contents | |
| # ------------------------------------ | |
| ['1', 'foo bar', 'foo bar and 1', 'a = foo bar', 'a = foo bar and 1', | |
| 'return 1', 'alias a b'].each do |expr| | |
| cases << '"#{' + expr + '}"' | |
| end | |
| # ------------------------------------ | |
| # 18. Conditional contexts (condition expressions) | |
| # ------------------------------------ | |
| ['1', '1 + 2', 'foo(bar)', 'a = 1', '1 and 2'].each do |expr| | |
| cases << "if #{expr}; end" | |
| cases << "while #{expr}; break; end" | |
| end | |
| # rescue not allowed in conditions | |
| ['1 rescue nil'].each do |expr| | |
| cases << "if #{expr}; end" | |
| cases << "while #{expr}; break; end" | |
| end | |
| # ------------------------------------ | |
| # 19. for loop with various enumerables | |
| # ------------------------------------ | |
| cases << 'for a in [1]; end' | |
| cases << 'for a in foo bar; end' | |
| cases << 'for a in 1..10; end' | |
| # ------------------------------------ | |
| # 20. private/public wrapping endless def | |
| # ------------------------------------ | |
| ['private', 'public', 'protected'].each do |vis| | |
| cases << "#{vis} def f = 1" | |
| cases << "#{vis} def f = foo bar" | |
| cases << "#{vis} def f = foo bar and 1" | |
| cases << "#{vis} def f = foo bar if true" | |
| cases << "#{vis} def f = 1 and 2" | |
| end | |
| # ------------------------------------ | |
| # 21. Endless def with block bodies | |
| # ------------------------------------ | |
| cases += [ | |
| 'def f = foo(bar) { }', | |
| 'def f = foo(bar) do end', | |
| 'def f = foo { }', | |
| 'def f = foo do end', | |
| 'def f = foo(bar) { } and 1', | |
| 'def f = foo(bar) do end and 1', | |
| 'def f = foo bar { }', | |
| 'def f = foo bar { } and 1', | |
| 'def f = foo bar do end', | |
| ] | |
| # ------------------------------------ | |
| # 22. safe navigation commands | |
| # ------------------------------------ | |
| ['a&.foo bar', 'a&.foo bar, baz'].each do |cmd| | |
| cases << cmd | |
| COMPOSITION_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| MATCH_OPS.each { |op| cases << "#{cmd} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 23. Block pass | |
| # ------------------------------------ | |
| cases += [ | |
| 'foo(&bar)', | |
| 'foo &bar', | |
| 'foo &bar and 1', | |
| 'foo &bar if true', | |
| 'foo &bar in a', | |
| 'foo bar, &baz', | |
| 'foo bar, &baz and 1', | |
| 'foo bar, &baz in a', | |
| ] | |
| # ------------------------------------ | |
| # 24. Attribute write followed by operators | |
| # ------------------------------------ | |
| ['a.b = 1', 'a::b = 1', 'a[0] = 1'].each do |write| | |
| COMPOSITION_OPS.each { |op| cases << "#{write} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{write} #{op}" } | |
| MATCH_OPS.each { |op| cases << "#{write} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 25. Op-asgn followed by operators | |
| # ------------------------------------ | |
| ['a += 1', 'a ||= 1', 'a.b += 1', 'a[0] += 1'].each do |write| | |
| COMPOSITION_OPS.each { |op| cases << "#{write} #{op}" } | |
| MODIFIER_OPS.each { |op| cases << "#{write} #{op}" } | |
| MATCH_OPS.each { |op| cases << "#{write} #{op}" } | |
| end | |
| # ------------------------------------ | |
| # 26. Primary expressions used as values (should accept all operators) | |
| # ------------------------------------ | |
| ['begin; 1; end', 'if true; 1; end', 'class A; end', 'def f; end', | |
| 'proc { 1 }', '-> { 1 }', 'super', 'super()'].each do |expr| | |
| COMPOSITION_OPS.each { |op| cases << "#{expr} #{op}" } | |
| MATCH_OPS.each { |op| cases << "#{expr} #{op}" } | |
| ARITH_OPS.each { |op| cases << "#{expr} #{op}" } | |
| end | |
| # Deduplicate | |
| cases.uniq! | |
| # ============================================================ | |
| # Test runner | |
| # ============================================================ | |
| mismatches = [] | |
| cases.each_with_index do |input, i| | |
| File.write(TMPFILE, input) | |
| py = `#{MINIRUBY} --parser=parse.y -c #{TMPFILE} 2>&1` | |
| pr = `#{MINIRUBY} --parser=prism -c #{TMPFILE} 2>&1` | |
| py_ok = py.include?('Syntax OK') | |
| pr_ok = pr.include?('Syntax OK') | |
| if py_ok != pr_ok | |
| mismatches << [i, input, "parse.y=#{py_ok ? 'OK' : 'ERR'}", "prism=#{pr_ok ? 'OK' : 'ERR'}"] | |
| end | |
| end | |
| if mismatches.empty? | |
| puts "ALL #{cases.size} GENERATED CASES MATCH (both parsers agree)" | |
| else | |
| puts "#{mismatches.size} MISMATCHES out of #{cases.size} cases:" | |
| mismatches.each do |fields| | |
| puts " ##{fields[0]}: #{fields[1..].join(', ')}" | |
| end | |
| exit 1 | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment