Class: Reflexive::ParseTreeTopDownWalker

Inherits:
Object
  • Object
show all
Defined in:
lib/reflexive/parse_tree_top_down_walker.rb

Instance Method Summary collapse

Constructor Details

#initialize(events_tree) ⇒ ParseTreeTopDownWalker

Returns a new instance of ParseTreeTopDownWalker.



6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 6

def initialize(events_tree)
  @events_tree = events_tree
  @local_variables = []
  @dynamic_variables = []
  @variables_scope_id = 1
  VariablesScope.reset_guid
  # empty - top level
  # ["Module", "Class"] - class Class in module Module, class dev level
  # ["Module", "Class", :instance] - class Class in module Module, instance level
  @scope = [] # @scope.last - is basically "implicit self"
  # also used for constant lookup
end

Instance Method Details

#add_local_variable(scanner_event) ⇒ Object



35
36
37
38
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 35

def add_local_variable(scanner_event)
  current_variables_scope.merge!(scanner_event[:ident] => scanner_event)
  local_variable_assignment(scanner_event)
end

#add_local_variables_from_lhs(lhs_event) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 59

def add_local_variables_from_lhs(lhs_event)
  # [:var_field, {:ident=>"v1"}]
  if lhs_event[0] == :var_field
    if (scanner_event = lhs_event[1]).is_a?(Hash)
      if scanner_event[:ident]
        add_local_variable(scanner_event)
      end
    end
  end
  # raise "don't know how to add local variables from lhs_event : #{ lhs_event }"
end

#add_local_variables_from_mlhs(mlhs_event) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 71

def add_local_variables_from_mlhs(mlhs_event)
  # [{:ident=>"a"}, {:ident=>"b"}],
  if mlhs_event.is_a?(Array)
    if mlhs_event[0] == :mlhs_add_star
      add_local_variables_from_mlhs(mlhs_event[1])
      add_local_variable(mlhs_event[2]) if mlhs_event[2][:ident]
      add_local_variables_from_mlhs(mlhs_event[3]) if mlhs_event[3].is_a?(Array)
    else
      mlhs_event.each do |event|
        next unless scanner_event?(event)
        if event[:ident]
          add_local_variable(event)
        end
      end
    end
  end
  # raise "don't know how to add local variables from lhs_event : #{ lhs_event }"
end

#add_local_variables_from_params_event(params_event) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 90

def add_local_variables_from_params_event(params_event)
  return unless params_event
  params_event = params_event[1] if params_event[0] == :paren # ?
  found = false

  if options_arguments = params_event[2]
    options_arguments.each do |optional_argument|
      if scanner_event?(event = optional_argument[0])
        add_local_variable(event)
        keep_walking(optional_argument[1..-1])
      end
    end
  end

  for scanner_event in extract_scanner_events_from_tree(params_event.values_at(1,3,4,5))
    if scanner_event[:ident]
      found = true
      add_local_variable(scanner_event)
    end
  end
  # raise "don't know how to add local variables from params_event: #{ params_event }" unless found
end

#constant_access(scanner_event, name = nil) ⇒ Object



414
415
416
417
418
419
420
421
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 414

def constant_access(scanner_event, name = nil)
  unless name
    name = scanner_event[:const]
  end
  merge_tags(scanner_event,
             :constant_access => { :name => name,
                                   :scope => constant_access_scope })
end

#constant_access_scopeObject



408
409
410
411
412
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 408

def constant_access_scope
  scope = @scope.dup
  scope.pop if scope.last == :instance # class/instance doesn't matter for constant lookup
  scope
end

#current_variables_scopeObject



31
32
33
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 31

def current_variables_scope
  (@dynamic_variables.size > 0 ? @dynamic_variables : @local_variables).last
end

#extract_scanner_events_from_tree(tree) ⇒ Object



113
114
115
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 113

def extract_scanner_events_from_tree(tree)
  tree.flatten.select { |e| scanner_event?(e) }
end

#is_ident?(event) ⇒ Boolean

Returns:

  • (Boolean)


449
450
451
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 449

def is_ident?(event)
  scanner_event?(event) && event[:ident]
end

#keep_walking(*args) ⇒ Object



168
169
170
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 168

def keep_walking(*args)
  on_default(nil, args)
