Skip to content

Commit efef514

Browse files
authored
Merge pull request #140 from sue445/feature/rb_num2int_inline
[WIP] Support static inline function
2 parents b38864f + 8552960 commit efef514

File tree

7 files changed

+150
-44
lines changed

7 files changed

+150
-44
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ gem "rake"
66
gem "rspec"
77
gem "rspec-its"
88
gem "rspec-parameterized"
9+
gem "rspec-temp_dir"
910
gem "rubocop", require: false
1011
gem "rubocop_auto_corrector", require: false
1112
gem "serverspec"

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ GEM
8888
binding_of_caller
8989
rspec-parameterized-core (< 2)
9090
rspec-support (3.13.1)
91+
rspec-temp_dir (1.1.1)
92+
rspec (>= 3.0)
9193
rubocop (1.66.1)
9294
json (~> 2.3)
9395
language_server-protocol (>= 3.17.0)
@@ -154,6 +156,7 @@ DEPENDENCIES
154156
rspec
155157
rspec-its
156158
rspec-parameterized
159+
rspec-temp_dir
157160
rubocop
158161
rubocop_auto_corrector
159162
serverspec

_tools/ruby_h_to_go/lib/ruby_h_to_go/cli.rb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ def perform
3030
go_fmt
3131
end
3232

33-
private
34-
35-
# @return [RubyHeaderParser::Parser]
36-
def parser
37-
@parser ||= RubyHeaderParser::Parser.new(header_dir)
38-
end
39-
4033
def write_type_definitions_to_go_file
4134
type_definitions = parser.extract_type_definitions.map do |definition|
4235
RubyHToGo::TypeDefinition.new(definition:, header_dir:)
@@ -62,11 +55,28 @@ def write_function_definitions_to_go_file
6255
RubyHToGo::FunctionDefinition.new(definition:, header_dir:)
6356
end
6457

58+
static_inline_function_definitions = parser.extract_static_inline_function_definitions.map do |definition|
59+
RubyHToGo::FunctionDefinition.new(definition:, header_dir:)
60+
end
61+
62+
static_inline_function_definitions.each do |static_inline_function_definition|
63+
unless function_definitions.map(&:name).include?(static_inline_function_definition.name)
64+
function_definitions << static_inline_function_definition
65+
end
66+
end
67+
6568
function_definitions.each do |definition|
6669
definition.write_go_file(dist_dir)
6770
end
6871
end
6972

73+
private
74+
75+
# @return [RubyHeaderParser::Parser]
76+
def parser
77+
@parser ||= RubyHeaderParser::Parser.new(header_dir)
78+
end
79+
7080
# Clean all generated files in dist/
7181
def clean_generated_files
7282
FileUtils.rm_f(Dir.glob(File.join(dist_dir, "*.go")))

_tools/ruby_h_to_go/lib/ruby_header_parser/parser.rb

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module RubyHeaderParser
44
# parse `ruby.h` using `ctags`
5-
class Parser
5+
class Parser # rubocop:disable Metrics/ClassLength
66
# @!attribute [r] header_dir
77
# @return [String]
88
attr_reader :header_dir
@@ -19,43 +19,17 @@ def initialize(header_dir)
1919

2020
# @return [Array<RubyHeaderParser::FunctionDefinition>]
2121
def extract_function_definitions
22-
stdout = `ctags --recurse --c-kinds=p --languages=C --language-force=C --fields=+nS --extras=+q -f - #{header_dir}` # rubocop:disable Layout/LineLength
23-
24-
stdout.each_line.with_object([]) do |line, definitions|
25-
parts = line.split("\t")
26-
27-
function_name = parts[0]
28-
29-
next unless data.should_generate_function?(function_name)
30-
31-
definition =
32-
if parts[2].end_with?(";$/;\"")
33-
parts[2].delete_prefix("/^").delete_suffix(";$/;\"")
34-
else
35-
line_num = Util.find_field(parts, "line").to_i
36-
read_definition_from_header_file(parts[1], line_num).delete_suffix(";")
37-
end
38-
39-
definition.gsub!(/\);.*/, ")")
40-
41-
args = parse_definition_args(function_name, Util.find_field(parts, "signature"))
42-
43-
# Exclude functions with variable-length arguments
44-
next if args&.last&.type == "..."
22+
__extract_function_definitions(c_kinds: "p", is_parse_multiline_definition: true)
23+
end
4524

46-
definitions << FunctionDefinition.new(
47-
definition:,
48-
name: parts[0],
49-
filepath: parts[1],
50-
typeref: create_typeref(definition, function_name),
51-
args:,
52-
)
53-
end
25+
# @return [Array<RubyHeaderParser::FunctionDefinition>]
26+
def extract_static_inline_function_definitions
27+
__extract_function_definitions(c_kinds: "+p-d", is_parse_multiline_definition: false)
5428
end
5529

5630
# @return [Array<RubyHeaderParser::StructDefinition>]
5731
def extract_struct_definitions
58-
stdout = `ctags --recurse --c-kinds=s --languages=C --language-force=C --fields=+n -f - #{header_dir}`
32+
stdout = execute_ctags("--c-kinds=s --fields=+n")
5933

6034
stdout.each_line.with_object([]) do |line, definitions|
6135
parts = line.split("\t")
@@ -73,7 +47,7 @@ def extract_struct_definitions
7347

7448
# @return [Array<RubyHeaderParser::TyperefDefinition>]
7549
def extract_type_definitions
76-
stdout = `ctags --recurse --c-kinds=t --languages=C --language-force=C --fields=+n -f - #{header_dir}`
50+
stdout = execute_ctags("--c-kinds=t --fields=+n")
7751

7852
stdout.each_line.with_object([]) do |line, definitions|
7953
parts = line.split("\t")
@@ -91,6 +65,46 @@ def extract_type_definitions
9165

9266
private
9367

68+
# @param c_kinds [String]
69+
# @param is_parse_multiline_definition [Boolean]
70+
# @return [Array<RubyHeaderParser::FunctionDefinition>]
71+
def __extract_function_definitions(c_kinds:, is_parse_multiline_definition:)
72+
stdout = execute_ctags("--c-kinds=#{c_kinds} --fields=+nS --extras=+q")
73+
74+
stdout.each_line.with_object([]) do |line, definitions|
75+
parts = line.split("\t")
76+
77+
function_name = parts[0]
78+
79+
next unless data.should_generate_function?(function_name)
80+
81+
line_num = Util.find_field(parts, "line").to_i
82+
definition =
83+
parse_function_definition(filepath: parts[1], pattern: parts[2], line_num:, is_parse_multiline_definition:)
84+
85+
args = parse_definition_args(function_name, Util.find_field(parts, "signature"))
86+
87+
# Exclude functions with variable-length arguments
88+
next if args&.last&.type == "..."
89+
90+
typeref_field = Util.find_field(parts, "typeref:typename")
91+
92+
definitions << FunctionDefinition.new(
93+
definition:,
94+
name: parts[0],
95+
filepath: parts[1],
96+
typeref: create_typeref(definition:, function_name:, typeref_field:),
97+
args:,
98+
)
99+
end
100+
end
101+
102+
# @param args [String]
103+
# @return [String]
104+
def execute_ctags(args = "")
105+
`ctags --recurse --languages=C --language-force=C #{args} -f - #{header_dir}`
106+
end
107+
94108
# @param file [String]
95109
# @param line_num [Integer]
96110
def read_definition_from_header_file(file, line_num)
@@ -107,6 +121,24 @@ def read_definition_from_header_file(file, line_num)
107121
""
108122
end
109123

