Class: FFIDB::HeaderParser
- Inherits:
-
Object
- Object
- FFIDB::HeaderParser
- Defined in:
- lib/ffidb/header_parser.rb
Instance Attribute Summary collapse
-
#base_directory ⇒ Object
readonly
Returns the value of attribute base_directory.
-
#debug ⇒ Object
readonly
Returns the value of attribute debug.
-
#defines ⇒ Object
readonly
Returns the value of attribute defines.
-
#exclude_symbols ⇒ Object
readonly
Returns the value of attribute exclude_symbols.
-
#include_paths ⇒ Object
readonly
Returns the value of attribute include_paths.
-
#include_symbols ⇒ Object
readonly
Returns the value of attribute include_symbols.
Instance Method Summary collapse
- #add_include_path!(path) ⇒ void
- #define_macro!(var, val = 1) ⇒ void
-
#initialize(base_directory: nil, debug: nil) ⇒ HeaderParser
constructor
A new instance of HeaderParser.
- #parse_enum(declaration, typedef_name: nil) ⇒ Enum
- #parse_function(declaration) ⇒ Function
- #parse_header(path) {|exception| ... } ⇒ Header
- #parse_location(location) ⇒ Location
- #parse_macro!(var_and_val) ⇒ void
- #parse_parameter(declaration, default_name: '_') ⇒ Parameter
- #parse_struct(declaration, typedef_name: nil) ⇒ Struct
- #parse_type(type) ⇒ Type
- #parse_typedef(declaration, &block) ⇒ Typedef
- #parse_union(declaration, typedef_name: nil) ⇒ Union
- #preserve_type?(type_name) ⇒ Boolean
Constructor Details
#initialize(base_directory: nil, debug: nil) ⇒ HeaderParser
Returns a new instance of HeaderParser.
23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/ffidb/header_parser.rb', line 23 def initialize(base_directory: nil, debug: nil) require 'ffi/clang' # https://rubygems.org/gems/ffi-clang @base_directory = base_directory @debug = debug @defines = {} @include_paths = [] @include_symbols = {} @exclude_symbols = {} @clang_index = FFI::Clang::Index.new end |
Instance Attribute Details
#base_directory ⇒ Object (readonly)
Returns the value of attribute base_directory.
14 15 16 |
# File 'lib/ffidb/header_parser.rb', line 14 def base_directory @base_directory end |
#debug ⇒ Object (readonly)
Returns the value of attribute debug.
15 16 17 |
# File 'lib/ffidb/header_parser.rb', line 15 def debug @debug end |
#defines ⇒ Object (readonly)
Returns the value of attribute defines.
16 17 18 |
# File 'lib/ffidb/header_parser.rb', line 16 def defines @defines end |
#exclude_symbols ⇒ Object (readonly)
Returns the value of attribute exclude_symbols.
19 20 21 |
# File 'lib/ffidb/header_parser.rb', line 19 def exclude_symbols @exclude_symbols end |
#include_paths ⇒ Object (readonly)
Returns the value of attribute include_paths.
17 18 19 |
# File 'lib/ffidb/header_parser.rb', line 17 def include_paths @include_paths end |
#include_symbols ⇒ Object (readonly)
Returns the value of attribute include_symbols.
18 19 20 |
# File 'lib/ffidb/header_parser.rb', line 18 def include_symbols @include_symbols end |
Instance Method Details
#add_include_path!(path) ⇒ void
This method returns an undefined value.
55 56 57 |
# File 'lib/ffidb/header_parser.rb', line 55 def add_include_path!(path) self.include_paths << Pathname(path) end |
#define_macro!(var, val = 1) ⇒ void
This method returns an undefined value.
48 49 50 |
# File 'lib/ffidb/header_parser.rb', line 48 def define_macro!(var, val = 1) self.defines[var.to_sym] = val.to_s end |
#parse_enum(declaration, typedef_name: nil) ⇒ Enum
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/ffidb/header_parser.rb', line 169 def parse_enum(declaration, typedef_name: nil) enum_name = declaration.spelling enum_name = typedef_name if enum_name.empty? FFIDB::Enum.new(enum_name).tap do |enum| declaration.visit_children do |node, _| case node.kind when :cursor_enum_constant_decl k = node.spelling v = node.enum_value enum.values[k] = v end :continue # visit the next sibling end end end |
#parse_function(declaration) ⇒ Function
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/ffidb/header_parser.rb', line 224 def parse_function(declaration) name = declaration.spelling comment = declaration.comment&.text function = FFIDB::Function.new( name: name, type: self.parse_type(declaration.type.result_type), parameters: {}, definition: nil, # set in #parse_header() comment: comment && !(comment.empty?) ? comment : nil, ) declaration.visit_children do |node, _| case node.kind when :cursor_parm_decl default_name = "_#{function.parameters.size + 1}" parameter = self.parse_parameter(node, default_name: default_name) function.parameters[parameter.name.to_sym] = parameter end :continue # visit the next sibling end function.parameters.freeze function.instance_variable_set(:@debug, declaration.type.spelling.sub(/\s*\(/, " #{name}(")) if self.debug # TODO: __attribute__((noreturn)) function end |
#parse_header(path) {|exception| ... } ⇒ Header
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ffidb/header_parser.rb', line 64 def parse_header(path) path = Pathname(path.to_s) unless path.is_a?(Pathname) name = (self.base_directory ? path.relative_path_from(self.base_directory) : path).to_s args = self.defines.inject([]) { |r, (k, v)| r << "-D#{k}=#{v}" } args += self.include_paths.map { |p| "-I#{p}" } translation_unit = nil begin translation_unit = @clang_index.parse_translation_unit(path.to_s, args) rescue FFI::Clang::Error => error raise ParsePanic.new(error.to_s) end translation_unit.diagnostics.each do |diagnostic| exception_class = case diagnostic.severity.to_sym when :fatal then raise ParsePanic.new(diagnostic.format) when :error then ParseError when :warning then ParseWarning else ParseWarning end yield exception_class.new(diagnostic.format) end okayed_files = {} FFIDB::Header.new(name: name, typedefs: [], enums: [], structs: [], unions: [], functions: []).tap do |header| root_cursor = translation_unit.cursor root_cursor.visit_children do |declaration, _| location = declaration.location location_file = location.file if (okayed_files[location_file] ||= self.consider_path?(location_file)) case declaration.kind when :cursor_typedef_decl typedef = self.parse_typedef(declaration) do |symbol| case when symbol.enum? then header.enums << symbol when symbol.struct? then header.structs << symbol when symbol.union? then header.unions << symbol end end header.typedefs << typedef if typedef when :cursor_enum_decl enum_name = declaration.spelling if enum_name && !enum_name.empty? header.enums << self.parse_enum(declaration) end when :cursor_struct struct_name = declaration.spelling if struct_name && !struct_name.empty? if (struct = self.parse_struct(declaration)) header.structs << struct end end when :cursor_union union_name = declaration.spelling if union_name && !union_name.empty? if (union = self.parse_union(declaration)) header.unions << union end end when :cursor_function function_name = declaration.spelling if self.consider_function?(function_name) function = self.parse_function(declaration) function.definition = self.parse_location(location) header.functions << function end else # TODO: other declarations of interest? end end :continue # visit the next sibling end header.comment = root_cursor.comment&.text end end |
#parse_location(location) ⇒ Location
301 302 303 304 305 306 307 |
# File 'lib/ffidb/header_parser.rb', line 301 def parse_location(location) return nil if location.nil? FFIDB::Location.new( file: location.file ? self.make_relative_path(location.file).to_s : nil, line: location.line, ) end |
#parse_macro!(var_and_val) ⇒ void
This method returns an undefined value.
38 39 40 41 42 |
# File 'lib/ffidb/header_parser.rb', line 38 def parse_macro!(var_and_val) var, val = var_and_val.to_s.split('=', 2) val = 1 if val.nil? self.define_macro! var, val end |
#parse_parameter(declaration, default_name: '_') ⇒ Parameter
252 253 254 255 256 257 |
# File 'lib/ffidb/header_parser.rb', line 252 def parse_parameter(declaration, default_name: '_') name = declaration.spelling type = self.parse_type(declaration.type) FFIDB::Parameter.new( ((name.nil? || name.empty?) ? default_name.to_s : name).to_sym, type) end |
#parse_struct(declaration, typedef_name: nil) ⇒ Struct
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/ffidb/header_parser.rb', line 189 def parse_struct(declaration, typedef_name: nil) struct_name = declaration.spelling struct_name = typedef_name if struct_name.empty? FFIDB::Struct.new(struct_name).tap do |struct| declaration.visit_children do |node, _| case node.kind when :cursor_field_decl field_name = node.spelling field_type = nil node.visit_children do |node, _| case node.kind when :cursor_type_ref field_type = node.spelling :break else :continue end end struct.fields[field_name.to_sym] = Type.for(field_type) end :continue # visit the next sibling end end end |
#parse_type(type) ⇒ Type
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/ffidb/header_parser.rb', line 262 def parse_type(type) ostensible_type = type.spelling ostensible_type.sub!(/\*const$/, '*') # remove private const qualifiers pointer_suffix = case ostensible_type when /(\s\*+)$/ ostensible_type.delete_suffix!($1) $1 else nil end resolved_type = if self.preserve_type?(ostensible_type) ostensible_type << pointer_suffix if pointer_suffix ostensible_type else type.canonical.spelling end resolved_type.sub!(/\*const$/, '*') # remove private const qualifiers Type.for(resolved_type) end |
#parse_typedef(declaration, &block) ⇒ Typedef
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/ffidb/header_parser.rb', line 142 def parse_typedef(declaration, &block) typedef_name = declaration.spelling typedef_type = nil declaration.visit_children do |node, _| node_name = node.spelling case node.kind when :cursor_type_ref typedef_type = node_name when :cursor_enum_decl typedef_type = "enum #{node_name}".rstrip yield self.parse_enum(node, typedef_name: typedef_name) when :cursor_struct typedef_type = "struct #{node_name}".rstrip yield self.parse_struct(node, typedef_name: typedef_name) when :cursor_union typedef_type = "union #{node_name}".rstrip #yield self.parse_union(node, typedef_name: typedef_name) # TODO end :continue # visit the next sibling end FFIDB::Typedef.new(typedef_name, typedef_type) if typedef_type end |
#parse_union(declaration, typedef_name: nil) ⇒ Union
217 218 219 |
# File 'lib/ffidb/header_parser.rb', line 217 def parse_union(declaration, typedef_name: nil) # TODO: parse union declarations end |
#preserve_type?(type_name) ⇒ Boolean
284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/ffidb/header_parser.rb', line 284 def preserve_type?(type_name) case type_name.to_s when 'va_list' then true # <stdarg.h> when '_Bool' then true # <stdbool.h> when 'size_t', 'wchar_t' then true # <stddef.h> when 'const size_t', 'const wchar_t' then true # <stddef.h> # FIXME: need a better solution when /^u?int\d+_t$/, /^u?int\d+_t \*$/ then true # <stdint.h> when /^u?intptr_t$/ then true # <stdint.h> when 'FILE' then true # <stdio.h> when 'ssize_t', 'off_t', 'off64_t' then true # <sys/types.h> else false end end |