Class: Reflexive::ReflexiveRipper

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

Overview

Records all scanner events, injects meta-events when scope changes (required to implement constant/method look-up)

Constant Summary collapse

META_EVENT =
[ :meta_scope ].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ ReflexiveRipper

Returns a new instance of ReflexiveRipper.



12
13
14
15
16
17
18
# File 'lib/reflexive/reflexive_ripper.rb', line 12

def initialize(*args)
  super
  @scanner_events = []
  @await_scope_change = false
  @scope = []
  @new_scope = nil
end

Instance Attribute Details

#scanner_eventsObject

Returns the value of attribute scanner_events.



8
9
10
# File 'lib/reflexive/reflexive_ripper.rb', line 8

def scanner_events
  @scanner_events
end

Class Method Details

.destruct_scanner_event(scanner_event) ⇒ Object

Returns array in format: [event_value, event_name, tags]



33
34
35
36
# File 'lib/reflexive/reflexive_ripper.rb', line 33

def self.destruct_scanner_event(scanner_event)
  tags = scanner_event.delete(:tags)
  [ scanner_event.values.first, scanner_event.keys.first, tags ]  
end

.is_meta_event?(event) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
# File 'lib/reflexive/reflexive_ripper.rb', line 38

def self.is_meta_event?(event)
  META_EVENT.include?(event)
end

.resolve_constant_ref(tokens) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/reflexive/reflexive_ripper.rb', line 158

def self.resolve_constant_ref(tokens)
  if tokens[0] == :var_ref && tokens[1][1] == :const
    # [:var_ref, ["B", :const]]
    tokens[1][0]
  elsif tokens[0] == :top_const_ref && tokens[1][1] == :const
    # [:top_const_ref, ["A", :const]]
    "::" + tokens[1][0]
    # [:const_path_ref, [:top_const_ref, ["A", :const]], ["B", :const]]
    # [:const_path_ref, [:var_ref, ["A", :const]], ["B", :const]]
  elsif tokens[0] == :const_path_ref && (constant = resolve_constant_ref(tokens[1]))
    "#{ constant }::#{ tokens[2][0] }"
  end
end

Instance Method Details

#inject_current_scopeObject



64
65
66
# File 'lib/reflexive/reflexive_ripper.rb', line 64

def inject_current_scope
  @scanner_events << { :meta_scope => @scope.dup }
end

#on_class(*args) ⇒ Object

Two parser events which fire when class/module definition ends



92
93
94
95
96
97
# File 'lib/reflexive/reflexive_ripper.rb', line 92

def on_class(*args)
  @scope.pop
  inject_current_scope
  
  [:class, *args]
end

#on_const(const_name) ⇒ Object

cname : tIDENTIFIER

    {
    /*%%%*/
  yyerror("class/module name must be CONSTANT");
    /*%
  $$ = dispatch1(class_name_error, $1);
    %*/
    }
| tCONSTANT
;

“ClassName” or “ClassName::NestedClassName”



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/reflexive/reflexive_ripper.rb', line 240

def on_const(const_name)
  if @await_scope_change == "k_class tCOLON3" # "class ::TopClass"
    @new_scope = "::#{ const_name }"
  elsif @await_scope_change == "k_class" # "class NormalClass"
    #		| cname
    #		    {
    #		    /*%%%*/
    #			$$ = NEW_COLON2(0, $$);
    #		    /*%
    #			$$ = dispatch1(const_ref, $1);
    #		    %*/
    #		    }
    @new_scope = "#{ const_name }"
    @await_scope_change = "k_class primary_value tCOLON2" # "class Class::NestedClass"
  elsif @await_scope_change == "k_class primary_value tCOLON2"
    #
    #		| primary_value tCOLON2 cname
    #		    {
    #		    /*%%%*/
    #			$$ = NEW_COLON2($1, $3);
    #		    /*%
    #			$$ = dispatch2(const_path_ref, $1, $3);
    #		    %*/
    #		    }
    #		;
    #
    # cname		: tIDENTIFIER
    @new_scope << "#{ const_name }" # append to the scope
  else
    stop_await_scope_change
  end

  @scanner_events << { :const => const_name }
  
  @scanner_events[-1]
end

#on_kw(kw) ⇒ Object

parse.y:

| k_class cpath superclass
| k_module cpath

matches “class”



