require_relative '../spec_helper'
require_relative 'fixtures/precedence'

# Specifying the behavior of operators in combination could
# lead to combinatorial explosion. A better way seems to be
# to use a technique from formal proofs that involve a set of
# equivalent statements. Suppose you have statements A, B, C.
# If they are claimed to be equivalent, this can be shown by
# proving that A implies B, B implies C, and C implies A.
# (Actually any closed circuit of implications.)
#
# Here, we can use a similar technique where we show starting
# at the top that each level of operator has precedence over
# the level below (as well as showing associativity within
# the precedence level).

# Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide'
# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324
#
# Table 22.4. Ruby operators (high to low precedence)
# Method   Operator                  Description
# -----------------------------------------------------------------------
#          :: .
#  x*      [ ] [ ]=                  Element reference, element set
#  x       **                        Exponentiation
#  x       ! ~ + -                   Not, complement, unary plus and minus
#                                    (method names for the last two are +@ and -@)
#  x       * / %                     Multiply, divide, and modulo
#  x       + -                       Plus and minus
#  x       >> <<                     Right and left shift
#  x       &                         “And” (bitwise for integers)
#  x       ^ |                       Exclusive “or” and regular “or” (bitwise for integers)
#  x       <= < > >=                 Comparison operators
#  x       <=> == === != =~ !~       Equality and pattern match operators (!=
#                                    and !~ may not be defined as methods)
#          &&                        Logical “and”
#          ||                        Logical “or”
#          .. ...                    Range (inclusive and exclusive)
#          ? :                       Ternary if-then-else
#          = %= /= -= += |= &=       Assignment
#          >>= <<= *= &&= ||= **=
#          defined?                  Check if symbol defined
#          not                       Logical negation
#          or and                    Logical composition
#          if unless while until     Expression modifiers
#          begin/end                 Block expression
# -----------------------------------------------------------------------
#
# * Operators marked with 'x' in the Method column are implemented as methods
# and can be overridden (except != and !~ as noted). (But see the specs
# below for implementations that define != and !~ as methods.)
#
# ** These are not included in the excerpted table but are shown here for
# completeness.

# -----------------------------------------------------------------------
# It seems that this table is not correct anymore
# The correct table derived from MRI's parse.y is as follows:
#
# Operator              Assoc    Description
#---------------------------------------------------------------
# ! ~ +                   >      Not, complement, unary plus
# **                      >      Exponentiation
# -                       >      Unary minus
# * / %                   <      Multiply, divide, and modulo
# + -                     <      Plus and minus
# >> <<                   <      Right and left shift
# &                       <      “And” (bitwise for integers)
# ^ |                     <      Exclusive “or” and regular “or” (bitwise for integers)
# <= < > >=               <      Comparison operators
# <=> == === != =~ !~     no     Equality and pattern match operators (!=
#                                and !~ may not be defined as methods)
# &&                      <      Logical “and”
# ||                      <      Logical “or”
# .. ...                  no     Range (inclusive and exclusive)
# ? :                     >      Ternary if-then-else
# rescue                  <      Rescue modifier
# = %= /= -= += |= &=     >      Assignment
# >>= <<= *= &&= ||= **=
# defined?                no     Check if symbol defined
# not                     >      Logical negation
# or and                  <      Logical composition
# if unless while until   no     Expression modifiers
# -----------------------------------------------------------------------
#
# [] and []= seem to fall out of here, as well as begin/end
#

# TODO: Resolve these two tables with actual specs. As the comment at the
# top suggests, these specs need to be reorganized into a single describe
# block for each operator. The describe block should include an example
# for associativity (if relevant), an example for any short circuit behavior
# (e.g. &&, ||, etc.) and an example block for each operator over which the
# instant operator has immediately higher precedence.

