#!/usr/bin/ruby require 'xml/libxml' class Logger def initialize @level = 4 # FATAL > ERROR > WARN > INFO > MOREINFO > DEBUG end def debug( msg ) log( 0, msg ) end def moreinfo( msg ) log( 1, msg ) end def info( msg ) log( 2, msg ) end def warn( msg ) log( 3, "! Warning: " + msg ) end def error( msg ) log( 4, "!! Error: " + msg ) end def fatal( msg ) puts "!!! Fatal: " + msg end def log( l, msg ) if l >= @level then puts msg end end def decrease_level( n ) @level -= n end end def internal_test s=normalize_function_declaration( "template >void bla(XX a, XX b, YY c)" ) fail unless s=="void bla($0,$0,$1)" s=normalize_function_declaration( "template > void bla(XX a, XX b, xYYz c)" ) fail unless s=="void bla($0,$0,xYYz)" s=normalize_function_declaration( "template< class ForwardIterator1, class ForwardIterator2, class Callback > void box_intersection_all_pairs_d( ForwardIterator1 begin1, ForwardIterator1 end1, ForwardIterator2 begin2, ForwardIterator2 end2, Callback callback, Box_intersection_d::Topology topology = Box_intersection_d::blubber>::CLOSED);" ) fail unless s=="void box_intersection_all_pairs_d($0,$0,$1,$1,$2,Box_intersection_d::Topology);" end # input: "templatevoid bla(XX t)" # result: void bla($1 t) def normalize_template_parameters( decl ) typemap=Hash.new if decl =~ /^template\s*/ then decl=$' outer, inner, rest = split_nesting_levels( '<', '>', decl ) $log.debug "outer: #{outer} inner: #{inner} rest: #{rest}" decl=rest.sub( /^\s*>/, "" ) counter = 0 protected_split( '<', '>', ',', inner ).each do |x| x=x.gsub( /(class|typename)/, "" ) x=x.gsub( /=.*$/, "" ).strip $log.debug "typemap[#{x}]=$#{counter}" typemap[x]="$#{counter}" counter+=1 end end return decl, typemap end def normalize_declaration( decl ) decl = decl.gsub( /\s+/, ' ' ); decl = decl.gsub( /(\w) ([&()=:;,<>])/, '\1\2' ); decl = decl.gsub( /([()=:;,<>]) (\w)/, '\1\2' ); decl = decl.gsub( /([()=:;,<>]) ([&();,])/, '\1\2' ); decl = decl.gsub( /([();,]) ([()=:;,<>])/, '\1\2' ); return decl.strip end def normalize_function_declaration( decl ) decl = normalize_declaration( decl ) decl,typemap = normalize_template_parameters( decl ) if decl =~ /([^(]+[(])/ then decl=$1+remove_argnames($',typemap) end end # input: s="xxxx<dsad>dssd" # returns: outer="xxxx<>dssd<>" inner="dsad",x def split_nesting_levels( openchar, closechar, s, nesting_level = 0 ) inner = "" outer = "" rest = "" closed = false s.each_byte do |c| if closed then rest << c next end char = "" char << c if char == closechar then nesting_level -= 1 closed = true end if nesting_level <= 0 then outer << char else inner << char end if char == openchar then nesting_level += 1 end end return outer, inner, rest end # input s="a, b=c::e >, h,y" # returns: # part: a # part: b=c::e > # part: h # part: y def protected_split( openchar, closechar, splitchar, s, nesting_level = 0 ) result_array = Array.new part = "" char = "" s.each_byte do |c| char = "" char << c if char == closechar then nesting_level -= 1 end if char == openchar then nesting_level += 1 end if char == splitchar then if nesting_level == 0 then #puts "new part: #{part}" result_array << part part = "" end else part << char end end if char != splitchar then result_array << part end return result_array end def split_typename( decl ) nesting_level = 0 position = 0 result = "" decl.each_byte do |c| char = "" char << c #puts decl.slice( position, decl.length ) case char when '<' then nesting_level += 1 when '>' then nesting_level -= 1 when /[,)]/ if nesting_level == 0 then return result, decl.slice( position, decl.length ) end end result << c position += 1 end return result, "" end def remove_argnames( decl, typemap ) result = "" until decl =~ /^[)]/ || decl == "" do type_with_default, decl = split_typename( decl ) $log.debug "type with default: #{type_with_default}" # remove default if type_with_default =~ /^([^=]+)=/ then type = $1 else type = type_with_default end # remove argname (preserving declarators like * and []) if type =~ /\s*(\*+)?\s*\w+(\[\w+\])?$/ then type = $` type += $1 if $1 != nil type += $2 if $2 != nil end if typemap.has_key?( type ) then type = typemap[type] end result << type if( decl =~ /,/ ) then result << "," decl = $' end $log.debug "type: [#{type}] \t ......rest: #{decl}" end return result + decl end #def match_and_consume( s, regex_string ) # printf " match: [#{regex_string}] .. " # regex = Regexp.new( regex_string ) # if s =~ regex then # s = s.sub( regex, "" ) # #puts "ok! remaining #{s}" # return true # end # return false #end # convenience class #class Declaration # def initialize( name ) # @name = name # end # # def match( regex_string ) # name = match_and_consume( name, regex_string ) # end # #end class Doxygen def initialize( filename ) @doxy_xml=XML::Document.file(filename) @items = {} end def member_matches_texname?( memberdef, texname, strip_const_from_return_types ) is_const="" if memberdef['const'] == 'yes' then is_const = "const" end argsstring=get_xpath_content(memberdef,'argsstring') type=get_xpath_content(memberdef,'type') if strip_const_from_return_types then type=type.gsub( /_const_/, "_" ) end name=get_xpath_content(memberdef,'name') templateparamlist="" memberdef.find( 'templateparamlist/param' ).each do |param| declname=get_xpath_content(param,'declname') templateparamlist += declname+"," end if templateparamlist != "" then templateparamlist.gsub!( /,$/, "" ) templateparamlist = "template<#{templateparamlist}>" end doxyname=templateparamlist+"#{type} #{name}#{argsstring};" doxyname=normalize_function_declaration( doxyname ) $log.moreinfo " doxyname: #{doxyname}" return doxyname == texname end def find_member( compoundname, memberkind, texname ) $log.moreinfo "-- raw texname: "+texname texname=normalize_function_declaration( texname ) $log.moreinfo "- find_member" $log.moreinfo "-- compoundname: #{compoundname}" $log.moreinfo "-- memberkind: #{memberkind}" $log.moreinfo "-- texname: #{texname}" xpath_ckind='(@kind=\'struct\' or @kind=\'class\' or @kind=\'namespace\')' xpath_cname="(compoundname=\'#{compoundname}\')" xpath_mkind="(@kind=\'#{memberkind}\')" #xpath_mname="(name=\'#{membername}\')" xpath_compound="/doxygen/compounddef[#{xpath_ckind} and #{xpath_cname}]" xpath_member="/sectiondef/memberdef[#{xpath_mkind}]" # and #{xpath_mname} xpath_query="#{xpath_compound}#{xpath_member}" #puts "xpath query: #{xpath_query}" for strip_const_from_return_type in [false,true] @doxy_xml.find( xpath_query ).each do |memberdef| if member_matches_texname?( memberdef, texname, strip_const_from_return_type ) return memberdef end end end return nil end end class Tex def initialize( filename, doxy ) @tex_xml = XML::Document.file( filename ) @doxy = doxy end def list_all_refpages() @tex_xml.find('/manual_tools_output/package').each do |package| $log.info "package: #{package['id']}" package.find('refpage[refcat!=\'Concept\']').each do |refpage| scope=get_xpath_content(refpage,'globalscope') refcat=get_xpath_content(refpage,'refcat') compoundname = "#{scope}#{refpage['id']}" # if applicable case refcat when /Function/ then compoundname=scope.gsub( /::$/, '' ) end $log.info "refpage id: #{refpage['id']} \tscope: #{scope} \trefcat: #{refcat}" refpage.find('item').each do |item| name=get_xpath_content(item,'name') kind=get_xpath_content(item,'kind') comment=get_xpath_content(item,'comment') #puts " item name: #{name}" #puts " item kind: #{kind}" #puts " item comment: #{comment}" $log.moreinfo "compoundname: #{compoundname}" if kind == "memberfunction" || kind == "constructor" then kind="function" elsif kind == "nested_type" then kind="typedef" end memberdef=@doxy.find_member( compoundname , kind, name ) if memberdef == nil then $log.error "did not find doxygen equivalent for \"#{name}\" in \"#{compoundname}\"" else definition=get_xpath_content(memberdef,'definition') $log.info "OK! found memberdef: #{definition}" end end # each item end # each refpage end # each package end # def list_all_refpages end def get_xpath_content( node, xpath ) s=node.find( xpath ).to_a.first.to_s if s == nil then return "" else return s.strip end end $log = Logger.new for x in 0...ARGV.length do case ARGV[x] when /^-v(.*)$/ if $1 != nil then $log.decrease_level( $1.length ) end $log.decrease_level( 1 ) when /^-doxy$/ x += 1 fail "-doxy needs an argument" unless x < ARGV.length doxyfilename = ARGV[x] when /^-tex$/ x += 1 fail "-tex needs an argument" unless x < ARGV.length texfilename = ARGV[x] end end fail "-doxy and -tex are required" if texfilename == nil || doxyfilename == nil doxy = Doxygen.new( doxyfilename ) tex = Tex.new( texfilename, doxy ) tex.list_all_refpages #s = remove_argnames( "long int bla=CGAL::my_default_value::bla>,bool blubb)" ) #s = remove_argnames( "void bla(type bla,bool blubb,float x)const;" ) #s = normalize_declaration( "void bla(NT values[D],int *bla = default);" ) #puts "result: #{s}" #puts normalize_function_declaration( "template >void bla(XX a, XX b, YY c)" #)