end

#local_variable_access(scanner_event) ⇒ Object



402
403
404
405
406
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 402

def local_variable_access(scanner_event)
  existing_variable_id = "#{ local_variable_scope_id(scanner_event[:ident]) }:#{ scanner_event[:ident] }"
  merge_tags(scanner_event,
             :local_variable_access => existing_variable_id )
end

#local_variable_assignment(scanner_event) ⇒ Object



423
424
425
426
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 423

def local_variable_assignment(scanner_event)
  merge_tags(scanner_event,
             :local_variable_assignment => variable_id(scanner_event))
end

#local_variable_defined?(name) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 51

def local_variable_defined?(name)
  variables_scope.any? { |variables| variables.has_key?(name) }
end

#local_variable_scope_id(name) ⇒ Object



55
56
57
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 55

def local_variable_scope_id(name)
  variables_scope.detect { |variables| variables.has_key?(name) }.guid
end

#merge_tags(scanner_event, tags) ⇒ Object



432
433
434
435
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 432

def merge_tags(scanner_event, tags)
  scanner_event[:tags] ||= {}
  scanner_event[:tags].merge!(tags)
end

#method_call(scanner_event, receiver, *args) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 390

def method_call(scanner_event, receiver, *args)
  unless receiver
    # implict self concept (will be fetched from constant_access_scope)
    receiver = @scope.last == :instance ? :instance : :class
  end
  merge_tags(scanner_event,
             {:method_call =>
                     {:name => scanner_event[:ident],
                      :receiver => receiver,
                      :scope => constant_access_scope}})
end

#on_assign(lhs, rhs) ⇒ Object



235
236
237
238
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 235

def on_assign(lhs, rhs)
  add_local_variables_from_lhs(lhs)
  keep_walking(rhs)
end

#on_brace_block(params, body) ⇒ Object



228
229
230
231
232
233
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 228

def on_brace_block(params, body)
  push_dynamic_variables_context
  add_local_variables_from_params_event(params)
  keep_walking(body)
  pop_dynamic_variables_context
end

#on_call(receiver, dot, method) ⇒ Object

