require 'nodedump' # # ftable.rb # require 'amstd/orderedhash' class RubySourceCodeParser def RubySourceCodeParser.parse_file( fname, table ) new().parse_file(fname, table) end def RubySourceCodeParser.parse_files( fnames, table ) new().parse_files(fnames, table) end def parse_file( fname, table ) parse_add fname, table resolve table mkxref table table end def parse_files( fnames, table ) fnames.each do |n| parse_add n, table end resolve table mkxref table table end private def resolve( table ) table.each_value do |data| data.resolve_symbols table end end def mkxref( table ) table.each_value do |m| m.refs.each do |n| dest = table[n] dest.add_rev_link m if dest end end end # # parse main # def parse_add( fname, table ) File.open(fname) {|f| skip_yacc_grammer f if /\.y\z/ === fname parse_add_f f, table } end def skip_yacc_grammer( f ) while line = f.gets break if /\A%%/ === line end while line = f.gets break if /\A%%/ === line end end def parse_add_f( f, table ) while line = f.gets do if /\A\#\s*define\s+/ === line lineno = f.lineno buf = read_macro(line, f) next unless /\A#\s*define\s+\w+\(/ === line name = macro_name(buf) table[name] = MacroData.new(f.path, lineno, name, macro_body(buf)) next end next unless /\A\w/ === line next if /_\(\(|\ANORETURN|[{;:=]/ === line header1 = line lineno = f.lineno header2 = f.gets next if /\A\#\s*end/ === header2 if /\A\#\s*if/ === header2 header2 = f.gets until /\A\#\s*endif/ === f.gets # skip end end next if /\A\s*(?:union|struct)\s+\w+\s*\{/ === line + header2 name = get_function_name(header2) or raise "no function name (#{lineno}): #{(line+header2).inspect}" table[name] = FunctionData.new(f.path, lineno, name, *read_function(f,header1+header2)) end end # # macro # def read_macro( head, f ) buf = line = head.dup while /\\\n/ === line line = f.gets buf << line.sub(/\s*\\\n/, "\n") end buf end def macro_name( str ) str.slice(/\A#\s*define\s+(\w+)/, 1) end def macro_body( str ) str.sub(/\A#\s*define\s+\w+\(\s*(?:\w+(?:\s*,\s*\w+)*\s*)?\)\s*/, '') end # # function # def get_function_name( str ) str.slice(/\A\w+/) end def read_function( f, header ) while line = f.gets do break if /\A\{/ === line header << line end body = line while line = f.gets do if /\A\#\s*define/ === line begin line = f.gets end while /\\\n/ === line next end body << line break if /\A\}/ === line end return header, body end end # class RubySourceCodeParser class FunctionTable < OrderedHash def FunctionTable.parse( fname ) RubySourceCodeParser.parse_file(fname, self.new) end def FunctionTable.parse_files( fnames ) RubySourceCodeParser.parse_files(fnames, self.new) end # # by scope # def remove_links_to_external_function each_value do |m| m.refs.reject! {|name| if not key?(name) $stderr.puts "removed: #{m.name} -> #{name}" if $DEBUG true else false end } end adjust_reverse_ref_to_ordered_ref end def remove_links_to_extern_function each_value do |m| m.refs.reject! {|dest| not key?(dest) or self[dest].extern? } end adjust_reverse_ref_to_ordered_ref end def adjust_reverse_ref_to_ordered_ref each do |name, m| m.rev_links.reject! {|m| not m.refs.index name } end end private :adjust_reverse_ref_to_ordered_ref # # by structure # def reject_isolated_functions reject! do |name, m| m.terminal? and m.rev_links.select {|m| key?(m.name) }.empty? end end def reject_terminals reject! do |name, m| m.terminal? end end def reject_DAGs( level = nil ) level ||= 1.0/0 # infinity oldsize = self.size + 1 i = 0 while self.size < oldsize break if i == level i += 1 oldsize = self.size $stderr.puts "----- turn #{i}" if $DEBUG reject_terminals end $stderr.puts "----- end loop" if $DEBUG end #### remove 1 level cycle # require 'amstd/enum_falseall' # m.refs.false_all? {|n| key?(n) and not m.name == n } def remove_self_link each do |name, m| m.refs.delete name m.rev_links.delete m end end # # by mark # def mark( name ) m = @hash[name] return unless m return if m.marked? m.marked = true end def mark_recursive( name ) m = @hash[name] return unless m return if m.marked? m.marked = true m.refs.each do |n| mark_recursive n end end def mark_around( name ) data = @hash[name] or raise ArgumentError, "no such function: #{name}" $stderr.puts "marked: #{name}" data.marked = true data.refs.each do |n| f = @hash[n] or next $stderr.puts "marked: #{n}" f.marked = true end data.rev_links.each do |f| $stderr.puts "marked: #{f.name}" f.marked = true end end def reject_unmarked reject! {|name, m| if not m.marked? $stderr.puts "rejected: #{name}" true else false end } clear_all_mark end def clear_all_mark each_value do |m| m.marked = false end end # # generic # def reject! rejected = [] @list.dup.each do |name| if yield(name, @hash[name]) delete name rejected.push name end end @hash.each_value do |data| rejected.each do |rej| data.refs.delete rej data.rev_links.reject! {|m| m.name == rej } end end rejected.each {|n| $stderr.puts "rejected: #{n}" } if $DEBUG end end # class FunctionTable class ElementData def initialize( file, line, name, body ) @file = file @line = line @name = name @body = body @refs = using_functions(body) @tmp_refs = refering_symbols(body) @rev_links = [] @marked = false end def using_functions( str ) list = [] str.scan(/(\w+)\(/) do |name, | list.push name end list end private :using_functions def refering_symbols( str ) list = [] str.scan(/(\w+)/) do |name, | list.push name end list.uniq end private :refering_symbols RESWORDS = %w( sizeof ) # reserved words used with paren def resolve_symbols( table ) @tmp_refs.each do |name| ent = table[name] next unless ent if ent.static? @refs.push name if ent.file == @file else @refs.push name end end @refs = (@refs.uniq - RESWORDS).sort end def inspect "#<#{self.class} #{@name}>" end alias to_s inspect attr_reader :file attr_reader :line attr_reader :name attr_reader :header attr_reader :body attr_accessor :refs attr_reader :rev_links attr_accessor :marked alias marked? marked def add_rev_link( m ) @rev_links.push m end def name_with_scope @name + (static? ? '' : '*') end def terminal? @refs.empty? end end # class ElementData class MacroData < ElementData def static? true end def extern? false end def scope_type 'static' end end # class MacroData class FunctionData < ElementData def initialize( file, line, name, header, body ) super file, line, name, body @header = header end attr_reader :header def static? /static / === @header end def extern? not static? end def scope_type static? ? 'static' : 'extern' end end # class FunctionData