1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/irb/cmd/show_source.rb
schneems 2b22c93533 Compatibility with IRB
Instead of accessing the struct as an array, access it via methods. There are other places inside of this file already using this API (for example e0a5c3d2b7/lib/irb/ruby-lex.rb (L829-L830)).

This commit moves all struct array-ish calls to use their method calls instead. It is also ~1.23 faster accessing values via a method instead of as an array according to this microbenchmark:

```ruby
Elem = Struct.new(:pos, :event, :tok, :state, :message) do
  def initialize(pos, event, tok, state, message = nil)
    super(pos, event, tok, State.new(state), message)
  end

  # ...

  def to_a
    a = super
    a.pop unless a.empty?
    a
  end
end

class ElemClass
  attr_accessor :pos, :event, :tok, :state, :message

  def initialize(pos, event, tok, state, message = nil)
    @pos = pos
    @event = event
    @tok = tok
    @state = State.new(state)
    @message = message
  end

  def to_a
    if @message
      [@pos, @event, @tok, @state, @message]
    else
      [@pos, @event, @tok, @state]
    end
  end
end

# stub state class creation for now
class State; def initialize(val); end; end
```

```ruby
Benchmark.ips do |x|
  x.report("struct") { struct[1] }
  x.report("class ") { from_class.event }
  x.compare!
end; nil
```

```
Warming up --------------------------------------
              struct     1.624M i/100ms
              class      1.958M i/100ms
Calculating -------------------------------------
              struct     17.139M (± 2.6%) i/s -     86.077M in   5.025801s
              class      21.104M (± 3.4%) i/s -    105.709M in   5.015193s

Comparison:
              class : 21103826.3 i/s
              struct: 17139201.5 i/s - 1.23x  (± 0.00) slower
```
2021-12-02 15:55:42 +09:00

93 lines
3.2 KiB
Ruby

# frozen_string_literal: true
require_relative "nop"
require_relative "../color"
require_relative "../ruby-lex"
# :stopdoc:
module IRB
module ExtendCommand
class ShowSource < Nop
def execute(str = nil)
unless str.is_a?(String)
puts "Error: Expected a string but got #{str.inspect}"
return
end
source = find_source(str)
if source && File.exist?(source.file)
show_source(source)
else
puts "Error: Couldn't locate a definition for #{str}"
end
nil
end
private
# @param [IRB::ExtendCommand::ShowSource::Source] source
def show_source(source)
puts
puts "#{bold("From")}: #{source.file}:#{source.first_line}"
puts
code = IRB::Color.colorize_code(File.read(source.file))
puts code.lines[(source.first_line - 1)...source.last_line].join
puts
end
def find_source(str)
case str
when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
eval(str, irb_context.workspace.binding) # trigger autoload
base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
method = Regexp.last_match[:method]
if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
file, line = owner.instance_method(method).source_location
end
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
method = Regexp.last_match[:method]
file, line = receiver.method(method).source_location if receiver.respond_to?(method)
end
if file && line
Source.new(file: file, first_line: line, last_line: find_end(file, line))
end
end
def find_end(file, first_line)
return first_line unless File.exist?(file)
lex = RubyLex.new
lines = File.read(file).lines[(first_line - 1)..-1]
tokens = RubyLex.ripper_lex_without_warning(lines.join)
prev_tokens = []
# chunk with line number
tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
code = lines[0..lnum].join
prev_tokens.concat chunk
continue = lex.process_continue(prev_tokens)
code_block_open = lex.check_code_block(code, prev_tokens)
if !continue && !code_block_open
return first_line + lnum
end
end
first_line
end
def bold(str)
Color.colorize(str, [:BOLD])
end
Source = Struct.new(
:file, # @param [String] - file name
:first_line, # @param [String] - first line
:last_line, # @param [String] - last line
keyword_init: true,
)
private_constant :Source
end
end
end
# :startdoc: