Class: FFI::UCtags
- Inherits:
-
Object
- Object
- FFI::UCtags
- Defined in:
- lib/ffi/uctags.rb,
lib/ffi/uctags/version.rb,
lib/ffi/uctags/directory.rb
Overview
Auto-load FFI functions and etc. by parsing a C header file. See the README for an overview of the gem with an example.
Most use cases are only concerned with calling the main method UCtags.call and perhaps an UCtags.ffi_module customization.
Other class and instance methods (including #initialize) are for advanced uses such as extending the gem.
Since instantiating is not intended, ::new
has turned private;
of course, nothing’s stopping you from un-privatizing it.
Technical developers, you may also be interested in:
Constant Summary collapse
- VERSION =
VERSION
'1.1.0'.freeze
- EXE_ROOT =
Absolute path to the Universal Ctags root (
PREFIX
) where thebin
andsrc
folders are located. File.('../../../../u-ctags/', __FILE__).freeze
- EXE_PATH =
Absolute path to the Universal
ctags
executable – EXE_ROOT/bin/ctags
. File.join(EXE_ROOT, 'bin/ctags').freeze
Class Attribute Summary collapse
-
.ffi_module ⇒ Module & FFI::Library
deprecated
Deprecated.
This was originally designed to integrate with alternate FFI implementations such as
Instance Attribute Summary collapse
-
#composite_namespacing ⇒ Hash[singleton(FFI::Struct) | FFI::Enum, singleton(FFI::Struct)]
readonly
A hash that maps inner structs/unions/enums to their outer structs/unions.
-
#composite_typedefs ⇒ Hash[Symbol, singleton(FFI::Struct) | FFI::Enum]
readonly
Table of typedef-struct/unions/enums.
-
#composite_types ⇒ Hash[Symbol, Symbol | singleton(FFI::Struct) | FFI::Enum]
readonly
A hash that maps struct/union/enum names to either: * the class [singleton(FFI::Struct)] or enum [FFI::Enum] directly * its (newest) #composite_typedefs key [Symbol], for structs/unions with typedefs.
-
#library ⇒ Module & FFI::Library
readonly
The
Library
module this instance is working on. -
#stack ⇒ Array[[Array[untyped], ^(Array[untyped], String?) -> void, String?]
readonly
A LIFO array for work-in-progress constructs, most notably functions and structs.
Class Method Summary collapse
- .call(library_name, header_path, &blk) ⇒ Module & FFI::Library
-
.ffi_const(name) ⇒ bot
deprecated
Deprecated.
This was originally designed to integrate with alternate FFI implementations such as
Instance Method Summary collapse
-
#close ⇒ Module & FFI::Library
Complete the work of this instance: 1.
-
#composite_type(name) ⇒ singleton(FFI::Struct) | FFI::Enum
Find the named struct or union (or enum in future versions) from #composite_types.
-
#const_composites ⇒ Array[Symbol]
Assign each struct, union or enum in #composite_types to constants.
-
#extract_and_process_type ⇒ FFI::Type
Extract and process (#find_type or #composite_type) the type from
@fields
(see #process). -
#extract_type ⇒ [String, bool?]
Extract the type name from
@fields
(see #process). -
#ffi_const ⇒ Object
deprecated
Deprecated.
This was originally designed to integrate with alternate FFI implementations such as
-
#find_type(name) ⇒ FFI::Type
Find the named type from #library.
-
#initialize(library_name) ⇒ UCtags
constructor
Create an instance for working on the named shared library.
-
#new_composite {|members| ... } ⇒ String?
Prepare to build a new struct, union or enum.
-
#new_construct {|members, namespace| ... } ⇒ String?
Prepare to build a new construct.
-
#process(k, name, fields) ⇒ void
Process the u-ctags entry.
-
#stack_push ⇒ void
Array#push
the given args to the top of the #stack. -
#struct(superclass, name) ⇒ singleton(FFI::Struct)
Build and record a new struct or union class.
-
#typedef(name) ⇒ FFI::Type | singleton(FFI::Struct) | FFI::Enum
Register a typedef.
Constructor Details
#initialize(library_name) ⇒ UCtags
::new
is private. See the class description for the intention.
Create an instance for working on the named shared library.
The attribute #library is set to a new Library
module with the named shared library loaded.
149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/ffi/uctags.rb', line 149 def initialize(library_name) @library = Module.new #: FFI::library @library.extend(ffi_const :Library) @library.ffi_lib(library_name) @composite_types = {} @composite_typedefs = {} @composite_namespacing = {} @stack = [] @fields = {} # `nil` error prevention end |
Class Attribute Details
.ffi_module ⇒ Module & FFI::Library
This was originally designed to integrate with alternate FFI implementations such as
The module for call to source constants (namely modules and classes) from; the default is FFI.
The customized module does not have to cover all utilized FFI modules/classes – call will fall back to source from FFI for modules/classes not found from this module (see ffi_const). However, those the module does provide must match in layouts and functionalities as those of FFI.
Nice-FFI or another custom subset of patches. However, the OG FFI library have grown to be a complete platform, to the point that contributing into FFI is more practical than developing mods that may one day go obsolete.
38 39 40 |
# File 'lib/ffi/uctags.rb', line 38 def ffi_module @ffi_module end |
Instance Attribute Details
#composite_namespacing ⇒ Hash[singleton(FFI::Struct) | FFI::Enum, singleton(FFI::Struct)] (readonly)
A hash that maps inner structs/unions/enums to their outer structs/unions
127 128 129 |
# File 'lib/ffi/uctags.rb', line 127 def composite_namespacing @composite_namespacing end |
#composite_typedefs ⇒ Hash[Symbol, singleton(FFI::Struct) | FFI::Enum] (readonly)
Table of typedef-struct/unions/enums
123 124 125 |
# File 'lib/ffi/uctags.rb', line 123 def composite_typedefs @composite_typedefs end |
#composite_types ⇒ Hash[Symbol, Symbol | singleton(FFI::Struct) | FFI::Enum] (readonly)
A hash that maps struct/union/enum names to either:
- the class [singleton(FFI::Struct)] or enum [FFI::Enum] directly
- its (newest) #composite_typedefs key [Symbol], for structs/unions with typedefs.
- This design allows #const_composites to prefer the (newest) typedef alias over the original, which is often omitted through the typedef-struct and equivalent patterns.
119 120 121 |
# File 'lib/ffi/uctags.rb', line 119 def composite_types @composite_types end |
#library ⇒ Module & FFI::Library (readonly)
The Library
module this instance is working on
110 111 112 |
# File 'lib/ffi/uctags.rb', line 110 def library @library end |
#stack ⇒ Array[[Array[untyped], ^(Array[untyped], String?) -> void, String?] (readonly)
A LIFO array for work-in-progress constructs, most notably functions and structs. The stack design enables building an inner construct (top of the stack) while putting outer constructs on hold.
Each element is a 3-tuple of
- a construct member queue
- a proc (or equivalent)
- the namespace in which this construct should define under When ready, the proc is called with the populated member list (as a single arg) and the namespace.
140 141 142 |
# File 'lib/ffi/uctags.rb', line 140 def stack @stack end |
Class Method Details
.call(library_name, header_path, &blk) ⇒ Module & FFI::Library
Create a new Library
module,
load the named shared library,
and utilize ctags
to parse the C header located at header_path
.
If providing a block, also evaluate it in the context of the new module (Module#module_eval
).
Beware that module_eval
does not scope constants – you have to retrieve/write them like self::THIS
.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/ffi/uctags.rb', line 79 def call(library_name, header_path, &blk) instance = new(library_name) #noinspection SpellCheckingInspection this command use letter flags cmd = %W[#{EXE_PATH} --language-force=C --param-CPreProcessor._expand=1 --kinds-C=defgmpstuxz --fields=NFPkSst --fields-C={macrodef} -nuo -] #: Array[_ToS] cmd.insert(2, '-V') if $DEBUG cmd << header_path IO.popen(cmd) do|cmd_out| cmd_out.each_line(chomp: true) do|line| # Note for maintainers: # For compilers’ convenience, C doesn’t allow use before declaration (except for functions pre-C11), # so we don’t need to worry about types used before they’re loaded as that’d be the library’s fault. name, file, line, k, *fields = line.split("\t") line.delete_suffix!(';"') puts "processing `#{name}` of kind `#{k}` (#{file}@#{line})" if $VERBOSE instance.process(k, name, fields.to_h { _1.split(':', 2) }) end end instance.close.tap { _1.module_eval(&blk) if block_given? } end |
.ffi_const(name) ⇒ bot
This was originally designed to integrate with alternate FFI implementations such as
Look up the named constant from ffi_module or its ancestors, or from FFI if not found in that module.
52 53 54 55 56 |
# File 'lib/ffi/uctags.rb', line 52 def ffi_const(name) ffi_module.const_get(name, true) rescue NameError FFI.const_get(name, true) end |
Instance Method Details
#close ⇒ Module & FFI::Library
it is possible, albeit unorthodox, to continue using this instance after close
ing it.
Complete the work of this instance:
- Finish up any ongoing progress (see #new_construct)
- Assign structs, unions and enums to constants
495 496 497 498 499 500 501 502 |
# File 'lib/ffi/uctags.rb', line 495 def close puts 'finishing up' if $VERBOSE @fields.clear new_construct # flush the last construct const_composites puts 'done' if $VERBOSE library # return end |
#composite_type(name) ⇒ singleton(FFI::Struct) | FFI::Enum
Find the named struct or union (or enum in future versions) from #composite_types.
307 308 309 310 311 312 313 314 |
# File 'lib/ffi/uctags.rb', line 307 def composite_type(name) # Find from {#composite_typedefs} first, process if not found #noinspection RubyMismatchedReturnType RubyMine cannot follow that `type` can no longer be a Symbol composite_typedefs.fetch(name.to_sym) do|name_sym| type = composite_types.fetch(name_sym) type.is_a?(Symbol) ? composite_typedefs.fetch(type) : type end end |
#const_composites ⇒ Array[Symbol]
Assign each struct, union or enum in #composite_types to constants.
If the type’s name is invalid (not capitalized), capitalize the first character if possible
(e.g., qoi_desc
➡ Qoi_desc
), and fall back to prefixing S_
, U_
or E_
depending on the type if not.
If names collide or the constant is already defined (e.g., due to a previous call to this method),
the previous definition is implicitly overridden (with Ruby complaining “already initialized constant”).
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/ffi/uctags.rb', line 454 def const_composites union_class = self.ffi_const :Union #noinspection RubyMismatchedReturnType RubyMine cannot follow that `type` is a Symbol when set to `name` composite_types.map do|name, type| # Prefer typedef name if type.is_a?(Symbol) name = type type = composite_typedefs.fetch(type) end #noinspection RubyMismatchedArgumentType RubyMine cannot follow that `type` can no longer be a Symbol namespace = composite_namespacing.fetch(type, @library) #: Module puts "\tdefining constant for construct `#{name}`" if $VERBOSE begin namespace.const_set(name, type) name rescue NameError # not a capitalized name # Capitalize first letter, prefix if cannot name = name.to_s first_char = name[0] name = if first_char&.capitalize! # capitalized name[0] = first_char name.to_sym elsif type.is_a? Class # struct or union (type < union_class) ? :"U_#{name}" : :"S_#{name}" else # enum (or something else) :"E_#{name}" end puts "\tas `#{name}`" if $VERBOSE namespace.const_set(name, type) name end end end |
#extract_and_process_type ⇒ FFI::Type
Extract and process (#find_type or #composite_type) the type from @fields
(see #process).
321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/ffi/uctags.rb', line 321 def extract_and_process_type name, is_composite = extract_type if is_composite.nil? # basic type find_type(name) else type = composite_type(name) if type.is_a? Class is_composite ? type.by_value : type.by_ref else is_composite ? type : @library.find_type(:pointer) end end end |
#extract_type ⇒ [String, bool?]
Extract the type name from @fields
(see #process).
Rip off names of types it nests under as all public names in C live in the same global namespace. Identify and processes pointers to and arrays of structs, unions or enums.
Do not process the extracted name to a usable FFI::Type
;
follow up with #find_type or #composite_type, or use #extract_and_process_type instead.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/ffi/uctags.rb', line 232 def extract_type type_type, *_, name = @fields.fetch('typeref').split(':') if name.end_with?(']') puts "\tarray type" if $VERBOSE name = 'pointer' # FFI does not support typed array auto-casting for functions # (for struct/union members: https://github.com/ParadoxV5/FFI-UCtags/issues/14) is_composite = nil elsif 'typename'.eql?(type_type) # basic type or typedef name_without_star = name.dup is_composite = name_without_star.delete_suffix!(' *').nil? # whether pointer suffix not deleted if composite_typedefs.include?(name_without_star.to_sym) # typedef-composite name = name_without_star puts "\ttypedef `#{name}`" if $VERBOSE else # basic type puts "\tbasic type `#{name}`" if $VERBOSE is_composite = nil end else # non-typedef composite puts "\t#{type_type} type `#{name}`" if $VERBOSE is_composite = name.delete_suffix!(' *').nil? # whether pointer suffix not deleted end [name, is_composite] end |
#ffi_const ⇒ Object
This was originally designed to integrate with alternate FFI implementations such as
Instance-level delegate for ffi_const
104 |
# File 'lib/ffi/uctags.rb', line 104 def ffi_const(...) = self.class.ffi_const(...) |
#find_type(name) ⇒ FFI::Type
Find the named type from #library.
Find typedefs. Do not find structs, unions and enums; use #composite_type for those.
Fall back to TYPE_POINTER
for unrecognized unique names.
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/ffi/uctags.rb', line 265 def find_type(name) fallback = false name_sym = case name when /\*/ # `t *`, t (*) []`, `t (*)(…)`, etc. :pointer when '_Bool' :bool when 'long double' :long_double else # Check multi-keyword integer types (does not match unconventional styles such as `int long untyped long`) # duplicate `int_type` capture name is intentional if /\A((?<unsigned>un)?signed )?((?<int_type>long|short|long long)( int)?|(?<int_type>int|char))\z/ =~ name #noinspection RubyResolve RubyMine cannot extract =~ local vars int_type.tr!(' ', '_') # namely `long long` -> 'long_long' #noinspection RubyResolve RubyMine cannot extract =~ local vars unsigned ? :"u#{int_type}" : int_type.to_sym else # use type map and fallback fallback = true name.to_sym end end begin @library.find_type(name_sym) rescue TypeError => e raise e unless fallback # Assume the unknown type is a pointer alias defined in another file. # This should just propagate an exception once multi-file parsing is supported. warn "unrecognized type `#{name}`, falling back to `TYPE_POINTER`" ffi_const :TYPE_POINTER end end |
#new_composite {|members| ... } ⇒ String?
Does not register the type in #composite_types – caller need to do that separately (structs/unions) or in the block (enums).
Prepare to build a new struct, union or enum.
413 414 415 416 417 418 419 |
# File 'lib/ffi/uctags.rb', line 413 def new_composite(&blk) #noinspection RubyMismatchedReturnType RubyMine prefers Yardoc type over RBS type new_construct do|members, namespace| composite = blk.(members) composite_namespacing[composite] = composite_type(namespace) if namespace end end |
#new_construct {|members, namespace| ... } ⇒ String?
Prepare to build a new construct. This method is designed for every new construct to call near the beginning.
Array#slice!
off topmost entries in the #stack according to @fields
.
Invoke the procs of the removed entries in reverse order to ensure these previous constructs flush through.
Finally, if given a block, start a new stack entry with it.
call processes a composite construct (e.g., a function or struct) as a sequence of consecutive components, which starts with the construct itself followed by its original-ordered list of members (e.g., function params, struct members), all as separate full-sized entries. Therefore, a list must queue the members to compile later until the next sequence commences, especially since these sequences do not have terminator parts nor a member count in the header entry.
Simpler constructs with only one u-ctags entry can simply call this method with no block (“nil
block &nil
”).
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/ffi/uctags.rb', line 184 def new_construct(&blk) full_namespace = @fields.fetch('struct') { @fields.fetch('union', nil) } prev_namespace = nil depth = if full_namespace full_namespace = full_namespace.split('::') prev_namespace = full_namespace.last #: String puts "\tunder `#{prev_namespace}`" if $VERBOSE full_namespace.size else 0 end if (prev = stack.slice!(depth..)) and not prev.empty? puts "\tflushing #{prev.size} stack entries" if $VERBOSE prev.reverse_each do|members, a_proc, namespace| if $VERBOSE puts "\t\twith #{members.size} members" puts "\t\tunder `#{namespace}`" if namespace end a_proc.(members, namespace) end end if blk puts "\tstarting new stack entry" if $VERBOSE stack << [[], blk, prev_namespace] end puts "\tstack has #{stack.size} entries" if $VERBOSE #noinspection RubyMismatchedReturnType RubyMine prefers Yardoc type over RBS type prev_namespace end |
#process(k, name, fields) ⇒ void
UCtags holds off from creating access points (constants) for structs/unions/enums until calling #const_composites (or #close), as they may later receive a preferred typedef name.
This method returns an undefined value.
Process the u-ctags entry.
This is the controller for processing various u-ctags kinds. Due to its popularity,
this stores the argument fields
in @fields
instead of passing it as an arg when calling helper methods.
For convenience (leading to performance), this method expects entries for composite construct (e.g., a function or struct) to be consecutive. call achieves this by executing u-ctags unsorted, preserving the order from the original file. See #new_construct.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/ffi/uctags.rb', line 352 def process(k, name, fields) @fields.replace(fields) case k # Functions when 'z' # function parameters inside function or prototype definitions stack_push extract_and_process_type when 'p', 'f' # function prototypes, function definitions type = extract_and_process_type # check type and fail fast new_construct { library.attach_function name, _1, type } # Structs/Unions when 'm' # struct, and union members new_construct stack_push name.to_sym, extract_and_process_type when 's' # structure names struct :Struct, name.to_sym when 'u' # union names struct :Union, name.to_sym # Enums when 'e' # enumerators (values inside an enumeration) stack_push name.to_sym when 'g' # enumeration names new_composite { composite_types[name.to_sym] = library.enum(_1) } # Miscellaneous when 't' # typedefs typedef name.to_sym when 'd' # macro definitions # https://github.com/ParadoxV5/FFI-UCtags/issues/2 when 'x' # external and forward variable declarations new_construct @library.attach_variable name, extract_and_process_type else warn "\tunsupported kind ignored" if $VERBOSE end end |
#stack_push ⇒ void
This method returns an undefined value.
Array#push
the given args to the top of the #stack.
217 218 219 |
# File 'lib/ffi/uctags.rb', line 217 def stack_push(...) stack.last&.first&.push(...) end |
#struct(superclass, name) ⇒ singleton(FFI::Struct)
Build and record a new struct or union class
393 394 395 396 397 398 |
# File 'lib/ffi/uctags.rb', line 393 def struct(superclass, name) new_struct = Class.new(ffi_const superclass) #: singleton(FFI::Struct) new_composite { new_struct.layout(*_1) } #noinspection RubyMismatchedReturnType RubyMine ignores inline RBS annotations composite_types[name] = new_struct end |
#typedef(name) ⇒ FFI::Type | singleton(FFI::Struct) | FFI::Enum
Register a typedef. Register in #library directly for basic types; store in #composite_typedefs (and update #composite_types) for structs, unions and enums.
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 |
# File 'lib/ffi/uctags.rb', line 426 def typedef(name) new_construct type_name, is_composite = extract_type if is_composite.nil? # basic type @library.typedef find_type(type_name), name else # composite type type = composite_type(type_name) if is_composite # configure typedef name only if not aliasing a pointer composite_typedefs[name] = type composite_types[type_name.to_sym] = name type elsif type.is_a? Class @library.typedef type.by_ref, name else @library.typedef :pointer, name end end end |