yard2steep 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,66 +1,12 @@
1
- require 'yard2steep/ast'
1
+ require 'ripper'
2
+
2
3
  require 'yard2steep/type'
4
+ require 'yard2steep/ast'
5
+ require 'yard2steep/util'
3
6
 
4
7
  module Yard2steep
5
8
  class Parser
6
- S_RE = /[\s\t]*/
7
- S_P_RE = /[\s\t]+/
8
- PRE_RE = /^#{S_RE}/
9
- POST_RE = /#{S_RE}$/
10
-
11
- CLASS_RE = /
12
- #{PRE_RE}
13
- (class)
14
- #{S_P_RE}
15
- (\w+)
16
- (
17
- #{S_P_RE}
18
- <
19
- #{S_P_RE}
20
- \w+
21
- )?
22
- #{POST_RE}
23
- /x
24
- MODULE_RE = /
25
- #{PRE_RE}
26
- (module)
27
- #{S_P_RE}
28
- (\w+)
29
- #{POST_RE}
30
- /x
31
- S_CLASS_RE = /#{PRE_RE}class#{S_P_RE}<<#{S_P_RE}\w+#{POST_RE}/
32
- END_RE = /#{PRE_RE}end#{POST_RE}/
33
-
34
- CONSTANT_ASSIGN_RE = /
35
- #{PRE_RE}
36
- (
37
- [A-Z\d_]+
38
- )
39
- #{S_RE}
40
- =
41
- .*
42
- #{POST_RE}
43
- /x
44
-
45
- # TODO(south37) `POSTFIX_IF_RE` is wrong. Fix it.
46
- POSTFIX_IF_RE = /
47
- #{PRE_RE}
48
- (?:return|break|next|p|print|raise)
49
- #{S_P_RE}
50
- .*
51
- (?:if|unless)
52
- #{S_P_RE}
53
- .*
54
- $
55
- /x
56
-
57
- BEGIN_END_RE = /
58
- #{S_P_RE}
59
- (if|unless|do|while|until|case|for|begin)
60
- (?:#{S_P_RE}.*)?
61
- $/x
62
-
63
- COMMENT_RE = /#{PRE_RE}#/
9
+ S_RE = /[\s\t]*/
64
10
  TYPE_WITH_PAREN_RE = /
65
11
  \[
66
12
  (
@@ -69,73 +15,30 @@ module Yard2steep
69
15
  )
70
16
  \]
71
17
  /x
72
-
18
+ COMMENT_RE = /^
19
+ \#
20
+ #{S_RE}
21
+ @(?:param|return)
22
+ #{S_RE}
23
+ #{TYPE_WITH_PAREN_RE}
24
+ /x
73
25
  PARAM_RE = /
74
- #{COMMENT_RE}
75
- #{S_P_RE}
26
+ \#
27
+ #{S_RE}
76
28
  @param
77
- #{S_P_RE}
29
+ #{S_RE}
78
30
  #{TYPE_WITH_PAREN_RE}
79
- #{S_P_RE}
31
+ #{S_RE}
80
32
  (\w+)
81
33
  /x
82
34
  RETURN_RE = /
83
- #{COMMENT_RE}
84
- #{S_P_RE}
85
- @return
86
- #{S_P_RE}
87
- #{TYPE_WITH_PAREN_RE}
88
- /x
89
-
90
- PAREN_RE = /
91
- \(
92
- ([^)]*)
93
- \)
94
- /x
95
-
96
- # NOTE: This implementation should be fixed.
97
- ARGS_RE = /
35
+ \#
98
36
  #{S_RE}
99
- \(
100
- [^)]*
101
- \)
102
- |
103
- #{S_P_RE}
104
- .*
105
- /x
106
-
107
- METHOD_RE = /
108
- #{PRE_RE}
109
- def
110
- #{S_P_RE}
111
- (
112
- (?:\w+\.)?
113
- \w+
114
- (?:\!|\?)?
115
- )
37
+ @return
116
38
  #{S_RE}
