assert('__FILE__') do
  file = __FILE__[-9, 9]
  assert_equal 'syntax.rb', file
end

assert('__LINE__') do
  assert_equal 7, __LINE__
end

assert('super', '11.3.4') do
  assert_raise NoMethodError do
    super
  end

  class SuperFoo
    def foo
      true
    end
    def bar(*a)
      a
    end
  end
  class SuperBar < SuperFoo
    def foo
      super
    end
    def bar(*a)
      super(*a)
    end
  end
  bar = SuperBar.new

  assert_true bar.foo
  assert_equal [1,2,3], bar.bar(1,2,3)
end

assert('yield', '11.3.5') do
# it's syntax error now
#  assert_raise LocalJumpError do
#    yield
#  end
  assert_raise LocalJumpError do
    o = Object.new
    def o.foo
      yield
    end
    o.foo
  end
end

assert('break', '11.5.2.4.3') do
  n = 0
  a = []
  while true
    n += 1
    a.push(n)
    if n > 3
      break
    end
  end

  assert_equal [1,2,3,4], a

  n = 0
  a = []
  6.times do
    n += 1
    a.push(n)
    if n > 3
      break
    end
  end
  assert_equal [1,2,3,4], a

  a = []
  begin
    while true
      a.push 1
      break
      a.push "NG"
    end
  ensure
    a.push 2
  end
  assert_equal [1, 2], a

  a = []
  begin
    while true
      a.push 1
      break
    end
    a.push 2
  ensure
    a.push 3
  end
  assert_equal [1, 2, 3], a

  a = []
  begin
    while true
      begin
        a.push 1
        break
      ensure
        a.push 2
      end
      a.push "NG"
    end
  ensure
    a.push 3
  end
  assert_equal [1, 2, 3], a

  a = []
  begin
    while true
      begin
        a.push 1
        break
      ensure
        a.push 2
      end
      a.push "NG"
    end
    a.push 3
  ensure
    a.push 4
  end
  assert_equal [1, 2, 3, 4], a
end

assert('redo', '11.5.2.4.5') do
  sum = 0
  for i in 1..10
    sum += i
    i -= 1
    if i > 0
      redo
    end
  end

  assert_equal 220, sum

  n = 0
  a = []
  3.times do
    n += 1
    if n == 2
      redo
    end
    a.push(n)
  end
  assert_equal [1,3,4], a

  a = []
  limit = 3
  e = RuntimeError.new("!")
  for i in 0...3
    begin
      limit -= 1
      break unless limit > 0
      a.push i * 3 + 1
      raise e
    rescue
      a.push i * 3 + 2
      redo
    ensure
      a.push i * 3 + 3
    end
  end
  assert_equal [1, 2, 3, 1, 2, 3, 3], a

  a = []
  limit = 3
  e = RuntimeError.new("!")
  for i in 0...3
    a.push i * 4 + 1
    begin
      limit -= 1
      break unless limit > 0
      a.push i * 4 + 2
      raise e
    rescue
      a.push i * 4 + 3
      redo
    ensure
      a.push i * 4 + 4
    end
  end
  assert_equal [1, 2, 3, 4, 1, 2, 3, 4, 1, 4], a
end

assert('Abbreviated variable assignment', '11.4.2.3.2') do
  a ||= 1
  b &&= 1
  c = 1
  c += 2

  assert_equal 1, a
  assert_nil b
  assert_equal 3, c
end

