rbgccxml 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ module RbGCCXML
2
+
3
+ class NotQueryableException < RuntimeError; end
4
+
5
+ # A Node is part of the C++ code as dictated by GCC-XML. This class
6
+ # defines all of the starting points into the querying system, along
7
+ # with helper methods to access data from the C++ code itself.
8
+ #
9
+ # Any Node further down the heirarchy chain can and should define which
10
+ # finder methods are and are not avaiable at that level. For example, the
11
+ # Class Node cannot search for other Namespaces within that class.
12
+ class Node
13
+ attr_reader :node
14
+
15
+ # Initialize this node according to the XML element passed in
16
+ # Only to be used internally.
17
+ def initialize(node)
18
+ @node = node
19
+ end
20
+
21
+ # Get the C++ name of this node
22
+ def name
23
+ @node.attributes['name']
24
+ end
25
+
26
+ # Get the fully qualified (demangled) C++ name of this node.
27
+ # The 'demangled' attribute of the node for methods / functions is the
28
+ # full signature, so cut that out.
29
+ def qualified_name
30
+ if @node.attributes["demangled"]
31
+ @node.attributes["demangled"].split(/\(/)[0]
32
+ else
33
+ self.name
34
+ end
35
+ end
36
+
37
+ # Any unknown methods get sent to the XML node
38
+ def method_missing(name, *args)
39
+ if @node.respond_to?(name)
40
+ @node.send(name, *args)
41
+ else
42
+ # Make sure we still throw NoMethodErrors
43
+ super
44
+ end
45
+ end
46
+
47
+ # Get the file name of the file this node is found in.
48
+ def file_name(basename = true)
49
+ file_id = @node.attributes["file"]
50
+ file_node = XMLParsing::find(:type => "File", :id => file_id)
51
+ name = file_node.attributes["name"]
52
+ basename ? ::File.basename(name) : name
53
+ end
54
+
55
+ # Get the parent node of this node. e.g. function.parent will get the class
56
+ # the function is contained in.
57
+ def parent
58
+ return nil if @node.attributes["context"] == "_1"
59
+ XMLParsing::find(:id => @node.attributes["context"])
60
+ end
61
+
62
+ # Find all namespaces. There are two ways of calling this method:
63
+ # #namespaces => Get all namespaces in this scope
64
+ # #namespaces(name) => Shortcut for namespaces.find(:name => name)
65
+ def namespaces(name = nil)
66
+ if name
67
+ namespaces.find(:name => name)
68
+ else
69
+ XMLParsing::find_nested_nodes_of_type(@node, "Namespace")
70
+ end
71
+ end
72
+
73
+ # Find all classes in this scope. Like #namespaces, there are
74
+ # two ways of calling this method.
75
+ def classes(name = nil)
76
+ if name
77
+ classes.find(:name => name)
78
+ else
79
+ XMLParsing::find_nested_nodes_of_type(@node, "Class")
80
+ end
81
+ end
82
+
83
+ # Find all structs in this scope. Like #namespaces, there are
84
+ # two ways of calling this method.
85
+ def structs(name = nil)
86
+ if name
87
+ structs.find(:name => name)
88
+ else
89
+ XMLParsing::find_nested_nodes_of_type(@node, "Struct")
90
+ end
91
+ end
92
+
93
+ # Find all functions in this scope. Functions are free non-class
94
+ # functions. To search for class methods, use #methods.
95
+ #
96
+ # Like #namespaces, there are two ways of calling this method.
97
+ def functions(name = nil)
98
+ if name
99
+ functions.find(:name => name)
100
+ else
101
+ XMLParsing::find_nested_nodes_of_type(@node, "Function")
102
+ end
103
+ end
104
+
105
+ # Special equality testing. A given node can be tested against
106
+ # a String to test against the name of the node. For example
107
+ #
108
+ # source.classes("MyClass") == "MyClass" #=> true
109
+ # source.classes("MyClass") == source.classes.find(:name => "MyClass") #=> true
110
+ #
111
+ def ==(val)
112
+ if val.is_a?(String)
113
+ return true if self.name == val
114
+ return true if self.qualified_name =~ /#{val.gsub("*", "\\*")}/
115
+ false
116
+ else
117
+ super
118
+ end
119
+ end
120
+ end
121
+
122
+ end
@@ -0,0 +1,11 @@
1
+ module RbGCCXML
2
+ # Class representing a single <Argument ...> node.
3
+ class Argument < Node
4
+
5
+ # Get the C++ type of this argument.
6
+ def cpp_type
7
+ XMLParsing.find_type_of(node, "type")
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module RbGCCXML
2
+ # Node type represending <Class ...> nodes.
3
+ class Class < Node
4
+
5
+ # Classes cannot have nested namespaces
6
+ def namespaces(name = nil)
7
+ raise NotQueryableException.new("Cannot query for Namespaces while in a Class")
8
+ end
9
+
10
+ # Find all the constructors for this class
11
+ def constructors
12
+ XMLParsing::find_nested_nodes_of_type(@node, "Constructor")
13
+ end
14
+
15
+ # Find all methods for this class. The typical two-ways apply:
16
+ #
17
+ # <tt>methods</tt>:: Get all methods in this scope
18
+ # <tt>methods(name)</tt>:: Shortcut for methods.find(:name => name)
19
+ #
20
+ def methods(name = nil)
21
+ if name
22
+ methods.find(:name => name)
23
+ else
24
+ XMLParsing::find_nested_nodes_of_type(@node, "Method")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ module RbGCCXML
2
+ # Class representing <Constructor ...> nodes.
3
+ # Has arguments
4
+ class Constructor < Function
5
+
6
+ # Constructors do not have a return_type, this raises an
7
+ # exception.
8
+ def return_type
9
+ raise "There is no return_type of a constructor"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module RbGCCXML
2
+
3
+ # Handles the <File...> node.
4
+ class File < Node
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ module RbGCCXML
2
+ # Function query node. Functions are the end point of the tree.
3
+ # They have arguments and return types.
4
+ class Function < Node
5
+
6
+ # First of all, no more querying once you're this far in
7
+ %w(classes namespaces functions).each do |f|
8
+ define_method(f) do
9
+ raise NotQueryableException.new("Cannot query for #{f} while in a Function")
10
+ end
11
+ end
12
+
13
+ # Get the list of arguments for this Function.
14
+ # Returns an array of Argument nodes
15
+ def arguments
16
+ XMLParsing.find_arguments_for(node)
17
+ end
18
+
19
+ # Find the return type of this function
20
+ def return_type
21
+ XMLParsing.find_type_of(node, "returns")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module RbGCCXML
2
+ # Node type represending <Method ...> nodes, which are representation
3
+ # of class methods.
4
+ class Method < Function
5
+
6
+ # Is this method static?
7
+ def static?
8
+ @node.attributes["static"] == "1"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module RbGCCXML
2
+ # Namespace query node. Namespaces can have in it
3
+ # more namespaces, methods, classes, structs, attributes, ... everything.
4
+ class Namespace < Node
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RbGCCXML
2
+ # Node type represending <Struct ...> nodes.
3
+ # It's really just a class
4
+ class Struct < ::RbGCCXML::Class
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module RbGCCXML
2
+
3
+ # The base class for all type management classes.
4
+ class Type < Node
5
+ end
6
+
7
+ end
@@ -0,0 +1,8 @@
1
+ module RbGCCXML
2
+
3
+ # The FundamentalType. This represents all the built-in types
4
+ # of C++ like int, double, char, etc.
5
+ class FundamentalType < Type
6
+ end
7
+
8
+ end
@@ -0,0 +1,17 @@
1
+ module RbGCCXML
2
+
3
+ # This represents a Pointer of any other kind of type
4
+ class PointerType < Type
5
+
6
+ # Does this type match the given name string?
7
+ def ==(val)
8
+ # Look for the '*' denoting a pointer type.
9
+ # Assuming we find one, drop it and look for the
10
+ # base type.
11
+ return false unless val =~ /\*/
12
+ new_val = val.gsub("*", "").strip
13
+ XMLParsing.find_type_of(self.node, "type") == new_val
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,8 @@
1
+ module RbGCCXML
2
+
3
+ # This represents a Typedef, basically the renaming of one type to
4
+ # another.
5
+ class Typedef < Type
6
+ end
7
+
8
+ end
@@ -0,0 +1,87 @@
1
+ require 'tempfile'
2
+
3
+ module RbGCCXML
4
+
5
+ # This class starts the whole process.
6
+ # Please use RbGCCXML.parse and not this class directly
7
+ class Parser
8
+
9
+ def initialize(config = {})
10
+ @gccxml = GCCXML.new
11
+
12
+ if includes = config.delete(:includes)
13
+ @gccxml.add_include includes
14
+ end
15
+
16
+ validate_glob(config[:files])
17
+ end
18
+
19
+ # Start the parsing process. This includes
20
+ # 1. Creating a temp file for the resulting xml
21
+ # 2. Finding all the files to run through gccxml
22
+ # 3. If applicable, build another temp file and #include
23
+ # the header files to ensure one pass into gccxml
24
+ # 4. Build up our :: Namespace node and pass that back
25
+ # to the user for querying
26
+ def parse
27
+ @results_file = Tempfile.new("rbgccxml")
28
+ parse_file = nil
29
+
30
+ if @files.length == 1
31
+ parse_file = @files[0]
32
+ else
33
+ # Otherwise we need to build up a single header file
34
+ # that #include's all of the files in the list, and
35
+ # parse that out instead
36
+ parse_file = build_header_for(@files)
37
+ end
38
+
39
+ @gccxml.parse(parse_file, @results_file.path)
40
+
41
+ # Everything starts at the :: Namespace
42
+ document = Hpricot.XML(@results_file)
43
+ global_ns = document.search("//Namespace[@name='::']")[0]
44
+
45
+ XMLParsing.doc_root = document
46
+
47
+ Namespace.new global_ns
48
+ end
49
+
50
+ private
51
+
52
+ def build_header_for(files)
53
+ header = Tempfile.new("header_wrapper")
54
+ header.open
55
+
56
+ @files.each do |file|
57
+ header.write "#include \"#{file}\"\n"
58
+ end
59
+
60
+ header.close
61
+
62
+ header.path
63
+ end
64
+
65
+ def validate_glob(files)
66
+ found = []
67
+
68
+ if files.is_a?(Array)
69
+ files.each {|f| found << Dir[f] }
70
+ elsif ::File.directory?(files)
71
+ found = Dir[files + "/*"]
72
+ else
73
+ found = Dir[files]
74
+ end
75
+
76
+ found.flatten!
77
+
78
+ if found.empty?
79
+ raise SourceNotFoundError.new(
80
+ "Cannot find files matching #{files.inspect}. " +
81
+ "You might need to specify a full path.")
82
+ end
83
+
84
+ @files = found.select {|f| !::File.directory?(f) }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,113 @@
1
+ module RbGCCXML
2
+ # Any node query will return an instance of this class, QueryResult. This
3
+ # class is an Array with slightly different handling of #find. Array#find
4
+ # expects a block to find elements, we want #find to take an options
5
+ # hash.
6
+ class QueryResult < Array
7
+
8
+ # Find within this result set any nodes that match the given options
9
+ # Options can be any or all of the following, based on the type of node:
10
+ #
11
+ # All nodes:
12
+ # <tt>:name</tt>:: The unmangled name of the node. Can be a string or Regexp. Works on all nodes.
13
+ # <tt>:arguments</tt>:: Search according to argument types.
14
+ # This needs to be an array of strings or symbols. nil can be
15
+ # used as a "any" flag. Only works on Functions, Methods, and Constructors
16
+ # <tt>:returns</tt>:: Search according to the return type. Can be a string or symbol.
17
+ # Only works on Functions and Methods
18
+ #
19
+ # All arguments added to the options are processed in an AND format. If you
20
+ # are looking for 3 random arguments with a return type of int:
21
+ #
22
+ # find(:arguments => [nil, nil, nil], :returns => :int)
23
+ #
24
+ # It's also possible to do this in two steps, chaining the +find+ calls, as long as
25
+ # each +find+ in the chain has multiple results:
26
+ #
27
+ # find(:arguments => [nil, nil, nil]).find(:returns => :int)
28
+ #
29
+ # However if you want 3 random arguments OR returning int, you should use
30
+ # two seperate queries:
31
+ #
32
+ # find(:arguments => [nil, nil, nil])
33
+ # find(:returns => :int)
34
+ #
35
+ # When dealing with how to specify the types, Typedefs, user defined types, fundamental
36
+ # types (int, char, etc) and pointers to all of these are currently supported. To find
37
+ # functions that return a pointer to MyClass:
38
+ #
39
+ # find(:returns => "MyClass*")
40
+ #
41
+ # Returns: A new QueryResult containing the results, allowing for nested +finds+.
42
+ # However, If there is only one result, returns that single Node instead.
43
+ def find(options = {})
44
+ result = QueryResult.new
45
+
46
+ # Handler hash for doing AND intersection checking
47
+ found = {}
48
+
49
+ name = options.delete(:name)
50
+ returns = options.delete(:returns)
51
+ arguments = options.delete(:arguments)
52
+
53
+ raise ":arguments must be an array" if arguments && !arguments.is_a?(Array)
54
+ raise "Unknown keys #{option.keys.join(", ")}. " +
55
+ "Expected are: :name, :arguments, and :returns" unless options.empty?
56
+
57
+ self.each do |node|
58
+ # C++ name
59
+ if name
60
+ found[:name] ||= []
61
+ if name.is_a?(Regexp)
62
+ found_name = (node.attributes["name"] =~ name)
63
+ else
64
+ found_name = (node.attributes["name"] == name)
65
+ end
66
+
67
+ found[:name] << node if found_name
68
+ end
69
+
70
+ # Return type
71
+ if returns && [Function, Method].include?(node.class)
72
+ found[:returns] ||= []
73
+ found[:returns] << node if node.return_type == returns.to_s
74
+ end
75
+
76
+ # Arguments list
77
+ if arguments && [Function, Method, Constructor].include?(node.class)
78
+ found[:arguments] ||= []
79
+ keep = false
80
+ args = node.arguments
81
+
82
+ if args.size == arguments.size
83
+ keep = true
84
+ arguments.each_with_index do |arg, idx|
85
+ # nil is the "any" flag
86
+ if !arg.nil? && args[idx].cpp_type != arg.to_s
87
+ keep = false
88
+ break
89
+ end
90
+ end
91
+ end
92
+
93
+ found[:arguments] << node if keep
94
+ end
95
+ end
96
+
97
+ # Now we do an intersection of all the found nodes,
98
+ # which ensures that we AND together all the parts
99
+ # the user is looking for
100
+ tmp = self
101
+ found.each_value do |value|
102
+ tmp = (tmp & value)
103
+ end
104
+
105
+ # But make sure that we always have a QueryResult and
106
+ # not a plain Array
107
+ result << tmp
108
+ result.flatten!
109
+
110
+ result.length == 1 ? result[0] : result
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,42 @@
1
+ # RbGCCXML, the library to parse and query C++ header code.
2
+ module RbGCCXML
3
+
4
+ class << self
5
+
6
+ # This is where it all happens. This method must be after any calls
7
+ # to RbGCCXML.gccxml_path= or RbGCCXML.add_include_paths.
8
+ # Files can be one of many formats (and should always be full directory paths):
9
+ #
10
+ # <tt>"/path/to/file.h"</tt>
11
+ #
12
+ # <tt>"/dir/glob/**/*.h"</tt>
13
+ #
14
+ # An array of either of the above.
15
+ #
16
+ # +options+ can be any of:
17
+ #
18
+ # includes:: A single string, or an array of strings of directory includes (-I directives)
19
+ #
20
+ # Returns the Namespace Node linked to the global namespace "::".
21
+ #
22
+ def parse(files, options = {})
23
+ # Steps here:
24
+ # 1. Find gccxml
25
+ # 2. Find all files expected to be parsed
26
+ # 3. If multiple files:
27
+ # Build up a temp file with #includes to each file to be parsed
28
+ # 4. If multiple files:
29
+ # Run gccxml on this generated file
30
+ # else
31
+ # Run gccxml on the expected file
32
+ # 5. Parse out XML into class tree
33
+
34
+ @parser = Parser.new :files => files, :includes => options[:includes]
35
+ @parser.parse
36
+ end
37
+
38
+ end
39
+
40
+ class SourceNotFoundError < RuntimeError; end
41
+ class ConfigurationError < RuntimeError; end
42
+ end
@@ -0,0 +1,89 @@
1
+ module RbGCCXML
2
+
3
+ # A module of methods used to parse out the flat GCC-XML file into
4
+ # a proper heirarchy. These methods are used internally and not intended
5
+ # for outside use.
6
+ module XMLParsing
7
+
8
+ # I'm not sure if Hpricot is unable to jump back out to the document
9
+ # root or if I just can't find how, but we give this module the
10
+ # Hpricot document to do proper searching.
11
+ def self.doc_root=(root)
12
+ @@doc_root = root
13
+ end
14
+
15
+ # Generic finding of nodes according to attributes.
16
+ # Special options:
17
+ #
18
+ # <tt>:type</tt>:: Specify a certain node type to search by
19
+ #
20
+ # Any other options is directly mapped to attributes on the node. For example, to find
21
+ # a Function node that have the name "functor":
22
+ #
23
+ # XMLParsing.find(:type => "Function", :name => "functor")
24
+ #
25
+ # Returns the first found node
26
+ def self.find(options = {})
27
+ return nil if options.empty?
28
+
29
+ type = options.delete(:type)
30
+ attrs = options.map {|key, value| "[@#{key}='#{value}']"}.join
31
+
32
+ xpath = "//#{type}#{attrs}"
33
+
34
+ got = @@doc_root.search(xpath)[0]
35
+
36
+ if got
37
+ RbGCCXML.const_get(type || got.name).new(got)
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ # Look through the DOM under +node+ for +node_type+ nodes.
44
+ # +node_type+ must be the string name of an existing Node subclass.
45
+ #
46
+ # Returns a QueryResult with the findings.
47
+ def self.find_nested_nodes_of_type(node, node_type)
48
+ results = QueryResult.new
49
+
50
+ # First of all limit which elements we're searching for, to ease processing.
51
+ # In the GCCXML output, node heirarchy is designated by id, members, and context
52
+ # attributes:
53
+ #
54
+ # id => Unique identifier of a given node
55
+ # members => Space-delimited array of node id's that are under this node
56
+ # context => The parent node id of this node
57
+ #
58
+ # We only want those nodes in node's context.
59
+ xpath = "//#{node_type}[@context='#{node.attributes["id"]}']"
60
+
61
+ @@doc_root.search(xpath).each do |found|
62
+ results << RbGCCXML.const_get(node_type).new(found)
63
+ end
64
+
65
+ results.flatten
66
+ end
67
+
68
+ # Arguments are a special case in gccxml as they are actual children of
69
+ # the functions / methods they are a part of.
70
+ def self.find_arguments_for(node)
71
+ results = QueryResult.new
72
+
73
+ node.get_elements_by_tag_name("Argument").each do |argument|
74
+ results << Argument.new(argument)
75
+ end
76
+
77
+ results.flatten
78
+ end
79
+
80
+ # Entrance into the type management. Given a GCCXML node and an attribute
81
+ # to reference, find the C++ type related. For example, finding the return
82
+ # type of a function:
83
+ #
84
+ # +find_type_of(func_node, "returns")+ could return "std::string" node, "int" node, etc
85
+ def self.find_type_of(node, attribute)
86
+ XMLParsing.find(:id => node.attributes[attribute])
87
+ end
88
+ end
89
+ end
data/lib/rbgccxml.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'hpricot'
2
+ gem 'gccxml_gem'
3
+ require 'gccxml'
4
+
5
+ require 'rbgccxml/rbgccxml'
6
+
7
+ module RbGCCXML
8
+
9
+ # Core classes
10
+ autoload :Parser, "rbgccxml/parser"
11
+ autoload :Node, "rbgccxml/node"
12
+ autoload :QueryResult, "rbgccxml/query_result"
13
+ autoload :XMLParsing, "rbgccxml/xml_parsing"
14
+
15
+ # Nodes
16
+ autoload :Argument, "rbgccxml/nodes/argument"
17
+ autoload :Class, "rbgccxml/nodes/class"
18
+ autoload :Constructor, "rbgccxml/nodes/constructor"
19
+ autoload :File, "rbgccxml/nodes/file"
20
+ autoload :Function, "rbgccxml/nodes/function"
21
+ autoload :Method, "rbgccxml/nodes/method"
22
+ autoload :Namespace, "rbgccxml/nodes/namespace"
23
+ autoload :Struct, "rbgccxml/nodes/struct"
24
+
25
+ # Type Management
26
+ autoload :Type, "rbgccxml/nodes/type"
27
+ autoload :FundamentalType, "rbgccxml/nodes/types/fundamental_type"
28
+ autoload :PointerType, "rbgccxml/nodes/types/pointer_type"
29
+ autoload :Typedef, "rbgccxml/nodes/types/typedef"
30
+
31
+ end