123456789_123456789_123456789_123456789_123456789_

Class: IRB::Irb

Relationships & Source Files
Inherits: Object
Defined in: lib/irb.rb,
lib/irb/ext/multi-irb.rb

Constant Summary

  • ASSIGNMENT_NODE_TYPES =
    # File 'lib/irb.rb', line 442
    [
      # Local, instance, global, class, constant, instance, and index assignment:
      #   "foo = bar",
      #   "@foo = bar",
      #   "$foo = bar",
      #   "@@foo = bar",
      #   "::Foo = bar",
      #   "a::Foo = bar",
      #   "Foo = bar"
      #   "foo.bar = 1"
      #   "foo[1] = bar"
      :assign,
    
      # Operation assignment:
      #   "foo += bar"
      #   "foo -= bar"
      #   "foo ||= bar"
      #   "foo &&= bar"
      :opassign,
    
      # Multiple assignment:
      #   "foo, bar = 1, 2
      :massign,
    ]
  • ATTR_PLAIN =
    # File 'lib/irb.rb', line 895
    ""
  • ATTR_TTY =
    # File 'lib/irb.rb', line 893
    "\e[%sm"

Class Method Summary

Instance Attribute Summary

  • #context readonly

    Returns the current context of this irb session.

  • #scanner rw

    The lexer used by this irb session.

Instance Method Summary

Constructor Details

.new(workspace = nil, input_method = nil) ⇒ Irb

Creates a new irb session

[ GitHub ]

  
# 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

Instance Attribute Details

#context (readonly)

Returns the current context of this irb session

[ GitHub ]

  
# File 'lib/irb.rb', line 509

attr_reader :context

#scanner (rw)

The lexer used by this irb session

[ GitHub ]

  
# File 'lib/irb.rb', line 511

attr_accessor :scanner

Instance Method Details

#assignment_expression?(line) ⇒ Boolean

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

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

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

Evaluates input for this session.

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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

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

[ GitHub ]

  
# 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

#output_value(omit = false)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/irb.rb', line 822

def output_value(omit = false) # :nodoc:
  str = @context.inspect_last_value
  multiline_p = str.include?("\n")
  if omit
    winwidth = @context.io.winsize.last
    if multiline_p
      first_line = str.split("\n").first
      result = @context.newline_before_multiline_output? ? (@context.return_format % first_line) : first_line
      output_width = Reline::Unicode.calculate_width(result, true)
      diff_size = output_width - Reline::Unicode.calculate_width(first_line, true)
      if diff_size.positive? and output_width > winwidth
        lines, _ = Reline::Unicode.split_by_width(first_line, winwidth - diff_size - 3)
        str = "%s..." % lines.first
        str += "\e[0m" if Color.colorable?
        multiline_p = false
      else
        str = str.gsub(/(\A.*?\n).*/m, "\\1...")
        str += "\e[0m" if Color.colorable?
      end
    else
      output_width = Reline::Unicode.calculate_width(@context.return_format % str, true)
      diff_size = output_width - Reline::Unicode.calculate_width(str, true)
      if diff_size.positive? and output_width > winwidth
        lines, _ = Reline::Unicode.split_by_width(str, winwidth - diff_size - 3)
        str = "%s..." % lines.first
        str += "\e[0m" if Color.colorable?
      end
    end
  end
  if multiline_p && @context.newline_before_multiline_output?
    printf @context.return_format, "\n#{str}"
  else
    printf @context.return_format, str
  end
end

#prompt(prompt, ltype, indent, line_no)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/irb.rb', line 783

def prompt(prompt, ltype, indent, line_no) # :nodoc:
  p = prompt.dup
  p.gsub!(/%([0-9]+)?([a-zA-Z])/) do
    case $2
    when "N"
      @context.irb_name
    when "m"
      @context.main.to_s
    when "M"
      @context.main.inspect
    when "l"
      ltype
    when "i"
      if indent < 0
        if $1
          "-".rjust($1.to_i)
        else
          "-"
        end
      else
        if $1
          format("%" + $1 + "d", indent)
        else
          indent.to_s
        end
      end
    when "n"
      if $1
        format("%" + $1 + "d", line_no)
      else
        line_no.to_s
      end
    when "%"
      "%"
    end
  end
  p
end

#run(conf = IRB.conf)

[ GitHub ]

  
# 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

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

See additional method definition at file lib/irb.rb line 749.

[ GitHub ]

  
# File 'lib/irb/ext/multi-irb.rb', line 237

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)

Evaluates the given block using the given status.

[ GitHub ]

  
# 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)

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

[ GitHub ]

  
# 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)

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

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

[ GitHub ]

  
# 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)

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@IRB+Sessions for more information.

[ GitHub ]

  
# 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)

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

Used by the irb command irb_load, see IRB@IRB+Sessions for more information.

[ GitHub ]

  
# 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