Mercurial > projects > dil
comparison src/cmd/Highlight.d @ 808:28e1ff1dcfcf
Renamed generate command to highlight and refactored it.
author | Aziz K?ksal <aziz.koeksal@gmail.com> |
---|---|
date | Sun, 09 Mar 2008 16:10:25 +0100 |
parents | src/cmd/Generate.d@bcb74c9b895c |
children | 80eb3251e010 |
comparison
equal
deleted
inserted
replaced
807:a2880c95eda3 | 808:28e1ff1dcfcf |
---|---|
1 /++ | |
2 Author: Aziz Köksal | |
3 License: GPL3 | |
4 +/ | |
5 module cmd.Highlight; | |
6 | |
7 import dil.ast.DefaultVisitor; | |
8 import dil.ast.Node, | |
9 dil.ast.Declaration, | |
10 dil.ast.Statement, | |
11 dil.ast.Expression, | |
12 dil.ast.Types; | |
13 import dil.lexer.Lexer; | |
14 import dil.parser.Parser; | |
15 import dil.semantic.Module; | |
16 import dil.SourceText; | |
17 import dil.Information; | |
18 import SettingsLoader; | |
19 import Settings; | |
20 import common; | |
21 | |
22 import tango.io.GrowBuffer; | |
23 import tango.io.Print; | |
24 | |
25 /// The highlight command. | |
26 struct HighlightCommand | |
27 { | |
28 /// Options for the command. | |
29 enum Option | |
30 { | |
31 None = 0, | |
32 Tokens = 1, | |
33 Syntax = 1<<1, | |
34 HTML = 1<<2, | |
35 XML = 1<<3, | |
36 PrintLines = 1<<4 | |
37 } | |
38 alias Option Options; | |
39 | |
40 Options options; /// Command options. | |
41 string filePath; /// File path to the module to be highlighted. | |
42 InfoManager infoMan; | |
43 | |
44 /// Adds o to the options. | |
45 void add(Option o) | |
46 { | |
47 options |= o; | |
48 } | |
49 | |
50 /// Executes the command. | |
51 void run() | |
52 { | |
53 add(HighlightCommand.Option.Tokens); | |
54 if (!(options & (Option.XML | Option.HTML))) | |
55 add(Option.XML); // Default to XML. | |
56 | |
57 auto mapFilePath = options & Option.HTML ? GlobalSettings.htmlMapFile | |
58 : GlobalSettings.xmlMapFile; | |
59 auto map = TagMapLoader(infoMan).load(mapFilePath); | |
60 auto tags = new TagMap(map); | |
61 | |
62 if (infoMan.hasInfo) | |
63 return; | |
64 | |
65 if (options & Option.Syntax) | |
66 highlightSyntax(filePath, tags, Stdout, options); | |
67 else | |
68 highlightTokens(filePath, tags, Stdout, options); | |
69 } | |
70 } | |
71 | |
72 /// Escapes the characters '<', '>' and '&' with named character entities. | |
73 char[] xml_escape(char[] text) | |
74 { | |
75 char[] result; | |
76 foreach(c; text) | |
77 switch(c) | |
78 { | |
79 case '<': result ~= "<"; break; | |
80 case '>': result ~= ">"; break; | |
81 case '&': result ~= "&"; break; | |
82 default: result ~= c; | |
83 } | |
84 if (result.length != text.length) | |
85 return result; | |
86 // Nothing escaped. Return original text. | |
87 delete result; | |
88 return text; | |
89 } | |
90 | |
91 /// Maps tokens to (format) strings. | |
92 class TagMap | |
93 { | |
94 string[string] table; | |
95 string[TOK.MAX] tokenTable; | |
96 | |
97 this(string[string] table) | |
98 { | |
99 this.table = table; | |
100 Identifier = this["Identifier", "{0}"]; | |
101 String = this["String", "{0}"]; | |
102 Char = this["Char", "{0}"]; | |
103 Number = this["Number", "{0}"]; | |
104 Keyword = this["Keyword", "{0}"]; | |
105 LineC = this["LineC", "{0}"]; | |
106 BlockC = this["BlockC", "{0}"]; | |
107 NestedC = this["NestedC", "{0}"]; | |
108 Shebang = this["Shebang", "{0}"]; | |
109 HLine = this["HLine", "{0}"]; | |
110 Filespec = this["Filespec", "{0}"]; | |
111 Illegal = this["Illegal", "{0}"]; | |
112 Newline = this["Newline", "{0}"]; | |
113 SpecialToken = this["SpecialToken", "{0}"]; | |
114 Declaration = this["Declaration", "d"]; | |
115 Statement = this["Statement", "s"]; | |
116 Expression = this["Expression", "e"]; | |
117 Type = this["Type", "t"]; | |
118 Other = this["Other", "o"]; | |
119 EOF = this["EOF", ""]; | |
120 | |
121 foreach (i, tokStr; tokToString) | |
122 if (auto pStr = tokStr in this.table) | |
123 tokenTable[i] = *pStr; | |
124 } | |
125 | |
126 /// Returns the value for str, or 'fallback' if str is not in the table. | |
127 string opIndex(string str, string fallback = "") | |
128 { | |
129 auto p = str in table; | |
130 if (p) | |
131 return *p; | |
132 return fallback; | |
133 } | |
134 | |
135 /// Returns the value for tok in O(1) time. | |
136 string opIndex(TOK tok) | |
137 { | |
138 return tokenTable[tok]; | |
139 } | |
140 | |
141 /// Shortcuts for quick access. | |
142 string Identifier, String, Char, Number, Keyword, LineC, BlockC, | |
143 NestedC, Shebang, HLine, Filespec, Illegal, Newline, SpecialToken, | |
144 Declaration, Statement, Expression, Type, Other, EOF; | |
145 | |
146 /// Returns the tag for the category 'nc'. | |
147 string getTag(NodeCategory nc) | |
148 { | |
149 string tag; | |
150 switch (nc) | |
151 { alias NodeCategory NC; | |
152 case NC.Declaration: tag = Declaration; break; | |
153 case NC.Statement: tag = Statement; break; | |
154 case NC.Expression: tag = Expression; break; | |
155 case NC.Type: tag = Type; break; | |
156 case NC.Other: tag = Other; break; | |
157 default: assert(0); | |
158 } | |
159 return tag; | |
160 } | |
161 } | |
162 | |
163 /// Find the last occurrence of object in subject. | |
164 /// Returns: the index if found, or -1 if not. | |
165 int rfind(char[] subject, char object) | |
166 { | |
167 foreach_reverse(i, c; subject) | |
168 if (c == object) | |
169 return i; | |
170 return -1; | |
171 } | |
172 | |
173 /// Returns the short class name of a class descending from Node.$(BR) | |
174 /// E.g.: dil.ast.Declarations.ClassDeclaration -> Class | |
175 char[] getShortClassName(Node node) | |
176 { | |
177 static char[][] name_table; | |
178 if (name_table is null) | |
179 name_table = new char[][NodeKind.max+1]; // Create a new table. | |
180 // Look up in table. | |
181 char[] name = name_table[node.kind]; | |
182 if (name !is null) | |
183 return name; // Return cached name. | |
184 | |
185 name = node.classinfo.name; // Get the fully qualified name of the class. | |
186 name = name[rfind(name, '.')+1 .. $]; // Remove package and module name. | |
187 | |
188 uint suffixLength; | |
189 switch (node.category) | |
190 { | |
191 alias NodeCategory NC; | |
192 case NC.Declaration: | |
193 suffixLength = "Declaration".length; | |
194 break; | |
195 case NC.Statement: | |
196 suffixLength = "Statement".length; | |
197 break; | |
198 case NC.Expression: | |
199 suffixLength = "Expression".length; | |
200 break; | |
201 case NC.Type: | |
202 suffixLength = "Type".length; | |
203 break; | |
204 case NC.Other: | |
205 break; | |
206 default: | |
207 assert(0); | |
208 } | |
209 // Remove common suffix. | |
210 name = name[0 .. $ - suffixLength]; | |
211 // Store the name in the table. | |
212 name_table[node.kind] = name; | |
213 return name; | |
214 } | |
215 | |
216 /// Extended token structure. | |
217 struct TokenEx | |
218 { | |
219 Token* token; /// The lexer token. | |
220 Node[] beginNodes; /// beginNodes[n].begin == token | |
221 Node[] endNodes; /// endNodes[n].end == token | |
222 } | |
223 | |
224 /// Builds an array of TokenEx items. | |
225 class TokenExBuilder : DefaultVisitor | |
226 { | |
227 private TokenEx*[Token*] tokenTable; | |
228 | |
229 TokenEx[] build(Node root, Token* first) | |
230 { | |
231 auto token = first; | |
232 | |
233 uint count; // Count tokens. | |
234 for (; token; token = token.next) | |
235 count++; | |
236 // Creat the exact number of TokenEx instances. | |
237 auto toks = new TokenEx[count]; | |
238 token = first; | |
239 foreach (ref tokEx; toks) | |
240 { | |
241 tokEx.token = token; | |
242 if (!token.isWhitespace) | |
243 tokenTable[token] = &tokEx; | |
244 token = token.next; | |
245 } | |
246 | |
247 super.visitN(root); | |
248 tokenTable = null; | |
249 return toks; | |
250 } | |
251 | |
252 TokenEx* getTokenEx()(Token* t) | |
253 { | |
254 auto p = t in tokenTable; | |
255 assert(p, t.srcText~" is not in tokenTable"); | |
256 return *p; | |
257 } | |
258 | |
259 // Override dispatch function. | |
260 override Node dispatch(Node n) | |
261 { | |
262 auto begin = n.begin; | |
263 if (begin) | |
264 { assert(n.end); | |
265 auto txbegin = getTokenEx(begin); | |
266 auto txend = getTokenEx(n.end); | |
267 txbegin.beginNodes ~= n; | |
268 txend.endNodes ~= n; | |
269 } | |
270 return super.dispatch(n); | |
271 } | |
272 } | |
273 | |
274 void printErrors(Lexer lx, TagMap tags, Print!(char) print) | |
275 { | |
276 foreach (e; lx.errors) | |
277 print.format(tags["LexerError"], e.filePath, e.loc, e.col, xml_escape(e.getMsg)); | |
278 } | |
279 | |
280 void printErrors(Parser parser, TagMap tags, Print!(char) print) | |
281 { | |
282 foreach (e; parser.errors) | |
283 print.format(tags["ParserError"], e.filePath, e.loc, e.col, xml_escape(e.getMsg)); | |
284 } | |
285 | |
286 void printLines(uint lines, TagMap tags, Print!(char) print) | |
287 { | |
288 auto lineNumberFormat = tags["LineNumber"]; | |
289 for (auto lineNum = 1; lineNum <= lines; lineNum++) | |
290 print.format(lineNumberFormat, lineNum); | |
291 } | |
292 | |
293 /// Highlights the syntax in a source file. | |
294 void highlightSyntax(string filePath, TagMap tags, | |
295 Print!(char) print, | |
296 HighlightCommand.Options options) | |
297 { | |
298 auto parser = new Parser(new SourceText(filePath, true)); | |
299 auto root = parser.start(); | |
300 auto lx = parser.lexer; | |
301 | |
302 auto builder = new TokenExBuilder(); | |
303 auto tokenExList = builder.build(root, lx.firstToken()); | |
304 | |
305 print(tags["DocHead"]); | |
306 if (lx.errors.length || parser.errors.length) | |
307 { // Output error messages. | |
308 print(tags["CompBegin"]); | |
309 printErrors(lx, tags, print); | |
310 printErrors(parser, tags, print); | |
311 print(tags["CompEnd"]); | |
312 } | |
313 | |
314 if (options & HighlightCommand.Option.PrintLines) | |
315 { | |
316 print(tags["LineNumberBegin"]); | |
317 printLines(lx.lineNum, tags, print); | |
318 print(tags["LineNumberEnd"]); | |
319 } | |
320 | |
321 print(tags["SourceBegin"]); | |
322 | |
323 auto tagNodeBegin = tags["NodeBegin"]; | |
324 auto tagNodeEnd = tags["NodeEnd"]; | |
325 | |
326 // Iterate over list of tokens. | |
327 foreach (ref tokenEx; tokenExList) | |
328 { | |
329 auto token = tokenEx.token; | |
330 | |
331 token.ws && print(token.wsChars); // Print preceding whitespace. | |
332 if (token.isWhitespace) { | |
333 printToken(token, tags, print); | |
334 continue; | |
335 } | |
336 // <node> | |
337 foreach (node; tokenEx.beginNodes) | |
338 print.format(tagNodeBegin, tags.getTag(node.category), getShortClassName(node)); | |
339 // Token text. | |
340 printToken(token, tags, print); | |
341 // </node> | |
342 if (options & HighlightCommand.Option.HTML) | |
343 foreach_reverse (node; tokenEx.endNodes) | |
344 print(tagNodeEnd); | |
345 else | |
346 foreach_reverse (node; tokenEx.endNodes) | |
347 print.format(tagNodeEnd, tags.getTag(node.category)); | |
348 } | |
349 print(tags["SourceEnd"]); | |
350 print(tags["DocEnd"]); | |
351 } | |
352 | |
353 /// Highlights all tokens of a source file. | |
354 void highlightTokens(string filePath, TagMap tags, | |
355 Print!(char) print, | |
356 HighlightCommand.Options options) | |
357 { | |
358 auto lx = new Lexer(new SourceText(filePath, true)); | |
359 lx.scanAll(); | |
360 | |
361 print(tags["DocHead"]); | |
362 if (lx.errors.length) | |
363 { | |
364 print(tags["CompBegin"]); | |
365 printErrors(lx, tags, print); | |
366 print(tags["CompEnd"]); | |
367 } | |
368 | |
369 if (options & HighlightCommand.Option.PrintLines) | |
370 { | |
371 print(tags["LineNumberBegin"]); | |
372 printLines(lx.lineNum, tags, print); | |
373 print(tags["LineNumberEnd"]); | |
374 } | |
375 | |
376 print(tags["SourceBegin"]); | |
377 // Traverse linked list and print tokens. | |
378 for (auto token = lx.firstToken(); token; token = token.next) { | |
379 token.ws && print(token.wsChars); // Print preceding whitespace. | |
380 printToken(token, tags, print); | |
381 } | |
382 print(tags["SourceEnd"]); | |
383 print(tags["DocEnd"]); | |
384 } | |
385 | |
386 /// A token highlighter designed for DDoc. | |
387 class TokenHighlighter | |
388 { | |
389 TagMap tags; | |
390 this(InfoManager infoMan, bool useHTML = true) | |
391 { | |
392 string filePath = GlobalSettings.htmlMapFile; | |
393 if (!useHTML) | |
394 filePath = GlobalSettings.xmlMapFile; | |
395 auto map = TagMapLoader(infoMan).load(filePath); | |
396 tags = new TagMap(map); | |
397 } | |
398 | |
399 /// Highlights tokens in a DDoc code section. | |
400 /// Returns: a string with the highlighted tokens (in HTML tags.) | |
401 string highlight(string text, string filePath) | |
402 { | |
403 auto buffer = new GrowBuffer(text.length); | |
404 auto print = new Print!(char)(Format, buffer); | |
405 | |
406 auto lx = new Lexer(new SourceText(filePath, text)); | |
407 lx.scanAll(); | |
408 | |
409 // Traverse linked list and print tokens. | |
410 print("$(D_CODE\n"); | |
411 if (lx.errors.length) | |
412 { // Output error messages. | |
413 print(tags["CompBegin"]); | |
414 printErrors(lx, tags, print); | |
415 print(tags["CompEnd"]); | |
416 } | |
417 // Traverse linked list and print tokens. | |
418 for (auto token = lx.firstToken(); token; token = token.next) { | |
419 token.ws && print(token.wsChars); // Print preceding whitespace. | |
420 printToken(token, tags, print); | |
421 } | |
422 print("\n)"); | |
423 return cast(char[])buffer.slice(); | |
424 } | |
425 } | |
426 | |
427 /// Prints a token to the stream print. | |
428 void printToken(Token* token, TagMap tags, Print!(char) print) | |
429 { | |
430 switch(token.kind) | |
431 { | |
432 case TOK.Identifier: | |
433 print.format(tags.Identifier, token.srcText); | |
434 break; | |
435 case TOK.Comment: | |
436 string formatStr; | |
437 switch (token.start[1]) | |
438 { | |
439 case '/': formatStr = tags.LineC; break; | |
440 case '*': formatStr = tags.BlockC; break; | |
441 case '+': formatStr = tags.NestedC; break; | |
442 default: assert(0); | |
443 } | |
444 print.format(formatStr, xml_escape(token.srcText)); | |
445 break; | |
446 case TOK.String: | |
447 print.format(tags.String, xml_escape(token.srcText)); | |
448 break; | |
449 case TOK.CharLiteral: | |
450 print.format(tags.Char, xml_escape(token.srcText)); | |
451 break; | |
452 case TOK.Int32, TOK.Int64, TOK.Uint32, TOK.Uint64, | |
453 TOK.Float32, TOK.Float64, TOK.Float80, | |
454 TOK.Imaginary32, TOK.Imaginary64, TOK.Imaginary80: | |
455 print.format(tags.Number, token.srcText); | |
456 break; | |
457 case TOK.Shebang: | |
458 print.format(tags.Shebang, xml_escape(token.srcText)); | |
459 break; | |
460 case TOK.HashLine: | |
461 auto formatStr = tags.HLine; | |
462 // The text to be inserted into formatStr. | |
463 auto buffer = new GrowBuffer; | |
464 auto print2 = new Print!(char)(Format, buffer); | |
465 | |
466 void printWS(char* start, char* end) | |
467 { | |
468 start != end && print2(start[0 .. end - start]); | |
469 } | |
470 | |
471 auto num = token.tokLineNum; | |
472 if (num is null) | |
473 { // Malformed #line | |
474 print.format(formatStr, token.srcText); | |
475 break; | |
476 } | |
477 | |
478 // Print whitespace between #line and number. | |
479 printWS(token.start, num.start); // Prints "#line" as well. | |
480 printToken(num, tags, print2); // Print the number. | |
481 | |
482 if (auto filespec = token.tokLineFilespec) | |
483 { // Print whitespace between number and filespec. | |
484 printWS(num.end, filespec.start); | |
485 print2.format(tags.Filespec, xml_escape(filespec.srcText)); | |
486 } | |
487 // Finally print the whole token. | |
488 print.format(formatStr, cast(char[])buffer.slice()); | |
489 break; | |
490 case TOK.Illegal: | |
491 print.format(tags.Illegal, token.srcText()); | |
492 break; | |
493 case TOK.Newline: | |
494 print.format(tags.Newline, token.srcText()); | |
495 break; | |
496 case TOK.EOF: | |
497 print(tags.EOF); | |
498 break; | |
499 default: | |
500 if (token.isKeyword()) | |
501 print.format(tags.Keyword, token.srcText); | |
502 else if (token.isSpecialToken) | |
503 print.format(tags.SpecialToken, token.srcText); | |
504 else | |
505 print(tags[token.kind]); | |
506 } | |
507 } |