{% skip_file if flag?(:without_interpreter) %}
require "./spec_helper"

describe Crystal::Repl::Interpreter do
  context "exception handling" do
    it "does ensure without rescue/raise" do
      interpret(<<-CODE).should eq(12)
        x = 1
        y =
          begin
            10
          ensure
            x = 2
          end
        x + y
      CODE
    end

    it "does rescue when nothing is raised" do
      interpret(<<-CODE).should eq(1)
          a = begin
            1
          rescue
            'a'
          end

          if a.is_a?(Int32)
            a
          else
            10
          end
        CODE
    end

    it "raises and rescues anything" do
      interpret(<<-CODE, prelude: "prelude").should eq("2")
          a = begin
            if 1 == 1
              raise "OH NO"
            else
              'a'
            end
          rescue
            2
          end

          if a.is_a?(Int32)
            a
          else
            10
          end
        CODE
    end

    it "raises and rescues anything, does ensure when an exception is rescued" do
      interpret(<<-CODE, prelude: "prelude").should eq("3")
          a = 0
          b = 0

          begin
            raise "OH NO"
          rescue
            a = 1
          ensure
            b = 2
          end

          a + b
        CODE
    end

    it "raises and rescues specific exception type" do
      interpret(<<-CODE, prelude: "prelude").should eq("2")
          class Ex1 < Exception; end
          class Ex2 < Exception; end

          a = 0

          begin
            raise Ex2.new
          rescue Ex1
            a = 1
          rescue Ex2
            a = 2
          end

          a
        CODE
    end

    it "captures exception in variable" do
      interpret(<<-CODE, prelude: "prelude").should eq("10")
          class Ex1 < Exception
            getter value

            def initialize(@value : Int32)
            end
          end

          a = 0

          begin
            raise Ex1.new(10)
          rescue ex : Ex1
            a = ex.value
          end

          a
        CODE
    end

    it "executes ensure when exception is raised in body" do
      interpret(<<-CODE, prelude: "prelude").should eq("10")
          a = 0

          begin
            begin
              raise "OH NO"
            ensure
              a = 10
            end
          rescue
          end

          a
        CODE
    end

    it "executes ensure when exception is raised in rescue" do
      interpret(<<-CODE, prelude: "prelude").should eq("10")
          a = 0

          begin
            begin
              raise "OH NO"
            rescue
              raise "OOPS"
            ensure
              a = 10
            end
          rescue
          end

          a
        CODE
    end

    it "does else" do
      interpret(<<-CODE).should eq(3)
          a =
            begin
              'a'
            rescue
              1
            else
              2
            end

          a + 1
        CODE
    end

    it "does ensure for else" do
      interpret(<<-CODE).should eq(2 + ((1 * 2) + 3))
          x = 1

          a =
            begin
              'a'
            rescue
              1
            else
              x *= 2
              2
            ensure
              x += 3
            end

          a + x
        CODE
    end

    it "does ensure for else when else raises" do
      interpret(<<-CODE, prelude: "prelude").should eq("2")
          x = 1

          begin
            begin
              1
            rescue
              1
            else
              raise "OH NO"
            ensure
              x += 1
            end
          rescue
          end

          x
        CODE
    end

    it "does ensure with explicit return" do
      interpret(<<-CODE).should eq(22)
        module Global
          @@property = 0

          def self.property
            @@property
          end

          def self.property=(@@property)
          end
        end

        def foo
          x = 1

          begin
            begin
              x += 1
              if x == 2
                return x
              end
            ensure
              Global.property = 10
            end
          ensure
            Global.property *= 2
          end

          0
        end

        x = foo
        Global.property + x
      CODE
    end

    it "executes ensure when returning from a block" do
      interpret(<<-CODE).should eq(21)
        module Global
          @@property = 0

          def self.property
            @@property
          end

          def self.property=(@@property)
          end
        end

        def block
          yield
        ensure
          Global.property *= 2
        end

        def foo
          block do
            return 1
          ensure
            Global.property = 10
          end

          0
        end

        x = foo
        Global.property + x
      CODE
    end

    it "executes ensure when returning from a block (2)" do
      interpret(<<-CODE).should eq(21)
        module Global
          @@property = 0

          def self.property
            @@property
          end

          def self.property=(@@property)
          end
        end

        def block
          yield
        ensure
          Global.property *= 2
        end

        def another_block
          yield
        end

        def something
          another_block do
            return
          end
        end

        def foo
          block do
            return 1
          ensure
            something
            Global.property = 10
          end

          0
        end

        x = foo
        Global.property + x
      CODE
    end

    it "executes ensure when breaking from a block" do
      interpret(<<-CODE).should eq(18)
        module Global
          @@property = 0

          def self.property
            @@property
          end

          def self.property=(@@property)
          end
        end

        def block
          yield
        ensure
          Global.property *= 2
        end

        def foo
          x = block do
            break 1
          ensure
            Global.property = 10
          end

          x - 3
        end

        x = foo
        Global.property + x
      CODE
    end

    it "executes ensure when returning a big value from a block" do
      interpret(<<-CODE, prelude: "prelude").should eq("32405")
        module Global
          @@property = 0

          def self.property
            @@property
          end

          def self.property=(@@property)
          end
        end

        def block
          yield
        ensure
          Global.property *= 2
        end

        def foo
          block do
            static_array = StaticArray(Int32, 255).new { |i| i }
            return static_array
          ensure
            Global.property = 10
          end

          nil
        end

        x = foo
        if x
          Global.property + x.sum
        else
          0
        end
      CODE
    end
  end
end
