From: Brent Roman Date: 2003-02-22T05:03:43+09:00 Subject: RCR: Add __file__ and __line__ methods to Method and Proc classes This is a multi-part message in MIME format. --------------070807040300090706010506 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit 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 --------------070807040300090706010506 Content-Type: text/plain; name="eval-1.6.8.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="eval-1.6.8.patch" --- 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); --------------070807040300090706010506 Content-Type: text/plain; name="sourceref.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="sourceref.rb" ################## 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 --------------070807040300090706010506--