124+
# @param filepath [String]
125+
# @param pattern [String]
126+
# @param line_num [Integer]
127+
# @param is_parse_multiline_definition [Boolean]
128+
# @return [String]
129+
def parse_function_definition(filepath:, pattern:, line_num:, is_parse_multiline_definition:)
130+
definition =
131+
if pattern.end_with?("$/;\"")
132+
pattern.delete_prefix("/^").delete_suffix("$/;\"")
133+
elsif is_parse_multiline_definition
134+
read_definition_from_header_file(filepath, line_num)
135+
else
136+
pattern.delete_prefix("/^")
137+
end
138+
139+
definition.delete_suffix(";")
140+
end
141+
110142
# @param function_name [String]
111143
# @param signature [String,nil]
112144
# @return [Array<RubyHeaderParser::ArgumentDefinition>]
@@ -176,10 +208,17 @@ def parse_definition_args(function_name, signature)
176208

177209
# @param definition [String]
178210
# @param function_name [String]
211+
# @param typeref_field [String,nil]
179212
# @return [RubyHeaderParser::TyperefDefinition]
180-
def create_typeref(definition, function_name)
181-
typeref_type = definition[0...definition.index(function_name)].gsub("char *", "char*").strip
182-
typeref_type = Util.sanitize_type(typeref_type)
213+
def create_typeref(definition:, function_name:, typeref_field:)
214+
typeref_type =
215+
if typeref_field
216+
typeref_field.gsub(/^RBIMPL_ATTR_NONNULL\s*\(\(\)\)/, "").strip
217+
else
218+
# parse typeref in definition
219+
type = definition[0...definition.index(function_name)].gsub("char *", "char*").strip
220+
Util.sanitize_type(type)
221+
end
183222

184223
typeref_pointer = nil
185224
if typeref_type.match?(/\*+$/)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RubyHToGo::Cli do
4+
include_context "uses temp dir"
5+
6+
let(:cli) do
7+
RubyHToGo::Cli.new(
8+
header_dir: RbConfig::CONFIG["rubyhdrdir"],
9+
dist_dir: temp_dir,
10+
)
11+
end
12+
13+
describe "#write_type_definitions_to_go_file" do
14+
subject { cli.write_type_definitions_to_go_file }
15+
16+
it { expect { subject }.not_to raise_error }
17+
end
18+
19+
describe "#write_struct_definitions_to_go_file" do
20+
subject { cli.write_struct_definitions_to_go_file }
21+
22+
it { expect { subject }.not_to raise_error }
23+
end
24+
25+
describe "#write_function_definitions_to_go_file" do
26+
subject { cli.write_function_definitions_to_go_file }
27+
28+
it { expect { subject }.not_to raise_error }
29+
end
30+
end

_tools/ruby_h_to_go/spec/ruby_header_parser/parser_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,28 @@
138138
end
139139
end
140140

141+
describe "#extract_static_inline_function_definitions" do
142+
subject(:definitions) { parser.extract_static_inline_function_definitions }
143+
144+
its(:count) { should be > 0 }
145+
146+
context "rb_num2int_inline" do
147+
subject { definitions.find { |d| d.name == "rb_num2int_inline" } }
148+
149+
let(:args) do
150+
[
151+
argument(type: "VALUE", name: "x"),
152+
]
153+
end
154+
155+
its(:name) { should eq "rb_num2int_inline" }
156+
its(:definition) { should eq "rb_num2int_inline(VALUE x)" }
157+
its(:filepath) { should be_end_with "/ruby/internal/arithmetic/int.h" }
158+
its(:typeref) { should eq typedef(type: "int") }
159+
its(:args) { should eq args }
160+
end
161+
end
162+
141163
describe "#extract_struct_definitions" do
142164
subject(:definitions) { parser.extract_struct_definitions }
143165

_tools/ruby_h_to_go/spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "rspec/its"
44
require "rspec/parameterized"
5+
require "rspec/temp_dir"
56

67
require_relative "../lib/ruby_header_parser"
78
require_relative "../lib/ruby_h_to_go"

0 commit comments

Comments
 (0)