Class: RMTools::CodeReader

Inherits:
Object show all
Defined in:
lib/rmtools/dev/code_reader.rb

Defined Under Namespace

Modules: Defaults

Constant Summary collapse

Closers =
{'<' => '>', '{' => '}', '[' => ']', '(' => ')'}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(instructions_module = Defaults) ⇒ CodeReader

Returns a new instance of CodeReader.



236
237
238
239
240
241
242
243
244
# File 'lib/rmtools/dev/code_reader.rb', line 236

def initialize(instructions_module=Defaults)
  @MethodCache = {'Object' => {}}
  @ReadPaths = {}
  #extend instructions_module
  self.class.__send__(:include, instructions_module)
  @Instructions = init_instructions
  @MainParseRE = MainParseRE
  add_method_seeker('.get_opts') {|s, args| $log <= args}
end

Instance Attribute Details

#MethodCacheObject (readonly)

Returns the value of attribute MethodCache.



6
7
8
# File 'lib/rmtools/dev/code_reader.rb', line 6

def MethodCache
  @MethodCache
end

#stackObject (readonly)

Returns the value of attribute stack.



6
7
8
# File 'lib/rmtools/dev/code_reader.rb', line 6

def stack
  @stack
end

Instance Method Details

#add_instruction(re, &callback) ⇒ Object



246
247
248
249
# File 'lib/rmtools/dev/code_reader.rb', line 246

def add_instruction(re, &callback)
  @MainParseRE |= re 
  @Instructions << [re, callback]
end

#add_method_seeker(name, *args, &callback) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/rmtools/dev/code_reader.rb', line 251

