class Proc

Constants

LBRACE
TLAMBDA
TLAMBEG
TOKEN_PAIRS

Public Class Methods

from_source(prc_src) click to toggle source
# File lib/core_extensions/proc.rb, line 95
def self.from_source(prc_src)
  raise ArgumentError unless prc_src.kind_of?(String)
  prc = eval(prc_src)
  prc.instance_variable_set(:@source, prc_src)
  prc
end

Public Instance Methods

_actually_starting_a_proc?(tokens, tok) click to toggle source
# File lib/core_extensions/proc.rb, line 58
def _actually_starting_a_proc?(tokens, tok)
  return true if tokens.index(tok).eql?(0)
  look_back = tokens.slice(0..tokens.index(tok)-1)
  look_back.pop if look_back.last.try(:[], 1).eql? :on_sp
  if [:on_tlambeg, :on_tlambda].include?(tok[1])
    true
  else
    ![:on_comma, :on_lparen, :on_label].include?(look_back.last.try(:[], 1))
  end
end
source() click to toggle source

Make a best effort to provide the original source for a block based on extracting a string from the file identified in Proc#source_location using Ruby's tokenizer.

This works for first block declared on a line in a source file. If additional blocks are specified inside the first block on the same line as the start of the block, only the outer-most block declaration will be identified as a the block we want.

If you require only the source of blocks-within-other-blocks, start them on a new line as would be best practice for clarity and readability.

# File lib/core_extensions/proc.rb, line 24
def source
  @source ||= begin
    file, line_no = source_location
    raise "no file provided by source_location: #{self}" if file.nil?
    raise "no line number provided for source_location: #{self}" if line_no.nil?
    tokens =  Ripper.lex File.read(file)
    tokens_on_line = tokens.select {|pos, lbl, str| pos[0].eql?(line_no) }
    starting_token = tokens_on_line.detect do |pos, lbl, str|
        TOKEN_PAIRS.keys.include?([lbl, str]) &&
        _actually_starting_a_proc?(tokens, [pos, lbl, str])
    end
    starting_token_type = [starting_token[1], starting_token[2]]
    ending_token_type = TOKEN_PAIRS[starting_token_type]
    source_str = ""
    remaining_tokens = tokens[tokens.index(starting_token)..-1]
    nesting = -1
    starting_nesting_token_types = if [TLAMBDA, LBRACE].include?(starting_token_type)
      [TLAMBDA, LBRACE]
    else
      [starting_token_type]
    end

    while token = remaining_tokens.shift
      token = [token[1], token[2]] # strip position
      source_str << token[1]
      nesting += 1 if starting_nesting_token_types.include? token
      is_ending_token = token.eql?(ending_token_type)
      break if is_ending_token && nesting.eql?(0)
      nesting -= 1 if is_ending_token
    end
    source_str
  end
end
source_body() click to toggle source

Examines the source of a proc to extract the body by removing the outermost block delimiters and any surrounding. whitespace.

Raises exception if the block takes arguments.

# File lib/core_extensions/proc.rb, line 75
def source_body
  raise "Cannot extract proc body on non-zero arity" unless arity.eql?(0)
  tokens = Ripper.lex source
  body_start_idx = 2
  if tokens[0][1].eql?(:on_tlambda)
    body_start_idx = tokens.index(tokens.detect { |t| t[1].eql?(:on_tlambeg) }) + 1
  end
  body_tokens = tokens[body_start_idx..-1]

  body_tokens.pop # ending token of proc
  # remove trailing whitespace
  whitespace = [:on_sp, :on_nl, :on_ignored_nl]
  body_tokens.pop while whitespace.include?(body_tokens[-1][1])
  # remove leading whitespace
  body_tokens.shift while whitespace.include?(body_tokens[0][1])

  # put them back together
  body_tokens.map {|token| token[2] }.join
end