describe "Operators" do
  it "! ~ + is right-associative" do
    (!!true).should == true
    (~~0).should == 0
    (++2).should == 2
  end

  it "** is right-associative" do
    (2**2**3).should == 256
  end

  it "** has higher precedence than unary minus" do
    (-2**2).should == -4
  end

  it "unary minus is right-associative" do
    (--2).should == 2
  end

  it "unary minus has higher precedence than * / %" do
    class UnaryMinusTest; def -@; 50; end; end
    b = UnaryMinusTest.new

    (-b * 5).should == 250
    (-b / 5).should == 10
    (-b % 7).should == 1
  end

  it "treats +/- as a regular send if the arguments are known locals or block locals" do
    a = PrecedenceSpecs::NonUnaryOpTest.new
    a.add_num(1).should == [3]
    a.sub_num(1).should == [1]
    a.add_str.should == ['11']
    a.add_var.should == [2]
  end

  it "* / % are left-associative" do
    (2*1/2).should == (2*1)/2
    # TODO: Make these specs not rely on specific behaviour / result values
    # by using mocks.
    (2*1/2).should_not == 2*(1/2)

    (10/7/5).should == (10/7)/5
    (10/7/5).should_not == 10/(7/5)

    (101 % 55 % 7).should == (101 % 55) % 7
    (101 % 55 % 7).should_not == 101 % (55 % 7)

    (50*20/7%42).should == ((50*20)/7)%42
    (50*20/7%42).should_not == 50*(20/(7%42))
  end

  it "* / % have higher precedence than + -" do
    (2+2*2).should == 6
    (1+10/5).should == 3
    (2+10%5).should == 2

    (2-2*2).should == -2
    (1-10/5).should == -1
    (10-10%4).should == 8
  end

  it "+ - are left-associative" do
    (2-3-4).should == -5
    (4-3+2).should == 3

    binary_plus = Class.new(String) do
      alias_method :plus, :+
      def +(a)
        plus(a) + "!"
      end
    end
    s = binary_plus.new("a")

    (s+s+s).should == (s+s)+s
    (s+s+s).should_not == s+(s+s)
  end

  it "+ - have higher precedence than >> <<" do
    (2<<1+2).should == 16
    (8>>1+2).should == 1
    (4<<1-3).should == 1
    (2>>1-3).should == 8
  end

  it ">> << are left-associative" do
    (1 << 2 << 3).should == 32
    (10 >> 1 >> 1).should == 2
    (10 << 4 >> 1).should == 80
  end

  it ">> << have higher precedence than &" do
    (4 & 2 << 1).should == 4
    (2 & 4 >> 1).should == 2
  end

  it "& is left-associative" do
    class BitwiseAndTest; def &(a); a+1; end; end
    c = BitwiseAndTest.new

    (c & 5 & 2).should == (c & 5) & 2
    (c & 5 & 2).should_not == c & (5 & 2)
  end

  it "& has higher precedence than ^ |" do
    (8 ^ 16 & 16).should == 24
    (8 | 16 & 16).should == 24
  end

  it "^ | are left-associative" do
    class OrAndXorTest; def ^(a); a+10; end; def |(a); a-10; end; end
    d = OrAndXorTest.new

    (d ^ 13 ^ 16).should == (d ^ 13) ^ 16
    (d ^ 13 ^ 16).should_not == d ^ (13 ^ 16)

    (d | 13 | 4).should == (d | 13) | 4
    (d | 13 | 4).should_not == d | (13 | 4)
  end

  it "^ | have higher precedence than <= < > >=" do
    (10 <= 7 ^ 7).should == false
    (10 < 7 ^ 7).should == false
    (10 > 7 ^ 7).should == true
    (10 >= 7 ^ 7).should == true
    (10 <= 7 | 7).should == false
    (10 < 7 | 7).should == false
    (10 > 7 | 7).should == true
    (10 >= 7 | 7).should == true
  end

  it "<= < > >= are left-associative" do
    class ComparisonTest
      def <=(a); 0; end;
      def <(a);  0; end;
      def >(a);  0; end;
      def >=(a); 0; end;
    end

    e = ComparisonTest.new

    (e <= 0 <= 1).should == (e <= 0) <= 1
    (e <= 0 <= 1).should_not == e <= (0 <= 1)

    (e < 0 < 1).should == (e < 0) < 1
    (e < 0 < 1).should_not == e < (0 < 1)

    (e >= 0 >= 1).should == (e >= 0) >= 1
    (e >= 0 >= 1).should_not == e >= (0 >= 1)

    (e > 0 > 1).should == (e > 0) > 1
    (e > 0 > 1).should_not == e > (0 > 1)
  end

  it "<=> == === != =~ !~ are non-associative" do
    -> { eval("1 <=> 2 <=> 3")  }.should.raise(SyntaxError)
    -> { eval("1 == 2 == 3")  }.should.raise(SyntaxError)
    -> { eval("1 === 2 === 3")  }.should.raise(SyntaxError)
    -> { eval("1 != 2 != 3")  }.should.raise(SyntaxError)
    -> { eval("1 =~ 2 =~ 3")  }.should.raise(SyntaxError)
    -> { eval("1 !~ 2 !~ 3")  }.should.raise(SyntaxError)
  end

  it "<=> == === != =~ !~ have higher precedence than &&" do
    (false && 2 <=> 3).should == false
    (false && 3 == false).should == false
    (false && 3 === false).should == false
    (false && 3 != true).should == false

    class FalseClass; def =~(o); o == false; end; end
    (false && true =~ false).should == (false && (true =~ false))
    (false && true =~ false).should_not == ((false && true) =~ false)
    class FalseClass; undef_method :=~; end

    (false && true !~ true).should == false
  end

  # XXX: figure out how to test it
  # (a && b) && c equals to a && (b && c) for all a,b,c values I can imagine so far
  it "&& is left-associative"

  it "&& has higher precedence than ||" do
    (true || false && false).should == true
  end

  # XXX: figure out how to test it
  it "|| is left-associative"

  it "|| has higher precedence than .. ..." do
    (1..false||10).should == (1..10)
    (1...false||10).should == (1...10)
  end

  it ".. ... are non-associative" do
    -> { eval("1..2..3")  }.should.raise(SyntaxError)
    -> { eval("1...2...3")  }.should.raise(SyntaxError)
  end

  it ".. ... have higher precedence than ? :" do
    # Use variables to avoid warnings
    from = 1
    to = 2
    # These are flip-flop, not Range instances
    (from..to ? 3 : 4).should == 3
    (from...to ? 3 : 4).should == 3
  end

  it "? : is right-associative" do
    (true ? 2 : 3 ? 4 : 5).should == 2
  end

  def oops; raise end

  it "? : has higher precedence than rescue" do
    (true ? oops : 0 rescue 10).should == 10
  end

  # XXX: figure how to test it (problem similar to || associativity)
  it "rescue is left-associative"

  it "rescue has higher precedence than =" do
    a = oops rescue 10
    a.should == 10

    # rescue doesn't have the same sense for %= /= and friends
  end

  it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= are right-associative" do
    a = b = 10
    a.should == 10
    b.should == 10

    a = b = 10
    a %= b %= 3
    a.should == 0
    b.should == 1

    a = b = 10
    a /= b /= 2
    a.should == 2
    b.should == 5

    a = b = 10
    a -= b -= 2
    a.should == 2
    b.should == 8

    a = b = 10
    a += b += 2
    a.should == 22
    b.should == 12

    a,b = 32,64
    a |= b |= 2
    a.should == 98
    b.should == 66

    a,b = 25,13
    a &= b &= 7
    a.should == 1
    b.should == 5

    a,b=8,2
    a >>= b >>= 1
    a.should == 4
    b.should == 1

    a,b=8,2
    a <<= b <<= 1
    a.should == 128
    b.should == 4

    a,b=8,2
    a *= b *= 2
    a.should == 32
    b.should == 4

    a,b=10,20
    a &&= b &&= false
    a.should == false
    b.should == false

    a,b=nil,nil
    a ||= b ||= 10
    a.should == 10
    b.should == 10

    a,b=2,3
    a **= b **= 2
    a.should == 512
    b.should == 9
  end

  it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= have higher precedence than defined? operator" do
    (defined? a =   10).should == "assignment"
    (defined? a %=  10).should == "assignment"
    (defined? a /=  10).should == "assignment"
    (defined? a -=  10).should == "assignment"
    (defined? a +=  10).should == "assignment"
    (defined? a |=  10).should == "assignment"
    (defined? a &=  10).should == "assignment"
    (defined? a >>= 10).should == "assignment"
    (defined? a <<= 10).should == "assignment"
    (defined? a *=  10).should == "assignment"
    (defined? a &&= 10).should == "assignment"
    (defined? a ||= 10).should == "assignment"
    (defined? a **= 10).should == "assignment"
  end

  # XXX: figure out how to test it
  it "defined? is non-associative"

  it "defined? has higher precedence than not" do
    # does it have sense?
    (not defined? qqq).should == true
  end

  it "not is right-associative" do
    (not not false).should == false
    (not not 10).should == true
  end

  it "not has higher precedence than or/and" do
    (not false and false).should == false
    (not false or true).should == true
  end

  # XXX: figure out how to test it
  it "or/and are left-associative"

  it "or/and have higher precedence than if unless while until modifiers" do
    (1 if 2 and 3).should == 1
    (1 if 2 or 3).should == 1

    (1 unless false and true).should == 1
    (1 unless false or false).should == 1

    (1 while true and false).should == nil    # would hang upon error
    (1 while false or false).should == nil

    ((raise until true and false) rescue 10).should == 10
    (1 until false or true).should == nil    # would hang upon error
  end

  # XXX: it seems to me they are right-associative
  it "if unless while until are non-associative"
end
