OSDN Git Service

ruby-1.9.1-rc1
[splhack/AndroidRuby.git] / lib / ruby-1.9.1-rc1 / ext / ripper / lib / ripper / lexer.rb
1 #
2 # $Id: lexer.rb 13966 2007-11-19 07:10:09Z matz $
3 #
4 # Copyright (c) 2004,2005 Minero Aoki
5 #
6 # This program is free software.
7 # You can distribute and/or modify this program under the Ruby License.
8 # For details of Ruby License, see ruby/COPYING.
9 #
10
11 require 'ripper/core'
12
13 class Ripper
14
15   # Tokenizes Ruby program and returns an Array of String.
16   def Ripper.tokenize(src, filename = '-', lineno = 1)
17     Lexer.new(src, filename, lineno).tokenize
18   end
19
20   # Tokenizes Ruby program and returns an Array of Array,
21   # which is formatted like [[lineno, column], type, token].
22   #
23   #   require 'ripper'
24   #   require 'pp'
25   #
26   #   p Ripper.lex("def m(a) nil end")
27   #     #=> [[[1,  0], :on_kw,     "def"],
28   #          [[1,  3], :on_sp,     " "  ],
29   #          [[1,  4], :on_ident,  "m"  ],
30   #          [[1,  5], :on_lparen, "("  ],
31   #          [[1,  6], :on_ident,  "a"  ],
32   #          [[1,  7], :on_rparen, ")"  ],
33   #          [[1,  8], :on_sp,     " "  ],
34   #          [[1,  9], :on_kw,     "nil"],
35   #          [[1, 12], :on_sp,     " "  ],
36   #          [[1, 13], :on_kw,     "end"]]
37   #
38   def Ripper.lex(src, filename = '-', lineno = 1)
39     Lexer.new(src, filename, lineno).lex
40   end
41
42   class Lexer < ::Ripper   #:nodoc: internal use only
43     def tokenize
44       lex().map {|pos, event, tok| tok }
45     end
46
47     def lex
48       parse().sort_by {|pos, event, tok| pos }
49     end
50
51     def parse
52       @buf = []
53       super
54       @buf
55     end
56
57     private
58
59     SCANNER_EVENTS.each do |event|
60       module_eval(<<-End, __FILE__+'/module_eval', __LINE__ + 1)
61         def on_#{event}(tok)
62           @buf.push [[lineno(), column()], :on_#{event}, tok]
63         end
64       End
65     end
66   end
67
68   # [EXPERIMENTAL]
69   # Parses +src+ and return a string which was matched to +pattern+.
70   # +pattern+ should be described as Regexp.
71   #
72   #   require 'ripper'
73   #
74   #   p Ripper.slice('def m(a) nil end', 'ident')                   #=> "m"
75   #   p Ripper.slice('def m(a) nil end', '[ident lparen rparen]+')  #=> "m(a)"
76   #   p Ripper.slice("<<EOS\nstring\nEOS",
77   #                  'heredoc_beg nl $(tstring_content*) heredoc_end', 1)
78   #       #=> "string\n"
79   #
80   def Ripper.slice(src, pattern, n = 0)
81     if m = token_match(src, pattern)
82     then m.string(n)
83     else nil
84     end
85   end
86
87   def Ripper.token_match(src, pattern)   #:nodoc:
88     TokenPattern.compile(pattern).match(src)
89   end
90
91   class TokenPattern   #:nodoc:
92
93     class Error < ::StandardError; end
94     class CompileError < Error; end
95     class MatchError < Error; end
96
97     class << self
98       alias compile new
99     end
100
101     def initialize(pattern)
102       @source = pattern
103       @re = compile(pattern)
104     end
105
106     def match(str)
107       match_list(::Ripper.lex(str))
108     end
109
110     def match_list(tokens)
111       if m = @re.match(map_tokens(tokens))
112       then MatchData.new(tokens, m)
113       else nil
114       end
115     end
116
117     private
118
119     def compile(pattern)
120       if m = /[^\w\s$()\[\]{}?*+\.]/.match(pattern)
121         raise CompileError, "invalid char in pattern: #{m[0].inspect}"
122       end
123       buf = ''
124       pattern.scan(/(?:\w+|\$\(|[()\[\]\{\}?*+\.]+)/) do |tok|
125         case tok
126         when /\w/
127           buf.concat map_token(tok)
128         when '$('
129           buf.concat '('
130         when '('
131           buf.concat '(?:'
132         when /[?*\[\])\.]/
133           buf.concat tok
134         else
135           raise 'must not happen'
136         end
137       end
138       Regexp.compile(buf)
139     rescue RegexpError => err
140       raise CompileError, err.message
141     end
142
143     def map_tokens(tokens)
144       tokens.map {|pos,type,str| map_token(type.to_s.sub(/\Aon_/,'')) }.join
145     end
146
147     MAP = {}
148     seed = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
149     SCANNER_EVENT_TABLE.each do |ev, |
150       raise CompileError, "[RIPPER FATAL] too many system token" if seed.empty?
151       MAP[ev.to_s.sub(/\Aon_/,'')] = seed.shift
152     end
153
154     def map_token(tok)
155       MAP[tok]  or raise CompileError, "unknown token: #{tok}"
156     end
157
158     class MatchData
159       def initialize(tokens, match)
160         @tokens = tokens
161         @match = match
162       end
163
164       def string(n = 0)
165         return nil unless @match
166         match(n).join
167       end
168
169       private
170
171       def match(n = 0)
172         return [] unless @match
173         @tokens[@match.begin(n)...@match.end(n)].map {|pos,type,str| str }
174       end
175     end
176   
177   end
178
179 end