RCR: Add __file__ and __line__ methods to Method and Proc classes
From:
Brent Roman <brent@...>
Date:
2003-02-21 20:03:43 UTC
List:
ruby-core #872
Please consider the attached patch to the eval.c supplied with Ruby 1.6.8. This patch adds instance methods __file__ and __line__ to the built-in classes Method and Proc. These methods are analogous to the reserved words __FILE__ and __LINE__ The reserved words __FILE__ and __LINE__ cannot be used to extract the source location of a previously parsed block of ruby code, because the parser does a literal substitution of the String or FixNum, respectively. So, at least in Ruby 1.6.8, Matz's suggested technique of evaluating __FILE__ and __LINE__ in the binding of the block does not work. Please see: http://www.ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-talk/21555 -------- Aside: I'd be very interested in hearing about alternative approaches to this problem. Has it already been addressed beta test versions of ruby? Is there any reason why a similar approach shouldn't be taken for later versions? -------- On top of these methods for source reflection, I've written a simple ruby class called SourceRef that manages "Source References". I've attached sourceref.rb just as an example of what's possible and hopefully to motivate folks to accept the patch because it's just plain useful and doesn't impact performance or memory footprint. I've also modified the rbbr package to display the source to ruby methods instead of a useless (no document) message. And, I've added to rbbr menu items to view, edit and reload the source file. The combination of irb, rbbr, nedit (or some similar editor) and my simple hacks actually become a lightweight, modular ruby IDE! If this patch is approved -- or someone can show me how to write the attached sourceref.rb without resorting to a patch to eval.c! -- I will work with the owners of the irb and rbbr packages to incorporate my (tiny) changes. None of this is "rocket science", but I think it helps make Ruby more accessible to a larger audience. -- Brent Roman MBARI Software Engineer Tel: (831) 775-1808 7700 Sandholdt Road, Moss Landing, CA 95039 mailto:brent@mbari.org http://www.mbari.org/~brent
Attachments (2)
eval-1.6.8.patch
(2.27 KB, text/x-diff)
--- eval.c 2003/02/11 20:56:01 1.1
+++ eval.c 2003/02/20 08:21:42 1.3
@@ -3,7 +3,7 @@
eval.c -
$Author: brent $
- $Date: 2003/02/11 20:56:01 $
+ $Date: 2003/02/20 08:21:42 $
created at: Thu Jun 10 14:22:17 JST 1993
Copyright (C) 1993-2001 Yukihiro Matsumoto
@@ -6971,6 +6971,54 @@
return body;
}
+/* NK: added */
+
+static VALUE
+method_source_file_name(VALUE method)
+{
+ struct METHOD *data;
+ const char *filename;
+
+ Data_Get_Struct(method, struct METHOD, data);
+ filename = data->body->nd_file;
+ return rb_str_new2( filename == NULL ? "" : filename );
+}
+
+static VALUE
+method_source_line(VALUE method)
+{
+ struct METHOD *data;
+
+ Data_Get_Struct(method, struct METHOD, data);
+ return INT2FIX( nd_line(data->body) );
+}
+
+/* NK: end */
+
+/* BAR: added */
+
+static VALUE
+proc_source_file_name(VALUE block)
+{
+ struct BLOCK *data;
+ const char *filename;
+
+ Data_Get_Struct(block, struct BLOCK, data);
+ filename = data->body->nd_file;
+ return rb_str_new2( filename == NULL ? "" : filename );
+}
+
+static VALUE
+proc_source_line(VALUE block)
+{
+ struct BLOCK *data;
+
+ Data_Get_Struct(block, struct BLOCK, data);
+ return INT2FIX( nd_line(data->body) );
+}
+
+/* BAR: end */
+
void
Init_Proc()
{
@@ -6985,6 +7033,8 @@
rb_define_method(rb_cProc, "[]", proc_call, -2);
rb_define_method(rb_cProc, "==", proc_eq, 1);
rb_define_method(rb_cProc, "to_s", proc_to_s, 0);
+ rb_define_method(rb_cProc, "__file__", proc_source_file_name, 0); /* BAR */
+ rb_define_method(rb_cProc, "__line__", proc_source_line, 0); /* BAR */
rb_define_global_function("proc", rb_f_lambda, 0);
rb_define_global_function("lambda", rb_f_lambda, 0);
rb_define_global_function("binding", rb_f_binding, 0);
@@ -7002,6 +7052,8 @@
rb_define_method(rb_cMethod, "to_s", method_inspect, 0);
rb_define_method(rb_cMethod, "to_proc", method_proc, 0);
rb_define_method(rb_cMethod, "unbind", method_unbind, 0);
+ rb_define_method(rb_cMethod, "__file__", method_source_file_name, 0); /* NK */
+ rb_define_method(rb_cMethod, "__line__", method_source_line, 0); /* NK */
rb_define_method(rb_mKernel, "method", rb_obj_method, 1);
rb_cUnboundMethod = rb_define_class("UnboundMethod", rb_cMethod);
sourceref.rb
(9.37 KB, text/x-ruby)
################## sourceref.rb -- brent@mbari.org #####################
# $Source: /home/cvs/ESP/gen2/software/ruby/sourceref.rb,v $
# $Id: sourceref.rb,v 1.15 2003/02/21 03:38:34 brent Exp $
#
# The SourceRef class relies on a patched version of ruby/eval.c
# to provide access to the source file and line # where every Method
# or Proc is defined. SourceRef is a container for these and implements
# methods to:
#
# list source code to stdout,
# view source code in an editor,
# edit source code
# reload source code
#
# This file also adds methods to Object and Module to list, view,
# edit, and reload methods and modules as a convenience.
#
# Examples:
# SourceRef.instance_method (:list).source ==> ./sourceref.rb:47
# SourceRef.instance_method (:list).edit ==> opens an editor window
# (SourceRef/:list).edit ==> opens same window
# SourceRef.source[:list].view ==> opens same window r/o
# Date.edit ==> edits all files that define methods in class Date
# Date.source ==> returns hash of Date methods to SourceRefs
# puts Date.source.join ==> display Data methods with SourceRefs
# Date.sources ==> returns the list of files containing Date methods
# Date.reload ==> (re-)loads Date.sources
#
# With a version of irb.rb modified to store the last backtrace,
# the following features are available at the irb prompt:
#
# list|edit|view|reload Module|Method|Integer|Symbol|String
#
# Module operates on the list of files that comprise the Module
# Method operates on the text of the Method
# Integer n operates on the most recent backtrace at level n
# Symbol :sym searches backtrace for level corresponding to :sym
# String fn:nnn operates on file "fn" at line nnn
# no argument is equivalent to backtrace level 0
#
############################################################################
class SourceRef #combines source file name and line number
def initialize (file_name, line=0)
@file = file_name
@line = line
end
attr_reader :file, :line
def to_s
return file unless line > 0
file+':'+line.to_s
end
alias_method :inspect, :to_s
def to_srcRef
self
end
def list (lineCount=16, lineOffset=0)
# return the next lineCount lines of source text
# or "" if no source is available
text = ""; lineno=0
begin
File.open (file) {|f|
2.upto(line+lineOffset) { f.readline; lineno+=1 }
1.upto(lineCount) { text += f.readline; lineno+=1 }
}
rescue EOFError # don't sweat EOF unless its before target line #
if lineno < line
raise $!,"--> Truncated ruby source file: #{self}"
end
rescue
raise $!,"--> Missing ruby source: #{self}"
end
text
end
def edit (options=nil, readonly=false)
# start an editor session on file at line
# If X-windows display available, try nedit client, then nedit directly
if ENV["DISPLAY"]
neditArgs = "-lm Ruby #{options} '#{file}'"
neditArgs = "-line #{line} " + neditArgs if line > 0
neditArgs = "-read " + neditArgs if readonly
# nclient will normally be a symlink to /usr/bin/X11/nc
return self if
system ("nclient -noask -svrname ruby #{neditArgs} 2>/dev/null") ||
system ("nedit #{neditArgs} &")
end
# if all else fails, fall back on the venerable 'vi'
system ("vi #{"-R " if readonly} + #{file}")
self
end
def view (options=nil)
# start a read-only editor session on file at line
edit (options, true)
end
def reload
# load file referenced by receiver
begin
load file
rescue LoadError
raise $!, "--> Missing ruby source fle: #{file}"
end
end
def SourceRef.find_in_back_trace (trace, symbol)
# return first element in trace containing symbol
# returns nil if no such stack level found
for msg in trace
if inPart = msg.split(':',4)[2]
name = inPart.split('in\s*', 2)[1][1...-1]
return msg if symbol == name.intern
end
end
return nil
end
def SourceRef.from_back_trace (trace, level=0)
# return sourceref at level in backtace
# or return level if no such level found
traceLvl=level.kind_of?(Symbol) ?
SourceRef.find_in_back_trace (trace, level) : trace[level]
return level if traceLvl==nil
possibleIRBprefix, traceLvl = traceLvl.split(' ', 3)
traceLvl = possibleIRBprefix unless possibleIRBprefix == "from"
traceLvl.split(':',3)[0..1].join(':').to_srcRef
end
module Code #for objects supporting __file__ & __line__
def source
SourceRef.new (__file__, __line__)
end
alias_method :to_srcRef, :source
# can't use define_method because in ruby 1.6 self would be SourceRef::Code
(OPS = [ :list, :edit, :view, :reload ]).each {|m|
eval "def #{m}; source.#{m}; end"
}
end #module SourceRef::Code
module CommandBundle #add convenient commands for viewing source code
private
Code::OPS.each {|m|
define_method (m) { |*args|
src = args.length==0 ? IRB.conf[:exception] : args.shift
#convert src to an appropriate SourceRef by whatever means possible
#Modified irb.rb saves last back_trace & exception in IRB.conf
if src.kind_of?(Module)
src.sources.each {|srcRef| srcRef.method(m).call (*args)}
else
if src.kind_of?(Integer) || src.kind_of?(Symbol)
#assume parameter is a backtrace level or method name
src = SourceRef.from_back_trace(IRB.conf[:exception].backtrace, src)
end
if src.respond_to? :to_srcRef
src.to_srcRef.method(m).call (*args)
else
print "No source file corresponds to ",src.type,':',src.inspect,"\n"
end
end
}
}
def startRBBR #start another Ruby Class Browser
require 'rbbr'
(rbbrThread=Thread.new {RBBR.main}).priority=Thread.current.priority+1
rbbrThread
end
end
end #class SourceRef
#mix source code manipulation utilites into the appropriate classes
class Proc; include SourceRef::Code; end
class Method; include SourceRef::Code; end
class Object; include SourceRef::CommandBundle; end
class Symbol
def intern #just for mathematical completeness!
self
end
end
class Hash
def join (sep = " => ")
# most useful for displaying hashes with puts hsh.join
strAry = []
each {|key,value| strAry << key.inspect+sep+value.to_s}
strAry
end
end
class String
def to_srcRef
# parse a source reference from string of form fn:line#
strip!
a = split(':')
return SourceRef.new (self, 0) if a.length < 2
SourceRef.new (a[0..-2].join(':'), a[-1].to_i)
end
end
class Exception
def to_srcRef (level=0)
# default given any exception the best guess as to its location
SourceRef.from_back_trace(backtrace, level)
end
end
class SyntaxError < ScriptError
# Decode the Source Ref from the compiler error message
def to_srcRef (level=nil)
return super.to_srcRef level if level
to_s.split("\s",2)[0].to_srcRef
end
end
class Module
def sourceHash (methodType, methodNameArray)
# private method to build a hash of methodNames to sourceRefs
# exclude methods for which no ruby source is available
h = {};
methodGetter = method(methodType)
methodNameArray.each {|m|
m = m.intern
src = methodGetter[m].source
h[m] = src if src.line > 0 && src.file != "(eval)"
}
h
end
private :sourceHash
def singleton_source
# return hash on receiver's singleton methods to corresponding SourceRefs
sourceHash (:method, singleton_methods)
end
def instance_source (includeAncestors=false)
# return hash on receiver's instance methods to corresponding SourceRefs
# optionally include accessible methods in ancestor classes
sourceHash (:instance_method,
private_instance_methods+
protected_instance_methods(includeAncestors)+
public_instance_methods(includeAncestors))
end
def source (*args)
# return hash on receiver's methods to corresponding SourceRefs
# note that instance_methods will overwrite singletons of the same name
singleton_source.update(instance_source(*args))
end
def % (method_name)
# return singleton method named method_name in module
# Use / below unless method_name is also an instance method
method method_name
end
def / (method_name)
# return method named method_name in module
begin
return instance_method method_name
rescue NameError
end
method method_name
end
def sources (*args)
# return array of unique source file names for all receiver's methods
(singleton_source.values+instance_source(*args).values).collect{|s|
s.file
}.uniq.collect{|fn| SourceRef.new(fn)}
end
def reload (*args)
# load all source files that define receiver's methods
sources(*args).each {|s| s.reload}
end
def edit (*args)
# start editor sessions on all files that define receiver's methods
sources(*args).each{|srcFile| srcFile.edit (*args)}
end
def view (*args)
# start read-only editor sessions on files containing receiver's methods
sources(*args).each{|srcFile| srcFile.view (*args)}
end
def list (*args)
# return first few lines of all files containing self.methods
result=""
sources.each{|srcFile| result+=srcFile.list (*args)}
result
end
end