assert('case expression', '11.5.2.2.4') do
  # case-expression-with-expression, one when-clause
  x = 0
  case "a"
  when "a"
    x = 1
  end
  assert_equal 1, x

  # case-expression-with-expression, multiple when-clauses
  x = 0
  case "b"
  when "a"
    x = 1
  when "b"
    x = 2
  end
  assert_equal 2, x

  # no matching when-clause
  x = 0
  case "c"
  when "a"
    x = 1
  when "b"
    x = 2
  end
  assert_equal 0, x

  # case-expression-with-expression, one when-clause and one else-clause
  a = 0
  case "c"
  when "a"
    x = 1
  else
    x = 3
  end
  assert_equal 3, x

  # case-expression-without-expression, one when-clause
  x = 0
  case
  when true
    x = 1
  end
  assert_equal 1, x

  # case-expression-without-expression, multiple when-clauses
  x = 0
  case
  when 0 == 1
    x = 1
  when 1 == 1
    x = 2
  end
  assert_equal 2, x

  # case-expression-without-expression, one when-clause and one else-clause
  x = 0
  case
  when 0 == 1
    x = 1
  else
    x = 3
  end
  assert_equal 3, x

  # multiple when-arguments
  x = 0
  case 4
  when 1, 3, 5
    x = 1
  when 2, 4, 6
    x = 2
  end
  assert_equal 2, x

  # when-argument with splatting argument
  x = :integer
  odds  = [ 1, 3, 5, 7, 9 ]
  evens = [ 2, 4, 6, 8 ]
  case 5
  when *odds
    x = :odd
  when *evens
    x = :even
  end
  assert_equal :odd, x

  true
end

assert('Nested const reference') do
  module Syntax4Const
    CONST1 = "hello world"
    class Const2
      def const1
        CONST1
      end
    end
  end
  assert_equal "hello world", Syntax4Const::CONST1
  assert_equal "hello world", Syntax4Const::Const2.new.const1
  assert_raise(NameError) { Syntax4Const::Object }
end

assert('Abbreviated variable assignment as returns') do
  module Syntax4AbbrVarAsgnAsReturns
    class A
      def b
        @c ||= 1
      end
    end
  end
  assert_equal 1, Syntax4AbbrVarAsgnAsReturns::A.new.b
end

assert('Abbreviated variable assignment of object attribute') do
  module Syntax4AbbrVarAsgnObjectAttr
    class A
      attr_accessor :c
      def b
        self.c ||= 1
      end
    end
  end
  assert_equal 1, Syntax4AbbrVarAsgnObjectAttr::A.new.b
end

assert('Splat and multiple assignment') do
  *a = *[1,2,3]
  b, *c = *[7,8,9]

  assert_equal [1,2,3], a
  assert_equal 7, b
  assert_equal [8,9], c

  (a, b), c = [1,2],3
  assert_equal [1,2,3], [a,b,c]
  (a, b), c = 1,2,3
  assert_equal [1,nil,2], [a,b,c]
end

assert('Splat and multiple assignment from variable') do
  a = [1, 2, 3]
  b, *c = a

  assert_equal 1, b
  assert_equal [2, 3], c
end

assert('Splat and multiple assignment from variables') do
  a = [1, 2, 3]
  b = [4, 5, 6, 7]
  c, d, *e, f, g = *a, *b

  assert_equal 1, c
  assert_equal 2, d
  assert_equal [3, 4, 5], e
  assert_equal 6, f
  assert_equal 7, g
end

assert('Splat and multiple assignment in for') do
  a = [1, 2, 3, 4, 5, 6, 7]
  for b, c, *d, e, f in [a] do
  end

  assert_equal 1, b
  assert_equal 2, c
  assert_equal [3, 4, 5], d
  assert_equal 6, e
  assert_equal 7, f
end

assert('Splat without assignment') do
  * = [0]
  a, * = [1, 2]
  assert_equal 1, a
end

assert('multiple assignment (rest)') do
  *a = 0
  assert_equal [0], a
end

assert('multiple assignment (rest+post)') do
  *a, b = 0, 1, 2
  *c, d = 3

  assert_equal [0, 1], a
  assert_equal 2, b
  assert_equal [], c
  assert_equal 3, d
end

assert('multiple assignment (nosplat array rhs)') do
  a, *b = []
  *c, d = [0]
  e, *f, g = [1, 2]

  assert_nil a
  assert_equal [], b
  assert_equal [], c
  assert_equal 0, d
  assert_equal 1, e
  assert_equal [], f
  assert_equal 2, g
end

assert('multiple assignment (empty array rhs #3236, #3239)') do
  a,b,*c = []; assert_equal [nil, nil, []], [a, b, c]
  a,b,*c = [1]; assert_equal [1, nil, []], [a, b, c]
  a,b,*c = [nil]; assert_equal [nil,nil, []], [a, b, c]
  a,b,*c = [[]]; assert_equal [[], nil, []], [a, b, c]
end

assert('Return values of case statements') do
  a = [] << case 1
  when 3 then 2
  when 2 then 2
  when 1 then 2
  end

  b = [] << case 1
  when 2 then 2
  else
  end

  def fb
    n = 0
    Proc.new do
      n += 1
      case
      when n % 15 == 0
      else n
      end
    end
  end

  assert_equal [2], a
  assert_equal [nil], b
  assert_equal 1, fb.call
end

assert('Return values of if and case statements') do
  true_clause_value =
    if true
      1
    else
      case 2
      when 3
      end
      4
    end

  assert_equal 1, true_clause_value
end

assert('Return values of no expression case statement') do
  when_value =
    case
    when true
      1
    end

  assert_equal 1, when_value
end

assert('splat object in assignment') do
  o = Object.new
  def o.to_a
    nil
  end
  assert_equal [o], (a = *o)

  def o.to_a
    1
  end
  assert_raise(TypeError) { a = *o }

  def o.to_a
    [2]
  end
  assert_equal [2], (a = *o)
end

assert('one-line pattern match') do
  1 => a
  assert_equal(1, a)
end

assert('splat object in case statement') do
  o = Object.new
  def o.to_a
    nil
  end
  a = case o
  when *o
    1
  end
  assert_equal 1, a
end

assert('splat in case statement') do
  values = [3,5,1,7,8]
  testa = [1,2,7]
  testb = [5,6]
  resulta = []
  resultb = []
  resultc = []
  values.each do |value|
    case value
    when *testa
      resulta << value
    when *testb
      resultb << value
    else
      resultc << value
    end
  end

  assert_equal [1,7], resulta
  assert_equal [5], resultb
  assert_equal [3,8], resultc
end

assert('External command execution.') do
  module Kernel
    sym = '`'.to_sym
    alias_method :old_cmd, sym

    results = []
    define_method(sym) do |str|
      results.push str
      str
    end

    `test` # NOVAL NODE_XSTR
    `test dynamic #{sym}` # NOVAL NODE_DXSTR
    assert_equal ['test', 'test dynamic `'], results

    t = `test` # VAL NODE_XSTR
    assert_equal 'test', t
    assert_equal ['test', 'test dynamic `', 'test'], results

    t = `test dynamic #{sym}` # VAL NODE_DXSTR
    assert_equal 'test dynamic `', t
    assert_equal ['test', 'test dynamic `', 'test', 'test dynamic `'], results

    results = []
    assert_equal 'test sym test sym test', `test #{:sym} test #{:sym} test`

    alias_method sym, :old_cmd
  end
  true
end

assert('parenthesed do-block in cmdarg') do
  class ParenDoBlockCmdArg
    def test(block)
      block.call
    end
  end
  x = ParenDoBlockCmdArg.new
  result = x.test (Proc.new do :ok; end)
  assert_equal :ok, result
end

assert('method definition in cmdarg') do
  result = class MethodDefinitionInCmdarg
    def self.bar(arg); arg end
    bar def foo; self.each do end end
  end
  assert_equal(:foo, result)
end

assert('optional argument in the rhs default expressions') do
  class OptArgInRHS
    def foo
      "method called"
    end
    def t(foo = foo)
      foo
    end
    def t2(foo = foo())
      foo
    end
  end
  o = OptArgInRHS.new
  assert_nil(o.t)
  assert_equal("method called", o.t2)
end

assert('optional block argument in the rhs default expressions') do
  assert_nil(Proc.new {|foo = foo| foo}.call)
end

