Chris@0
|
1 module CodeRay
|
Chris@0
|
2 module Scanners
|
Chris@0
|
3
|
Chris@0
|
4 class JavaScript < Scanner
|
Chris@0
|
5
|
Chris@0
|
6 include Streamable
|
Chris@0
|
7
|
Chris@0
|
8 register_for :java_script
|
Chris@0
|
9 file_extension 'js'
|
Chris@0
|
10
|
Chris@0
|
11 # The actual JavaScript keywords.
|
Chris@0
|
12 KEYWORDS = %w[
|
Chris@0
|
13 break case catch continue default delete do else
|
Chris@0
|
14 finally for function if in instanceof new
|
Chris@0
|
15 return switch throw try typeof var void while with
|
Chris@0
|
16 ]
|
Chris@0
|
17 PREDEFINED_CONSTANTS = %w[
|
Chris@0
|
18 false null true undefined
|
Chris@0
|
19 ]
|
Chris@0
|
20
|
Chris@0
|
21 MAGIC_VARIABLES = %w[ this arguments ] # arguments was introduced in JavaScript 1.4
|
Chris@0
|
22
|
Chris@0
|
23 KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[
|
Chris@0
|
24 case delete in instanceof new return throw typeof with
|
Chris@0
|
25 ]
|
Chris@0
|
26
|
Chris@0
|
27 # Reserved for future use.
|
Chris@0
|
28 RESERVED_WORDS = %w[
|
Chris@0
|
29 abstract boolean byte char class debugger double enum export extends
|
Chris@0
|
30 final float goto implements import int interface long native package
|
Chris@0
|
31 private protected public short static super synchronized throws transient
|
Chris@0
|
32 volatile
|
Chris@0
|
33 ]
|
Chris@0
|
34
|
Chris@0
|
35 IDENT_KIND = WordList.new(:ident).
|
Chris@0
|
36 add(RESERVED_WORDS, :reserved).
|
Chris@0
|
37 add(PREDEFINED_CONSTANTS, :pre_constant).
|
Chris@0
|
38 add(MAGIC_VARIABLES, :local_variable).
|
Chris@0
|
39 add(KEYWORDS, :keyword)
|
Chris@0
|
40
|
Chris@0
|
41 ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x
|
Chris@0
|
42 UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x
|
Chris@0
|
43 REGEXP_ESCAPE = / [bBdDsSwW] /x
|
Chris@0
|
44 STRING_CONTENT_PATTERN = {
|
Chris@0
|
45 "'" => /[^\\']+/,
|
Chris@0
|
46 '"' => /[^\\"]+/,
|
Chris@0
|
47 '/' => /[^\\\/]+/,
|
Chris@0
|
48 }
|
Chris@0
|
49 KEY_CHECK_PATTERN = {
|
Chris@0
|
50 "'" => / [^\\']* (?: \\.? [^\\']* )* '? \s* : /x,
|
Chris@0
|
51 '"' => / [^\\"]* (?: \\.? [^\\"]* )* "? \s* : /x,
|
Chris@0
|
52 }
|
Chris@0
|
53
|
Chris@0
|
54 def scan_tokens tokens, options
|
Chris@0
|
55
|
Chris@0
|
56 state = :initial
|
Chris@0
|
57 string_delimiter = nil
|
Chris@0
|
58 value_expected = true
|
Chris@0
|
59 key_expected = false
|
Chris@0
|
60 function_expected = false
|
Chris@0
|
61
|
Chris@0
|
62 until eos?
|
Chris@0
|
63
|
Chris@0
|
64 kind = nil
|
Chris@0
|
65 match = nil
|
Chris@0
|
66
|
Chris@0
|
67 case state
|
Chris@0
|
68
|
Chris@0
|
69 when :initial
|
Chris@0
|
70
|
Chris@0
|
71 if match = scan(/ \s+ | \\\n /x)
|
Chris@0
|
72 value_expected = true if !value_expected && match.index(?\n)
|
Chris@0
|
73 tokens << [match, :space]
|
Chris@0
|
74 next
|
Chris@0
|
75
|
Chris@0
|
76 elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx)
|
Chris@0
|
77 value_expected = true
|
Chris@0
|
78 kind = :comment
|
Chris@0
|
79
|
Chris@0
|
80 elsif check(/\.?\d/)
|
Chris@0
|
81 key_expected = value_expected = false
|
Chris@0
|
82 if scan(/0[xX][0-9A-Fa-f]+/)
|
Chris@0
|
83 kind = :hex
|
Chris@0
|
84 elsif scan(/(?>0[0-7]+)(?![89.eEfF])/)
|
Chris@0
|
85 kind = :oct
|
Chris@0
|
86 elsif scan(/\d+[fF]|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/)
|
Chris@0
|
87 kind = :float
|
Chris@0
|
88 elsif scan(/\d+/)
|
Chris@0
|
89 kind = :integer
|
Chris@0
|
90 end
|
Chris@0
|
91
|
Chris@0
|
92 elsif value_expected && match = scan(/<([[:alpha:]]\w*) (?: [^\/>]*\/> | .*?<\/\1>)/xim)
|
Chris@0
|
93 # FIXME: scan over nested tags
|
Chris@0
|
94 xml_scanner.tokenize match
|
Chris@0
|
95 value_expected = false
|
Chris@0
|
96 next
|
Chris@0
|
97
|
Chris@0
|
98 elsif match = scan(/ [-+*=<>?:;,!&^|(\[{~%]+ | \.(?!\d) /x)
|
Chris@0
|
99 value_expected = true
|
Chris@0
|
100 last_operator = match[-1]
|
Chris@0
|
101 key_expected = (last_operator == ?{) || (last_operator == ?,)
|
Chris@0
|
102 function_expected = false
|
Chris@0
|
103 kind = :operator
|
Chris@0
|
104
|
Chris@0
|
105 elsif scan(/ [)\]}]+ /x)
|
Chris@0
|
106 function_expected = key_expected = value_expected = false
|
Chris@0
|
107 kind = :operator
|
Chris@0
|
108
|
Chris@0
|
109 elsif match = scan(/ [$a-zA-Z_][A-Za-z_0-9$]* /x)
|
Chris@0
|
110 kind = IDENT_KIND[match]
|
Chris@0
|
111 value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match]
|
Chris@0
|
112 # TODO: labels
|
Chris@0
|
113 if kind == :ident
|
Chris@0
|
114 if match.index(?$) # $ allowed inside an identifier
|
Chris@0
|
115 kind = :predefined
|
Chris@0
|
116 elsif function_expected
|
Chris@0
|
117 kind = :function
|
Chris@0
|
118 elsif check(/\s*[=:]\s*function\b/)
|
Chris@0
|
119 kind = :function
|
Chris@0
|
120 elsif key_expected && check(/\s*:/)
|
Chris@0
|
121 kind = :key
|
Chris@0
|
122 end
|
Chris@0
|
123 end
|
Chris@0
|
124 function_expected = (kind == :keyword) && (match == 'function')
|
Chris@0
|
125 key_expected = false
|
Chris@0
|
126
|
Chris@0
|
127 elsif match = scan(/["']/)
|
Chris@0
|
128 if key_expected && check(KEY_CHECK_PATTERN[match])
|
Chris@0
|
129 state = :key
|
Chris@0
|
130 else
|
Chris@0
|
131 state = :string
|
Chris@0
|
132 end
|
Chris@0
|
133 tokens << [:open, state]
|
Chris@0
|
134 string_delimiter = match
|
Chris@0
|
135 kind = :delimiter
|
Chris@0
|
136
|
Chris@0
|
137 elsif value_expected && (match = scan(/\/(?=\S)/))
|
Chris@0
|
138 tokens << [:open, :regexp]
|
Chris@0
|
139 state = :regexp
|
Chris@0
|
140 string_delimiter = '/'
|
Chris@0
|
141 kind = :delimiter
|
Chris@0
|
142
|
Chris@0
|
143 elsif scan(/ \/ /x)
|
Chris@0
|
144 value_expected = true
|
Chris@0
|
145 key_expected = false
|
Chris@0
|
146 kind = :operator
|
Chris@0
|
147
|
Chris@0
|
148 else
|
Chris@0
|
149 getch
|
Chris@0
|
150 kind = :error
|
Chris@0
|
151
|
Chris@0
|
152 end
|
Chris@0
|
153
|
Chris@0
|
154 when :string, :regexp, :key
|
Chris@0
|
155 if scan(STRING_CONTENT_PATTERN[string_delimiter])
|
Chris@0
|
156 kind = :content
|
Chris@0
|
157 elsif match = scan(/["'\/]/)
|
Chris@0
|
158 tokens << [match, :delimiter]
|
Chris@0
|
159 if state == :regexp
|
Chris@0
|
160 modifiers = scan(/[gim]+/)
|
Chris@0
|
161 tokens << [modifiers, :modifier] if modifiers && !modifiers.empty?
|
Chris@0
|
162 end
|
Chris@0
|
163 tokens << [:close, state]
|
Chris@0
|
164 string_delimiter = nil
|
Chris@0
|
165 key_expected = value_expected = false
|
Chris@0
|
166 state = :initial
|
Chris@0
|
167 next
|
Chris@0
|
168 elsif state != :regexp && (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox))
|
Chris@0
|
169 if string_delimiter == "'" && !(match == "\\\\" || match == "\\'")
|
Chris@0
|
170 kind = :content
|
Chris@0
|
171 else
|
Chris@0
|
172 kind = :char
|
Chris@0
|
173 end
|
Chris@0
|
174 elsif state == :regexp && scan(/ \\ (?: #{ESCAPE} | #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox)
|
Chris@0
|
175 kind = :char
|
Chris@0
|
176 elsif scan(/\\./m)
|
Chris@0
|
177 kind = :content
|
Chris@0
|
178 elsif scan(/ \\ | $ /x)
|
Chris@0
|
179 tokens << [:close, state]
|
Chris@0
|
180 kind = :error
|
Chris@0
|
181 key_expected = value_expected = false
|
Chris@0
|
182 state = :initial
|
Chris@0
|
183 else
|
Chris@0
|
184 raise_inspect "else case \" reached; %p not handled." % peek(1), tokens
|
Chris@0
|
185 end
|
Chris@0
|
186
|
Chris@0
|
187 else
|
Chris@0
|
188 raise_inspect 'Unknown state', tokens
|
Chris@0
|
189
|
Chris@0
|
190 end
|
Chris@0
|
191
|
Chris@0
|
192 match ||= matched
|
Chris@0
|
193 if $CODERAY_DEBUG and not kind
|
Chris@0
|
194 raise_inspect 'Error token %p in line %d' %
|
Chris@0
|
195 [[match, kind], line], tokens
|
Chris@0
|
196 end
|
Chris@0
|
197 raise_inspect 'Empty token', tokens unless match
|
Chris@0
|
198
|
Chris@0
|
199 tokens << [match, kind]
|
Chris@0
|
200
|
Chris@0
|
201 end
|
Chris@0
|
202
|
Chris@0
|
203 if [:string, :regexp].include? state
|
Chris@0
|
204 tokens << [:close, state]
|
Chris@0
|
205 end
|
Chris@0
|
206
|
Chris@0
|
207 tokens
|
Chris@0
|
208 end
|
Chris@0
|
209
|
Chris@0
|
210 protected
|
Chris@0
|
211
|
Chris@0
|
212 def reset_instance
|
Chris@0
|
213 super
|
Chris@0
|
214 @xml_scanner.reset if defined? @xml_scanner
|
Chris@0
|
215 end
|
Chris@0
|
216
|
Chris@0
|
217 def xml_scanner
|
Chris@0
|
218 @xml_scanner ||= CodeRay.scanner :xml, :tokens => @tokens, :keep_tokens => true, :keep_state => true
|
Chris@0
|
219 end
|
Chris@0
|
220
|
Chris@0
|
221 end
|
Chris@0
|
222
|
Chris@0
|
223 end
|
Chris@0
|
224 end
|