117
- (
118
- (?:#{ARGS_RE})
119
- ?
120
- )
121
- #{POST_RE}
122
- /x
123
-
124
- # TODO(south37) Support attr_writer, attr_accessor
125
- ATTR_RE = /
126
- #{PRE_RE}
127
- attr_reader
128
- #{S_P_RE}
129
- (:\w+.*)
130
- #{POST_RE}
39
+ #{TYPE_WITH_PAREN_RE}
131
40
  /x
132
41
 
133
- STATES = {
134
- class: "STATES.class",
135
- s_class: "STATES.s_class", # singleton class
136
- method: "STATES.method",
137
- }
138
-
139
42
  ANY_TYPE = 'any'
140
43
  ANY_BLOCK_TYPE = '{ (any) -> any }'
141
44
 
@@ -146,18 +49,7 @@ module Yard2steep
146
49
  # NOTE: set at parse
147
50
  @file = nil
148
51
 
149
- main = AST::ClassNode.create_main
150
- @ast = main
151
-
152
- # Stack of parser state
153
- @stack = [STATES[:class]]
154
- # Parser state. Being last one of STATES in @stack.
155
- @state = STATES[:class]
156
-
157
- # NOTE: reset class context
158
- @current_class = main
159
-
160
- reset_method_context!
52
+ @comments_map = {}
161
53
  end
162
54
 
163
55
  # @param [String] file
@@ -167,171 +59,170 @@ module Yard2steep
167
59
  def parse(file, text, debug: false)
168
60
  @debug = debug
169
61
 
170
- debug_print!("Start parsing: #{file}")
171
-
172
62
  @file = file
173
- text.split(/\n|;/).each do |l|
174
- parse_line(l)
175
- end
63
+ debug_print!("Start parsing: #{file}")
176
64
 
177
- @ast
178
- end
65
+ @comments_map = extract_comments(text)
179
66
 
180
- private
67
+ main = AST::ClassNode.create_main
68
+ @ast = main
181
69
 
182
- # @return [void]
183
- def reset_method_context!
184
- # Current method context. Flushed when method definition is parsed.
185
- @p_types = {}
186
- @r_type = nil
187
- @m_name = nil
188
- end
70
+ # NOTE: reset class context
71
+ @current_class = main
189
72
 
190
- # NOTE: Current implementation override `@p_type`, `@r_type` if it appears
191
- # multiple times before method definition.
192
- #
193
- # @param [String] l
194
- # @return [void]
195
- def parse_line(l)
196
- # At first, try parsing comment
197
- return if try_parse_comment(l)
198
-
199
- return if try_parse_end(l)
200
- return if try_parse_postfix_if(l)
201
- return if try_parse_begin_end(l)
202
-
203
- case @state
204
- when STATES[:class]
205
- return if try_parse_constant(l)
206
- return if try_parse_class(l)
207
- return if try_parse_singleton_class(l)
208
- return if try_parse_method(l)
209
- return if try_parse_attr(l)
210
- when STATES[:s_class]
211
- return if try_parse_method_with_no_action(l)
212
- when STATES[:method]
213
- # Do nothing
214
- else
215
- raise "invalid state: #{@state}"
216
- end
73
+ parse_program(text)
217
74
 
218
- # NOTE: Reach here when other case
75
+ @ast
219
76
  end
220
77
 
221
- # @param [String] l
222
- # @return [bool]
223
- def try_parse_comment(l)
224
- return false unless l.match?(COMMENT_RE)
225
-
226
- try_parse_param_or_return(l)
227
-
228
- true
78
+ # @param [String] text
79
+ # @return [void]
80
+ def parse_program(text)
81
+ r_ast = Ripper.sexp(text)
82
+ # NOTE: r_ast is array such as
83
+ # [:program,
84
+ # [...]]
85
+ Util.assert! { r_ast[0] == :program && r_ast[1].is_a?(Array) }
86
+ parse_stmts(r_ast[1])
229
87
  end
230
88
 
231
- # @param [String] l
232
- # @return [bool]
233
- def try_parse_param_or_return(l)
234
- if @state == STATES[:class]
235
- return if try_parse_param(l)
236
- return if try_parse_return(l)
89
+ # @param [Array] r_ast
90
+ # @return [void]
91
+ def parse_stmts(r_ast)
92
+ r_ast.each do |node|
93
+ parse_stmt(node)
237
94
  end
238
95
  end
239
96
 
240
- # @param [String] l
241
- # @return [bool]
242
- def try_parse_end(l)
243
- return false unless l.match?(END_RE)
244
-
245
- # NOTE: Print before pop state, so offset is -2
246
- debug_print!("end", offset: -2)
247
-
248
- if stack_is_empty?
249
- raise "Invalid end: #{@file}"
97
+ # @param [Array] r_ast
98
+ # @return [void]
99
+ def parse_stmt(r_ast)
100
+ n_type = r_ast[0]
101
+
102
+ case n_type
103
+ when :defs
104
+ parse_defs(r_ast)
105
+ when :def
106
+ parse_def(r_ast)
107
+ when :class, :module
108
+ parse_class_or_module(r_ast)
109
+ when :assign
110
+ parse_assign(r_ast)
111
+ when :command
112
+ parse_command(r_ast)
113
+ when :method_add_arg
114
+ parse_method_add_arg(r_ast)
250
115
  end
251
116
 
252
- pop_state!
253
-
254
- true
117
+ # Do nothing for other stmt
255
118
  end
256
119
 
257
- # NOTE: This implementation is wrong. Used only for skipping postfix if.
258
- #
259
- # @param [String] l
260
- # @return [bool]
261
- def try_parse_postfix_if(l)
262
- l.match?(POSTFIX_IF_RE)
120
+ # @param [Array] r_ast
121
+ # @return [void]
122
+ def parse_defs(r_ast)
123
+ # NOTE: r_ast is array such as
124
+ # [:defs,
125
+ # [:var_ref, [:@kw, "self", [1, 14]]],
126
+ # [:@period, ".", [1, 18]],
127
+ # [:@ident, "my_method", [1, 19]],
128
+ # [:params, nil, nil, nil, nil, nil, nil, nil],
129
+ # [:bodystmt, [[:void_stmt]], nil, nil, nil]]
130
+
131
+ Util.assert! {
132
+ r_ast.size == 6 &&
133
+ r_ast[0] == :defs &&
134
+ r_ast[1][0] == :var_ref &&
135
+ r_ast[1][1][0] == :@kw &&
136
+ r_ast[1][1][1].is_a?(String) &&
137
+ r_ast[1][1][2].is_a?(Array) &&
138
+ r_ast[2][0] == :@period &&
139
+ r_ast[3][0] == :@ident &&
140
+ r_ast[3][1].is_a?(String)
141
+ }
142
+
143
+ m_name = "#{r_ast[1][1][1]}.#{r_ast[3][1]}"
144
+ m_loc = r_ast[1][1][2][0]
145
+ params = r_ast[4]
146
+ # NOTE: We also want to check bodystmt
147
+ # bodystmt = r_ast[5]
148
+
149
+ parse_method_impl(m_name, m_loc, params)
263
150
  end
264
151
 
265
- # @param [String] l
266
- # @return [bool]
267
- def try_parse_begin_end(l)
268
- m = l.match(BEGIN_END_RE)
269
- return false unless m
270
-
271
- debug_print!(m[1])
272
-
273
- push_state!(m[1])
274
-
275
- true
152
+ # @param [Array] r_ast
153
+ # @return [void]
154
+ def parse_def(r_ast)
155
+ # NOTE: r_ast is array such as
156
+ # [:def,
157
+ # [:@ident, "first", [3, 4]],
158
+ # [:paren, [:params, ...]],
159
+ # [:bodystmt, [[:array, nil]], nil, nil, nil]]]
160
+
161
+ Util.assert! {
162
+ r_ast.size == 4 &&
163
+ r_ast[0] == :def &&
164
+ r_ast[1][0] == :@ident &&
165
+ r_ast[1][1].is_a?(String) &&
166
+ r_ast[1][2].is_a?(Array)
167
+ }
168
+
169
+ m_name = r_ast[1][1]
170
+ m_loc = r_ast[1][2][0]
171
+ params = r_ast[2]
172
+ # NOTE: We also want to check bodystmt
173
+ # bodystmt = r_ast[3]
174
+
175
+ parse_method_impl(m_name, m_loc, params)
276
176
  end
277
177
 
278
- # @param [String] l
279
- # @return [bool]
280
- def try_parse_class(l)
281
- m = (l.match(MODULE_RE) || l.match(CLASS_RE))
282
- return false unless m
178
+ # @param [String] m_name
179
+ # @param [Integer] m_loc
180
+ # @param [Array] params
181
+ # @return [void]
182
+ def parse_method_impl(m_name, m_loc, params)
183
+ within_context do
184
+ extract_p_types!(m_loc)
283
185
 
284
- debug_print!("#{m[1]} #{m[2]}")
186
+ p_list = parse_params(params)
285
187
 
286
- # NOTE: If class definition is found before method definition, yard
287
- # annotation is ignored.
288
- reset_method_context!
188
+ # NOTE: We also want to check bodystmt
189
+ # parse_mbody(bodystmt)
289
190
 
290
- c = AST::ClassNode.new(
291
- kind: m[1],
292
- c_name: m[2],
293
- super_c: m[3] && m[3].gsub('<', '').strip,
294
- parent: @current_class,
295
- )
296
- @current_class.append_child(c)
297
- @current_class = c
298
-
299
- push_state!(STATES[:class])
300
-
301
- true
191
+ m_node = AST::MethodNode.new(
192
+ p_list: p_list,
193
+ r_type: (@r_type || ANY_TYPE),
194
+ m_name: m_name,
195
+ )
196
+ @current_class.append_m(m_node)
197
+ end
302
198
  end
303
199
 
304
- # @param [String] l
305
- # @return [bool]
306
- def try_parse_constant(l)
307
- m = l.match(CONSTANT_ASSIGN_RE)
308
- return false unless m
309
-
310
- c = AST::ConstantNode.new(
311
- name: m[1],
312
- klass: @current_class,
313
- )
314
- @current_class.append_constant(c)
315
- true
200
+ # @param [Integer] m_loc represents location of method definition
201
+ # @return [void]
202
+ def extract_p_types!(m_loc)
203
+ Util.assert! { m_loc >= 0 }
204
+ l = m_loc - 1
205
+ while l >= 0
206
+ comment = @comments_map[l]
207
+ break unless comment # nil when no more comment exist
208
+
209
+ parse_comment!(comment)
210
+ l -= 1
211
+ end
316
212
  end
317
213
 
318
- # @param [String] l
319
- # @return [bool]
320
- def try_parse_singleton_class(l)
321
- m = l.match(S_CLASS_RE)
322
- return false unless m
323
-
324
- debug_print!("class <<")
325
-
326
- push_state!(STATES[:s_class])
327
-
328
- true
214
+ # @param [String] comment
215
+ # @return [void]
216
+ def parse_comment!(comment)
217
+ return if try_param_comment(comment)
218
+ return if try_return_comment(comment)
219
+ raise "Must not reach here!"
329
220
  end
330
221
 
331
- # @param [String] l
222
+ # @param [String] comment
332
223
  # @return [bool]
333
- def try_parse_param(l)
334
- m = l.match(PARAM_RE)
224
+ def try_param_comment(comment)
225
+ m = comment.match(PARAM_RE)
335
226
  return false unless m
336
227
 
337
228
  p = AST::PTypeNode.new(
@@ -344,10 +235,10 @@ module Yard2steep
344
235
  true
345
236
  end
346
237
 
347
- # @param [String] l
238
+ # @param [String] comment
348
239
  # @return [bool]
349
- def try_parse_return(l)
350
- m = l.match(RETURN_RE)
240
+ def try_return_comment(comment)
241
+ m = comment.match(RETURN_RE)
351
242
  return false unless m
352
243
 
353
244
  @r_type = normalize_type(m[1])
@@ -355,94 +246,399 @@ module Yard2steep
355
246
  true
356
247
  end
357
248
 
358
- # @param [String] l
359
- # @return [bool]
360
- def try_parse_method(l)
361
- m = l.match(METHOD_RE)
362
- return false unless m
363
-
364
- debug_print!("def #{m[1]}")
365
-
366
- Util.assert! { m[1].is_a?(String) && m[2].is_a?(String) }
367
-
368
- @m_name = m[1]
369
- p_list = parse_method_params(m[2].strip)
370
-
371
- m_node = AST::MethodNode.new(
372
- p_list: p_list,
373
- r_type: (@r_type || ANY_TYPE),
374
- m_name: @m_name,
375
- )
376
- @current_class.append_m(m_node)
377
- reset_method_context!
378
-
379
- push_state!(STATES[:method])
380
-
381
- true
249
+ # @param [Array] r_ast
250
+ # @return [Array<AST::PNode>]
251
+ def parse_params(r_ast)
252
+ # NOTE: parrams is `paren_params` or `no_paren_params`
253
+ # paren_params: [:paren, [:params, ...]]
254
+ # no_paren_params: [:params, ...]
255
+ case r_ast[0]
256
+ when :paren
257
+ parse_paren_params(r_ast)
258
+ when :params
259
+ parse_no_paren_params(r_ast)
260
+ else
261
+ raise "invalid node"
262
+ end
382
263
  end
383
264
 
384
- # @param [String] l
385
- # @return [bool]
386
- def try_parse_method_with_no_action(l)
387
- m = l.match(METHOD_RE)
388
- return false unless m
389
-
390
- debug_print!("def #{m[1]}")
391
-
392
- # Do no action
393
-
394
- push_state!(STATES[:method])
395
-
396
- true
265
+ # @param [Array] r_ast
266
+ # @return [Array<AST::PNode>]
267
+ def parse_paren_params(r_ast)
268
+ # NOTE: r_ast is array such as
269
+ # [:paren, [:params, ...]]
270
+ Util.assert! {
271
+ r_ast[0] == :paren &&
272
+ r_ast[1].is_a?(Array) &&
273
+ r_ast[1][0] == :params
274
+ }
275
+ parse_no_paren_params(r_ast[1])
397
276
  end
398
277
 
399
- # @param [String] params_s
278
+ # @param [Array] params
400
279
  # @return [Array<AST::PNode>]
401
- def parse_method_params(params_s)
402
- Util.assert! { params_s.is_a?(String) }
280
+ def parse_no_paren_params(params)
281
+ # NOTE: params is array such as
282
+ # [:params,
283
+ # nil,
284
+ # nil,
285
+ # nil,
286
+ # nil,
287
+ # [
288
+ # [
289
+ # [:@label, "contents:", [3, 10]],
290
+ # false
291
+ # ]
292
+ # ],
293
+ # nil,
294
+ # nil
295
+ # ]],
296
+ Util.assert! { params[0] == :params && params.size == 8 }
297
+
298
+ r = []
299
+ n_params = (params[1] || [])
300
+ # NOTE: n_params is array such as
301
+ # [[:@ident, "a", [1, 7]], ...]
302
+ n_params.each do |key|
303
+ # TODO(south37) Optimize this check
304
+ Util.assert! { key[0] == :@ident && key[1].is_a?(String) }
305
+ name = key[1]
306
+ r.push(
307
+ AST::PNode.new(
308
+ type_node: type_node(name),
309
+ style: AST::PNode::STYLE[:normal],
310
+ )
311
+ )
312
+ end
403
313
 
404
- # NOTE: Remove parenthesis
405
- if (m = params_s.match(PAREN_RE))
406
- params_s = m[1]
314
+ v_params = (params[2] || [])
315
+ # NOTE: v_params is array such as
316
+ # [
317
+ # [
318
+ # [:@ident, "a", [1, 7]],
319
+ # [:@int, "2", [1, 9]]
320
+ # ],
321
+ # ...
322
+ # ]
323
+ v_params.each do |v_param|
324
+ key, default_v = v_param
325
+
326
+ # TODO(south37) Optimize this check
327
+ Util.assert! {
328
+ key[0] == :@ident &&
329
+ key[1].is_a?(String) &&
330
+ default_v.is_a?(Array)
331
+ }
332
+ name = key[1]
333
+ r.push(
334
+ AST::PNode.new(
335
+ type_node: type_node(name),
336
+ style: AST::PNode::STYLE[:normal_with_default],
337
+ )
338
+ )
407
339
  end
408
340
 
409
- if params_s == ''
410
- if @p_types.size > 0
411
- print "warn: #{@m_name} has no args, but annotated as #{@p_types}"
412
- end
413
- return []
341
+ n_params2 = (params[4] || [])
342
+ # NOTE: n_params2 is array such as
343
+ # [[:@ident, "a", [1, 7]], ...]
344
+ n_params2.each do |key|
345
+ # TODO(south37) Optimize this check
346
+ Util.assert! { key[0] == :@ident && key[1].is_a?(String) }
347
+ name = key[1]
348
+ r.push(
349
+ AST::PNode.new(
350
+ type_node: type_node(name),
351
+ style: AST::PNode::STYLE[:normal],
352
+ )
353
+ )
414
354
  end
415
355
 
416
- params_s.split(',').map { |s| s.strip }.map do |p|
417
- if p.include?(':')
418
- name, default_value = p.split(':')
419
- if default_value
356
+ k_params = (params[5] || [])
357
+ # NOTE: k_params is array such as
358
+ # [
359
+ # [
360
+ # [:@label, "contents:", [3, 10]],
361
+ # false
362
+ # ],
363
+ # ...
364
+ # ],
365
+ k_params.each do |v_param|
366
+ key, default_v = v_param
367
+
368
+ # TODO(south37) Optimize this check
369
+ Util.assert! {
370
+ key[0] == :@label &&
371
+ key[1][-1] == ':'
372
+ }
373
+ name = key[1][0..-2]
374
+ if default_v
375
+ r.push(
420
376
  AST::PNode.new(
421
377
  type_node: type_node(name),
422
378
  style: AST::PNode::STYLE[:keyword_with_default],
423
379
  )
424
- else
380
+ )
381
+ else
382
+ r.push(
425
383
  AST::PNode.new(
426
384
  type_node: type_node(name),
427
385
  style: AST::PNode::STYLE[:keyword],
428
386
  )
429
- end
430
- else
387
+ )
388
+ end
389
+ end
390
+
391
+ b_param = params[7]
392
+ # NOTE: b_params is array such as
393
+ # [:blockarg, [:@ident, "block", [1, 8]]]
394
+ if b_param
395
+ Util.assert! {
396
+ b_param[0] == :blockarg &&
397
+ b_param[1].is_a?(Array) &&
398
+ b_param[1][0] == :@ident &&
399
+ b_param[1][1].is_a?(String)
400
+ }
401
+ name = b_param[1][1]
402
+ r.push(
431
403
  AST::PNode.new(
432
- type_node: type_node(p),
404
+ type_node: block_type_node(name),
433
405
  style: AST::PNode::STYLE[:normal],
434
406
  )
407
+ )
408
+ end
409
+
410
+ # NOTE: params[3] is `*a`, params[6] is `**a`
411
+
412
+ r
413
+ end
414
+
415
+ # @return [void]
416
+ def within_context(&block)
417
+ # Current method context.
418
+ @p_types = {}
419
+ @r_type = nil
420
+ block.call
421
+ end
422
+
423
+ # @param [Array] r_ast
424
+ # @return [void]
425
+ def parse_class_or_module(r_ast)
426
+ # NOTE: r_ast is array such as
427
+ # [:class,
428
+ # [:const_ref, [:@const, "OtherClass", [119, 6]]],
429
+ # [:var_ref, [:@const, "MyClass", [119, 19]]],
430
+ # [:bodystmt, [...]]]
431
+
432
+ kind = r_ast[0].to_s
433
+
434
+ case kind
435
+ when "class"
436
+ Util.assert! { r_ast.size == 4 }
437
+ c_name_node = r_ast[1]
438
+ super_c_node = r_ast[2]
439
+ bodystmt_node = r_ast[3]
440
+ when "module"
441
+ Util.assert! { r_ast.size == 3 }
442
+ c_name_node = r_ast[1]
443
+ super_c_node = nil
444
+ bodystmt_node = r_ast[2]
445
+ else
446
+ raise "invalid kind: #{kind}"
447
+ end
448
+
449
+ Util.assert! {
450
+ c_name_node[0] == :const_ref &&
451
+ c_name_node[1].is_a?(Array) &&
452
+ c_name_node[1][0] == :@const &&
453
+ c_name_node[1][1].is_a?(String)
454
+ }
455
+ c_name = c_name_node[1][1]
456
+ Util.assert! {
457
+ !super_c_node || (
458
+ super_c_node[0] == :var_ref &&
459
+ super_c_node[1].is_a?(Array) &&
460
+ super_c_node[1][0] == :@const &&
461
+ super_c_node[1][1].is_a?(String)
462
+ )
463
+ }
464
+ super_c_name = super_c_node ? super_c_node[1][1] : nil
465
+
466
+ c = AST::ClassNode.new(
467
+ kind: kind,
468
+ c_name: c_name,
469
+ super_c: super_c_name,
470
+ parent: @current_class,
471
+ )
472
+ @current_class.append_child(c)
473
+ @current_class = c
474
+
475
+ parse_bodystmt(bodystmt_node)
476
+
477
+ @current_class = @current_class.parent
478
+ end
479
+
480
+ # @param [Array] r_ast
481
+ # @return [void]
482
+ def parse_bodystmt(r_ast)
483
+ # NOTE:
484
+ # [:bodystmt,
485
+ # [...]]
486
+ Util.assert! {
487
+ r_ast[0] == :bodystmt &&
488
+ r_ast[1].is_a?(Array)
489
+ }
490
+ parse_stmts(r_ast[1])
491
+ end
492
+
493
+ # @param [Array] r_ast
494
+ # @return [void]
495
+ def parse_assign(r_ast)
496
+ # NOTE: r_ast is array such as
497
+ # [:assign,
498
+ # [:var_field, [:@const, "DNA", [1, 15]]],
499
+ # [...]]
500
+ Util.assert! {
501
+ r_ast[0] == :assign &&
502
+ r_ast[1].is_a?(Array) &&
503
+ r_ast[1][0] == :var_field
504
+ }
505
+ var_type = r_ast[1][1][0]
506
+ if var_type == :@const
507
+ # TODO(south37) Check the value node to predict the type of the const.
508
+ c = AST::ConstantNode.new(
509
+ name: r_ast[1][1][1],
510
+ klass: @current_class,
511
+ )
512
+ @current_class.append_constant(c)
513
+ end
514
+ end
515
+
516
+ # @param [Array] r_ast
517
+ # @return [void]
518
+ def parse_command(r_ast)
519
+ # NOTE: r_ast is array such as
520
+ # [:command,
521
+ # [:@ident, "attr_reader", [1, 15]],
522
+ # [:args_add_block,
523
+ # [
524
+ # [:symbol_literal, [:symbol, [:@ident, "ok", [1, 28]]]]
525
+ # ],
526
+ # false
527
+ # ]
528
+ # ]
529
+ Util.assert! {
530
+ r_ast[0] == :command &&
531
+ r_ast[1].is_a?(Array)
532
+ }
533
+
534
+ # NOTE: check attr_*
535
+ if r_ast[1][0] == :@ident
536
+ case r_ast[1][1]
537
+ when "attr_reader"
538
+ parse_attr_reader(parse_command_args_add_block(r_ast[2]))
539
+ when "attr_writer"
540
+ # TODO(south37) Impl
541
+ # parse_attr_writer(parse_command_args_add_block(r_ast[2]))
542
+ when "attr_accessor"
543
+ # TODO(south37) Impl
544
+ # parse_attr_accessor(parse_command_args_add_block(r_ast[2]))
435
545
  end
546
+
547
+ # Do nothing for other case
436
548
  end
437
549
  end
438
550
 
439
- # @param [String] l
440
- # @return [bool]
441
- def try_parse_attr(l)
442
- m = l.match(ATTR_RE)
443
- return false unless m
551
+ # @param [Array] r_ast
552
+ # @return [Array<String>]
553
+ def parse_command_args_add_block(r_ast)
554
+ # NOTE: r_ast is array such as
555
+ #
556
+ # [:args_add_block,
557
+ # [
558
+ # [:symbol_literal, [:symbol, [:@ident, "ok", [1, 28]]]],
559
+ # [:symbol_literal, [:symbol, [:@ident, "no", [1, 33]]]],
560
+ # [:string_literal, [:string_content, [:@tstring_content, "b", [1, 38]]]]
561
+ # ],
562
+ # false
563
+ # ]
564
+ Util.assert! {
565
+ r_ast[0] == :args_add_block &&
566
+ r_ast[1].is_a?(Array)
567
+ }
568
+ r = []
569
+ r_ast[1].each do |ast|
570
+ case ast[0]
571
+ when :symbol_literal
572
+ Util.assert! {
573
+ ast[1].is_a?(Array) &&
574
+ ast[1][0] == :symbol &&
575
+ ast[1][1].is_a?(Array) &&
576
+ ast[1][1][0] == :@ident &&
577
+ ast[1][1][1].is_a?(String)
578
+ }
579
+ r.push(ast[1][1][1])
580
+ when :string_literal
581
+ Util.assert! {
582
+ ast[1].is_a?(Array) &&
583
+ ast[1][0] == :string_content &&
584
+ ast[1][1].is_a?(Array) &&
585
+ ast[1][1][0] == :@tstring_content &&
586
+ ast[1][1][1].is_a?(String)
587
+ }
588
+ r.push(ast[1][1][1])
589
+ end
590
+ # Do nothing for other case
591
+ end
592
+ r
593
+ end
444
594
 
445
- ivars = m[1].split(",").map { |s| s.strip.gsub(/^:/, '') }
595
+ # @param [Array] r_ast
596
+ # @return [void]
597
+ def parse_method_add_arg(r_ast)
598
+ # NOTE: r_ast is Array such as
599
+ # [:method_add_arg,
600
+ # [:fcall, [:@ident, "attr_reader", [1, 15]]],
601
+ # [:arg_paren,
602
+ # [
603
+ # :args_add_block,
604
+ # [
605
+ # [:symbol_literal, [:symbol, [:@ident, "ok", [1, 28]]]]
606
+ # ],
607
+ # false
608
+ # ]
609
+ # ]
610
+ # ]
611
+ Util.assert! {
612
+ r_ast[0] == :method_add_arg &&
613
+ r_ast[1].is_a?(Array) &&
614
+ r_ast[1][0] == :fcall &&
615
+ r_ast[1][1].is_a?(Array) &&
616
+ r_ast[2].is_a?(Array) &&
617
+ r_ast[2][0] == :arg_paren &&
618
+ r_ast[2][1].is_a?(Array) &&
619
+ r_ast[2][1][0] == :args_add_block &&
620
+ r_ast[2][1][1].is_a?(Array)
621
+ }
622
+
623
+ # NOTE: check attr_*
624
+ if r_ast[1][1][0] == :@ident
625
+ case r_ast[1][1][1]
626
+ when "attr_reader"
627
+ parse_attr_reader(parse_command_args_add_block(r_ast[2][1]))
628
+ when "attr_writer"
629
+ # TODO(south37) Impl
630
+ # parse_attr_writer(parse_command_args_add_block(r_ast[2][1]))
631
+ when "attr_accessor"
632
+ # TODO(south37) Impl
633
+ # parse_attr_accessor(parse_command_args_add_block(r_ast[2][1]))
634
+ end
635
+ # Do nothing for other case
636
+ end
637
+ end
638
+
639
+ # @param [Array<String>] vars
640
+ # @return [void]
641
+ def parse_attr_reader(ivars)
446
642
  ivars.each do |ivarname|
447
643
  @current_class.append_ivar(
448
644
  AST::IVarNode.new(
@@ -459,70 +655,67 @@ module Yard2steep
459
655
  )
460
656
  )
461
657
  end
462
-
463
- true
464
- end
465
-
466
- # @param [String] state
467
- # @return [void]
468
- def push_state!(state)
469
- if STATES.values.include?(state)
470
- @state = state
471
- end
472
- @stack.push(state)
473
658
  end
474
659
 
475
- # @return [void]
476
- def pop_state!
477
- state = @stack.pop
478
- if STATES.values.include?(state)
479
- # Restore prev class
480
- if state == STATES[:class]
481
- @current_class = @current_class.parent
482
- end
483
-
484
- # Restore prev state
485
- @state = @stack.select { |s| STATES.values.include?(s) }.last
486
- Util.assert! { !@state.nil? }
660
+ # @param [String] text
661
+ # @return [Hash{ String => String }]
662
+ def extract_comments(text)
663
+ # NOTE: `Ripper.lex` returns array of array such as
664
+ # [
665
+ # [[1, 0], :on_comment, "# @param [Array] contents\n", EXPR_BEG],
666
+ # ...
667
+ # ]
668
+ r = {}
669
+ Ripper.lex(text).each do |t|
670
+ # Check token type
671
+ type = t[1]
672
+ next if type != :on_comment
673
+ # Check comment body
674
+ comment = t[2]
675
+ next unless comment.match?(COMMENT_RE)
676
+
677
+ line = t[0][0]
678
+ r[line] = comment
487
679
  end
488
- end
489
680
 
490
- # @return [bool]
491
- def stack_is_empty?
492
- @stack.size <= 1
681
+ r
493
682
  end
494
683
 
495
684
  ##
496
685
  # Helper
497
686
 
687
+ # @param [String] message
688
+ # @return [void]
689
+ def debug_print!(message)
690
+ print "#{message}\n" if @debug
691
+ end
692
+
498
693
  # @param [String] p
499
694
  # @return [AST::PTypeNode]
500
695
  def type_node(p)
501
696
  if @p_types[p]
502
697
  @p_types[p]
503
698
  else
504
- # NOTE: `&` represents block variable
505
- if p[0] == '&'
506
- AST::PTypeNode.new(
507
- p_type: ANY_BLOCK_TYPE,
508
- p_name: p[1..-1],
509
- kind: AST::PTypeNode::KIND[:block],
510
- )
511
- else
512
- AST::PTypeNode.new(
513
- p_type: ANY_TYPE,
514
- p_name: p,
515
- kind: AST::PTypeNode::KIND[:normal],
516
- )
517
- end
699
+ AST::PTypeNode.new(
700
+ p_type: ANY_TYPE,
701
+ p_name: p,
702
+ kind: AST::PTypeNode::KIND[:normal],
703
+ )
518
704
  end
519
705
  end
520
706
 
521
- # @param [String] message
522
- # @param [Integer] offset
523
- # @return [void]
524
- def debug_print!(message, offset: 0)
525
- print "#{' ' * (@stack.size * 2 + offset)}#{message}\n" if @debug
707
+ # @param [String] p
708
+ # @return [AST::PTypeNode]
709
+ def block_type_node(p)
710
+ if @p_types[p]
711
+ @p_types[p]
712
+ else
713
+ AST::PTypeNode.new(
714
+ p_type: ANY_BLOCK_TYPE,
715
+ p_name: p,
716
+ kind: AST::PTypeNode::KIND[:block],
717
+ )
718
+ end
526
719
  end
527
720
 
528
721
  # @param [String] type