assert('local variable definition in default value and subsequent arguments') do
  def m(a = b = 1, c) [a, b, c] end
  assert_equal([1, 1, :c], m(:c))
  assert_equal([:a, nil, :c], m(:a, :c))

  def m(a = b = 1, &c) [a, b, c ? true : nil] end
  assert_equal([1, 1, nil], m)
  assert_equal([1, 1, true], m{})
  assert_equal([:a, nil, nil], m(:a))
  assert_equal([:a, nil, true], m(:a){})
end

assert('multiline comments work correctly') do
=begin
this is a comment with nothing after begin and end
=end
=begin  this is a comment
this is a comment with extra after =begin
=end
=begin
this is a comment that has =end with spaces after it
=end
=begin this is a comment
this is a comment that has extra after =begin and =end with spaces after it
=end
  line = __LINE__
=begin  this is a comment
this is a comment that has extra after =begin and =end with tabs after it
=end  xxxxxxxxxxxxxxxxxxxxxxxxxx
  assert_equal(line + 4, __LINE__)
end

assert 'keyword arguments' do
  def m(a, b:1) [a, b] end
  assert_equal [1, 1], m(1)
  assert_equal [1, 2], m(1, b: 2)

  def m(a, b:) [a, b] end
  assert_equal [1, 2], m(1, b: 2)
  assert_raise(ArgumentError) { m b: 1 }
  assert_raise(ArgumentError) { m 1 }

  def m(a:) a end
  assert_equal 1, m(a: 1)
  assert_raise(ArgumentError) { m }
  assert_raise(ArgumentError) { m 'a'  => 1, a: 1 }
  h = { a: 1 }
  assert_equal 1, m(**h)

  def m(a: 1) a end
  assert_equal 1, m
  assert_equal 2, m(a: 2)
  assert_raise(ArgumentError) { m 1 }

  def m(**) end
  assert_nil m
  assert_nil m a: 1, b: 2
  assert_raise(ArgumentError) { m 2 }

  def m(a, **) a end
  assert_equal 1, m(1)
  assert_equal 1, m(1, a: 2, b: 3)
  assert_raise(ArgumentError) { m('a' => 1, b: 2) }

  def m(a, **k) [a, k] end
  assert_equal [1, {}], m(1)
  assert_equal [1, {a: 2, b: 3}], m(1, a: 2, b: 3)
  assert_raise(ArgumentError) { m('a' => 1, b: 2) }

  def m(a=1, **) a end
  assert_equal 1, m
  assert_equal 2, m(2, a: 1, b: 0)

  def m(a=1, **k) [a, k] end
  assert_equal [1, {}], m
  assert_equal [1, {a: 1}], m(a: 1)
  assert_equal [2, {a: 1, b: 2}], m(2, a: 1, b: 2)
  assert_equal [{a: 1}, {b: 2}], m({a: 1}, b: 2)
  assert_raise(ArgumentError) { m({a: 1}, {b: 2}) }

  def m1(a: {}) a end
  assert_equal({}, m1)
  assert_equal(:abc, m1(a: :abc))

  def m2(a: +1) a end
  assert_equal(1, m2)

  assert_nothing_raised do
    def m3 arg:
      123
    end
  end
  assert_equal(123, m3(arg: 456))

  def m(*, a:) a end
  assert_equal 1, m(a: 1)
  assert_equal 3, m(1, 2, a: 3)
  assert_raise(ArgumentError) { m('a' => 1, a: 2) }

  def m(*a, b:) [a, b] end
  assert_equal [[], 1], m(b: 1)
  assert_equal [[1, 2], 3], m(1, 2, b: 3)
  assert_raise(ArgumentError) { m('a' => 1, b: 2) }

  def m(*a, b: 1) [a, b] end
  assert_equal [[], 1], m
  assert_equal [[1, 2, 3], 4], m(1, 2, 3, b: 4)
  assert_raise(ArgumentError) { m('a' => 1, b: 2) }

  def m(*, **) end
  assert_nil m()
  assert_nil m(a: 1, b: 2)
  assert_nil m(1, 2, 3, a: 4, b: 5)

  def m(*a, **) a end
  assert_equal [], m()
  assert_equal [1, 2, 3], m(1, 2, 3, a: 4, b: 5)
  assert_equal [1], m(1, **{a: 2})

  def m(*, **k) k end
  assert_equal({}, m())
  assert_equal({a: 4, b: 5}, m(1, 2, 3, a: 4, b: 5))

  def m(a = nil, b = nil, **k) [a, k] end
  assert_equal [nil, {}], m()
  assert_equal([nil, {a: 1}], m(a: 1))
  assert_equal([{"a" => 1}, {a: 1}], m({ "a" => 1 }, a: 1))
  assert_equal([{a: 1}, {}], m({a: 1}, {}))
  assert_equal([{}, {}], m({}))

  def m(*a, **k) [a, k] end
  assert_equal([[], {}], m())
  assert_equal([[1], {}], m(1))
  assert_equal([[], {a: 1, b: 2}], m(a: 1, b: 2))
  assert_equal([[1, 2, 3], {a: 2}], m(1, 2, 3, a: 2))
  assert_equal([[], {a: 1}], m(a: 1))
  assert_equal([[{"a" => 1}], {a: 1}], m({ "a" => 1 }, a: 1))
  assert_equal([[{a: 1}, {}], {}], m({a: 1}, {}))

  def m(a:, b:) [a, b] end
  assert_equal([1, 2], m(a: 1, b: 2))
  assert_raise(ArgumentError) { m("a" => 1, a: 1, b: 2) }

  def m(a:, b: 1) [a, b] end
  assert_equal([1, 1], m(a: 1))
  assert_equal([1, 2], m(a: 1, b: 2))
  assert_raise(ArgumentError) { m(b: 1) }
  assert_raise(ArgumentError) { m("a" => 1, a: 1, b: 2) }

  def m(a:, **) a end
  assert_equal(1, m(a: 1))
  assert_equal(1, m(a: 1, b: 2))

  def m(a:, **k) [a, k] end
  assert_equal([1, {}], m(a: 1))
  assert_equal([1, {b: 2, c: 3}], m(a: 1, b: 2, c: 3))

  def m(a:, &b) [a, b] end
  assert_equal([1, nil], m(a: 1))
  result = m(a: 1, &(l = ->{}))
  assert_equal([1, l], result)

  def m(a: 1, b:) [a, b] end
  assert_equal([1, 0], m(b: 0))
  assert_equal([3, 2], m(b: 2, a: 3))
  assert_raise(ArgumentError) { m a: 1 }

  def m(a: def m(a: 1) a end, b:)
    [a, b]
  end
  assert_equal([2, 3], m(a: 2, b: 3))
  assert_equal([:m, 1], m(b: 1))
  # Note the default value of a: in the original method.
  assert_equal(1, m())

  def m(a: 1, b: 2) [a, b] end
  assert_equal([1, 2], m())
  assert_equal([4, 3], m(b: 3, a: 4))

  def m(a: 1, **) a end
  assert_equal(1, m())
  assert_equal(2, m(a: 2, b: 1))

  def m(a: 1, **k) [a, k] end
  assert_equal([1, {b: 2, c: 3}], m(b: 2, c: 3))

  def m(a:, **) yield end
  assert_raise(ArgumentError) { m { :blk } }
  assert_equal :blk, m(a: 1){ :blk }

  def m(a:, **k, &b) [b.call, k] end
  assert_raise(ArgumentError) { m { :blk } }
  assert_equal [:blk, {b: 2}], m(a: 1, b: 2){ :blk }

  def m(**k, &b) [k, b] end
  assert_equal([{ a: 1, b: 2}, nil], m(a: 1, b: 2))
  assert_equal :blk, m{ :blk }[1].call