def add_method_seeker(name, *args, &callback)
  if name.ord == ?.
    pattern = /\w\.(#{name[1..-1]})([(`"' ] *|:['"]?)/
  else
    pattern = /(?:^|[\s#{Leftop}])(#{name})([(`"' ] *|:['"]?)/
  end
  $log <= pattern
  add_instruction(pattern) {|s, m|
    $log << m
    yield s, arguments(s, m)
  }
end

#arguments(s, m) ⇒ Object

Parser methods



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/rmtools/dev/code_reader.rb', line 266

def arguments(s, m)
  $panic=true
  debug(s)    
  $panic=false
  $log<<m
  if m[2] =~ /['"`:]/
    s.pos -= 1
    s.matched_size -= 1
    if m[2] =~ /:['"]/
      s.pos -= 1
      s.matched_size -= 1
    end
  end
  arg_list = [m[1]]
  parens = m[2]=='('
  counts = {'}' => 0, ']' => 0, ')' => 0}
  eol = catch(:EOL) {s.each(ArgumentsParseRE, [
      [/^[{\[(]$/, lambda {|s, m| $log<<m;arg_list << m[0];counts[Closers[m[0]]] += 1}],
      [/^[}\])]$/, lambda {|s, m| 
      $log<<m;
        if counts[m[0]] > 0
          counts[m[0]] -= 1
        else
          curl_close if m[0]=='}'
          throw :EOL, :arguments
        end
      }],
      [/[#\n;]| end\b/, lambda {|s, m| $log<<m;
        s.scan_until(/\n/) if m[0] == '#'
        throw :EOL
      }],
      [StringRE, lambda {|s, m| $log<<m;
        str = [s.pos-1, string(s, m)]
        str[1] ? arg_list << s.string[str[0]...str[1]] : arg_list << s.string[str[0]-1..str[0]]
      }],
      [RERE, lambda {|s, m| $log<<m;
        str = [s.pos-1, string(s, m)]
        arg_list << s.string[str[0]...str[1]]
      }],
      [/^ *(?:( [:?]\s|=[>~]|[<!>]=|[+\-\|*%])|,)\s*$/, lambda {|s, m| $log<<m;arg_list << m[1] if m[1]}],
      [/^(\d[\d.]+|:[@$_a-z]\w*|[@$]\w+) *$/, lambda {|s, m| $log<<m;arg_list << m[1]}],
      [/^([@$_a-z][:.\w!?]+)([(`"' ] *|:['"]?)?$/, lambda {|s, m| $log<<m;
        str_beg = s.pos-s.matched_size
        a, eol = arguments(s, m)
        if a
          $log << [a, s.string[str_beg...s.pos]]
          arg_list << s.string[str_beg...s.pos]
        end
        if eol == :arguments
          throw :EOL
        end
      }]
  ])}
  $log << [arg_list, eol]
  [arg_list, eol]
end

#attr_accessors(s, m) ⇒ Object



452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/rmtools/dev/code_reader.rb', line 452

def attr_accessors(s, m)
  _stack = clean_stack
  if _stack[-1][0] == :class
    prefix = _stack.lasts*'::'
    attrs = (m[3]/',').map {|attr| (m[1] ? '.' : '#')+attr.strip[1..-1]}
    if m[2].in %w(reader accessor)
      attrs.each {|attr| (@MethodCache[prefix][attr] ||= []) << "def #{'self.' if m[1]}#{attr}\n  #{'@' if m[1]}@#{attr}\nend"}
    end
    if m[2].in %w(writer accessor)
      attrs.each {|attr| (@MethodCache[prefix][attr] ||= []) << "def #{'self.' if m[1]}#{attr}=value\n  #{'@' if m[1]}@#{attr} = value\nend"}
    end
  end
end

#clean_stack(no_def = false) ⇒ Object



489
490
491
# File 'lib/rmtools/dev/code_reader.rb', line 489

def clean_stack(no_def=false)
  @stack.select {|e| e[0] != :beginner and !no_def || e[0] != :def}
end

#code_of(path, name = nil, all = false) ⇒ Object



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/rmtools/dev/code_reader.rb', line 537

def code_of(path, name=nil, all=false)
  if name.in [true, :all]
    all = true
  end
  if path.is String
    prefix, name = path.sharp_split(/[.#]/, 2)
    raise "If first parameter is a string then it must be of Module{#|.}method form" if !name
  elsif Module === path
    prefix = path.name
    name = ".#{name}"
  else
    prefix = path.class.name
    name = "##{name}"
  end
  if SCRIPT_LINES__.size == @ReadPaths.size and (SCRIPT_LINES__.keys - @ReadPaths.keys).empty?
    puts "nothing was found for #{prefix}#{name}"
  else
    if !(@MethodCache[prefix]||{})[name]
      puts "looking up script lines, please wait..."
      SCRIPT_LINES__.each_key {|k| parse_file k
                                                  break if (@MethodCache[prefix]||{})[name]
      }
    end
    if !(@MethodCache[prefix]||{})[name]
      print "nothing was found for #{prefix}#{name}"
      name = name.tr('#.', '.#')
      if (@MethodCache[prefix]||{})[name]
        puts ", but found for #{name}:"
        print_lines prefix, name, all
      else puts
      end
    else
      puts "code for #{prefix}#{name}:"
      print_lines prefix, name, all
    end
  end
end

#curl_closeObject



427
428
429
430
431
432
433
# File 'lib/rmtools/dev/code_reader.rb', line 427

def curl_close(*)
  if @curls_count == 0
    @stack.pop
  else
    @curls_count -= 1
  end
end

#end!(s) ⇒ Object



435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/rmtools/dev/code_reader.rb', line 435

def end!(s, *)
  debug(s)
  if s.+ !~ /[?!(]/
    exit = @stack.pop
    case exit[0]
      when :def
        prefix, name = exit[1].sharp_split(/[.@#]/, 2)
        if !name
          prefix, name = 'Object', prefix
        end
        if @MethodCache[prefix]
          (@MethodCache[prefix][name] ||= []) << (@path.inline ? [@path, exit[2]...s.pos] : s.string[exit[2]...s.pos])
        end
    end
  end
end

#fix_module_name(current, name) ⇒ Object



505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/rmtools/dev/code_reader.rb', line 505

def fix_module_name(current, name)
  if name =~ /^::/ or current == ''
    current+name
  elsif name == current or name == 'self'
    current
  elsif name !~ /^[A-Z]/
    current+'#'+name
  else 
    path = current+'::'+name
    if @MethodCache[path]
      path
    else 
      @MethodCache[name] ||= {}
      name
    end
  end
end

#get_lines(path) ⇒ Object



523
524
525
# File 'lib/rmtools/dev/code_reader.rb', line 523

def get_lines(path)
  SCRIPT_LINES__.to_a.select {|d, f| d[path]}.to_a.lasts
end

#heredoc(s, m) ⇒ Object



413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/rmtools/dev/code_reader.rb', line 413

def heredoc(s, m)
  heredoc_list = [m[1..2]]
  catch(:EOL) {s.each(HeredocParseRE, [
      [/[#\n]/, lambda {|s, m| 
        s.scan_until(/\n/) if m[0] == '#'
        heredoc_list.each {|opener| string(s, [nil]*4+opener)}
        throw :EOL
      }],
      [HeredocRE, lambda {|s, m| heredoc_list << m[1..2]}],
      [StringRE, method(:string)],
      [RERE, method(:string)]
  ])}
end

#inherit!(descendant, ancestor) ⇒ Object



493
494
495
496
497
# File 'lib/rmtools/dev/code_reader.rb', line 493

def inherit!(descendant, ancestor)
  @MethodCache[descendant].reverse_merge((
    @MethodCache[fix_module_name(descendant, ancestor)] ||= {}
  ).map_values {|defs| defs.dup})
end

#inherit_singletons!(descendant, ancestor) ⇒ Object



499
500
501
502
503
# File 'lib/rmtools/dev/code_reader.rb', line 499

def inherit_singletons!(descendant, ancestor)
  (@MethodCache[fix_module_name(descendant, ancestor)] ||= {}).each {|name, defs|
    @MethodCache[descendant][name.sub('#', '.')] = defs.dup if name.ord == ?#
  }
end

#init_instructionsObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/rmtools/dev/code_reader.rb', line 154

def init_instructions
  [
      [/^\#/, lambda {|s, m| s.scan_until(/\n/)}],
      [StringRE, method(:string)],
      
      [/^\{$/, lambda {|s, m| 
        debug(s)
        $log.debug @curls_count
        @curls_count += 1
      }],
      
      [/^\}$/, method(:curl_close)],
      [Ord],
      
      [BlockOpen, lambda {|s, m| 
        debug(s)
        $log.debug @curls_count
        @stack << [:block]
      }],
      
      [ModDef, method(:Module)],
      [ClassDef, method(:Class)],
      [MethodDef, method(:Method)],
      [AliasDef, method(:Alias)],
      [Symbol],
      [RERE, method(:string)],
      [HeredocRE, method(:heredoc)],
      
      [/(^|\n)=begin/, lambda {|s, m| s.scan_until(/\n=end\s*\n/m)}],
      
      [Attrs, lambda {|s, m|
        attr_accessors s, m
        if s.matched =~ / end$/
          end!(s)
        elsif s.matched =~ /[^\?]\}$/
          curl_close
        end
      }],
      
      [Include, lambda {|s, m|
        _stack = clean_stack
        if _stack.empty?
          if m[1] == 'include'
            inherit! 'Object', m[2] 
          else
            inherit_singletons! 'Object', m[2] 
          end
        elsif !_stack[-1][0].in([:def, :block]) and m[2] =~ /^[A-Z]/
          if m[1] == 'include'
            inherit! _stack.lasts*'::', m[2] 
          else
            inherit_singletons! _stack.lasts*'::', m[2] 
          end
        end
      }],
      
      [AliasMethod, lambda {|s, m|
        _stack = clean_stack
        if _stack[-1][0] == :class
          new, old = m[1..2]
          prefix = _stack.lasts*'::'
          @MethodCache[prefix][new] = @MethodCache[prefix][old] || "def #{new}(*args)\n  #{old}(*args)\nend"
        end
      }],
      
      [Beginners, lambda {|s, m|
        debug(s)
        $log.debug [m, s.last, s.string[s.last-1,1].to_s]
        if (m[2] and s.last != 0 and m[2].tr(' \t', '').empty? and !(s.string[s.last-1,1].to_s)[/[\n;({\[]/])
        else
          if m[3] == 'if' and @stack.empty? and s.check_until(EOF) and s.matched != "\n"
            throw :EOF
          end
          @stack << [m[5] ? :block : :beginner]
        end
      }],
      
      [/(^|[\s;])end.?/, method(:end!)],
      [/(^|[\s;])__END__/, lambda {|s, m| throw :EOF}]
  ].dup
end

#parse_file(path) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/rmtools/dev/code_reader.rb', line 466

def parse_file(path)
  @stack = []
  @path = path
  
  if path.inline
    return if @ReadPaths[path]
    lines = get_lines(path)[0]
    @ReadPaths[path] = true
  else
    lines = path.sharp_split(/\n/)
  end
  if RUBY_VERSION > '1.9'
    ss = StringScanner.new lines.join.force_encoding('UTF-8')
  else
    ss = StringScanner.new lines.join
  end
  
  @curls_count = 0
  catch(:EOF) { ss.each @MainParseRE, @Instructions }
  raise "Can't parse: #{@stack.inspect}, #{ss.string[ss.last..ss.pos].inspect}\nfile:#{path}" if @stack.any?
  ss
end


527
528
529
530
531
532
533
534
535
# File 'lib/rmtools/dev/code_reader.rb', line 527

def print_lines(prefix, name, all)
  map = lambda {|lines|
      if lines.is Array
        lines = SCRIPT_LINES__[lines[0]].join[lines[1]]
      end
      lines           }
  methods = all ? @MethodCache[prefix][name].map(&map) : map.call(@MethodCache[prefix][name].last)
  puts methods
end

#string(s, m) ⇒ Object



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
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
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/rmtools/dev/code_reader.rb', line 323

def string(s, m)
  debug(s)
  return if m[1] and s.- == '$'
  opener = m[1] || m[3] || m[5]
  $log.log {"entering #{opener}-quotes, matched as '#{Painter.g s.matched}' at #{s.string[0..s.pos].count("\n")+1}"}
  if opener == m[5]
    closer = opener = m[5].tr('`\'"', '')
    quote_re = /\\|\n#{'\s*' if m[4]}#{closer}/
  else
    closer = Closers[opener] || opener
    quote_re = /\\|#{Regexp.escape closer}/
  end
  openers_cnt = 1
  inner_curls_count = 0
  backslash = false
  quote_re |= /#\{/ if (m[5] and m[5].ord != ?') or closer =~ /[\/"`]/ or (m[2] =~ /[xrQW]/ or m[3])
  instructions = [
    [Ord],
    [/\s*#{Regexp.escape closer}$/, lambda {|s, m|
      if backslash
        backslash = false
        break if s.- == '\\' and m[0] == closer
      end
      if (openers_cnt -= 1) == 0
        $log.log {"exiting through #{closer}-quotes at #{s.string[0...s.pos].count("\n")+1}"}
        throw :EOS 
    else
        $log.log 'decreasing openers count'
      end
    }],
    [/\\/, lambda {|s, m|
      prev = s.-
      backslash = true
      if prev == '\\'
        i = 2
        while prev == '\\'
          prev = s.prev_in i
          i += 1
          backslash = !backslash
        end
      end
    $log.log {"#{!backslash ? 'closed' : 'found'} \\ in #{opener}-quotes at #{s.string[0, s.pos].count("\n")+1}"}
    }],
    [/\#\{/, lambda {|s, m| 
      if backslash
        backslash = false
        if s.- == '\\'
          openers_cnt += 1 if closer == '}'
          break
        end
      end
    $log.log "entering curls"
      inner_curls_count += 1
      catch(:inner_out) {s.each(StringParseRE, [
          [/^\#$/, lambda {|s, m| 
          $log.log 'reading comment'
         s.scan_until(/\n/)}],
          [/^\{$/, lambda {|s, m| 
          $log.log "increasing curls count"
          inner_curls_count += 1}],
          [/^\}$/, lambda {|s, m| 
          if (inner_curls_count -= 1) == 0
            $log.log "exiting curls"
            throw :inner_out
          else
            $log.log "decreasing curls count"
          end}],
          [HeredocRE, method(:heredoc)],
          [StringRE, method(:string)],
          [RERE, method(:string)]
      ])}
    }]
  ]
  if closer != opener
    quote_re |= /#{Regexp.escape opener}/
    instructions << [/#{Regexp.escape opener}$/, lambda {|s, m|
      if backslash
        backslash = false
        break if s.- == '\\'
      end
    $log.log 'increasing openers count'
      openers_cnt += 1
    }]
    $log.debug [quote_re,instructions]
  end
    
  catch(:EOS) {s.each(quote_re, instructions)}
  s.pos
end