reflexive 0.0.1

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.
@@ -0,0 +1,392 @@
1
+ require "zlib"
2
+ require "reflexive/variables_scope"
3
+
4
+ module Reflexive
5
+ class ParseTreeTopDownWalker
6
+ def initialize(events_tree)
7
+ @events_tree = events_tree
8
+ @local_variables = []
9
+ @dynamic_variables = []
10
+ @variables_scope_id = 1
11
+ VariablesScope.reset_guid
12
+ # empty - top level
13
+ # ["Module", "Class"] - class Class in module Module, class dev level
14
+ # ["Module", "Class", :instance] - class Class in module Module, instance level
15
+ @scope = [] # @scope.last - is basically "implicit self"
16
+ # also used for constant lookup
17
+ end
18
+
19
+ def push_namespace_scope(namespace_name)
20
+ @scope << namespace_name
21
+ end
22
+
23
+ def push_namespace_instance_scope
24
+ @scope << :instance
25
+ end
26
+
27
+ def pop_namespace_scope
28
+ @scope.pop
29
+ end
30
+
31
+ def current_variables_scope
32
+ (@dynamic_variables.size > 0 ? @dynamic_variables : @local_variables).last
33
+ end
34
+
35
+ def add_local_variable(scanner_event)
36
+ current_variables_scope.merge!(scanner_event[:ident] => scanner_event)
37
+ local_variable_assignment(scanner_event)
38
+ end
39
+
40
+ def variables_scope
41
+ merged_scope = @dynamic_variables.dup.reverse
42
+ merged_scope << @local_variables.last if @local_variables.size > 0
43
+ merged_scope
44
+ end
45
+
46
+ # TODO crazy, replace that with something more appropriate
47
+ def variables_scope_id
48
+ current_variables_scope.guid
49
+ end
50
+
51
+ def local_variable_defined?(name)
52
+ variables_scope.any? { |variables| variables.has_key?(name) }
53
+ end
54
+
55
+ def local_variable_scope_id(name)
56
+ variables_scope.detect { |variables| variables.has_key?(name) }.guid
57
+ end
58
+
59
+ def add_local_variables_from_lhs(lhs_event)
60
+ # [:var_field, {:ident=>"v1"}]
61
+ if lhs_event[0] == :var_field
62
+ if (scanner_event = lhs_event[1]).is_a?(Hash)
63
+ if scanner_event[:ident]
64
+ add_local_variable(scanner_event)
65
+ end
66
+ end
67
+ end
68
+ # raise "don't know how to add local variables from lhs_event : #{ lhs_event }"
69
+ end
70
+
71
+ def add_local_variables_from_mlhs(mlhs_event)
72
+ # [{:ident=>"a"}, {:ident=>"b"}],
73
+ if mlhs_event.is_a?(Array)
74
+ if mlhs_event[0] == :mlhs_add_star
75
+ add_local_variables_from_mlhs(mlhs_event[1])
76
+ add_local_variable(mlhs_event[2]) if mlhs_event[2][:ident]
77
+ add_local_variables_from_mlhs(mlhs_event[3]) if mlhs_event[3].is_a?(Array)
78
+ else
79
+ mlhs_event.each do |event|
80
+ next unless scanner_event?(event)
81
+ if event[:ident]
82
+ add_local_variable(event)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ # raise "don't know how to add local variables from lhs_event : #{ lhs_event }"
88
+ end
89
+
90
+ def add_local_variables_from_params_event(params_event)
91
+ return unless params_event
92
+ params_event = params_event[1] if params_event[0] == :paren # ?
93
+ found = false
94
+ for scanner_event in extract_scanner_events_from_tree(params_event)
95
+ if scanner_event[:ident]
96
+ found = true
97
+ add_local_variable(scanner_event)
98
+ end
99
+ end
100
+ # raise "don't know how to add local variables from params_event: #{ params_event }" unless found
101
+ end
102
+
103
+ def extract_scanner_events_from_tree(tree)
104
+ tree.flatten.select { |e| scanner_event?(e) }
105
+ end
106
+
107
+ def push_local_variables_context
108
+ @local_variables << VariablesScope.new
109
+ end
110
+
111
+ def pop_local_variables_context
112
+ @local_variables.pop
113
+ end
114
+
115
+ def push_dynamic_variables_context
116
+ @dynamic_variables << VariablesScope.new
117
+ end
118
+
119
+ def pop_dynamic_variables_context
120
+ @dynamic_variables.pop
121
+ end
122
+
123
+ def walk(event = @events_tree)
124
+ type, *args = event
125
+ if respond_to?("on_#{ type }")
126
+ send("on_#{ type }", *args) #rescue r($!, event)
127
+ else
128
+ on_default(type, args)
129
+ end
130
+ end
131
+
132
+ def on_default(type, event_args)
133
+ return unless event_args # no-args event
134
+ event_args.each do |arg|
135
+ if arg == nil
136
+ # empty arg - pass
137
+
138
+ # why the following isn't reported with scanner events?
139
+ elsif type == :call && [:".", :"::"].include?(arg)
140
+ elsif type == :var_ref && [:".", :"::"].include?(arg)
141
+ elsif type == :field && [:".", :"::"].include?(arg)
142
+ elsif type == :command_call && ([:".", :"::"].include?(arg) || arg == false)
143
+ elsif type == :args_add_block && arg == false
144
+ elsif type == :unary && arg.is_a?(Symbol)
145
+ elsif type == :binary && arg.is_a?(Symbol)
146
+ elsif scanner_event?(arg)
147
+ # scanner event - pass
148
+ elsif (parser_events?(arg) rescue r(type, event_args))
149
+ arg.each do |event|
150
+ walk(event)
151
+ end
152
+ elsif parser_event?(arg)
153
+ walk(arg)
154
+ end
155
+ end
156
+ end
157
+
158
+ def keep_walking(*args)
159
+ on_default(nil, args)
160
+ end
161
+
162
+ def on_program(body)
163
+ push_local_variables_context
164
+ keep_walking(body)
165
+ pop_local_variables_context
166
+ end
167
+
168
+ def on_def(name, params, body)
169
+ push_local_variables_context
170
+ add_local_variables_from_params_event(params)
171
+ push_namespace_instance_scope
172
+ keep_walking(body)
173
+ pop_namespace_scope
174
+ pop_local_variables_context
175
+ end
176
+
177
+ def on_defs(target, period, name, params, body)
178
+ push_local_variables_context
179
+ add_local_variables_from_params_event(params)
180
+ keep_walking(body)
181
+ pop_local_variables_context
182
+ end
183
+
184
+ def on_class(name, ancestor, body)
185
+ keep_walking(name)
186
+ push_local_variables_context
187
+ push_namespace_scope(resolve_constant_ref(name))
188
+ keep_walking(body)
189
+ pop_namespace_scope
190
+ pop_local_variables_context
191
+ end
192
+
193
+ def on_sclass(target, body)
194
+ push_local_variables_context
195
+ keep_walking(body)
196
+ pop_local_variables_context
197
+ end
198
+
199
+ def on_module(name, body)
200
+ keep_walking(name)
201
+ push_local_variables_context
202
+ push_namespace_scope(resolve_constant_ref(name))
203
+ keep_walking(body)
204
+ pop_namespace_scope
205
+ pop_local_variables_context
206
+ end
207
+
208
+ def on_do_block(params, body)
209
+ push_dynamic_variables_context
210
+ add_local_variables_from_params_event(params)
211
+ keep_walking(body)
212
+ pop_dynamic_variables_context
213
+ end
214
+
215
+ def on_brace_block(params, body)
216
+ push_dynamic_variables_context
217
+ add_local_variables_from_params_event(params)
218
+ keep_walking(body)
219
+ pop_dynamic_variables_context
220
+ end
221
+
222
+ def on_assign(lhs, rhs)
223
+ add_local_variables_from_lhs(lhs)
224
+ keep_walking(rhs)
225
+ end
226
+
227
+ def on_massign(mlhs, mrhs)
228
+ add_local_variables_from_mlhs(mlhs)
229
+ keep_walking(mrhs)
230
+ end
231
+
232
+ def on_command(operation, command_args)
233
+ method_call(operation, nil) if is_ident?(operation)
234
+ keep_walking(command_args)
235
+ end
236
+
237
+ def on_fcall(operation)
238
+ method_call(operation, nil) if is_ident?(operation)
239
+ end
240
+
241
+ # primary_value => anything
242
+ # operation2 : tIDENTIFIER
243
+ # | tCONSTANT
244
+ # | tFID
245
+ # | op
246
+ # ;
247
+ # command_args => anything
248
+ def on_command_call(receiver, dot, method, args)
249
+ if is_ident?(method) &&
250
+ (constant = resolve_constant_ref(receiver))
251
+ method_call(method, [constant])
252
+ end
253
+ keep_walking(receiver, args)
254
+ end
255
+
256
+ def resolve_constant_ref(events)
257
+ if events[0] == :var_ref || events[0] == :const_ref &&
258
+ scanner_event?(events[1]) &&
259
+ events[1][:const]
260
+ events[1][:const]
261
+ elsif events[0] == :top_const_ref &&
262
+ scanner_event?(events[1]) &&
263
+ events[1][:const]
264
+ "::" + events[1][:const]
265
+ elsif events[0] == :const_path_ref &&
266
+ (constant = resolve_constant_ref(events[1]))
267
+ "#{ constant }::#{ events[2][:const] }"
268
+ end
269
+ end
270
+
271
+ def resolve_receiver(receiver)
272
+ resolve_constant_ref(receiver)
273
+ end
274
+
275
+ # [:call,
276
+ # [:var_ref, {:ident=>"subclasses"}]
277
+ def on_call(receiver, dot, method)
278
+ if rcv = resolve_receiver(receiver)
279
+ method_call(method, [rcv])
280
+ else
281
+ keep_walking(receiver)
282
+ end
283
+ end
284
+
285
+ def on_var_ref(ref_event)
286
+ # [:var_ref, {:kw=>"false"}]
287
+ # [:var_ref, {:cvar=>"@@subclasses"}]
288
+ # [:var_ref, {:ident=>"child"}] (sic!)
289
+ # [:binary,
290
+ # [:var_ref, {:ident=>"nonreloadables"}],
291
+ # :<<,
292
+ # [:var_ref, {:ident=>"klass"}]
293
+ #
294
+ #[:call,
295
+ # [:var_ref, {:ident=>"klass"}],
296
+ # :".",
297
+ # {:ident=>"instance_variables"}],
298
+ #
299
+ #
300
+ if scanner_event?(ref_event)
301
+ if ref_event[:ident]
302
+ if local_variable_defined?(ref_event[:ident])
303
+ local_variable_access(ref_event)
304
+ else
305
+ method_call(ref_event, nil)
306
+ end
307
+ elsif ref_event[:const]
308
+ constant_access(ref_event)
309
+ end
310
+ end
311
+ end
312
+
313
+ def on_const_ref(const_ref_event)
314
+ if scanner_event?(const_ref_event)
315
+ if const_ref_event[:const]
316
+ constant_access(const_ref_event)
317
+ end
318
+ end
319
+ end
320
+
321
+ def on_const_path_ref(primary_value, name)
322
+ keep_walking(primary_value)
323
+ if (constant = resolve_constant_ref(primary_value)) &&
324
+ scanner_event?(name) && name[:const]
325
+ constant_access(name, "#{ constant }::#{ name[:const] }")
326
+ end
327
+ end
328
+
329
+ def method_call(scanner_event, receiver, *args)
330
+ unless receiver
331
+ # implict self concept
332
+ receiver = @scope.dup
333
+ end
334
+ merge_tags(scanner_event,
335
+ {:method_call =>
336
+ {:name => scanner_event[:ident],
337
+ :receiver => receiver,
338
+ :scope => constant_access_scope}})
339
+ end
340
+
341
+ def local_variable_access(scanner_event)
342
+ existing_variable_id = "#{ local_variable_scope_id(scanner_event[:ident]) }:#{ scanner_event[:ident] }"
343
+ merge_tags(scanner_event,
344
+ :local_variable_access => existing_variable_id )
345
+ end
346
+
347
+ def constant_access_scope
348
+ scope = @scope.dup
349
+ scope.pop if scope.last == :instance # class/instance doesn't matter for constant lookup
350
+ scope
351
+ end
352
+
353
+ def constant_access(scanner_event, name = nil)
354
+ unless name
355
+ name = scanner_event[:const]
356
+ end
357
+ merge_tags(scanner_event,
358
+ :constant_access => { :name => name,
359
+ :scope => constant_access_scope })
360
+ end
361
+
362
+ def local_variable_assignment(scanner_event)
363
+ merge_tags(scanner_event,
364
+ :local_variable_assignment => variable_id(scanner_event))
365
+ end
366
+
367
+ def variable_id(scanner_event)
368
+ "#{ variables_scope_id }:#{ scanner_event[:ident] }"
369
+ end
370
+
371
+ def merge_tags(scanner_event, tags)
372
+ scanner_event[:tags] ||= {}
373
+ scanner_event[:tags].merge!(tags)
374
+ end
375
+
376
+ def scanner_event?(event)
377
+ event.is_a?(Hash)
378
+ end
379
+
380
+ def parser_event?(event)
381
+ event.is_a?(Array) && event[0].is_a?(Symbol)
382
+ end
383
+
384
+ def parser_events?(events)
385
+ events.all? { |event| parser_event?(event) }
386
+ end
387
+
388
+ def is_ident?(event)
389
+ scanner_event?(event) && event[:ident]
390
+ end
391
+ end
392
+ end
@@ -0,0 +1,274 @@
1
+ require "ripper"
2
+ require "reflexive/parse_tree_top_down_walker"
3
+
4
+ module Reflexive
5
+ # Records all scanner events, injects meta-events when scope
6
+ # changes (required to implement constant/method look-up)
7
+ class ReflexiveRipper < Ripper
8
+ attr_accessor :scanner_events
9
+
10
+ META_EVENT = [ :meta_scope ].freeze
11
+
12
+ def initialize(*args)
13
+ super
14
+ @scanner_events = []
15
+ @await_scope_change = false
16
+ @scope = []
17
+ @new_scope = nil
18
+ end
19
+
20
+ def parse
21
+ parse_tree = super
22
+ require 'pp'
23
+ # pp parse_tree
24
+
25
+ ParseTreeTopDownWalker.new(parse_tree).walk
26
+ parse_tree
27
+ end
28
+
29
+ # Returns array in format: [event_value, event_name, tags]
30
+ def self.destruct_scanner_event(scanner_event)
31
+ tags = scanner_event.delete(:tags)
32
+ [ scanner_event.values.first, scanner_event.keys.first, tags ]
33
+ end
34
+
35
+ def self.is_meta_event?(event)
36
+ META_EVENT.include?(event)
37
+ end
38
+
39
+ Ripper::SCANNER_EVENTS.each do |meth|
40
+ define_method("on_#{ meth }") do |*args|
41
+ result = super(*args)
42
+ @scanner_events << { meth.to_sym => args[0] }
43
+ stop_await_scope_change
44
+ @scanner_events[-1]
45
+ end
46
+ end
47
+
48
+ def stop_await_scope_change
49
+ # get here when any of 3 possible constant reference ends,
50
+ # which means we have new scope in effect
51
+ if @await_scope_change
52
+ if @new_scope
53
+ @scope << @new_scope.dup
54
+ inject_current_scope
55
+ @new_scope = nil
56
+ end
57
+ @await_scope_change = false
58
+ end
59
+ end
60
+
61
+ def inject_current_scope
62
+ @scanner_events << { :meta_scope => @scope.dup }
63
+ end
64
+
65
+ PARSER_EVENT_TABLE.each do |event, arity|
66
+ if /_new\z/ =~ event.to_s and arity == 0
67
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
68
+ def on_#{event}
69
+ []
70
+ end
71
+ End
72
+ elsif /_add\z/ =~ event.to_s
73
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
74
+ def on_#{event}(list, item)
75
+ list.push item
76
+ list
77
+ end
78
+ End
79
+ else
80
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
81
+ def on_#{event}(*args)
82
+ [:#{event}, *args]
83
+ end
84
+ End
85
+ end
86
+ end
87
+
88
+ # Two parser events which fire when class/module definition ends
89
+ def on_class(*args)
90
+ @scope.pop
91
+ inject_current_scope
92
+
93
+ [:class, *args]
94
+ end
95
+
96
+ def on_module(*args)
97
+ @scope.pop
98
+ inject_current_scope
99
+
100
+ [:module, *args]
101
+ end
102
+
103
+ def on_parse_error(*args)
104
+ raise SyntaxError, "#{ lineno }: #{ args[0] }"
105
+ end
106
+
107
+ # def on_call(*args)
108
+ # # puts "PARSE: on_call: #{ args.inspect }"
109
+ # if args.size == 3 &&
110
+ # (constant = resolve_constant_ref(args[0])) &&
111
+ # args[1] == :"." &&
112
+ # args[2][1] == :ident
113
+ #
114
+ # meth_ident_token = args[2]
115
+ # index = token_index(meth_ident_token)
116
+ # tags = { :constant => constant, :method => args[2][0] }
117
+ # # wrap the method identifier token in :method_call meta tokens
118
+ # @scanner_events[index .. index] = [
119
+ # # [ :method_call, :open, tags ],
120
+ # meth_ident_token,
121
+ # # [ :method_call, :close ]
122
+ # ]
123
+ # end
124
+ # [:call, *args]
125
+ # end
126
+ #
127
+ # def on_var_ref(*args)
128
+ # # puts "PARSER: var_ref: #{ args.inspect }"
129
+ # if args.size == 1 &&
130
+ # args[0].size == 2 &&
131
+ # args[0][1] == :ident
132
+ # meth_ident_token = args[0]
133
+ # index = token_index(meth_ident_token)
134
+ # tags = { :instance_method => args[0][0] }
135
+ # @scanner_events[index .. index] = [
136
+ # [ :method_call, :open, tags ],
137
+ # meth_ident_token,
138
+ # [ :method_call, :close ]
139
+ # ]
140
+ # # r @scanner_events[177]
141
+ # end
142
+ # [:var_ref, *args]
143
+ # end
144
+
145
+ def token_index(token)
146
+ @scanner_events.index do |t|
147
+ t.object_id == token.object_id
148
+ end
149
+ end
150
+
151
+ def resolve_constant_ref(tokens)
152
+ self.class.resolve_constant_ref(tokens)
153
+ end
154
+
155
+ def self.resolve_constant_ref(tokens)
156
+ if tokens[0] == :var_ref && tokens[1][1] == :const
157
+ # [:var_ref, ["B", :const]]
158
+ tokens[1][0]
159
+ elsif tokens[0] == :top_const_ref && tokens[1][1] == :const
160
+ # [:top_const_ref, ["A", :const]]
161
+ "::" + tokens[1][0]
162
+ # [:const_path_ref, [:top_const_ref, ["A", :const]], ["B", :const]]
163
+ # [:const_path_ref, [:var_ref, ["A", :const]], ["B", :const]]
164
+ elsif tokens[0] == :const_path_ref && (constant = resolve_constant_ref(tokens[1]))
165
+ "#{ constant }::#{ tokens[2][0] }"
166
+ end
167
+ end
168
+
169
+ def on_sp(*args)
170
+ @scanner_events << { :sp => args[0] }
171
+ # ignore space tokens when waiting for scope changes:
172
+ # stop_await_scope_change
173
+
174
+ @scanner_events[-1]
175
+ end
176
+
177
+ # parse.y:
178
+ #
179
+ # | k_class cpath superclass
180
+ # | k_module cpath
181
+ #
182
+ # matches "class"
183
+ def on_kw(kw)
184
+ if %w(class module).include?(kw)
185
+ @await_scope_change = "k_class"
186
+ else
187
+ stop_await_scope_change
188
+ end
189
+
190
+ @scanner_events << { :kw => kw }
191
+
192
+ @scanner_events[-1]
193
+ end
194
+
195
+ # matches "::" in two cases
196
+ # 1. "class ::TopLevel",
197
+ # 2. "class Nested::Class"
198
+ def on_op(token)
199
+ if @await_scope_change == "k_class" &&
200
+ token == "::"
201
+ # tCOLON2, tCOLON3
202
+ # cpath : tCOLON3 cname
203
+ # {
204
+ # /*%%%*/
205
+ # $$ = NEW_COLON3($2);
206
+ # /*%
207
+ # $$ = dispatch1(top_const_ref, $2);
208
+ # %*/
209
+ # }
210
+ @await_scope_change = "k_class tCOLON3"
211
+ elsif @await_scope_change == "k_class primary_value tCOLON2" &&
212
+ token == "::" # tCOLON2, tCOLON3
213
+ @new_scope << "::" # append to the last defined scope
214
+ else
215
+ stop_await_scope_change
216
+ end
217
+
218
+ @scanner_events << { :op => token }
219
+
220
+ @scanner_events[-1]
221
+ end
222
+
223
+ # cname : tIDENTIFIER
224
+ # {
225
+ # /*%%%*/
226
+ # yyerror("class/module name must be CONSTANT");
227
+ # /*%
228
+ # $$ = dispatch1(class_name_error, $1);
229
+ # %*/
230
+ # }
231
+ # | tCONSTANT
232
+ # ;
233
+ #
234
+ # "ClassName"
235
+ # or
236
+ # "ClassName::NestedClassName"
237
+ def on_const(const_name)
238
+ if @await_scope_change == "k_class tCOLON3" # "class ::TopClass"
239
+ @new_scope = "::#{ const_name }"
240
+ elsif @await_scope_change == "k_class" # "class NormalClass"
241
+ # | cname
242
+ # {
243
+ # /*%%%*/
244
+ # $$ = NEW_COLON2(0, $$);
245
+ # /*%
246
+ # $$ = dispatch1(const_ref, $1);
247
+ # %*/
248
+ # }
249
+ @new_scope = "#{ const_name }"
250
+ @await_scope_change = "k_class primary_value tCOLON2" # "class Class::NestedClass"
251
+ elsif @await_scope_change == "k_class primary_value tCOLON2"
252
+ #
253
+ # | primary_value tCOLON2 cname
254
+ # {
255
+ # /*%%%*/
256
+ # $$ = NEW_COLON2($1, $3);
257
+ # /*%
258
+ # $$ = dispatch2(const_path_ref, $1, $3);
259
+ # %*/
260
+ # }
261
+ # ;
262
+ #
263
+ # cname : tIDENTIFIER
264
+ @new_scope << "#{ const_name }" # append to the scope
265
+ else
266
+ stop_await_scope_change
267
+ end
268
+
269
+ @scanner_events << { :const => const_name }
270
+
271
+ @scanner_events[-1]
272
+ end
273
+ end
274
+ end