=begin
  def m(a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l)
    [a, b, c, d, e, f, g, h, k, l]
  end
  result = m(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{}))
  assert_equal([9, 8, [7], [], 6, 5, 4, 3, {}, l], result)
  def m a, b=1, *c, d, e:, f: 2, g:, **k, &l
    [a, b, c, d, e, f, g, k, l]
  end
  result = m(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{}))
  assert_equal([1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l], result)
=end

  def m(a: b = 1, c:) [a, b, c] end
  assert_equal([1, 1, :c], m(c: :c))
  assert_equal([:a, nil, :c], m(a: :a, c: :c))
end

assert('numbered parameters') do
  assert_equal(15, [1,2,3,4,5].reduce {_1+_2})
  assert_equal(45, Proc.new do _1 + _2 + _3 + _4 + _5 + _6 + _7 + _8 + _9 end.call(*[1, 2, 3, 4, 5, 6, 7, 8, 9]))
  assert_equal(5, -> { _1 }.call(5))
end

assert('_0 is not numbered parameter') do
  _0 = :l
  assert_equal(:l, ->{_0}.call)
end

assert('numbered parameters in symbol name (https://github.com/mruby/mruby/issues/5295)') do
  assert_equal([:_1], Array.new(1) {:_1})
end

assert('numbered parameters as hash key') do
  h = {_1: 3}
  assert_equal(3, h[:_1])
  assert_equal(7, -> { _1 }.call(7))
