coderay 0.8.357 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/lib/README +4 -3
  2. data/lib/coderay.rb +2 -1
  3. data/lib/coderay/encoder.rb +41 -15
  4. data/lib/coderay/encoders/_map.rb +3 -1
  5. data/lib/coderay/encoders/comment_filter.rb +43 -0
  6. data/lib/coderay/encoders/div.rb +2 -3
  7. data/lib/coderay/encoders/filter.rb +75 -0
  8. data/lib/coderay/encoders/html.rb +20 -3
  9. data/lib/coderay/encoders/html/css.rb +1 -1
  10. data/lib/coderay/encoders/html/numerization.rb +11 -2
  11. data/lib/coderay/encoders/html/output.rb +10 -1
  12. data/lib/coderay/encoders/json.rb +69 -0
  13. data/lib/coderay/encoders/lines_of_code.rb +90 -0
  14. data/lib/coderay/encoders/page.rb +1 -2
  15. data/lib/coderay/encoders/span.rb +2 -3
  16. data/lib/coderay/encoders/term.rb +137 -0
  17. data/lib/coderay/encoders/text.rb +4 -4
  18. data/lib/coderay/encoders/token_class_filter.rb +84 -0
  19. data/lib/coderay/encoders/xml.rb +1 -0
  20. data/lib/coderay/for_redcloth.rb +9 -4
  21. data/lib/coderay/helpers/file_type.rb +54 -15
  22. data/lib/coderay/helpers/plugin.rb +21 -3
  23. data/lib/coderay/helpers/word_list.rb +19 -4
  24. data/lib/coderay/scanner.rb +33 -2
  25. data/lib/coderay/scanners/_map.rb +10 -4
  26. data/lib/coderay/scanners/c.rb +61 -23
  27. data/lib/coderay/scanners/cpp.rb +228 -0
  28. data/lib/coderay/scanners/css.rb +9 -1
  29. data/lib/coderay/scanners/debug.rb +1 -0
  30. data/lib/coderay/scanners/delphi.rb +2 -2
  31. data/lib/coderay/scanners/diff.rb +1 -0
  32. data/lib/coderay/scanners/groovy.rb +263 -0
  33. data/lib/coderay/scanners/html.rb +9 -2
  34. data/lib/coderay/scanners/java.rb +18 -14
  35. data/lib/coderay/scanners/java_script.rb +42 -13
  36. data/lib/coderay/scanners/json.rb +7 -1
  37. data/lib/coderay/scanners/nitro_xhtml.rb +4 -0
  38. data/lib/coderay/scanners/php.rb +526 -0
  39. data/lib/coderay/scanners/plaintext.rb +4 -1
  40. data/lib/coderay/scanners/python.rb +285 -0
  41. data/lib/coderay/scanners/rhtml.rb +3 -0
  42. data/lib/coderay/scanners/ruby.rb +29 -11
  43. data/lib/coderay/scanners/ruby/patterns.rb +26 -20
  44. data/lib/coderay/scanners/scheme.rb +3 -0
  45. data/lib/coderay/scanners/sql.rb +162 -0
  46. data/lib/coderay/scanners/xml.rb +1 -1
  47. data/lib/coderay/scanners/yaml.rb +4 -1
  48. data/lib/coderay/styles/cycnus.rb +11 -7
  49. data/lib/coderay/token_classes.rb +4 -1
  50. data/lib/coderay/tokens.rb +50 -46
  51. metadata +14 -4
  52. data/lib/coderay/encoders/tokens.rb +0 -44
@@ -176,7 +176,7 @@ module PluginHost
176
176
  def inspect
177
177
  map = plugin_hash.dup
178
178
  map.each do |id, plugin|
179
- map[id] = plugin.to_s[/(?>[\w_]+)$/]
179
+ map[id] = plugin.to_s[/(?>\w+)$/]
180
180
  end
181
181
  "#{name}[#{host_id}]#{map.inspect}"
182
182
  end
@@ -280,6 +280,16 @@ module Plugin
280
280
  def register_for *ids
281
281
  plugin_host.register self, *ids
282
282
  end
