kanayago 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -0
  3. data/.ruby-version +1 -0
  4. data/README.md +20 -29
  5. data/Rakefile +43 -96
  6. data/ext/kanayago/extconf.rb +6 -0
  7. data/ext/kanayago/id.h +12 -5
  8. data/ext/kanayago/id_table.h +15 -0
  9. data/ext/kanayago/include/ruby/st.h +199 -0
  10. data/ext/kanayago/internal/array.h +3 -0
  11. data/ext/kanayago/internal/basic_operators.h +1 -0
  12. data/ext/kanayago/internal/bignum.h +1 -0
  13. data/ext/kanayago/internal/bits.h +82 -0
  14. data/ext/kanayago/internal/encoding.h +4 -1
  15. data/ext/kanayago/internal/error.h +33 -0
  16. data/ext/kanayago/internal/fixnum.h +1 -0
  17. data/ext/kanayago/internal/gc.h +47 -11
  18. data/ext/kanayago/internal/hash.h +3 -0
  19. data/ext/kanayago/internal/imemo.h +93 -32
  20. data/ext/kanayago/internal/io.h +30 -7
  21. data/ext/kanayago/internal/namespace.h +81 -0
  22. data/ext/kanayago/internal/numeric.h +1 -0
  23. data/ext/kanayago/internal/parse.h +17 -3
  24. data/ext/kanayago/internal/re.h +7 -2
  25. data/ext/kanayago/internal/sanitizers.h +88 -39
  26. data/ext/kanayago/internal/set_table.h +70 -0
  27. data/ext/kanayago/internal/string.h +33 -16
  28. data/ext/kanayago/internal/symbol.h +4 -3
  29. data/ext/kanayago/internal/thread.h +42 -9
  30. data/ext/kanayago/internal/variable.h +13 -11
  31. data/ext/kanayago/internal/vm.h +4 -5
  32. data/ext/kanayago/internal.h +0 -3
  33. data/ext/kanayago/kanayago.c +554 -235
  34. data/ext/kanayago/kanayago.h +5 -0
  35. data/ext/kanayago/literal_node.c +343 -0
  36. data/ext/kanayago/literal_node.h +30 -0
  37. data/ext/kanayago/method.h +18 -2
  38. data/ext/kanayago/node.c +7 -1
  39. data/ext/kanayago/node.h +14 -3
  40. data/ext/kanayago/parse.c +7602 -7156
  41. data/ext/kanayago/parse.h +39 -39
  42. data/ext/kanayago/parser_st.c +2 -1
  43. data/ext/kanayago/pattern_node.c +78 -0
  44. data/ext/kanayago/pattern_node.h +13 -0
  45. data/ext/kanayago/ruby_atomic.h +43 -0
  46. data/ext/kanayago/ruby_parser.c +7 -35
  47. data/ext/kanayago/rubyparser.h +83 -80
  48. data/ext/kanayago/scope_node.c +34 -0
  49. data/ext/kanayago/scope_node.h +8 -0
  50. data/ext/kanayago/shape.h +321 -111
  51. data/ext/kanayago/st.c +905 -21
  52. data/ext/kanayago/statement_node.c +795 -0
  53. data/ext/kanayago/statement_node.h +66 -0
  54. data/ext/kanayago/string_node.c +192 -0
  55. data/ext/kanayago/string_node.h +19 -0
  56. data/ext/kanayago/symbol.h +2 -9
  57. data/ext/kanayago/thread_pthread.h +10 -3
  58. data/ext/kanayago/universal_parser.c +1 -20
  59. data/ext/kanayago/variable_node.c +72 -0
  60. data/ext/kanayago/variable_node.h +12 -0
  61. data/ext/kanayago/vm_core.h +205 -71
  62. data/lib/kanayago/literal_node.rb +87 -0
  63. data/lib/kanayago/pattern_node.rb +19 -0
  64. data/lib/kanayago/statement_node.rb +222 -0
  65. data/lib/kanayago/string_node.rb +43 -0
  66. data/lib/kanayago/variable_node.rb +23 -0
  67. data/lib/kanayago/version.rb +1 -1
  68. data/lib/kanayago.rb +22 -0
  69. data/patch/3.4/copy_target.rb +78 -0
  70. data/patch/3.4/kanayago.patch +162 -0
  71. data/patch/head/copy_target.rb +84 -0
  72. data/patch/head/kanayago.patch +162 -0
  73. data/sample/minitest_generator.rb +266 -0
  74. data/sample/test_generator.rb +272 -0
  75. data/typeprof.conf.json +9 -0
  76. metadata +32 -4
  77. data/ext/kanayago/parse.tmp.y +0 -16145
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ RUBY_PARSER_COPY_TARGETS = %w[
4
+ ccan/check_type/check_type.h
5
+ ccan/container_of/container_of.h
6
+ ccan/list/list.h
7
+ ccan/str/str.h
8
+ constant.h
9
+ id.h
10
+ id_table.h
11
+ include/ruby/st.h
12
+ internal/array.h
13
+ internal/basic_operators.h
14
+ internal/bignum.h
15
+ internal/bits.h
16
+ internal/compile.h
17
+ internal/compilers.h
18
+ internal/complex.h
19
+ internal/encoding.h
20
+ internal/error.h
21
+ internal/fixnum.h
22
+ internal/gc.h
23
+ internal/hash.h
24
+ internal/imemo.h
25
+ internal/io.h
26
+ internal/namespace.h
27
+ internal/numeric.h
28
+ internal/parse.h
29
+ internal/rational.h
30
+ internal/re.h
31
+ internal/ruby_parser.h
32
+ internal/sanitizers.h
33
+ internal/serial.h
34
+ internal/set_table.h
35
+ internal/static_assert.h
36
+ internal/string.h
37
+ internal/symbol.h
38
+ internal/thread.h
39
+ internal/variable.h
40
+ internal/warnings.h
41
+ internal/vm.h
42
+ internal.h
43
+ lex.c
44
+ method.h
45
+ node.c
46
+ node.h
47
+ node_name.inc
48
+ parse.c
49
+ parse.h
50
+ parser_bits.h
51
+ parser_node.h
52
+ parser_st.c
53
+ parser_st.h
54
+ parser_value.h
55
+ ruby_assert.h
56
+ ruby_atomic.h
57
+ ruby_parser.c
58
+ rubyparser.h
59
+ shape.h
60
+ st.c
61
+ symbol.h
62
+ thread_pthread.h
63
+ universal_parser.c
64
+ vm_core.h
65
+ vm_opts.h
66
+ ].freeze
67
+
68
+ MAKE_DIRECTORIES = [
69
+ 'ccan',
70
+ 'ccan/check_type',
71
+ 'ccan/container',
72
+ 'ccan/container_of',
73
+ 'ccan/list',
74
+ 'ccan/str',
75
+ 'internal',
76
+ 'include',
77
+ 'include/ruby'
78
+ ].freeze
79
+
80
+ DELETE_DIRECTORIES = %w[
81
+ ccan
82
+ internal
83
+ include
84
+ ].freeze
@@ -0,0 +1,162 @@
1
+ diff --git a/ext/kanayago/internal/ruby_parser.h b/ext/kanayago/internal/ruby_parser.h
2
+ index 8e306d1..d420a02 100644
3
+ --- a/ext/kanayago/internal/ruby_parser.h
4
+ +++ b/ext/kanayago/internal/ruby_parser.h
5
+ @@ -15,6 +15,29 @@ struct lex_pointer_string {
6
+ long ptr;
7
+ };
8
+
9
+ +// Add Ruby's Parser struct and enum for Kanayago
10
+ +enum lex_type {
11
+ + lex_type_str,
12
+ + lex_type_io,
13
+ + lex_type_array,
14
+ + lex_type_generic,
15
+ +};
16
+ +
17
+ +struct ruby_parser {
18
+ + rb_parser_t *parser_params;
19
+ + enum lex_type type;
20
+ + union {
21
+ + struct lex_pointer_string lex_str;
22
+ + struct {
23
+ + VALUE file;
24
+ + } lex_io;
25
+ + struct {
26
+ + VALUE ary;
27
+ + } lex_array;
28
+ + } data;
29
+ +};
30
+ +// End for Kanayago
31
+ +
32
+ RUBY_SYMBOL_EXPORT_BEGIN
33
+ #ifdef UNIVERSAL_PARSER
34
+ const rb_parser_config_t *rb_ruby_parser_config(void);
35
+ diff --git a/ext/kanayago/ruby_parser.c b/ext/kanayago/ruby_parser.c
36
+ index 267f619..841db65 100644
37
+ --- a/ext/kanayago/ruby_parser.c
38
+ +++ b/ext/kanayago/ruby_parser.c
39
+ @@ -314,6 +314,44 @@ enc_mbc_to_codepoint(const char *p, const char *e, parser_encoding *enc)
40
+
41
+ extern VALUE rb_eArgError;
42
+
43
+ +// Add for Kanayago
44
+ +static void *
45
+ +xmalloc_mul_add(size_t x, size_t y, size_t z)
46
+ +{
47
+ + return rb_xmalloc_mul_add(x, y, z);
48
+ +}
49
+ +
50
+ +static VALUE
51
+ +suppress_tracing(VALUE (*func)(VALUE), VALUE arg)
52
+ +{
53
+ + return func(arg);
54
+ +}
55
+ +
56
+ +static ID
57
+ +make_temporary_id(size_t n)
58
+ +{
59
+ + return rb_make_temporary_id(n);
60
+ +}
61
+ +
62
+ +static int
63
+ +stderr_tty_p(void)
64
+ +{
65
+ + return rb_stderr_tty_p();
66
+ +}
67
+ +
68
+ +static VALUE
69
+ +reg_compile(VALUE str, int options, const char *sourcefile, int sourceline)
70
+ +{
71
+ + return rb_reg_compile(str, options, sourcefile, sourceline);
72
+ +}
73
+ +
74
+ +static VALUE
75
+ +reg_check_preprocess(VALUE val)
76
+ +{
77
+ + return rb_reg_check_preprocess(val);
78
+ +}
79
+ +// End of Add for Kanayago
80
+ +
81
+ static const rb_parser_config_t rb_global_parser_config = {
82
+ .malloc = ruby_xmalloc,
83
+ .calloc = ruby_xcalloc,
84
+ @@ -325,9 +363,9 @@ static const rb_parser_config_t rb_global_parser_config = {
85
+ .zalloc = zalloc,
86
+ .rb_memmove = memmove2,
87
+ .nonempty_memcpy = nonempty_memcpy,
88
+ - .xmalloc_mul_add = rb_xmalloc_mul_add,
89
+ + .xmalloc_mul_add = xmalloc_mul_add, // use xmalloc_mul_add for Kanayago
90
+
91
+ - .compile_callback = rb_suppress_tracing,
92
+ + .compile_callback = suppress_tracing, // use suppress_tracing for Kanayago
93
+ .reg_named_capture_assign = reg_named_capture_assign,
94
+
95
+ .attr_get = rb_attr_get,
96
+ @@ -335,7 +373,7 @@ static const rb_parser_config_t rb_global_parser_config = {
97
+ .ary_new_from_args = rb_ary_new_from_args,
98
+ .ary_unshift = rb_ary_unshift,
99
+
100
+ - .make_temporary_id = rb_make_temporary_id,
101
+ + .make_temporary_id = make_temporary_id, // use make_temporary_id for Kanayago
102
+ .is_local_id = is_local_id2,
103
+ .is_attrset_id = is_attrset_id2,
104
+ .is_global_name_punct = is_global_name_punct,
105
+ @@ -365,7 +403,7 @@ static const rb_parser_config_t rb_global_parser_config = {
106
+
107
+ .int2num = rb_int2num_inline,
108
+
109
+ - .stderr_tty_p = rb_stderr_tty_p,
110
+ + .stderr_tty_p = stderr_tty_p, //use stderr_tty_p for Kanayago
111
+ .write_error_str = rb_write_error_str,
112
+ .io_write = rb_io_write,
113
+ .io_flush = rb_io_flush,
114
+ @@ -412,8 +450,8 @@ static const rb_parser_config_t rb_global_parser_config = {
115
+ .gc_guard = gc_guard,
116
+ .gc_mark = rb_gc_mark,
117
+
118
+ - .reg_compile = rb_reg_compile,
119
+ - .reg_check_preprocess = rb_reg_check_preprocess,
120
+ + .reg_compile = reg_compile, // use reg_compile for Kanayago
121
+ + .reg_check_preprocess = reg_check_preprocess, // use reg_check_preprocess for Kanayago
122
+ .memcicmp = rb_memcicmp,
123
+
124
+ .compile_warn = rb_compile_warn,
125
+ @@ -443,27 +481,6 @@ static const rb_parser_config_t rb_global_parser_config = {
126
+ };
127
+ #endif
128
+
129
+ -enum lex_type {
130
+ - lex_type_str,
131
+ - lex_type_io,
132
+ - lex_type_array,
133
+ - lex_type_generic,
134
+ -};
135
+ -
136
+ -struct ruby_parser {
137
+ - rb_parser_t *parser_params;
138
+ - enum lex_type type;
139
+ - union {
140
+ - struct lex_pointer_string lex_str;
141
+ - struct {
142
+ - VALUE file;
143
+ - } lex_io;
144
+ - struct {
145
+ - VALUE ary;
146
+ - } lex_array;
147
+ - } data;
148
+ -};
149
+ -
150
+ static void
151
+ parser_mark(void *ptr)
152
+ {
153
+ @@ -501,7 +518,8 @@ parser_memsize(const void *ptr)
154
+ return rb_ruby_parser_memsize(parser->parser_params);
155
+ }
156
+
157
+ -static const rb_data_type_t ruby_parser_data_type = {
158
+ +// Not static const for Kanayago
159
+ +const rb_data_type_t ruby_parser_data_type = {
160
+ "parser",
161
+ {
162
+ parser_mark,
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/kanayago'
4
+
5
+ class MinitestGenerator
6
+ def initialize(code)
7
+ @code = code
8
+ @ast = Kanayago.parse(code)
9
+ @current_visibility = :public
10
+ end
11
+
12
+ def generate
13
+ class_infos = extract_classes(@ast)
14
+
15
+ return '# No classes found in the code' if class_infos.empty?
16
+
17
+ # Generate tests for all classes
18
+ class_infos.map { |class_info| generate_minitest(class_info) }.join("\n\n")
19
+ end
20
+
21
+ private
22
+
23
+ # rubocop:disable Metrics/CyclomaticComplexity
24
+ def extract_classes(node, namespace = [])
25
+ classes = []
26
+
27
+ case node
28
+ when Kanayago::ModuleNode
29
+ # Process module and its contents
30
+ module_name = extract_constant_name(node.cpath)
31
+ new_namespace = namespace + [module_name]
32
+ classes.concat(extract_classes(node.body, new_namespace)) if node.respond_to?(:body)
33
+
34
+ when Kanayago::ClassNode
35
+ # Process class and collect its methods
36
+ class_name = extract_constant_name(node.cpath)
37
+ full_name = (namespace + [class_name]).join('::')
38
+ methods = extract_methods(node.body)
39
+
40
+ classes << {
41
+ name: full_name,
42
+ simple_name: class_name,
43
+ methods: methods
44
+ }
45
+
46
+ # Also look for nested classes within this class
47
+ new_namespace = namespace + [class_name]
48
+ classes.concat(extract_classes(node.body, new_namespace)) if node.respond_to?(:body)
49
+
50
+ when Kanayago::ScopeNode
51
+ classes.concat(extract_classes(node.body, namespace)) if node.respond_to?(:body)
52
+
53
+ when Kanayago::BlockNode
54
+ # BlockNode is an Array, iterate through its elements
55
+ node.each do |child|
56
+ classes.concat(extract_classes(child, namespace))
57
+ end
58
+ end
59
+
60
+ classes
61
+ end
62
+ # rubocop:enable Metrics/CyclomaticComplexity
63
+
64
+ def extract_constant_name(node)
65
+ case node
66
+ when Kanayago::Colon2Node
67
+ # Extract the class name from mid (Symbol)
68
+ node.mid.to_s
69
+ else
70
+ 'UnknownClass'
71
+ end
72
+ end
73
+
74
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
75
+ def extract_methods(node, visibility = :public)
76
+ methods = []
77
+ current_visibility = visibility
78
+
79
+ return methods unless node
80
+
81
+ case node
82
+ when Kanayago::DefinitionNode
83
+ method_info = {
84
+ name: node.mid.to_s,
85
+ visibility: current_visibility,
86
+ parameters: extract_parameters(node)
87
+ }
88
+ methods << method_info if current_visibility == :public
89
+
90
+ when Kanayago::ScopeNode
91
+ # ScopeNode contains body, process it recursively
92
+ methods.concat(extract_methods(node.body, current_visibility)) if node.respond_to?(:body)
93
+
94
+ when Kanayago::BlockNode
95
+ # BlockNode is an Array, iterate through its elements
96
+ node.each do |child|
97
+ # Check if this is a visibility modifier
98
+ if child.is_a?(Kanayago::VariableCallNode) && child.respond_to?(:mid)
99
+ case child.mid.to_s
100
+ when 'private'
101
+ current_visibility = :private
102
+ when 'protected'
103
+ current_visibility = :protected
104
+ when 'public'
105
+ current_visibility = :public
106
+ end
107
+ else
108
+ # Process other nodes (including DefinitionNodes)
109
+ methods.concat(extract_methods(child, current_visibility))
110
+ end
111
+ end
112
+ end
113
+
114
+ methods
115
+ end
116
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
117
+
118
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
119
+ def extract_parameters(def_node)
120
+ params = []
121
+ return params unless def_node.respond_to?(:defn)
122
+
123
+ defn = def_node.defn
124
+ return params unless defn.respond_to?(:args)
125
+
126
+ args = defn.args
127
+ return params unless args.respond_to?(:ainfo)
128
+
129
+ ainfo = args.ainfo
130
+ return params unless ainfo.is_a?(Hash)
131
+
132
+ # Handle required positional parameters (pre_args)
133
+ pre_count = ainfo[:pre_args_num] || 0
134
+ pre_count.times do |i|
135
+ params << {
136
+ name: "arg#{i + 1}",
137
+ type: :required
138
+ }
139
+ end
140
+
141
+ # Handle optional parameters
142
+ if ainfo[:opt_args]
143
+ current_opt = ainfo[:opt_args]
144
+ while current_opt
145
+ if current_opt.respond_to?(:body) && current_opt.body.respond_to?(:id)
146
+ params << {
147
+ name: current_opt.body.id.to_s,
148
+ type: :optional
149
+ }
150
+ end
151
+ current_opt = current_opt.respond_to?(:next) ? current_opt.next : nil
152
+ end
153
+ end
154
+
155
+ # Handle keyword parameters
156
+ if ainfo[:kw_args]
157
+ current_kw = ainfo[:kw_args]
158
+ while current_kw
159
+ if current_kw.respond_to?(:body) && current_kw.body.respond_to?(:id)
160
+ params << {
161
+ name: current_kw.body.id.to_s,
162
+ type: :keyword
163
+ }
164
+ end
165
+ current_kw = current_kw.respond_to?(:next) ? current_kw.next : nil
166
+ end
167
+ end
168
+
169
+ params
170
+ end
171
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
172
+
173
+ def generate_minitest(class_info)
174
+ output = []
175
+ output << "require 'test_helper'"
176
+ output << ''
177
+ output << "class #{class_info[:name]}Test < Minitest::Test"
178
+
179
+ if class_info[:methods].empty?
180
+ output << ' # No public methods found'
181
+ else
182
+ class_info[:methods].each do |method|
183
+ output << generate_method_test(method)
184
+ end
185
+ end
186
+
187
+ output << 'end'
188
+ output.join("\n")
189
+ end
190
+
191
+ def generate_method_test(method)
192
+ lines = []
193
+ method_name = method[:name]
194
+ params = method[:parameters]
195
+
196
+ # Convert method name to test method name
197
+ test_base = method_name.gsub('?', '').gsub('!', '')
198
+
199
+ if method_name.end_with?('?')
200
+ # Boolean methods - generate true/false cases
201
+ lines << " def test_#{test_base}_returns_true_when_condition_is_met"
202
+ lines << ' # TODO: implement'
203
+ lines << ' end'
204
+ lines << ''
205
+ lines << " def test_#{test_base}_returns_false_when_condition_is_not_met"
206
+ lines << ' # TODO: implement'
207
+ lines << ' end'
208
+ elsif method_name == 'initialize'
209
+ # Constructor - check for optional parameters
210
+ lines << ' def test_initialize_creates_new_instance'
211
+ lines << ' # TODO: implement'
212
+ lines << ' end'
213
+
214
+ if params.any? { |p| %i[optional keyword].include?(p[:type]) }
215
+ lines << ''
216
+ lines << ' def test_initialize_uses_default_values_when_optional_parameters_are_not_provided'
217
+ lines << ' # TODO: implement'
218
+ lines << ' end'
219
+ end
220
+ else
221
+ # Regular methods
222
+ lines << " def test_#{test_base}_works_correctly"
223
+ lines << ' # TODO: implement'
224
+ lines << ' end'
225
+
226
+ if params.any? { |p| %i[optional keyword].include?(p[:type]) }
227
+ lines << ''
228
+ lines << " def test_#{test_base}_handles_optional_parameters"
229
+ lines << ' # TODO: implement'
230
+ lines << ' end'
231
+ end
232
+ end
233
+
234
+ lines << ''
235
+ lines.join("\n")
236
+ end
237
+ end
238
+
239
+ # Usage example
240
+ sample_code = <<~RUBY
241
+ class User
242
+ class Profile
243
+ def initialize(name, age = 20)
244
+ @name = name
245
+ @age = age
246
+ end
247
+
248
+ def adult?
249
+ @age >= 18
250
+ end
251
+
252
+ def greet(message = "Hello")
253
+ "\#{message}, \#{@name}!"
254
+ end
255
+
256
+ private
257
+
258
+ def internal_method
259
+ # This should not appear in tests
260
+ end
261
+ end
262
+ end
263
+ RUBY
264
+
265
+ generator = MinitestGenerator.new(sample_code)
266
+ puts generator.generate