end

assert('numbered parameters as singleton') do
  o = Object.new
  lambda { def _1.a(b) = "a#{b}" }.call(o)
  assert_equal('ab', o.a('b'))
end

assert('argument forwarding') do
  c = Class.new {
    def a0(*a,&b)
      assert_equal([1,2,3], a)
      assert_not_nil(b)
    end
    def a(...)
      a0(...)
    end
    def b(a,...)
      assert_equal(a,1)
      a0(1,...)
    end
    def c ...
      a(...)
    end
    def d a,...
      assert_equal(a,1)
      b(1,...)
    end
  }
  o = c.new
  o.a(1,2,3){}
  o.b(1,2,3){}
  o.c(1,2,3){}
  o.d(1,2,3){}
end

assert('endless def') do
  c = Class.new {
    def m1 = 42
    def m2() = 42
    def m3(x) = x+1
    def self.s1 = 42
    def self.s2() = 42
    def self.s3(x) = x + 1
    def cm1 = m3 42
    def cm2() = m3 42
    def cm3(x) = m3 x+1
    def self.cs1 = s3 42
    def self.cs2() = s3 42
    def self.cs3(x) = s3 x + 1
  }
  o = c.new
  assert_equal(42, o.m1)
  assert_equal(43, o.m3(o.m2))
  assert_equal(42, c.s1)
  assert_equal(43, c.s3(c.s2))
  assert_equal(43, o.cm1)
  assert_equal(45, o.cm3(o.cm2))
  assert_equal(43, c.cs1)
  assert_equal(45, c.cs3(c.cs2))
end

assert('at-least-once loop') do
  # basic at-least-once loop that executes once
  count = 0
  begin
    count += 1
  end while false
  assert_equal 1, count

  # at-least-once loop with true condition
  count = 0
  begin
    count += 1
  end while count < 3
  assert_equal 3, count

  # at-least-once loop with break
  count = 0
  begin
    count += 1
    break if count == 2
  end while count < 10
  assert_equal 2, count

  # at-least-once loop with next
  count = 0
  sum = 0
  begin
    count += 1
    if count == 2
      next
    end
    sum += count
  end while count < 4
  assert_equal 4, count
  assert_equal 8, sum  # 1 + 3 + 4 = 8 (skipped 2)

  # nested at-least-once loops
  outer_count = 0
  total = 0
  begin
    outer_count += 1
    inner_count = 0
    begin
      inner_count += 1
      total += inner_count
    end while inner_count < 2
  end while outer_count < 2
  assert_equal 2, outer_count
  assert_equal 6, total  # (1+2) + (1+2) = 6

  # at-least-once loop with exception handling
  count = 0
  begin
    count += 1
    raise "error" if count == 2
  rescue
    count += 10
  end while count < 5
  assert_equal 12, count  # 1, then 2+10=12
