--------------010506000505070401000702
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Michael Neumann wrote:

> Would be really great if that works.
> 
>   trace { x + y }           #x+y   
>   assert { x + y 0 }     #assertion 'x+y' failed...

It is possible with proc_source, but that's quite ugly.

--------------010506000505070401000702
Content-Type: text/plain;
 nameroc_source.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filenameroc_source.rb"

require 'stringio'
require 'irb/ruby-lex'

# Tell the ruby interpreter to load code lines of required files
# into this filename -> lines Hash. This behaviour seems to be
# very undocumented and therefore shouldn't really be relied on.
SCRIPT_LINES__  } unless defined? SCRIPT_LINES__

module ProcSource
  def get_lines(filename, start_line  )
    case filename
      # special "(irb)" descriptor?
      when "(irb)"
        IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -1)
      # special "(eval...)" descriptor?
      when /^\(eval.+\)$/
        EVAL_LINES__[filename][start_line .. -1]
      # regular file
      else
        # Ruby already parsed this file? (see disclaimer above)
        if lines  CRIPT_LINES__[filename]
          lines[(start_line - 1) .. -1]
        # If the file exists we're going to try reading it in
        elsif File.exist?(filename)
          begin
            File.readlines(filename)[(start_line - 1) .. -1]
          rescue
            nil
          end
        end
    end
  end

  def handle(proc)
    filename, line  roc.source_descriptor
    lines  et_lines(filename, line) || []

    lexer  ubyLex.new
    lexer.set_input(StringIO.new(lines.join))

    state  before_constructor
    nesting_level  
    start_token, end_token  il, nil
    found  alse
    while token  exer.token
      puts "token: #{token.inspect}",
           "state: #{state.inspect}" if $DEBUG
      # we've not yet found any proc-constructor -- we'll try to find one.
      if [:before_constructor, :check_more].include?(state)
        # checking more and newline? -> done
        if token.is_a?(RubyToken::TkNL) and state :check_more
          state  done
          break
        end
        # token is Proc?
        if token.is_a?(RubyToken::TkCONSTANT) and
           token.instance_variable_get(:@name) "Proc"
          # method call?
          if lexer.token.is_a?(RubyToken::TkDOT)
            method  exer.token
            # constructor?
            if method.is_a?(RubyToken::TkIDENTIFIER) and
               method.instance_variable_get(:@name) "new"
              unless state :check_more
                # okay, code will follow soon.
                state  before_code
              else
                # multiple procs on one line
                return
              end
            end
          end
        # token is lambda or proc call?
        elsif token.is_a?(RubyToken::TkIDENTIFIER) and
              %w{proc lambda}.include?(token.instance_variable_get(:@name))
          unless state :check_more
            # okay, code will follow soon.
            state  before_code
          else
            # multiple procs on one line
            return
          end
        end

      # we're waiting for the code start to appear.
      elsif state :before_code
        if token.is_a?(RubyToken::TkfLBRACE) or token.is_a?(RubyToken::TkDO)
          # found the code start, update state and remember current token
          state  in_code
          start_token  oken
        end

      # okay, we're inside code
      elsif state :in_code
        if token.is_a?(RubyToken::TkRBRACE) or token.is_a?(RubyToken::TkEND)
          nesting_level - 
          if nesting_level 0
            # we're done!
            end_token  oken
            # parse another time to check if there are multiple procs on one line
            # we can't handle that case correctly so we return no source code at all
            state  check_more
          end
        elsif token.is_a?(RubyToken::TkfLBRACE) or token.is_a?(RubyToken::TkDO) or
              token.is_a?(RubyToken::TkBEGIN) or token.is_a?(RubyToken::TkCASE) or
              token.is_a?(RubyToken::TkCLASS) or token.is_a?(RubyToken::TkDEF) or
              token.is_a?(RubyToken::TkFOR) or token.is_a?(RubyToken::TkIF) or
              token.is_a?(RubyToken::TkMODULE) or token.is_a?(RubyToken::TkUNLESS) or
              token.is_a?(RubyToken::TkUNTIL) or token.is_a?(RubyToken::TkWHILE) or
              token.is_a?(RubyToken::TklBEGIN)
          nesting_level + 
        end
      end
    end

    if start_token and end_token
      start_line, end_line  tart_token.line_no - 1, end_token.line_no - 1 
      source  ines[start_line .. end_line]
      start_offset  tart_token.char_no
      start_offset +  if start_token.is_a?(RubyToken::TkDO)
      end_offset  (source.last.length - end_token.char_no)
      source.first.slice!(0 .. start_offset)
      source.last.slice!(end_offset .. -1)
      p [start_token, end_token, source] if $DEBUG

      # Can't use .strip because newline at end of code might be important
      # (Stuff would break when somebody does proc { ... #foo\n})
      proc.source  ource.join.gsub(/^ | $/, "")
    end
  end

  module_function :handle, :get_lines
end

class Proc
  def source_descriptor
    if md  ^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+)>$/.match(old_inspect)
      filename, line  d.captures
      return filename, line.to_i
    end
  end

  attr_accessor :source
  def source
    ProcSource.handle(self) unless @source
    @source
  end

  alias :old_inspect :inspect
  def inspect
    if source
      "proc {#{source}}"
    else
      old_inspect
    end
  end

  def other)
    if self.source and other.source
      self.source other.source
    else
      self.id other.id
    end
  end

  def _dump(depth  )
    if source
      source
    else
      raise(TypeError, "Can't serialize Proc with unknown source code.")
    end
  end

  def to_yaml(*args)
    self.source # force @source to be set
    super.sub("object:Proc", "proc")
  end

  def self.allocate; from_string ""; end

  def self.from_string(string)
    result  val("proc {#{string}}")
    result.source  tring
    return result
  end

  def self._load(code)
    self.from_string(code)
  end

  def self.marshal_load; end
  def marshal_load; end
end

if defined?(YAML)
  YAML.add_ruby_type(/^proc/) { |type, val|
    Proc.from_string(val["source"])
  }
end

EVAL_LINES__  ash.new

alias :old_eval :eval
def eval(code, *args)
  context, descriptor, start_line, *more  args
  descriptor || (eval#{code.hash})"
  start_line || 
  lines || ode.grep(/.*/)
  EVAL_LINES__[descriptor] || rray.new
  EVAL_LINES__[descriptor][start_line, lines.length]  ines
  old_eval(code, context, descriptor, start_line, *more)
end


# This uses .inspect instead of the original Hash dumping method.
#
#class Hash
#  def _dump(depth)
#    Marshal.dump [self.inspect, default_proc]
#  end
#
#  def self._load(data)
#    hash, default_proc  arshal.load(data)
#    hash  val(hash)
#    Hash.new(&default_proc).merge(hash)
#  end
#end

--------------010506000505070401000702--