Class: RubyHeaderParser::Parser
- Inherits:
-
Object
- Object
- RubyHeaderParser::Parser
- Defined in:
- lib/ruby_header_parser/parser.rb,
sig/ruby_header_parser/parser.rbs
Overview
parse ruby.h using ctags
Constant Summary collapse
- DEFAULT_HEADER_FILE =
"#{RbConfig::CONFIG["rubyhdrdir"]}/ruby.h".freeze
- DEFAULT_INCLUDE_PATHS =
[ RbConfig::CONFIG["rubyarchhdrdir"], RbConfig::CONFIG["rubyhdrdir"], ].freeze
Instance Attribute Summary collapse
- #config ⇒ RubyHeaderParser::Config readonly
- #dist_preprocessed_header_file ⇒ String readonly
- #header_file ⇒ String readonly
- #include_paths ⇒ Array<String> readonly
Instance Method Summary collapse
- #__extract_function_definitions(c_kinds:, kind:, is_parse_multiline_definition:) ⇒ Array<RubyHeaderParser::FunctionDefinition>
-
#analyze_argument_type(function_name:, arg_pos:, parts:) ⇒ Array<String, Symbol, Integer>
- type [String] - pointer [Symbol] - length [Integer].
- #create_typeref(definition:, function_name:, typeref_field:, filepath:, line_num:) ⇒ RubyHeaderParser::TyperefDefinition
- #execute_ctags(args = "") ⇒ String
- #extract_enum_definitions ⇒ Array<RubyHeaderParser::EnumDefinition>
- #extract_function_definitions ⇒ Array<RubyHeaderParser::FunctionDefinition>
- #extract_static_inline_function_definitions ⇒ Array<RubyHeaderParser::FunctionDefinition>
- #extract_struct_definitions ⇒ Array<RubyHeaderParser::StructDefinition>
- #extract_type_definitions ⇒ Array<RubyHeaderParser::TyperefDefinition>
- #generate_argument_definition(function_name:, arg:, arg_pos:) ⇒ ArgumentDefinition
- #generate_function_definition_from_line(line:, kind:, is_parse_multiline_definition:) ⇒ RubyHeaderParser::FunctionDefinition?
-
#initialize(dist_preprocessed_header_file: nil, header_file: DEFAULT_HEADER_FILE, include_paths: DEFAULT_INCLUDE_PATHS, config_file: nil) ⇒ Parser
constructor
A new instance of Parser.
- #parse_definition_args(function_name, signature) ⇒ Array<RubyHeaderParser::ArgumentDefinition>
- #parse_function_definition(filepath:, pattern:, line_num:, is_parse_multiline_definition:) ⇒ String
- #parse_typeref_type(definition:, function_name:, typeref_field:, filepath:, line_num:) ⇒ String
- #pointer_length(type) ⇒ Integer
-
#prepare_argument_parts(parts:, arg_pos:) ⇒ Array<Symbol, Integer>
- pointer [Symbol,nil] - length [Integer].
- #read_definition_from_header_file(file, line_num) ⇒ String
- #read_file_line(filepath:, line_num:) ⇒ String?
Constructor Details
#initialize(dist_preprocessed_header_file: nil, header_file: DEFAULT_HEADER_FILE, include_paths: DEFAULT_INCLUDE_PATHS, config_file: nil) ⇒ Parser
Note:
dist_preprocessed_header_file is used as the output destination for temporary files when the parser
is executed
Note:
See CONFIG.md for config file details
Returns a new instance of Parser.
39 40 41 42 43 44 45 46 47 |
# File 'lib/ruby_header_parser/parser.rb', line 39 def initialize(dist_preprocessed_header_file: nil, header_file: DEFAULT_HEADER_FILE, include_paths: DEFAULT_INCLUDE_PATHS, config_file: nil) @header_file = header_file @include_paths = include_paths @dist_preprocessed_header_file = dist_preprocessed_header_file || File.join(Dir.tmpdir, "ruby_preprocessed.h") config_file ||= File.("../../config/default.yml.erb", __dir__.to_s) @config = Config.new(config_file) end |
Instance Attribute Details
#config ⇒ RubyHeaderParser::Config (readonly)
20 21 22 |
# File 'lib/ruby_header_parser/parser.rb', line 20 def config @config end |
#dist_preprocessed_header_file ⇒ String (readonly)
16 17 18 |
# File 'lib/ruby_header_parser/parser.rb', line 16 def dist_preprocessed_header_file @dist_preprocessed_header_file end |
#header_file ⇒ String (readonly)
8 9 10 |
# File 'lib/ruby_header_parser/parser.rb', line 8 def header_file @header_file end |
#include_paths ⇒ Array<String> (readonly)
12 13 14 |
# File 'lib/ruby_header_parser/parser.rb', line 12 def include_paths @include_paths end |
Instance Method Details
#__extract_function_definitions(c_kinds:, kind:, is_parse_multiline_definition:) ⇒ Array<RubyHeaderParser::FunctionDefinition>
123 124 125 126 127 128 129 |
# File 'lib/ruby_header_parser/parser.rb', line 123 def __extract_function_definitions(c_kinds:, kind:, is_parse_multiline_definition:) stdout = ("--c-kinds=#{c_kinds} --fields=+nS --extras=+q") stdout.each_line.map do |line| generate_function_definition_from_line(line:, kind:, is_parse_multiline_definition:) end.compact.uniq(&:name) end |
#analyze_argument_type(function_name:, arg_pos:, parts:) ⇒ Array<String, Symbol, Integer>
Returns - type [String]
- pointer [Symbol]
- length [Integer].
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/ruby_header_parser/parser.rb', line 322 def analyze_argument_type(function_name:, arg_pos:, parts:) pointer, length = prepare_argument_parts(arg_pos:, parts:) type = parts[0...-1] || [] original_type = Util.sanitize_type(type.join(" ")) case original_type when /\*+$/ type = original_type.gsub(/\*+$/, "").strip pointer = config.function_arg_pointer_hint(function_name:, pos: arg_pos) when /^void\s*/, /\(.*\)/ # function pointer (e.g. void *(*func)(void *)) is treated as `void*` type = "void" pointer = config.function_arg_pointer_hint(function_name:, pos: arg_pos) else type = original_type end length = pointer_length(original_type) if pointer == :sref [type, pointer, length] end |
#create_typeref(definition:, function_name:, typeref_field:, filepath:, line_num:) ⇒ RubyHeaderParser::TyperefDefinition
233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/ruby_header_parser/parser.rb', line 233 def create_typeref(definition:, function_name:, typeref_field:, filepath:, line_num:) typeref_type = parse_typeref_type(definition:, function_name:, typeref_field:, filepath:, line_num:) typeref_pointer = nil if typeref_type.match?(/\*+$/) typeref_type = typeref_type.gsub(/\*+$/, "").strip typeref_pointer = config.function_self_pointer_hint(function_name) end TyperefDefinition.new(type: typeref_type, pointer: typeref_pointer) end |
#execute_ctags(args = "") ⇒ String
166 167 168 169 170 171 172 173 |
# File 'lib/ruby_header_parser/parser.rb', line 166 def (args = "") unless File.exist?(dist_preprocessed_header_file) include_args = include_paths.map { |path| "-I #{path}" }.join(" ") system("gcc -E #{include_args} #{header_file} -o #{dist_preprocessed_header_file}", exception: true) end `ctags --languages=C --language-force=C #{args} -f - #{dist_preprocessed_header_file}` end |
#extract_enum_definitions ⇒ Array<RubyHeaderParser::EnumDefinition>
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/ruby_header_parser/parser.rb', line 93 def extract_enum_definitions stdout = ("--c-kinds=e --fields=+n") # Workaround for Ruby::UnannotatedEmptyCollection on steep 1.9.0+ name_to_definition = {} #: Hash[String, RubyHeaderParser::EnumDefinition] name_to_definitions = stdout.each_line.with_object(name_to_definition) do |line, hash| parts = line.split("\t") enum_name = Util.find_field(parts, "enum") next unless enum_name value = parts[0] next unless config.should_generate_enum?(enum_name) hash[enum_name] ||= EnumDefinition.new(name: enum_name) hash[enum_name].values << value end name_to_definitions.values end |
#extract_function_definitions ⇒ Array<RubyHeaderParser::FunctionDefinition>
50 51 52 |
# File 'lib/ruby_header_parser/parser.rb', line 50 def extract_function_definitions __extract_function_definitions(c_kinds: "p", kind: "p", is_parse_multiline_definition: true) end |
#extract_static_inline_function_definitions ⇒ Array<RubyHeaderParser::FunctionDefinition>
55 56 57 |
# File 'lib/ruby_header_parser/parser.rb', line 55 def extract_static_inline_function_definitions __extract_function_definitions(c_kinds: "+p-d", kind: "f", is_parse_multiline_definition: false) end |
#extract_struct_definitions ⇒ Array<RubyHeaderParser::StructDefinition>
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/ruby_header_parser/parser.rb', line 60 def extract_struct_definitions stdout = ("--c-kinds=s --fields=+n") stdout.each_line.with_object([]) do |line, definitions| parts = line.split("\t") struct_name = parts[0] next unless config.should_generate_struct?(struct_name) definitions << StructDefinition.new( name: struct_name, ) end end |
#extract_type_definitions ⇒ Array<RubyHeaderParser::TyperefDefinition>
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/ruby_header_parser/parser.rb', line 76 def extract_type_definitions stdout = ("--c-kinds=t --fields=+n") stdout.each_line.with_object([]) do |line, definitions| parts = line.split("\t") type_name = parts[0] next unless config.should_generate_type?(type_name) definitions << TypeDefinition.new( name: type_name, ) end.uniq(&:name) end |
#generate_argument_definition(function_name:, arg:, arg_pos:) ⇒ ArgumentDefinition
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/ruby_header_parser/parser.rb', line 285 def generate_argument_definition(function_name:, arg:, arg_pos:) parts = arg.split if parts.count < 2 return ArgumentDefinition.new( type: parts[0], name: "arg#{arg_pos}", pointer: nil, ) end loop do pointer_index = parts.index("*") break unless pointer_index parts[pointer_index - 1] << "*" parts.delete_at(pointer_index) end type, pointer, length = analyze_argument_type(function_name:, arg_pos:, parts:) ArgumentDefinition.new( name: parts[-1], type:, pointer:, length:, ) end |
#generate_function_definition_from_line(line:, kind:, is_parse_multiline_definition:) ⇒ RubyHeaderParser::FunctionDefinition?
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/ruby_header_parser/parser.rb', line 136 def generate_function_definition_from_line(line:, kind:, is_parse_multiline_definition:) parts = line.split("\t") function_name = parts[0] filepath = parts[1] return nil unless config.should_generate_function?(function_name) return nil unless parts[3] == kind line_num = Util.find_field(parts, "line").to_i definition = parse_function_definition(filepath:, pattern: parts[2], line_num:, is_parse_multiline_definition:) args = parse_definition_args(function_name, Util.find_field(parts, "signature")) # Exclude functions with variable-length arguments return nil if args&.last&.type == "..." typeref_field = Util.find_field(parts, "typeref:typename") FunctionDefinition.new( definition:, name: function_name, typeref: create_typeref(definition:, function_name:, typeref_field:, filepath:, line_num:), args:, ) end |
#parse_definition_args(function_name, signature) ⇒ Array<RubyHeaderParser::ArgumentDefinition>
212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/ruby_header_parser/parser.rb', line 212 def parse_definition_args(function_name, signature) return [] unless signature signature = signature.strip.delete_prefix("(").delete_suffix(")") return [] if signature.match?(/^void$/i) args = Util.split_signature(signature) arg_pos = 0 args.map do |arg| arg_pos += 1 generate_argument_definition(function_name:, arg:, arg_pos:) end end |
#parse_function_definition(filepath:, pattern:, line_num:, is_parse_multiline_definition:) ⇒ String
196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/ruby_header_parser/parser.rb', line 196 def parse_function_definition(filepath:, pattern:, line_num:, is_parse_multiline_definition:) definition = if pattern.end_with?("$/;\"") pattern.delete_prefix("/^").delete_suffix("$/;\"") elsif is_parse_multiline_definition read_definition_from_header_file(filepath, line_num) else pattern.delete_prefix("/^") end definition.strip.delete_suffix(";") end |
#parse_typeref_type(definition:, function_name:, typeref_field:, filepath:, line_num:) ⇒ String
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/ruby_header_parser/parser.rb', line 251 def parse_typeref_type(definition:, function_name:, typeref_field:, filepath:, line_num:) typeref_type = if typeref_field typeref_field.gsub(/[A-Z_]+\s*\(\(.*\)\)/, "") else # parse typeref in definition type = definition[0...definition.index(function_name)] || "" type.gsub("char *", "char*").strip end typeref_type = Util.sanitize_type(typeref_type) return typeref_type unless typeref_type.empty? # Check prev line line = read_file_line(filepath:, line_num: line_num - 1) return Util.sanitize_type(line) if line "" end |
#pointer_length(type) ⇒ Integer
374 375 376 377 |
# File 'lib/ruby_header_parser/parser.rb', line 374 def pointer_length(type) type =~ /(\*+)$/ ::Regexp.last_match(1)&.length || 0 end |
#prepare_argument_parts(parts:, arg_pos:) ⇒ Array<Symbol, Integer>
Returns - pointer [Symbol,nil]
- length [Integer].
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/ruby_header_parser/parser.rb', line 352 def prepare_argument_parts(parts:, arg_pos:) if parts[-1] =~ /\[([0-9]+)?\]$/ parts[-1].gsub!(/\[([0-9]+)?\]$/, "") length = ::Regexp.last_match(1).to_i unless parts[-1] =~ /^[0-9a-zA-Z_]+$/ # last elements isn't dummy argument parts << "arg#{arg_pos}" end return [:array, length] end unless parts[-1] =~ /^[0-9a-zA-Z_]+$/ # last elements isn't dummy argument parts << "arg#{arg_pos}" end [nil, 0] end |
#read_definition_from_header_file(file, line_num) ⇒ String
177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/ruby_header_parser/parser.rb', line 177 def read_definition_from_header_file(file, line_num) definition = +"" File.open(file, "r") do |f| f.each_with_index do |line, index| if index + 1 >= line_num definition << line.strip return definition if definition.end_with?(");") end end end "" end |
#read_file_line(filepath:, line_num:) ⇒ String?
273 274 275 276 277 278 |
# File 'lib/ruby_header_parser/parser.rb', line 273 def read_file_line(filepath:, line_num:) return nil if line_num < 1 lines = File.open(filepath, "rb") { |f| f.readlines(chomp: true) } lines[line_num - 1] end |