end

assert('pattern matching - basic case/in') do
  # literal patterns
  result = case 1
  in 1 then :one
  in 2 then :two
  end
  assert_equal :one, result

  # variable binding
  case 42
  in x
    assert_equal 42, x
  end

  # else clause
  result = case 3
  in 1 then :one
  in 2 then :two
  else :other
  end
  assert_equal :other, result
end

assert('pattern matching - array patterns') do
  # simple array pattern
  case [1, 2, 3]
  in [a, b, c]
    assert_equal 1, a
    assert_equal 2, b
    assert_equal 3, c
  end

  # array pattern with rest
  case [1, 2, 3, 4, 5]
  in [first, *rest]
    assert_equal 1, first
    assert_equal [2, 3, 4, 5], rest
  end

  # array pattern with rest in middle
  case [1, 2, 3, 4, 5]
  in [first, *middle, last]
    assert_equal 1, first
    assert_equal [2, 3, 4], middle
    assert_equal 5, last
  end

  # nested array pattern
  case [[1, 2], [3, 4]]
  in [[a, b], [c, d]]
    assert_equal [1, 2, 3, 4], [a, b, c, d]
  end

  # array pattern with literal match
  result = case [1, 2, 3]
  in [1, 2, x]
    x
  end
  assert_equal 3, result
end

assert('pattern matching - find patterns') do
  # find pattern - element at end
  case [1, 2, 3]
  in [*pre, 3, *post]
    assert_equal [1, 2], pre
    assert_equal [], post
  end

  # find pattern - element at beginning
  case [1, 2, 3]
  in [*pre, 1, *post]
    assert_equal [], pre
    assert_equal [2, 3], post
  end

  # find pattern - element in middle
  case [1, 2, 3, 4, 5]
  in [*pre, 3, *post]
    assert_equal [1, 2], pre
    assert_equal [4, 5], post
  end

  # find pattern - multiple middle elements
  case [1, 2, 3, 4, 5]
  in [*pre, 2, 3, *post]
    assert_equal [1], pre
    assert_equal [4, 5], post
  end

  # find pattern - anonymous rest
  result = case [1, 2, 3, 4, 5]
  in [*, 3, *]
    :found
  else
    :not_found
  end
  assert_equal :found, result

  # find pattern - no match
  result = case [1, 2, 4, 5]
  in [*pre, 3, *post]
    :found
  else
    :not_found
  end
  assert_equal :not_found, result

  # find pattern with literal values
  case [1, 2, 3, 4, 5]
  in [*pre, 2, x, *post]
    assert_equal [1], pre
    assert_equal 3, x
    assert_equal [4, 5], post
  end
end

assert('pattern matching - hash patterns') do
  # simple hash pattern
  case {a: 1, b: 2}
  in {a: x, b: y}
    assert_equal 1, x
    assert_equal 2, y
  end

  # shorthand hash pattern
  case {a: 1, b: 2}
  in {a:, b:}
    assert_equal 1, a
    assert_equal 2, b
  end

  # hash pattern with extra keys (partial match)
  case {a: 1, b: 2, c: 3}
  in {a: x}
    assert_equal 1, x
  end

  # hash pattern with rest (captures unmatched keys)
  case {a: 1, b: 2, c: 3}
  in {a:, **rest}
    assert_equal 1, a
    assert_equal({b: 2, c: 3}, rest)
  end

  # hash value extraction
  h = {user: "Alice"}
  case h
  in {user: u}
    assert_equal "Alice", u
  end
end

assert('pattern matching - guard clauses') do
  # if guard
  result = case 10
  in x if x > 5
    :big
  in x
    :small
  end
  assert_equal :big, result

  # unless guard
  result = case 3
  in x unless x > 5
    :small
  in x
    :big
  end
  assert_equal :small, result

  # guard with pattern
  result = case [1, 2, 3]
  in [a, b, c] if a + b + c > 5
    :sum_big
  in [a, b, c]
    :sum_small
  end
  assert_equal :sum_big, result
