From 2ca723a9d74fbd561e20f2c27df32e81df94745f Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Tue, 9 Apr 2024 00:31:22 -0700 Subject: [PATCH] Switch to Ruby's enumerable module. (#80) This is a backwards incompatible change because it required renaming find_all to find_by_kind. In addition, the function signature changed to allow the caller to specify whether to search direct children or all children. --- lib/ffi/clang/cursor.rb | 68 ++++++++++--------------- spec/ffi/clang/comment_spec.rb | 2 +- spec/ffi/clang/cursor_spec.rb | 62 +++++++++++----------- spec/ffi/clang/source_location_spec.rb | 2 +- spec/ffi/clang/token_spec.rb | 4 +- spec/ffi/clang/translation_unit_spec.rb | 4 +- spec/ffi/clang/type_spec.rb | 4 +- spec/spec_helper.rb | 15 +++--- 8 files changed, 73 insertions(+), 88 deletions(-) diff --git a/lib/ffi/clang/cursor.rb b/lib/ffi/clang/cursor.rb index 8b286b6..6033a05 100644 --- a/lib/ffi/clang/cursor.rb +++ b/lib/ffi/clang/cursor.rb @@ -25,6 +25,8 @@ module FFI module Clang class Cursor + include Enumerable + attr_reader :cursor attr_reader :translation_unit @@ -163,7 +165,7 @@ def usr end def kind - @cursor[:kind] + @cursor ? @cursor[:kind] : nil end def kind_spelling @@ -277,14 +279,36 @@ def num_args Lib.get_num_args @cursor end - def visit_children(&block) + def each(recurse = true, &block) + return to_enum(:each, recurse) unless block_given? + adapter = Proc.new do |cxcursor, parent_cursor, unused| - block.call Cursor.new(cxcursor, @translation_unit), Cursor.new(parent_cursor, @translation_unit) + # Call the block and capture the result. This lets advanced users + # modify the recursion on a case by case basis if needed + result = block.call Cursor.new(cxcursor, @translation_unit), Cursor.new(parent_cursor, @translation_unit) + case result + when :continue + :continue + when :recurse + :recurse + else + recurse ? :recurse : :continue + end end - + Lib.visit_children(@cursor, adapter, nil) end + def find_by_kind(recurse, *kinds) + result = Array.new + self.each(recurse) do |child, parent| + if kinds.include?(child.kind) + result << child + end + end + result + end + def find_references_in_file(file = nil, &block) file ||= Lib.extract_string Lib.get_translation_unit_spelling(@translation_unit) @@ -392,46 +416,10 @@ def hash Lib.get_cursor_hash(@cursor) end - def find_all(*kinds) - filter do |child, parent| - kinds.include?(child.kind) - end - end - - def find_first(*kinds) - find_all(*kinds).first - end - - def filter - return to_enum(:select) unless block_given? - - matching = [] - - self.visit_children do |child, parent| - if yield(child, parent) - matching << child - end - - :recurse - end - - return matching - end - - def select - filter do |child, parent| - yield(child) - end - end - def to_s "Cursor <#{self.kind.to_s.gsub(/^cursor_/, '')}: #{self.spelling}>" end - def to_a - filter.collect{|child, parent| child} - end - def references(file = nil) refs = [] self.find_references_in_file(file) do |cursor, unused| diff --git a/spec/ffi/clang/comment_spec.rb b/spec/ffi/clang/comment_spec.rb index 2dece59..825d02d 100644 --- a/spec/ffi/clang/comment_spec.rb +++ b/spec/ffi/clang/comment_spec.rb @@ -8,7 +8,7 @@ describe Comment do let(:cursor) { Index.new.parse_translation_unit(fixture_path("docs.cc")).cursor } - let (:comment) { find_first(cursor, :cursor_function).comment } + let (:comment) { find_by_kind(cursor, :cursor_function).comment } it "can be obtained from a cursor" do expect(comment).to be_kind_of(Comment) diff --git a/spec/ffi/clang/cursor_spec.rb b/spec/ffi/clang/cursor_spec.rb index f551321..30a433e 100644 --- a/spec/ffi/clang/cursor_spec.rb +++ b/spec/ffi/clang/cursor_spec.rb @@ -14,7 +14,7 @@ describe "Function Call Cursors" do let(:translation_unit) {Index.new.parse_translation_unit(fixture_path("class.cpp"))} let(:cursor) {translation_unit.cursor} - let(:call) {find_first(cursor, :cursor_call_expr)} + let(:call) {find_by_kind(cursor, :cursor_call_expr)} it "should parse correctly" do expect(translation_unit.diagnostics).to be_empty @@ -29,9 +29,9 @@ describe FFI::Clang::Cursor do let(:translation_unit) {Index.new.parse_translation_unit(fixture_path("class.cpp"))} let(:cursor) {translation_unit.cursor} - let (:class1) { find_all(cursor, :cursor_class_decl)[0] } - let (:class2) { find_all(cursor, :cursor_class_decl)[1] } - let (:class3) { find_all(cursor, :cursor_class_decl)[2] } + let (:class1) { find_all_by_kind(cursor, :cursor_class_decl)[0] } + let (:class2) { find_all_by_kind(cursor, :cursor_class_decl)[1] } + let (:class3) { find_all_by_kind(cursor, :cursor_class_decl)[2] } it "can find the first class" do expect(class1).not_to equal(nil) @@ -48,7 +48,7 @@ end it "has constructors" do - constructors = find_all(class2, :cursor_constructor) + constructors = find_all_by_kind(class2, :cursor_constructor) expect(constructors.length).to eq(5) expect(constructors[0].default_constructor?).to eq(true) @@ -80,7 +80,7 @@ end it "has destructors" do - constructors = find_all(class2, :cursor_constructor) + constructors = find_all_by_kind(class2, :cursor_constructor) expect(constructors.length).to eq(5) end @@ -91,7 +91,7 @@ end it "field is mutable abstract" do - fields = find_all(class3, :cursor_field_decl) + fields = find_all_by_kind(class3, :cursor_field_decl) field = fields[0] expect(field.mutable?).to eq(true) @@ -143,7 +143,7 @@ it "allows us to visit its children" do counter = 0 - cursor.visit_children do |cursor, parent| + cursor.each do |cursor, parent| counter += 1 :recurse end @@ -174,7 +174,7 @@ end describe "Function Cursors" do - let (:func) { find_first(cursor, :cursor_function) } + let (:func) { find_by_kind(cursor, :cursor_function) } it "is not invalid?" do expect(func.invalid?).to equal(false) @@ -198,7 +198,7 @@ end describe "Struct Cursors" do - let (:struct) { find_first(cursor, :cursor_struct) } + let (:struct) { find_by_kind(cursor, :cursor_struct) } it "can find the first struct" do expect(struct).not_to equal(nil) @@ -217,7 +217,7 @@ end describe '#kind_spelling' do - let (:struct) { find_first(cursor, :cursor_struct) } + let (:struct) { find_by_kind(cursor, :cursor_struct) } it "returns the spelling of the given kind" do expect(struct.kind_spelling).to eq('StructDecl') @@ -225,7 +225,7 @@ end describe '#declaration?' do - let (:struct) { find_first(cursor, :cursor_struct) } + let (:struct) { find_by_kind(cursor, :cursor_struct) } it "checks the cursor is declaration" do expect(struct.declaration?).to be true @@ -233,7 +233,7 @@ end describe '#reference?' do - let (:ref) { find_first(cursor, :cursor_type_ref) } + let (:ref) { find_by_kind(cursor, :cursor_type_ref) } it "checks the cursor is reference" do expect(ref.reference?).to be true @@ -241,7 +241,7 @@ end describe '#expression?' do - let (:literal) { find_first(cursor, :cursor_integer_literal) } + let (:literal) { find_by_kind(cursor, :cursor_integer_literal) } it "checks the cursor is expression" do expect(literal.expression?).to be true @@ -249,7 +249,7 @@ end describe '#statement?' do - let (:return_stmt) { find_first(cursor, :cursor_return_stmt) } + let (:return_stmt) { find_by_kind(cursor, :cursor_return_stmt) } it "checks the cursor is statement" do expect(return_stmt.statement?).to be true @@ -257,7 +257,7 @@ end describe '#attribute?' do - let (:attr) { find_first(cursor_cxx, :cursor_unexposed_attr) } + let (:attr) { find_by_kind(cursor_cxx, :cursor_unexposed_attr) } it "checks the cursor is attribute" do expect(attr.attribute?).to be true @@ -292,7 +292,7 @@ end describe '#preprocessing?' do - let (:pp) { find_first(cursor_pp, :cursor_macro_definition) } + let (:pp) { find_by_kind(cursor_pp, :cursor_macro_definition) } it 'checks the cursor is preprocessing' do expect(pp.preprocessing?).to be true @@ -384,7 +384,7 @@ end describe '#canonical' do - let (:structs) { find_all(cursor_canon, :cursor_struct) } + let (:structs) { find_all_by_kind(cursor_canon, :cursor_struct) } it "mathes 3 cursors" do expect(structs.size).to eq(3) @@ -398,7 +398,7 @@ end describe '#definition' do - let (:structs) { find_all(cursor_canon, :cursor_struct) } + let (:structs) { find_all_by_kind(cursor_canon, :cursor_struct) } it "mathes 3 cursors" do expect(structs.size).to eq(3) @@ -444,7 +444,7 @@ end describe '#translation_unit' do - let (:struct) { find_first(cursor, :cursor_struct) } + let (:struct) { find_by_kind(cursor, :cursor_struct) } it "can find the first struct" do expect(struct).not_to equal(nil) @@ -457,7 +457,7 @@ end describe '#find_references_in_file' do - let (:struct_cursor) {find_first(cursor_canon, :cursor_struct) } + let (:struct_cursor) {find_by_kind(cursor_canon, :cursor_struct) } it "visits references to the cursor in the main file" do counter = 0 @@ -479,8 +479,8 @@ end describe '#linkage' do - let (:ref) { find_first(cursor, :cursor_type_ref) } - let (:func) { find_first(cursor, :cursor_function) } + let (:ref) { find_by_kind(cursor, :cursor_type_ref) } + let (:func) { find_by_kind(cursor, :cursor_function) } it "returns :external if the cursor is non-static function" do expect(func.linkage).to equal :external @@ -514,7 +514,7 @@ end describe '#definition?' do - let (:struct) { find_all(cursor_canon, :cursor_struct).at(2) } + let (:struct) { find_all_by_kind(cursor_canon, :cursor_struct).at(2) } it "checks cursor is a definition" do expect(struct.definition?).to be true @@ -522,7 +522,7 @@ end describe '#usr' do - let (:func) { find_first(cursor, :cursor_function) } + let (:func) { find_by_kind(cursor, :cursor_function) } it "returns something in string" do expect(func.usr).to be_kind_of(String) @@ -551,7 +551,7 @@ end describe '#hash' do - let (:func) { find_first(cursor, :cursor_function) } + let (:func) { find_by_kind(cursor, :cursor_function) } it "computes hash for the cursor" do expect(func.hash).to be_kind_of(Integer) @@ -559,7 +559,7 @@ end describe '#availability' do - let (:func) { find_first(cursor, :cursor_function) } + let (:func) { find_by_kind(cursor, :cursor_function) } it "returns :available for the cursor availability" do expect(func.availability).to equal(:available) @@ -567,7 +567,7 @@ end describe '#type' do - let (:field) { find_first(cursor, :cursor_field_decl) } + let (:field) { find_by_kind(cursor, :cursor_field_decl) } it "returns type for the cursor" do expect(field.type).to be_kind_of(Type) @@ -576,7 +576,7 @@ end describe '#underlying_type' do - let (:typedef) { find_first(cursor_cxx, :cursor_typedef_decl) } + let (:typedef) { find_by_kind(cursor_cxx, :cursor_typedef_decl) } it "returns type that the cursor type is underlying" do expect(typedef.underlying_type).to be_kind_of(Type) @@ -728,7 +728,7 @@ end describe '#included_file' do - let (:inclusion) { find_first(cursor_pp, :cursor_inclusion_directive) } + let (:inclusion) { find_by_kind(cursor_pp, :cursor_inclusion_directive) } it 'returns the file that is included by the given inclusion directive cursor' do expect(inclusion.included_file).to be_kind_of(FFI::Clang::File) @@ -737,7 +737,7 @@ end describe '#references' do - let (:struct_cursor) { find_first(cursor_canon, :cursor_struct) } + let (:struct_cursor) { find_by_kind(cursor_canon, :cursor_struct) } let (:unspecified_references) { struct_cursor.references } let (:specified_references) { struct_cursor.references(fixture_path("canonical.c")) } diff --git a/spec/ffi/clang/source_location_spec.rb b/spec/ffi/clang/source_location_spec.rb index 3909dea..fef8aef 100644 --- a/spec/ffi/clang/source_location_spec.rb +++ b/spec/ffi/clang/source_location_spec.rb @@ -11,7 +11,7 @@ let(:translation_unit_location) { translation_unit.cursor.location } let(:diagnostic_location) { translation_unit.diagnostics.first.location } let(:loc1_translation_unit) { Index.new.parse_translation_unit(fixture_path("location1.c")) } - let(:loc1_cursor) { find_first(loc1_translation_unit.cursor, :cursor_function) } + let(:loc1_cursor) { find_by_kind(loc1_translation_unit.cursor, :cursor_function) } let(:docs_cursor) { Index.new.parse_translation_unit(fixture_path("docs.c")).cursor } it "should have a nil File if the SourceLocation is for a Translation Unit" do diff --git a/spec/ffi/clang/token_spec.rb b/spec/ffi/clang/token_spec.rb index c8babd8..14b3dad 100644 --- a/spec/ffi/clang/token_spec.rb +++ b/spec/ffi/clang/token_spec.rb @@ -7,7 +7,7 @@ describe Tokens do let(:translation_unit) { Index.new.parse_translation_unit(fixture_path("list.c")) } let(:cursor) { translation_unit.cursor } - let(:range) { find_first(cursor, :cursor_struct).extent } + let(:range) { find_by_kind(cursor, :cursor_struct).extent } let(:tokens) { translation_unit.tokenize(range) } it "can be obtained from a translation unit" do @@ -39,7 +39,7 @@ describe Token do let(:translation_unit) { Index.new.parse_translation_unit(fixture_path("list.c")) } let(:cursor) { translation_unit.cursor } - let(:range) { find_first(cursor, :cursor_struct).extent } + let(:range) { find_by_kind(cursor, :cursor_struct).extent } let(:token) { translation_unit.tokenize(range).first } it "can be obtained from a translation unit" do diff --git a/spec/ffi/clang/translation_unit_spec.rb b/spec/ffi/clang/translation_unit_spec.rb index ad08a34..7aea488 100644 --- a/spec/ffi/clang/translation_unit_spec.rb +++ b/spec/ffi/clang/translation_unit_spec.rb @@ -164,9 +164,9 @@ File::open(path, "w+") { |io| io.write("int a;") } - expect(find_first(@reparse_translation_unit.cursor, :cursor_variable)).to be nil + expect(find_by_kind(@reparse_translation_unit.cursor, :cursor_variable)).to be nil expect{@reparse_translation_unit.reparse}.not_to raise_error - expect(find_first(@reparse_translation_unit.cursor, :cursor_variable).spelling).to eq("a") + expect(find_by_kind(@reparse_translation_unit.cursor, :cursor_variable).spelling).to eq("a") end it "raises exception if the file is not found when reparsing" do diff --git a/spec/ffi/clang/type_spec.rb b/spec/ffi/clang/type_spec.rb index 581edb9..008ea57 100644 --- a/spec/ffi/clang/type_spec.rb +++ b/spec/ffi/clang/type_spec.rb @@ -10,7 +10,7 @@ let(:cursor) { Index.new.parse_translation_unit(fixture_path("a.c")).cursor } let(:cursor_cxx) { Index.new.parse_translation_unit(fixture_path("test.cxx")).cursor } let(:cursor_list) { Index.new.parse_translation_unit(fixture_path("list.c")).cursor } - let(:type) { find_first(cursor, :cursor_function).type } + let(:type) { find_by_kind(cursor, :cursor_function).type } it "can tell us about the main function" do expect(type.variadic?).to equal(false) @@ -220,7 +220,7 @@ let(:struct_decl) { find_matching(cursor_cxx) { |child, parent| child.kind == :cursor_struct and child.spelling == 'D' } } - let(:no_decl) { find_first(cursor_cxx, :cursor_cxx_method).type } + let(:no_decl) { find_by_kind(cursor_cxx, :cursor_cxx_method).type } it 'returns the class type of the member pointer type' do expect(struct_ref.declaration).to be_kind_of(Cursor) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c6a6e63..8528606 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,20 +19,17 @@ def fixture_path(path) File.join File.expand_path("ffi/clang/fixtures", __dir__), path end - def find_all(cursor, kind) - cursor.find_all(kind) + def find_all_by_kind(cursor, kind) + cursor.find_by_kind(true, kind) end - def find_first(cursor, kind) - cursor.find_first(kind) - end - - def find_all_matching(cursor, &term) - cursor.filter(&term) + def find_by_kind(cursor, kind) + cursor.find_by_kind(true, kind).first end def find_matching(cursor, &term) - cursor.filter(&term).first + child, parent = cursor.find(&term) + child end end