# class IRB::Irb

### Constants

ASSIGNMENT_NODE_TYPES
ATTR_PLAIN
ATTR_TTY

### Attributes

context[R]

Returns the current context of this irb session

scanner[RW]

The lexer used by this irb session

### Public Class Methods

new(workspace = nil, input_method = nil) click to toggle source

Creates a new irb session

    # File lib/irb.rb, line 472
    def initialize(workspace = nil, input_method = nil)
      @context = Context.new(self, workspace, input_method)
      @context.main.extend ExtendCommandBundle
      @signal_status = :IN_IRB
      @scanner = RubyLex.new
    end

### Public Instance Methods

assignment_expression?(line) click to toggle source

    # File lib/irb.rb, line 875
    def assignment_expression?(line)
      # Try to parse the line and check if the last of possibly multiple
      # expressions is an assignment type.

      # If the expression is invalid, Ripper.sexp should return nil which will
      # result in false being returned. Any valid expression should return an
      # s-expression where the second element of the top level array is an
      # array of parsed expressions. The first element of each expression is the
      # expression's type.
      verbose, $VERBOSE = $VERBOSE, nil
      code = "#{RubyLex.generate_local_variables_assign_code(@context.local_variables) || 'nil;'}\n#{line}"
      # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part.
      node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0)
      ASSIGNMENT_NODE_TYPES.include?(node_type)
    ensure
      $VERBOSE = verbose
    end

convert_invalid_byte_sequence(str, enc) click to toggle source

    # File lib/irb.rb, line 614
    def convert_invalid_byte_sequence(str, enc)
      str.force_encoding(enc)
      str.scrub { |c|
        c.bytes.map{ |b| "\\x#{b.to_s(16).upcase}" }.join
      }
    end

debug_break() click to toggle source

A hook point for ‘debug` command’s `TracePoint` after :IRB_EXIT as well as its clean-up

    # File lib/irb.rb, line 480
    def debug_break
      # it means the debug command is executed
      if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb)
        # after leaving this initial breakpoint, revert the capture_frames patch
        DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb)
        # and remove the redundant method
        DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb)
      end
    end

encode_with_invalid_byte_sequence(str, enc) click to toggle source

    # File lib/irb.rb, line 621
    def encode_with_invalid_byte_sequence(str, enc)
      conv = Encoding::Converter.new(str.encoding, enc)
      dst = String.new
      begin
        ret = conv.primitive_convert(str, dst)
        case ret
        when :invalid_byte_sequence
          conv.insert_output(conv.primitive_errinfo[3].dump[1..-2])
          redo
        when :undefined_conversion
          c = conv.primitive_errinfo[3].dup.force_encoding(conv.primitive_errinfo[1])
          conv.insert_output(c.dump[1..-2])
          redo
        when :incomplete_input
          conv.insert_output(conv.primitive_errinfo[3].dump[1..-2])
        when :finished
        end
        break
      end while nil
      dst
    end

eval_input() click to toggle source

Evaluates input for this session.

    # File lib/irb.rb, line 514
    def eval_input
      exc = nil

      @scanner.set_prompt do
        |ltype, indent, continue, line_no|
        if ltype
          f = @context.prompt_s
        elsif continue
          f = @context.prompt_c
        elsif indent > 0
          f = @context.prompt_n
        else
          f = @context.prompt_i
        end
        f = "" unless f
        if @context.prompting?
          @context.io.prompt = p = prompt(f, ltype, indent, line_no)
        else
          @context.io.prompt = p = ""
        end
        if @context.auto_indent_mode and !@context.io.respond_to?(:auto_indent)
          unless ltype
            prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i
            ind = prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size +
              indent * 2 - p.size
            ind += 2 if continue
            @context.io.prompt = p + " " * ind if ind > 0
          end
        end
        @context.io.prompt
      end

      @scanner.set_input(@context.io, context: @context) do
        signal_status(:IN_INPUT) do
          if l = @context.io.gets
            print l if @context.verbose?
          else
            if @context.ignore_eof? and @context.io.readable_after_eof?
              l = "\n"
              if @context.verbose?
                printf "Use \"exit\" to leave %s\n", @context.ap_name
              end
            else
              print "\n" if @context.prompting?
            end
          end
          l
        end
      end

      @scanner.set_auto_indent(@context) if @context.auto_indent_mode

      @scanner.each_top_level_statement(@context) do |line, line_no|
        signal_status(:IN_EVAL) do
          begin
            line.untaint if RUBY_VERSION < '2.7'
            if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty?
              IRB.set_measure_callback
            end
            # Assignment expression check should be done before @context.evaluate to handle code like `a /2#/ if false; a = 1`
            is_assignment = assignment_expression?(line)
            if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty?
              result = nil
              last_proc = proc{ result = @context.evaluate(line, line_no, exception: exc) }
              IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) { |chain, item|
                _name, callback, arg = item
                proc {
                  callback.(@context, line, line_no, arg, exception: exc) do
                    chain.call
                  end
                }
              }.call
              @context.set_last_value(result)
            else
              @context.evaluate(line, line_no, exception: exc)
            end
            if @context.echo?
              if is_assignment
                if @context.echo_on_assignment?
                  output_value(@context.echo_on_assignment? == :truncate)
                end
              else
                output_value
              end
            end
          rescue Interrupt => exc
          rescue SystemExit, SignalException
            raise
          rescue Exception => exc
          else
            exc = nil
            next
          end
          handle_exception(exc)
          @context.workspace.local_variable_set(:_, exc)
          exc = nil
        end
      end
    end