end

assert('pattern matching - alternative patterns') do
  # simple alternatives
  result = case 2
  in 1 | 2 | 3
    :found
  else
    :not_found
  end
  assert_equal :found, result

  # alternatives in array
  result = case [1, 2]
  in [1, 2] | [3, 4]
    :match
  else
    :no_match
  end
  assert_equal :match, result
end

assert('pattern matching - pin operator') do
  x = 1
  # pin matches exact value
  result = case 1
  in ^x
    :matched
  else
    :not_matched
  end
  assert_equal :matched, result

  # pin in array pattern
  expected = 42
  case [42, 100]
  in [^expected, y]
    assert_equal 100, y
  end

  # pin prevents rebinding
  a = 1
  result = case 2
  in ^a
    :same
  else
    :different
  end
  assert_equal :different, result
end

assert('pattern matching - as pattern') do
  # bind entire match
  case [1, 2, 3]
  in [x, *rest] => whole
    assert_equal 1, x
    assert_equal [2, 3], rest
    assert_equal [1, 2, 3], whole
  end

  # as pattern with hash
  case {a: 1, b: 2}
  in {a:} => h
    assert_equal 1, a
    assert_equal({a: 1, b: 2}, h)
  end
end

assert('pattern matching - one-line in') do
  # basic true/false
  assert_true((1 in 1))
  assert_false((1 in 2))

  # with variable binding
  assert_true(([1, 2] in [x, y]))

  # in conditional
  matched = false
  if [1, 2, 3] in [a, *rest]
    matched = true
  end
  assert_true matched

  # with hash
  assert_true(({a: 1} in {a: 1}))
  assert_false(({a: 1} in {a: 2}))
end

assert('pattern matching - one-line =>') do
  # simple binding
  1 => x
  assert_equal 1, x

  # array destructuring
  [1, 2, 3] => [a, b, c]
  assert_equal [1, 2, 3], [a, b, c]

  # hash destructuring
  {name: "Bob", age: 25} => {name:, age:}
  assert_equal "Bob", name
  assert_equal 25, age

  # with rest
  [1, 2, 3, 4] => [first, *middle, last]
  assert_equal 1, first
  assert_equal [2, 3], middle
  assert_equal 4, last
end

assert('pattern matching - NoMatchingPatternError') do
  # => raises on mismatch
  assert_raise(NoMatchingPatternError) do
    1 => 2
  end

  # can be rescued
  begin
    [1, 2] => [1, 2, 3]
  rescue NoMatchingPatternError => e
    assert_true e.message.is_a?(String)
  end

  # case/in without else raises NoMatchingPatternError
  assert_raise(NoMatchingPatternError) do
    case 5
    in 1 then :one
    in 2 then :two
    end
  end
end

assert('pattern matching - complex patterns') do
  # array of hashes
  records = [{id: 1, value: "a"}, {id: 2, value: "b"}]
  case records
  in [first, second]
    assert_equal({id: 1, value: "a"}, first)
    assert_equal({id: 2, value: "b"}, second)
  end

  # hash with array value
  data = {items: [1, 2, 3]}
  case data
  in {items: [a, *rest]}
    assert_equal 1, a
    assert_equal [2, 3], rest
  end
end

assert('&nil in formal parameters') do
  def m(&nil); end
  m
  m(&nil)
  assert_raise(ArgumentError) { m {} }

  def m2(a, b, &nil); end
  m2(1, 2)
  assert_raise(ArgumentError) { m2(1, 2) {} }

  def m3(a, b=1, *c, &nil); end
  m3(1)
  assert_raise(ArgumentError) { m3(1) {} }

  def m4(a:, &nil); end
  m4(a: 1)
  assert_raise(ArgumentError) { m4(a: 1) {} }

  f = ->(&nil) { :ok }
  assert_equal :ok, f.call
  assert_raise(ArgumentError) { f.call {} }
end