283
+
284
+ # Returns the title of the plugin, or sets it to the
285
+ # optional argument +title+.
286
+ def title title = nil
287
+ if title
288
+ @title = title.to_s
289
+ else
290
+ @title ||= name[/([^:]+)$/, 1]
291
+ end
292
+ end
283
293
 
284
294
  # The host for this Plugin class.
285
295
  def plugin_host host = nil
@@ -301,15 +311,23 @@ module Plugin
301
311
  #
302
312
  # The above example loads the file myplugin/my_helper.rb relative to the
303
313
  # file in which MyPlugin was defined.
314
+ #
315
+ # You can also load a helper from a different plugin:
316
+ #
317
+ # helper 'other_plugin/helper_name'
304
318
  def helper *helpers
305
319
  for helper in helpers
306
- self::PLUGIN_HOST.require_helper plugin_id, helper.to_s
320
+ if helper.is_a?(String) && helper[/\//]
321
+ self::PLUGIN_HOST.require_helper $`, $'
322
+ else
323
+ self::PLUGIN_HOST.require_helper plugin_id, helper.to_s
324
+ end
307
325
  end
308
326
  end
309
327
 
310
328
  # Returns the pulgin id used by the engine.
311
329
  def plugin_id
312
- name[/[\w_]+$/].downcase
330
+ name[/\w+$/].downcase
313
331
  end
314
332
 
315
333
  end
@@ -98,15 +98,22 @@ class CaseIgnoringWordList < WordList
98
98
  # Creates a new case-insensitive WordList with +default+ as default value.
99
99
  #
100
100
  # You can activate caching to store the results for every [] request.
101
+ # This speeds up subsequent lookups for the same word, but also
102
+ # uses memory.
101
103
  def initialize default = false, caching = false
102
104
  if caching
103
105
  super(default, false) do |h, k|
104
106
  h[k] = h.fetch k.downcase, default
105
107
  end
106
108
  else
107
- def self.[] key # :nodoc:
108
- super(key.downcase)
109
- end
109
+ super(default, false)
110
+ extend Uncached
111
+ end
112
+ end
113
+
114
+ module Uncached # :nodoc:
115
+ def [] key
116
+ super(key.downcase)
110
117
  end
111
118
  end
112
119
 
@@ -120,4 +127,12 @@ class CaseIgnoringWordList < WordList
120
127
 
121
128
  end
122
129
 
123
- end
130
+ end
131
+
132
+ __END__
133
+ # check memory consumption
134
+ END {
135
+ ObjectSpace.each_object(CodeRay::CaseIgnoringWordList) do |wl|
136
+ p wl.inject(0) { |memo, key, value| memo + key.size + 24 }
137
+ end
138
+ }
@@ -43,6 +43,7 @@ module CodeRay
43
43
  # You can also use +map+, +any?+, +find+ and even +sort_by+,
44
44
  # if you want.
45
45
  class Scanner < StringScanner
46
+
46
47
  extend Plugin
47
48
  plugin_host Scanners
48
49
 
@@ -55,6 +56,8 @@ module CodeRay
55
56
  #
56
57
  # Define @default_options for subclasses.
57
58
  DEFAULT_OPTIONS = { :stream => false }
59
+
60
+ KINDS_NOT_LOC = [:comment, :doctype]
58
61
 
59
62
  class << self
60
63
 
@@ -65,7 +68,14 @@ module CodeRay
65
68
 
66
69
  def normify code
67
70
  code = code.to_s
68
- code.force_encoding 'binary' if code.respond_to? :force_encoding
71
+ if code.respond_to? :force_encoding
72
+ begin
73
+ code.force_encoding 'utf-8'
74
+ code[/\z/] # raises an ArgumentError when code contains a non-UTF-8 char
75
+ rescue ArgumentError
76
+ code.force_encoding 'binary'
77
+ end
78
+ end
69
79
  code.to_unix
70
80
  end
71
81
 
@@ -104,9 +114,10 @@ module CodeRay
104
114
  #
105
115
  # Else, a Tokens object is used.
106
116
  def initialize code='', options = {}, &block
107
- @options = self.class::DEFAULT_OPTIONS.merge options
108
117
  raise "I am only the basic Scanner class. I can't scan "\
109
118
  "anything. :( Use my subclasses." if self.class == Scanner
119
+
120
+ @options = self.class::DEFAULT_OPTIONS.merge options
110
121
 
111
122
  super Scanner.normify(code)
112
123
 
@@ -121,6 +132,7 @@ module CodeRay
121
132
  "but :stream is #{@options[:stream]}" if block_given?
122
133
  @tokens ||= Tokens.new
123
134
  end
135
+ @tokens.scanner = self
124
136
 
125
137
  setup
126
138
  end
@@ -140,6 +152,11 @@ module CodeRay
140
152
  alias code string
141
153
  alias code= string=
142
154
 
155
+ # Returns the Plugin ID for this scanner.
156
+ def lang
157
+ self.class.plugin_id
158
+ end
159
+
143
160
  # Scans the code and returns all tokens in a Tokens object.
144
161
  def tokenize new_string=nil, options = {}
145
162
  options = @options.merge(options)
@@ -181,8 +198,21 @@ module CodeRay
181
198
 
182
199
  def column pos = self.pos
183
200
  return 0 if pos <= 0
201
+ string = string()
202
+ if string.respond_to?(:bytesize) && (defined?(@bin_string) || string.bytesize != string.size)
203
+ @bin_string ||= string.dup.force_encoding(:binary)
204
+ string = @bin_string
205
+ end
184
206
  pos - (string.rindex(?\n, pos) || 0)
185
207
  end
208
+
209
+ def marshal_dump
210
+ @options
211
+ end
212
+
213
+ def marshal_load options
214
+ @options = options
215
+ end
186
216
 
187
217
  protected
188
218
 
@@ -207,6 +237,7 @@ module CodeRay
207
237
  def reset_instance
208
238
  @tokens.clear unless @options[:keep_tokens]
209
239
  @cached_tokens = nil
240
+ @bin_string = nil if defined? @bin_string
210
241
  end
211
242
 
212
243
  # Scanner error with additional status information
@@ -1,14 +1,20 @@
1
1
  module CodeRay
2
2
  module Scanners
3
3
 
4
- map :cpp => :c,
5
- :plain => :plaintext,
6
- :pascal => :delphi,
4
+ map \
5
+ :h => :c,
6
+ :cplusplus => :cpp,
7
+ :'c++' => :cpp,
8
+ :ecma => :java_script,
9
+ :ecmascript => :java_script,
10
+ :ecma_script => :java_script,
7
11
  :irb => :ruby,
8
- :xhtml => :nitro_xhtml,
9
12
  :javascript => :java_script,
10
13
  :js => :java_script,
11
14
  :nitro => :nitro_xhtml,
15
+ :pascal => :delphi,
16
+ :plain => :plaintext,
17
+ :xhtml => :html,
12
18
  :yml => :yaml
13
19
 
14
20
  default :plain
@@ -3,42 +3,50 @@ module Scanners
3
3
 
4
4
  class C < Scanner
5
5
 
6
- register_for :c
7
-
8
6
  include Streamable
7
+
8
+ register_for :c
9
+ file_extension 'c'
9
10
 
10
11
  RESERVED_WORDS = [
11
- 'asm', 'break', 'case', 'continue', 'default', 'do', 'else',
12
- 'for', 'goto', 'if', 'return', 'switch', 'while',
13
- 'struct', 'union', 'enum', 'typedef',
14
- 'static', 'register', 'auto', 'extern',
15
- 'sizeof',
16
- 'volatile', 'const', # C89
17
- 'inline', 'restrict', # C99
12
+ 'asm', 'break', 'case', 'continue', 'default', 'do',
13
+ 'else', 'enum', 'for', 'goto', 'if', 'return',
14
+ 'sizeof', 'struct', 'switch', 'typedef', 'union', 'while',
15
+ 'restrict', # added in C99
18
16
  ]
19
17
 
20
18
  PREDEFINED_TYPES = [
21
- 'int', 'long', 'short', 'char', 'void',
19
+ 'int', 'long', 'short', 'char',
22
20
  'signed', 'unsigned', 'float', 'double',
23
- 'bool', 'complex', # C99
21
+ 'bool', 'complex', # added in C99
24
22
  ]
25
23
 
26
24
  PREDEFINED_CONSTANTS = [
27
25
  'EOF', 'NULL',
28
- 'true', 'false', # C99
26
+ 'true', 'false', # added in C99
27
+ ]
28
+ DIRECTIVES = [
29
+ 'auto', 'extern', 'register', 'static', 'void',
30
+ 'const', 'volatile', # added in C89
31
+ 'inline', # added in C99
29
32
  ]
30
33
 
31
34
  IDENT_KIND = WordList.new(:ident).
32
35
  add(RESERVED_WORDS, :reserved).
33
36
  add(PREDEFINED_TYPES, :pre_type).
37
+ add(DIRECTIVES, :directive).
34
38
  add(PREDEFINED_CONSTANTS, :pre_constant)
35
39
 
36
- ESCAPE = / [rbfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x
40
+ ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x
37
41
  UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x
38
42
 
39
43
  def scan_tokens tokens, options
40
44
 
41
45
  state = :initial
46
+ label_expected = true
47
+ case_expected = false
48
+ label_expected_before_preproc_line = nil
49
+ in_preproc_line = false
42
50
 
43
51
  until eos?
44
52
 
@@ -49,8 +57,13 @@ module Scanners
49
57
 
50
58
  when :initial
51
59
 
52
- if scan(/ \s+ | \\\n /x)
53
- kind = :space
60
+ if match = scan(/ \s+ | \\\n /x)
61
+ if in_preproc_line && match != "\\\n" && match.index(?\n)
62
+ in_preproc_line = false
63
+ label_expected = label_expected_before_preproc_line
64
+ end
65
+ tokens << [match, :space]
66
+ next
54
67
 
55
68
  elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx)
56
69
  kind = :comment
@@ -59,16 +72,32 @@ module Scanners
59
72
  match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos?
60
73
  kind = :comment
61
74
 
62
- elsif scan(/ [-+*\/=<>?:;,!&^|()\[\]{}~%]+ | \.(?!\d) /x)
75
+ elsif match = scan(/ [-+*=<>?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x)
76
+ label_expected = match =~ /[;\{\}]/
77
+ if case_expected
78
+ label_expected = true if match == ':'
79
+ case_expected = false
80
+ end
63
81
  kind = :operator
64
82
 
65
83
  elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x)
66
84
  kind = IDENT_KIND[match]
67
- if kind == :ident and check(/:(?!:)/)
68
- match << scan(/:/)
85
+ if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/)
69
86
  kind = :label
87
+ match << matched
88
+ else
89
+ label_expected = false
90
+ if kind == :reserved
91
+ case match
92
+ when 'case', 'default'
93
+ case_expected = true
94
+ end
95
+ end
70
96
  end
71
97
 
98
+ elsif scan(/\$/)
99
+ kind = :ident
100
+
72
101
  elsif match = scan(/L?"/)
73
102
  tokens << [:open, :string]
74
103
  if match[0] == ?L
@@ -78,23 +107,30 @@ module Scanners
78
107
  state = :string
79
108
  kind = :delimiter
80
109
 
81
- elsif scan(/#\s*(\w*)/)
82
- kind = :preprocessor # FIXME multiline preprocs
110
+ elsif scan(/#[ \t]*(\w*)/)
111
+ kind = :preprocessor
112
+ in_preproc_line = true
113
+ label_expected_before_preproc_line = label_expected
83
114
  state = :include_expected if self[1] == 'include'
84
115
 
85
116
  elsif scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox)
117
+ label_expected = false
86
118
  kind = :char
87
119
 
88
120
  elsif scan(/0[xX][0-9A-Fa-f]+/)
121
+ label_expected = false
89
122
  kind = :hex
90
123
 
91
124
  elsif scan(/(?:0[0-7]+)(?![89.eEfF])/)
125
+ label_expected = false
92
126
  kind = :oct
93
127
 
94
- elsif scan(/(?:\d+)(?![.eEfF])/)
128
+ elsif scan(/(?:\d+)(?![.eEfF])L?L?/)
129
+ label_expected = false
95
130
  kind = :integer
96
131
 
97
132
  elsif scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/)
133
+ label_expected = false
98
134
  kind = :float
99
135
 
100
136
  else
@@ -110,6 +146,7 @@ module Scanners
110
146
  tokens << ['"', :delimiter]
111
147
  tokens << [:close, :string]
112
148
  state = :initial
149
+ label_expected = false
113
150
  next
114
151
  elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)
115
152
  kind = :char
@@ -117,6 +154,7 @@ module Scanners
117
154
  tokens << [:close, :string]
118
155
  kind = :error
119
156
  state = :initial
157
+ label_expected = false
120
158
  else
121
159
  raise_inspect "else case \" reached; %p not handled." % peek(1), tokens
122
160
  end
@@ -131,8 +169,8 @@ module Scanners
131
169
  state = :initial if match.index ?\n
132
170
 
133
171
  else
134
- getch
135
- kind = :error
172
+ state = :initial
173
+ next
136
174
 
137
175
  end
138
176
 
@@ -0,0 +1,228 @@
1
+ module CodeRay
2
+ module Scanners
3
+
4
+ class CPlusPlus < Scanner
5
+
6
+ include Streamable
7
+
8
+ register_for :cpp
9
+ file_extension 'cpp'
10
+ title 'C++'
11
+
12
+ # http://www.cppreference.com/wiki/keywords/start
13
+ RESERVED_WORDS = [
14
+ 'and', 'and_eq', 'asm', 'bitand', 'bitor', 'break',
15
+ 'case', 'catch', 'class', 'compl', 'const_cast',
16
+ 'continue', 'default', 'delete', 'do', 'dynamic_cast', 'else',
17
+ 'enum', 'export', 'for', 'goto', 'if', 'namespace', 'new',
18
+ 'not', 'not_eq', 'or', 'or_eq', 'reinterpret_cast', 'return',
19
+ 'sizeof', 'static_cast', 'struct', 'switch', 'template',
20
+ 'throw', 'try', 'typedef', 'typeid', 'typename', 'union',
21
+ 'while', 'xor', 'xor_eq'
22
+ ]
23
+
24
+ PREDEFINED_TYPES = [
25
+ 'bool', 'char', 'double', 'float', 'int', 'long',
26
+ 'short', 'signed', 'unsigned', 'wchar_t', 'string'
27
+ ]
28
+ PREDEFINED_CONSTANTS = [
29
+ 'false', 'true',
30
+ 'EOF', 'NULL',
31
+ ]
32
+ PREDEFINED_VARIABLES = [
33
+ 'this'
34
+ ]
35
+ DIRECTIVES = [
36
+ 'auto', 'const', 'explicit', 'extern', 'friend', 'inline', 'mutable', 'operator',
37
+ 'private', 'protected', 'public', 'register', 'static', 'using', 'virtual', 'void',
38
+ 'volatile'
39
+ ]
40
+
41
+ IDENT_KIND = WordList.new(:ident).
42
+ add(RESERVED_WORDS, :reserved).
43
+ add(PREDEFINED_TYPES, :pre_type).
44
+ add(PREDEFINED_VARIABLES, :local_variable).
45
+ add(DIRECTIVES, :directive).
46
+ add(PREDEFINED_CONSTANTS, :pre_constant)
47
+
48
+ ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x
49
+ UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x
50
+
51
+ def scan_tokens tokens, options
52
+
53
+ state = :initial
54
+ label_expected = true
55
+ case_expected = false
56
+ label_expected_before_preproc_line = nil
57
+ in_preproc_line = false
58
+
59
+ until eos?
60
+
61
+ kind = nil
62
+ match = nil
63
+
64
+ case state
65
+
66
+ when :initial
67
+
68
+ if match = scan(/ \s+ | \\\n /x)
69
+ if in_preproc_line && match != "\\\n" && match.index(?\n)
70
+ in_preproc_line = false
71
+ label_expected = label_expected_before_preproc_line
72
+ end
73
+ tokens << [match, :space]
74
+ next
75
+
76
+ elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx)
77
+ kind = :comment
78
+
79
+ elsif match = scan(/ \# \s* if \s* 0 /x)
80
+ match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos?
81
+ kind = :comment
82
+
83
+ elsif match = scan(/ [-+*=<>?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x)
84
+ label_expected = match =~ /[;\{\}]/
85
+ if case_expected
86
+ label_expected = true if match == ':'
87
+ case_expected = false
88
+ end
89
+ kind = :operator
90
+
91
+ elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x)
92
+ kind = IDENT_KIND[match]
93
+ if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/)
94
+ kind = :label
95
+ match << matched
96
+ else
97
+ label_expected = false
98
+ if kind == :reserved
99
+ case match
100
+ when 'class'
101
+ state = :class_name_expected
102
+ when 'case', 'default'
103
+ case_expected = true
104
+ end
105
+ end
106
+ end
107
+
108
+ elsif scan(/\$/)
109
+ kind = :ident
110
+
111
+ elsif match = scan(/L?"/)
112
+ tokens << [:open, :string]
113
+ if match[0] == ?L
114
+ tokens << ['L', :modifier]
115
+ match = '"'
116
+ end
117
+ state = :string
118
+ kind = :delimiter
119
+
120
+ elsif scan(/#[ \t]*(\w*)/)
121
+ kind = :preprocessor
122
+ in_preproc_line = true
123
+ label_expected_before_preproc_line = label_expected
124
+ state = :include_expected if self[1] == 'include'
125
+
126
+ elsif scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox)
127
+ label_expected = false
128
+ kind = :char
129
+
130
+ elsif scan(/0[xX][0-9A-Fa-f]+/)
131
+ label_expected = false
132
+ kind = :hex
133
+
134
+ elsif scan(/(?:0[0-7]+)(?![89.eEfF])/)
135
+ label_expected = false
136
+ kind = :oct
137
+
138
+ elsif scan(/(?:\d+)(?![.eEfF])L?L?/)
139
+ label_expected = false
140
+ kind = :integer
141
+
142
+ elsif scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/)
143
+ label_expected = false
144
+ kind = :float
145
+
146
+ else
147
+ getch
148
+ kind = :error
149
+
150
+ end
151
+
152
+ when :string
153
+ if scan(/[^\\"]+/)
154
+ kind = :content
155
+ elsif scan(/"/)
156
+ tokens << ['"', :delimiter]
157
+ tokens << [:close, :string]
158
+ state = :initial
159
+ label_expected = false
160
+ next
161
+ elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)
162
+ kind = :char
163
+ elsif scan(/ \\ | $ /x)
164
+ tokens << [:close, :string]
165
+ kind = :error
166
+ state = :initial
167
+ label_expected = false
168
+ else
169
+ raise_inspect "else case \" reached; %p not handled." % peek(1), tokens
170
+ end
171
+
172
+ when :include_expected
173
+ if scan(/<[^>\n]+>?|"[^"\n\\]*(?:\\.[^"\n\\]*)*"?/)
174
+ kind = :include
175
+ state = :initial
176
+
177
+ elsif match = scan(/\s+/)
178
+ kind = :space
179
+ state = :initial if match.index ?\n
180
+
181
+ else
182
+ state = :initial
183
+ next
184
+
185
+ end
186
+
187
+ when :class_name_expected
188
+ if scan(/ [A-Za-z_][A-Za-z_0-9]* /x)
189
+ kind = :class
190
+ state = :initial
191
+
192
+ elsif match = scan(/\s+/)
193
+ kind = :space
194
+
195
+ else
196
+ getch
197
+ kind = :error
198
+ state = :initial
199
+
200
+ end
201
+
202
+ else
203
+ raise_inspect 'Unknown state', tokens
204
+
205
+ end
206
+
207
+ match ||= matched
208
+ if $DEBUG and not kind
209
+ raise_inspect 'Error token %p in line %d' %
210
+ [[match, kind], line], tokens
211
+ end
212
+ raise_inspect 'Empty token', tokens unless match
213
+
214
+ tokens << [match, kind]
215
+
216
+ end
217
+
218
+ if state == :string
219
+ tokens << [:close, :string]
220
+ end
221
+
222
+ tokens
223
+ end
224
+
225
+ end
226
+
227
+ end
228
+ end