Skip to content

Instantly share code, notes, and snippets.

@kddnewton
Created March 8, 2026 17:32
Show Gist options
  • Select an option

  • Save kddnewton/5cefcd5ab1e5d361ddc9f8a791a71305 to your computer and use it in GitHub Desktop.

Select an option

Save kddnewton/5cefcd5ab1e5d361ddc9f8a791a71305 to your computer and use it in GitHub Desktop.
Parser compat
#!/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
#!/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