lib/opal/nodes/rescue.rb



require 'opal/nodes/base'

module Opal
  module Nodes
    class RescueModNode < Base
      handle :rescue_mod

      children :lhs, :rhs

      def body
        stmt? ? lhs : compiler.returns(lhs)
      end

      def rescue_val
        stmt? ? rhs : compiler.returns(rhs)
      end

      def compile
        push "try {", expr(body), " } catch ($err) { ", expr(rescue_val), " }"

        wrap '(function() {', '})()' unless stmt?
      end
    end

    class EnsureNode < Base
      handle :ensure

      children :begn, :ensr

      def compile
        push "try {"
        line compiler.process(body_sexp, @level)
        line "} finally {"
        line compiler.process(ensr_sexp, @level)
        line "}"

        wrap '(function() {', '; })()' if wrap_in_closure?
      end

      def body_sexp
        wrap_in_closure? ? compiler.returns(begn) : begn
      end

      def ensr_sexp
        ensr || s(:nil)
      end

      def wrap_in_closure?
        recv? or expr?
      end
    end

    class RescueNode < Base
      handle :rescue

      children :body

      def compile
        handled_else = false

        push "try {"
        line(indent { process(body_code, @level) })
        line "} catch ($err) {"

        children[1..-1].each_with_index do |child, idx|
          handled_else = true unless child.type == :resbody

          push "else " unless idx == 0
          push(indent { process(child, @level) })
        end

        # if no resbodys capture our error, then rethrow
        unless handled_else
          push "else { throw $err; }"
        end

        line "}"

        wrap '(function() { ', '})()' if expr?
      end

      def body_code
        body_code = (body.type == :resbody ? s(:nil) : body)

        if !stmt?
          compiler.returns body_code
        else
          body_code
        end
      end
    end

    class ResBodyNode < Base
      handle :resbody

      children :args, :body

      def compile
        push "if ("
        if rescue_exprs.empty?
          # if no expressions are given, then catch all errors
          push "true"
        else
          push "Opal.rescue($err, ["
          rescue_exprs.each_with_index do |rexpr, idx|
            push ', ' unless idx == 0
            push expr(rexpr)
          end
          push "])"
        end
        push ") {"

        if variable = rescue_variable
          variable[2] = s(:js_tmp, '$err')
          push expr(variable), ';'
        end

        # Need to ensure we clear the current exception out after the rescue block ends
        line "try {"
        indent do
          line process(rescue_body, @level)
        end
        line "} finally {"
        indent do
          line 'Opal.gvars["!"] = Opal.exceptions.pop() || Opal.nil;'
        end
        line "}"
        line "}"
      end

      def rescue_variable?(variable)
        Sexp === variable and [:lasgn, :iasgn].include?(variable.type)
      end

      def rescue_variable
        rescue_variable?(args.last) ? args.last.dup : nil
      end

      def rescue_exprs
        exprs = args.dup
        exprs.pop if rescue_variable?(exprs.last)
        exprs.children
      end

      def rescue_body
        body_code = (body || s(:nil))
        body_code = compiler.returns(body_code) if !stmt?
        body_code
      end
    end
  end
end