handle_exception(exc) click to toggle source

    # File lib/irb.rb, line 643
    def handle_exception(exc)
      if exc.backtrace && exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
         !(SyntaxError === exc) && !(EncodingError === exc)
        # The backtrace of invalid encoding hash (ex. {"\xAE": 1}) raises EncodingError without lineno.
        irb_bug = true
      else
        irb_bug = false
      end

      if exc.backtrace
        order = nil
        if RUBY_VERSION < '3.0.0'
          if STDOUT.tty?
            message = exc.full_message(order: :bottom)
            order = :bottom
          else
            message = exc.full_message(order: :top)
            order = :top
          end
        else # '3.0.0' <= RUBY_VERSION
          message = exc.full_message(order: :top)
          order = :top
        end
        message = convert_invalid_byte_sequence(message, exc.message.encoding)
        message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s)
        message = message.gsub(/((?:^\t.+$\n)+)/)  { |m|
          case order
          when :top
            lines = m.split("\n")
          when :bottom
            lines = m.split("\n").reverse
          end
          unless irb_bug
            lines = lines.map { |l| @context.workspace.filter_backtrace(l) }.compact
            if lines.size > @context.back_trace_limit
              omit = lines.size - @context.back_trace_limit
              lines = lines[0..(@context.back_trace_limit - 1)]
              lines << "\t... %d levels..." % omit
            end
          end
          lines = lines.reverse if order == :bottom
          lines.map{ |l| l + "\n" }.join
        }
        # The "<top (required)>" in "(irb)" may be the top level of IRB so imitate the main object.
        message = message.gsub(/\(irb\):(?<num>\d+):in `<(?<frame>top \(required\))>'/)  { "(irb):#{$~[:num]}:in `<main>'" }
        puts message
      end
      print "Maybe IRB bug!\n" if irb_bug
    end

inspect() click to toggle source

Outputs the local variables to this current session, including `signal_status` and `context`, using IRB::Locale.

    # File lib/irb.rb, line 860
    def inspect
      ary = []
      for iv in instance_variables
        case (iv = iv.to_s)
        when "@signal_status"
          ary.push format("%s=:%s", iv, @signal_status.id2name)
        when "@context"
          ary.push format("%s=%s", iv, eval(iv).__to_s__)
        else
          ary.push format("%s=%s", iv, eval(iv))
        end
      end
      format("#<%s: %s>", self.class, ary.join(", "))
    end

run(conf = IRB.conf) click to toggle source

    # File lib/irb.rb, line 490
    def run(conf = IRB.conf)
      conf[:IRB_RC].call(context) if conf[:IRB_RC]
      conf[:MAIN_CONTEXT] = context

      prev_trap = trap("SIGINT") do
        signal_handle
      end

      begin
        catch(:IRB_EXIT) do
          eval_input
        end
      ensure
        trap("SIGINT", prev_trap)
        conf[:AT_EXIT].each{|hook| hook.call}
      end
    end

signal_handle() click to toggle source

Handler for the signal SIGINT, see `Kernel#trap` for more information.

    # File lib/irb.rb, line 749
    def signal_handle
      unless @context.ignore_sigint?
        print "\nabort!\n" if @context.verbose?
        exit
      end

      case @signal_status
      when :IN_INPUT
        print "^C\n"
        raise RubyLex::TerminateLineInput
      when :IN_EVAL
        IRB.irb_abort(self)
      when :IN_LOAD
        IRB.irb_abort(self, LoadAbort)
      when :IN_IRB
        # ignore
      else
        # ignore other cases as well
      end
    end

signal_status(status) { || ... } click to toggle source

Evaluates the given block using the given `status`.

    # File lib/irb.rb, line 771
    def signal_status(status)
      return yield if @signal_status == :IN_LOAD

      signal_status_back = @signal_status
      @signal_status = status
      begin
        yield
      ensure
        @signal_status = signal_status_back
      end
    end

suspend_context(context) { |back_context| ... } click to toggle source

Evaluates the given block using the given `context` as the `Context`.

    # File lib/irb.rb, line 739
    def suspend_context(context)
      @context, back_context = context, @context
      begin
        yield back_context
      ensure
        @context = back_context
      end
    end

suspend_input_method(input_method) { |back_io| ... } click to toggle source

Evaluates the given block using the given `input_method` as the `Context#io`.

Used by the irb commands `source` and `irb_load`, see IRB Sessions at `IRB` for more information.

    # File lib/irb.rb, line 728
    def suspend_input_method(input_method)
      back_io = @context.io
      @context.instance_eval{@io = input_method}
      begin
        yield back_io
      ensure
        @context.instance_eval{@io = back_io}
      end
    end

suspend_name(path = nil, name = nil) { |back_path, back_name| ... } click to toggle source

Evaluates the given block using the given `path` as the `Context#irb_path` and `name` as the `Context#irb_name`.

Used by the irb command `source`, see IRB Sessions at `IRB` for more information.

    # File lib/irb.rb, line 698
    def suspend_name(path = nil, name = nil)
      @context.irb_path, back_path = path, @context.irb_path if path
      @context.irb_name, back_name = name, @context.irb_name if name
      begin
        yield back_path, back_name
      ensure
        @context.irb_path = back_path if path
        @context.irb_name = back_name if name
      end
    end

suspend_workspace(workspace) { |back_workspace| ... } click to toggle source

Evaluates the given block using the given `workspace` as the `Context#workspace`.

Used by the irb command `irb_load`, see IRB Sessions at `IRB` for more information.

    # File lib/irb.rb, line 714
    def suspend_workspace(workspace)
      @context.workspace, back_workspace = workspace, @context.workspace
      begin
        yield back_workspace
      ensure
        @context.workspace = back_workspace
      end
    end