[:call,

[:var_ref, {:ident=>"subclasses"}]


338
339
340
341
342
343
344
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 338

def on_call(receiver, dot, method)
  if rcv = resolve_receiver(receiver)
    method_call(method, [rcv])
  end
   
  keep_walking(receiver)
end

#on_class(name, ancestor, body) ⇒ Object



195
196
197
198
199
200
201
202
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 195

def on_class(name, ancestor, body)
  keep_walking(name, ancestor)
  push_local_variables_context
  push_namespace_scope(resolve_constant_ref(name))
  keep_walking(body)
  pop_namespace_scope
  pop_local_variables_context
end

#on_command(operation, command_args) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 245

def on_command(operation, command_args)
  method_call(operation, nil) if is_ident?(operation)
  if operation[:ident] == "autoload" &&
          (arguments = resolve_arguments(command_args))

    if [:const, :tstring_content].include?(arguments[0].keys.first)
      constant_access(arguments[0], arguments[0].values.first)
    end
  end
  keep_walking(command_args)
end

#on_command_call(receiver, dot, method, args) ⇒ Object

primary_value => anything operation2 : tIDENTIFIER | tCONSTANT | tFID | op ; command_args => anything



309
310
311
312
313
314
315
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 309

def on_command_call(receiver, dot, method, args)
  if is_ident?(method) &&
          (constant = resolve_constant_ref(receiver))
    method_call(method, [constant])
  end
  keep_walking(receiver, args)
end

#on_const_path_ref(primary_value, name) ⇒ Object



382
383
384
385
386
387
388
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 382

def on_const_path_ref(primary_value, name)
  keep_walking(primary_value)
  if (constant = resolve_constant_ref(primary_value)) &&
          scanner_event?(name) && name[:const]
    constant_access(name, "#{ constant }::#{ name[:const] }")
  end
end

#on_const_ref(const_ref_event) ⇒ Object



374
375
376
377
378
379
380
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 374

def on_const_ref(const_ref_event)
  if scanner_event?(const_ref_event)
    if const_ref_event[:const]
      constant_access(const_ref_event)
    end
  end
end

#on_def(name, params, body) ⇒ Object



178
179
180
181
182
183
184
185
186
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 178

def on_def(name, params, body)
  push_local_variables_context
  # TODO this is hack :(
  push_namespace_instance_scope unless @in_singleton_class_defition
  add_local_variables_from_params_event(params)
  keep_walking(body)
  pop_namespace_scope unless @in_singleton_class_defition
  pop_local_variables_context
end

#on_default(type, event_args) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 142

def on_default(type, event_args)
  return unless event_args # no-args event
  event_args.each do |arg|
    if arg == nil
      # empty arg - pass

      # why the following isn't reported with scanner events?
    elsif type == :call && [:".", :"::"].include?(arg)
    elsif type == :var_ref && [:".", :"::"].include?(arg)
    elsif type == :field && [:".", :"::"].include?(arg)
    elsif type == :command_call && ([:".", :"::"].include?(arg) || arg == false)
    elsif type == :args_add_block && arg == false
    elsif type == :unary && arg.is_a?(Symbol)
    elsif type == :binary && arg.is_a?(Symbol)
    elsif scanner_event?(arg)
      # scanner event - pass
    elsif (parser_events?(arg) rescue r(type, event_args))
      arg.each do |event|
        walk(event)
      end
    elsif parser_event?(arg)
      walk(arg)
    end
  end
end

#on_defs(target, period, name, params, body) ⇒ Object



188
189
190
191
192
193
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 188

def on_defs(target, period, name, params, body)
  push_local_variables_context
  add_local_variables_from_params_event(params)
  keep_walking(body)
  pop_local_variables_context
end

#on_do_block(params, body) ⇒ Object



221
222
223
224
225
226
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 221

def on_do_block(params, body)
  push_dynamic_variables_context
  add_local_variables_from_params_event(params)
  keep_walking(body)
  pop_dynamic_variables_context
end

#on_fcall(operation) ⇒ Object



284
285
286
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 284

def on_fcall(operation)
  method_call(operation, nil) if is_ident?(operation)
end

#on_massign(mlhs, mrhs) ⇒ Object



240
241
242
243
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 240

def on_massign(mlhs, mrhs)
  add_local_variables_from_mlhs(mlhs)
  keep_walking(mrhs)
end

#on_method_add_arg(method, arguments) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 288

def on_method_add_arg(method, arguments)

  if method[0] == :fcall &&
          scanner_event?(method[1]) &&
          method[1][:ident] == "autoload"
    if arguments = resolve_arguments(arguments)
      if [:const, :tstring_content].include?(arguments[0].keys.first)
        constant_access(arguments[0], arguments[0].values.first)
      end
    end
  end
  keep_walking(method, arguments)
end

#on_module(name, body) ⇒ Object



212
213
214
215
216
217
218
219
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 212

def on_module(name, body)
  keep_walking(name)
  push_local_variables_context
  push_namespace_scope(resolve_constant_ref(name))
  keep_walking(body)
  pop_namespace_scope
  pop_local_variables_context
end

#on_program(body) ⇒ Object



172
173
174
175
176
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 172

def on_program(body)
  push_local_variables_context
  keep_walking(body)
  pop_local_variables_context
end

#on_sclass(target, body) ⇒ Object



204
205
206
207
208
209
210
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 204

def on_sclass(target, body)
  push_local_variables_context
  @in_singleton_class_defition = true
  keep_walking(body)
  @in_singleton_class_defition = false
  pop_local_variables_context
end

#on_var_ref(ref_event) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 346

def on_var_ref(ref_event)
  # [:var_ref, {:kw=>"false"}]
  # [:var_ref, {:cvar=>"@@subclasses"}]
  # [:var_ref, {:ident=>"child"}] (sic!)
  #   [:binary,
#                 [:var_ref, {:ident=>"nonreloadables"}],
#                 :<<,
#                 [:var_ref, {:ident=>"klass"}]
  #
  #[:call,
#                 [:var_ref, {:ident=>"klass"}],
#                 :".",
#                 {:ident=>"instance_variables"}],
  #
  #
  if scanner_event?(ref_event)
    if ref_event[:ident]
      if local_variable_defined?(ref_event[:ident])
        local_variable_access(ref_event)
      else
        method_call(ref_event, nil)
      end
    elsif ref_event[:const]
      constant_access(ref_event)
    end
  end
end

#parser_event?(event) ⇒ Boolean

Returns:

  • (Boolean)


441
442
443
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 441

def parser_event?(event)
  event.is_a?(Array) && event[0].is_a?(Symbol)
end

#parser_events?(events) ⇒ Boolean

Returns:

  • (Boolean)


445
446
447
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 445

def parser_events?(events)
  events.all? { |event| parser_event?(event) }
end

#pop_dynamic_variables_contextObject



129
130
131
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 129

def pop_dynamic_variables_context
  @dynamic_variables.pop
end

#pop_local_variables_contextObject



121
122
123
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 121

def pop_local_variables_context
  @local_variables.pop
end

#pop_namespace_scopeObject



27
28
29
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 27

def pop_namespace_scope
  @scope.pop
end

#push_dynamic_variables_contextObject



125
126
127
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 125

def push_dynamic_variables_context
  @dynamic_variables << VariablesScope.new
end

#push_local_variables_contextObject



117
118
119
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 117

def push_local_variables_context
  @local_variables << VariablesScope.new
end

#push_namespace_instance_scopeObject



23
24
25
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 23

def push_namespace_instance_scope
  @scope << :instance
end

#push_namespace_scope(namespace_name) ⇒ Object



19
20
21
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 19

def push_namespace_scope(namespace_name)
  @scope << namespace_name
end

#resolve_argument(argument) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 266

def resolve_argument(argument)
  if argument[0] == :symbol_literal
    # [:symbol_literal, [:symbol, {:const=>"C"}]]
    if argument[1].is_a?(Array)
      if argument[1][0] == :symbol
        argument[1][1] # {:const=>"C"}
      end
    end
  elsif argument[0] == :string_literal
    # [:string_literal, [:string_content, {:tstring_content=>"C"}]]
    if argument[1].is_a?(Array)
      if argument[1][0] == :string_content
        argument[1][1] # {:tstring_content=>"C"}
      end
    end
  end
end

#resolve_arguments(arguments) ⇒ Object



257
258
259
260
261
262
263
264
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 257

def resolve_arguments(arguments)
  arguments = arguments[1] if arguments[0] == :arg_paren
  if arguments[0] == :args_add_block
    if arguments[1].is_a?(Array)
      arguments[1].map { |a| resolve_argument(a) }
    end
  end
end

#resolve_constant_ref(events) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 317

def resolve_constant_ref(events)
  if events[0] == :var_ref || events[0] == :const_ref &&
          scanner_event?(events[1]) &&
          events[1][:const]
    events[1][:const]
  elsif events[0] == :top_const_ref &&
          scanner_event?(events[1]) &&
          events[1][:const]
    "::" + events[1][:const]
  elsif events[0] == :const_path_ref &&
          (constant = resolve_constant_ref(events[1]))
    "#{ constant }::#{ events[2][:const] }"
  end
end

#resolve_receiver(receiver) ⇒ Object



332
333
334
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 332

def resolve_receiver(receiver)
  resolve_constant_ref(receiver)
end

#scanner_event?(event) ⇒ Boolean

Returns:

  • (Boolean)


437
438
439
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 437

def scanner_event?(event)
  event.is_a?(Hash)
end

#variable_id(scanner_event) ⇒ Object



428
429
430
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 428

def variable_id(scanner_event)
  "#{ variables_scope_id }:#{ scanner_event[:ident] }"
end

#variables_scopeObject



40
41
42
43
44
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 40

def variables_scope
  merged_scope = @dynamic_variables.dup.reverse
  merged_scope << @local_variables.last if @local_variables.size > 0
  merged_scope
end

#variables_scope_idObject

TODO crazy, replace that with something more appropriate



47
48
49
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 47

def variables_scope_id
  current_variables_scope.guid
end

#walk(event = @events_tree) ⇒ Object



133
134
135
136
137
138
139
140
# File 'lib/reflexive/parse_tree_top_down_walker.rb', line 133

def walk(event = @events_tree)
  type, *args = event
  if respond_to?("on_#{ type }")
    send("on_#{ type }", *args) #rescue r($!, event)
  else
    on_default(type, args)
  end
end