OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #library('classify'); | |
6 | |
7 #import('../../frog/lang.dart'); | |
8 #import('markdown.dart', prefix: 'md'); | |
9 | |
10 /** | |
11 * Kinds of tokens that we care to highlight differently. The values of the | |
12 * fields here will be used as CSS class names for the generated spans. | |
13 */ | |
14 class Classification { | |
15 static final NONE = null; | |
16 static final ERROR = "e"; | |
17 static final COMMENT = "c"; | |
18 static final IDENTIFIER = "i"; | |
19 static final KEYWORD = "k"; | |
20 static final OPERATOR = "o"; | |
21 static final STRING = "s"; | |
22 static final NUMBER = "n"; | |
23 static final PUNCTUATION = "p"; | |
24 | |
25 // A few things that are nice to make different: | |
26 static final TYPE_IDENTIFIER = "t"; | |
27 | |
28 // Between a keyword and an identifier | |
29 static final SPECIAL_IDENTIFIER = "r"; | |
30 | |
31 static final ARROW_OPERATOR = "a"; | |
32 | |
33 static final STRING_INTERPOLATION = 'si'; | |
34 } | |
35 | |
36 String classifySource(SourceFile src) { | |
37 var html = new StringBuffer(); | |
38 var tokenizer = new Tokenizer(src, /*skipWhitespace:*/false); | |
39 | |
40 var token; | |
41 var inString = false; | |
42 while ((token = tokenizer.next()).kind != TokenKind.END_OF_FILE) { | |
43 | |
44 // Track whether or not we're in a string. | |
45 switch (token.kind) { | |
46 case TokenKind.STRING: | |
47 case TokenKind.STRING_PART: | |
48 case TokenKind.INCOMPLETE_STRING: | |
49 case TokenKind.INCOMPLETE_MULTILINE_STRING_DQ: | |
50 case TokenKind.INCOMPLETE_MULTILINE_STRING_SQ: | |
51 inString = true; | |
52 break; | |
53 } | |
54 | |
55 final kind = classify(token); | |
56 final text = md.escapeHtml(token.text); | |
57 if (kind != null) { | |
58 // Add a secondary class to tokens appearing within a string so that | |
59 // we can highlight tokens in an interpolation specially. | |
60 var stringClass = inString ? Classification.STRING_INTERPOLATION : ''; | |
61 html.add('<span class="$kind $stringClass">$text</span>'); | |
62 } else { | |
63 html.add('<span>$text</span>'); | |
64 } | |
65 | |
66 // Track whether or not we're in a string. | |
67 if (token.kind == TokenKind.STRING) { | |
68 inString = false; | |
69 } | |
70 } | |
71 return html.toString(); | |
72 } | |
73 | |
74 bool _looksLikeType(String name) { | |
75 // If the name looks like an UppercaseName, assume it's a type. | |
76 return _looksLikePublicType(name) || _looksLikePrivateType(name); | |
77 } | |
78 | |
79 bool _looksLikePublicType(String name) { | |
80 // If the name looks like an UppercaseName, assume it's a type. | |
81 return name.length >= 2 && isUpper(name[0]) && isLower(name[1]); | |
82 } | |
83 | |
84 bool _looksLikePrivateType(String name) { | |
85 // If the name looks like an _UppercaseName, assume it's a type. | |
86 return (name.length >= 3 && name[0] == '_' && isUpper(name[1]) | |
87 && isLower(name[2])); | |
88 } | |
89 | |
90 // These ensure that they don't return "true" if the string only has symbols. | |
91 bool isUpper(String s) => s.toLowerCase() != s; | |
92 bool isLower(String s) => s.toUpperCase() != s; | |
93 | |
94 String classify(Token token) { | |
95 switch (token.kind) { | |
96 case TokenKind.ERROR: | |
97 return Classification.ERROR; | |
98 | |
99 case TokenKind.IDENTIFIER: | |
100 // Special case for names that look like types. | |
101 if (_looksLikeType(token.text) | |
102 || token.text == 'num' | |
103 || token.text == 'bool' | |
104 || token.text == 'int' | |
105 || token.text == 'double') { | |
106 return Classification.TYPE_IDENTIFIER; | |
107 } | |
108 return Classification.IDENTIFIER; | |
109 | |
110 // Even though it's a reserved word, let's try coloring it like a type. | |
111 case TokenKind.VOID: | |
112 return Classification.TYPE_IDENTIFIER; | |
113 | |
114 case TokenKind.THIS: | |
115 case TokenKind.SUPER: | |
116 return Classification.SPECIAL_IDENTIFIER; | |
117 | |
118 case TokenKind.STRING: | |
119 case TokenKind.STRING_PART: | |
120 case TokenKind.INCOMPLETE_STRING: | |
121 case TokenKind.INCOMPLETE_MULTILINE_STRING_DQ: | |
122 case TokenKind.INCOMPLETE_MULTILINE_STRING_SQ: | |
123 return Classification.STRING; | |
124 | |
125 case TokenKind.INTEGER: | |
126 case TokenKind.HEX_INTEGER: | |
127 case TokenKind.DOUBLE: | |
128 return Classification.NUMBER; | |
129 | |
130 case TokenKind.COMMENT: | |
131 case TokenKind.INCOMPLETE_COMMENT: | |
132 return Classification.COMMENT; | |
133 | |
134 // => is so awesome it is in a class of its own. | |
135 case TokenKind.ARROW: | |
136 return Classification.ARROW_OPERATOR; | |
137 | |
138 case TokenKind.HASHBANG: | |
139 case TokenKind.LPAREN: | |
140 case TokenKind.RPAREN: | |
141 case TokenKind.LBRACK: | |
142 case TokenKind.RBRACK: | |
143 case TokenKind.LBRACE: | |
144 case TokenKind.RBRACE: | |
145 case TokenKind.COLON: | |
146 case TokenKind.SEMICOLON: | |
147 case TokenKind.COMMA: | |
148 case TokenKind.DOT: | |
149 case TokenKind.ELLIPSIS: | |
150 return Classification.PUNCTUATION; | |
151 | |
152 case TokenKind.INCR: | |
153 case TokenKind.DECR: | |
154 case TokenKind.BIT_NOT: | |
155 case TokenKind.NOT: | |
156 case TokenKind.ASSIGN: | |
157 case TokenKind.ASSIGN_OR: | |
158 case TokenKind.ASSIGN_XOR: | |
159 case TokenKind.ASSIGN_AND: | |
160 case TokenKind.ASSIGN_SHL: | |
161 case TokenKind.ASSIGN_SAR: | |
162 case TokenKind.ASSIGN_SHR: | |
163 case TokenKind.ASSIGN_ADD: | |
164 case TokenKind.ASSIGN_SUB: | |
165 case TokenKind.ASSIGN_MUL: | |
166 case TokenKind.ASSIGN_DIV: | |
167 case TokenKind.ASSIGN_TRUNCDIV: | |
168 case TokenKind.ASSIGN_MOD: | |
169 case TokenKind.CONDITIONAL: | |
170 case TokenKind.OR: | |
171 case TokenKind.AND: | |
172 case TokenKind.BIT_OR: | |
173 case TokenKind.BIT_XOR: | |
174 case TokenKind.BIT_AND: | |
175 case TokenKind.SHL: | |
176 case TokenKind.SAR: | |
177 case TokenKind.SHR: | |
178 case TokenKind.ADD: | |
179 case TokenKind.SUB: | |
180 case TokenKind.MUL: | |
181 case TokenKind.DIV: | |
182 case TokenKind.TRUNCDIV: | |
183 case TokenKind.MOD: | |
184 case TokenKind.EQ: | |
185 case TokenKind.NE: | |
186 case TokenKind.EQ_STRICT: | |
187 case TokenKind.NE_STRICT: | |
188 case TokenKind.LT: | |
189 case TokenKind.GT: | |
190 case TokenKind.LTE: | |
191 case TokenKind.GTE: | |
192 case TokenKind.INDEX: | |
193 case TokenKind.SETINDEX: | |
194 return Classification.OPERATOR; | |
195 | |
196 // Color this like a keyword | |
197 case TokenKind.HASH: | |
198 | |
199 case TokenKind.ABSTRACT: | |
200 case TokenKind.ASSERT: | |
201 case TokenKind.CLASS: | |
202 case TokenKind.EXTENDS: | |
203 case TokenKind.FACTORY: | |
204 case TokenKind.GET: | |
205 case TokenKind.IMPLEMENTS: | |
206 case TokenKind.IMPORT: | |
207 case TokenKind.INTERFACE: | |
208 case TokenKind.LIBRARY: | |
209 case TokenKind.NATIVE: | |
210 case TokenKind.NEGATE: | |
211 case TokenKind.OPERATOR: | |
212 case TokenKind.SET: | |
213 case TokenKind.SOURCE: | |
214 case TokenKind.STATIC: | |
215 case TokenKind.TYPEDEF: | |
216 case TokenKind.BREAK: | |
217 case TokenKind.CASE: | |
218 case TokenKind.CATCH: | |
219 case TokenKind.CONST: | |
220 case TokenKind.CONTINUE: | |
221 case TokenKind.DEFAULT: | |
222 case TokenKind.DO: | |
223 case TokenKind.ELSE: | |
224 case TokenKind.FALSE: | |
225 case TokenKind.FINALLY: | |
226 case TokenKind.FOR: | |
227 case TokenKind.IF: | |
228 case TokenKind.IN: | |
229 case TokenKind.IS: | |
230 case TokenKind.NEW: | |
231 case TokenKind.NULL: | |
232 case TokenKind.RETURN: | |
233 case TokenKind.SWITCH: | |
234 case TokenKind.THROW: | |
235 case TokenKind.TRUE: | |
236 case TokenKind.TRY: | |
237 case TokenKind.WHILE: | |
238 case TokenKind.VAR: | |
239 case TokenKind.FINAL: | |
240 return Classification.KEYWORD; | |
241 | |
242 case TokenKind.WHITESPACE: | |
243 case TokenKind.END_OF_FILE: | |
244 return Classification.NONE; | |
245 | |
246 default: | |
247 return Classification.NONE; | |
248 } | |
249 } | |
OLD | NEW |