186
187
188
189
190
191
192
193
194
195
196
# File 'lib/reflexive/reflexive_ripper.rb', line 186

def on_kw(kw)
  if %w(class module).include?(kw)
    @await_scope_change = "k_class"
  else
    stop_await_scope_change
  end

  @scanner_events << { :kw => kw }
  
  @scanner_events[-1]
end

#on_module(*args) ⇒ Object



99
100
101
102
103
104
# File 'lib/reflexive/reflexive_ripper.rb', line 99

def on_module(*args)
  @scope.pop
  inject_current_scope

  [:module, *args]
end

#on_op(token) ⇒ Object

matches “::” in two cases

1. "class ::TopLevel",
2. "class Nested::Class"


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/reflexive/reflexive_ripper.rb', line 201

def on_op(token)
  if @await_scope_change == "k_class" &&
          token == "::"
    # tCOLON2, tCOLON3
    #  cpath		: tCOLON3 cname
    #		    {
    #		    /*%%%*/
    #			$$ = NEW_COLON3($2);
    #		    /*%
    #			$$ = dispatch1(top_const_ref, $2);
    #		    %*/
    #		    }
    @await_scope_change = "k_class tCOLON3"
  elsif @await_scope_change == "k_class primary_value tCOLON2" &&
          token == "::" # tCOLON2, tCOLON3
    @new_scope << "::" # append to the last defined scope
  else
    stop_await_scope_change
  end
  
  @scanner_events << { :op => token }
  
  @scanner_events[-1]
end

#on_parse_error(*args) ⇒ Object

Raises:

  • (SyntaxError)


106
107
108
# File 'lib/reflexive/reflexive_ripper.rb', line 106

def on_parse_error(*args)
  raise SyntaxError, "#{ lineno }: #{ args[0] }"
end

#on_sp(*args) ⇒ Object



172
173
174
175
176
177
178
# File 'lib/reflexive/reflexive_ripper.rb', line 172

def on_sp(*args)
  @scanner_events << { :sp => args[0] }
  # ignore space tokens when waiting for scope changes:
  # stop_await_scope_change
  
  @scanner_events[-1]
end

#parseObject



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/reflexive/reflexive_ripper.rb', line 20

def parse
  parse_tree = super
  
  if ENV["DEBUG"]
    require 'pp'
    pp parse_tree
  end

  ParseTreeTopDownWalker.new(parse_tree).walk
  parse_tree
end

#resolve_constant_ref(tokens) ⇒ Object



154
155
156
# File 'lib/reflexive/reflexive_ripper.rb', line 154

def resolve_constant_ref(tokens)
  self.class.resolve_constant_ref(tokens)
end

#stop_await_scope_changeObject



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/reflexive/reflexive_ripper.rb', line 51

def stop_await_scope_change
  # get here when any of 3 possible constant reference ends,
  # which means we have new scope in effect
  if @await_scope_change
    if @new_scope
      @scope << @new_scope.dup
      inject_current_scope
      @new_scope = nil
    end
    @await_scope_change = false
  end
end

#token_index(token) ⇒ Object

def on_call(*args)

  # puts "PARSE: on_call: #{ args.inspect }"
  if args.size == 3 &&
          (constant = resolve_constant_ref(args[0])) &&
          args[1] == :"." &&
          args[2][1] == :ident

    meth_ident_token = args[2]
    index = token_index(meth_ident_token)
    tags = { :constant => constant, :method => args[2][0] }
    # wrap the method identifier token in :method_call meta tokens
    @scanner_events[index .. index] = [
      # [ :method_call, :open, tags ],
      meth_ident_token,
      # [ :method_call, :close ]
    ]
  end
  [:call, *args]
end

def on_var_ref(*args)
  # puts "PARSER: var_ref: #{ args.inspect }"
  if args.size == 1 &&
          args[0].size == 2 &&
          args[0][1] == :ident
    meth_ident_token = args[0]
    index = token_index(meth_ident_token)
    tags = { :instance_method => args[0][0] }
    @scanner_events[index .. index] = [
      [ :method_call, :open, tags ],
      meth_ident_token,
      [ :method_call, :close ]
    ]
    # r @scanner_events[177]
  end
  [:var_ref, *args]
end


148
149
150
151
152
# File 'lib/reflexive/reflexive_ripper.rb', line 148

def token_index(token)
  @scanner_events.index do |t|
     t.object_id == token.object_id
  end
end