1 module dfix;
2 
3 import std.experimental.lexer;
4 import dparse.lexer;
5 import dparse.parser;
6 import dparse.ast;
7 import std.stdio;
8 import std.format;
9 import std.file;
10 
11 int main(string[] args)
12 {
13 	import std.getopt : getopt;
14 	import std.parallelism : parallel;
15 
16 	// http://wiki.dlang.org/DIP64
17 	bool dip64;
18 	// http://wiki.dlang.org/DIP65
19 	bool dip65 = true;
20 
21 	bool help;
22 
23 	try
24 		getopt(args, "dip64", &dip64, "dip65", &dip65, "help|h", &help);
25 	catch (Exception e)
26 	{
27 		stderr.writeln(e.msg);
28 		return 1;
29 	}
30 
31 	if (help)
32 	{
33 		printHelp();
34 		return 0;
35 	}
36 
37 	if (args.length < 2)
38 	{
39 		stderr.writeln("File path is a required argument");
40 		return 1;
41 	}
42 
43 	string[] files;
44 
45 	foreach (arg; args[1 .. $])
46 	{
47 		if (isDir(arg))
48 		{
49 			foreach (f; dirEntries(arg, "*.{d,di}", SpanMode.depth))
50 				files ~= f;
51 		}
52 		else
53 			files ~= arg;
54 	}
55 
56 	foreach (f; parallel(files))
57 	{
58 		try
59 			upgradeFile(f, dip64, dip65);
60 		catch (Exception e)
61 			stderr.writeln("Failed to upgrade ", f, ":(", e.file, ":", e.line, ") ", e.msg);
62 	}
63 
64 	return 0;
65 }
66 
67 /**
68  * Prints help message
69  */
70 void printHelp()
71 {
72 	stdout.writeln(`
73 Dfix automatically upgrades D source code to comply with new language changes.
74 Files are modified in place, so have backup copies ready or use a source
75 control system.
76 
77 Usage:
78 
79     dfix [Options] FILES DIRECTORIES
80 
81 Options:
82 
83     --dip64
84         Rewrites attributes to be compliant with DIP64. This defaults to
85         "false". Do not use this feature if you want your code to compile.
86 		It exists as a proof-of-concept for enabling DIP64.
87     --dip65
88         Rewrites catch blocks to be compliant with DIP65. This defaults to
89         "true". Use --dip65=false to disable this fix.
90     --help -h
91         Prints this help message
92 `);
93 }
94 
95 /**
96  * Fixes the given file.
97  */
98 void upgradeFile(string fileName, bool dip64, bool dip65)
99 {
100 	import std.algorithm : filter, canFind;
101 	import std.range : retro;
102 	import std.array : array, uninitializedArray;
103 	import dparse.formatter : Formatter;
104 	import std.exception : enforce;
105 	import dparse.rollback_allocator : RollbackAllocator;
106 
107 	File input = File(fileName, "rb");
108 	ubyte[] inputBytes = uninitializedArray!(ubyte[])(cast(size_t) input.size);
109 	input.rawRead(inputBytes);
110 	input.close();
111 	StringCache cache = StringCache(StringCache.defaultBucketCount);
112 	LexerConfig config;
113 	config.fileName = fileName;
114 	config.stringBehavior = StringBehavior.source;
115 	auto tokens = byToken(inputBytes, config, &cache).array;
116 	auto parseTokens = tokens.filter!(a => a != tok!"whitespace"
117 		&& a != tok!"comment" && a != tok!"specialTokenSequence").array;
118 
119 	RollbackAllocator allocator;
120 	uint errorCount;
121 	auto mod = parseModule(parseTokens, fileName, &allocator, &reportErrors, &errorCount);
122 	if (errorCount > 0)
123 	{
124 		stderr.writefln("%d parse errors encountered. Aborting upgrade of %s",
125 			errorCount, fileName);
126 		return;
127 	}
128 
129 	File output = File(fileName, "wb");
130 	auto visitor = new DFixVisitor;
131 	visitor.visit(mod);
132 	relocateMarkers(visitor.markers, tokens);
133 
134 	SpecialMarker[] markers = visitor.markers;
135 
136 	auto formatter = new Formatter!(File.LockingTextWriter)(File.LockingTextWriter.init);
137 
138 	void writeType(T)(File output, T tokens, ref size_t i)
139 	{
140 		if (isBasicType(tokens[i].type))
141 		{
142 			writeToken(output, tokens[i]);
143 			i++;
144 		}
145 		else if ((tokens[i] == tok!"const" || tokens[i] == tok!"immutable"
146 				|| tokens[i] == tok!"shared" || tokens[i] == tok!"inout")
147 				&& tokens[i + 1] == tok!"(")
148 		{
149 			writeToken(output, tokens[i]);
150 			i++;
151 			skipAndWrite!("(", ")")(output, tokens, i);
152 		}
153 		else
154 		{
155 			skipIdentifierChain(output, tokens, i, true);
156 			if (i < tokens.length && tokens[i] == tok!"!")
157 			{
158 				writeToken(output, tokens[i]);
159 				i++;
160 				if (i + 1 < tokens.length && tokens[i + 1] == tok!"(")
161 					skipAndWrite!("(", ")")(output, tokens, i);
162 				else if (tokens[i].type == tok!"identifier")
163 					skipIdentifierChain(output, tokens, i, true);
164 				else
165 				{
166 					writeToken(output, tokens[i]);
167 					i++;
168 				}
169 			}
170 		}
171 		skipWhitespace(output, tokens, i);
172 		// print out suffixes
173 		while (i < tokens.length && (tokens[i] == tok!"*" || tokens[i] == tok!"["))
174 		{
175 			if (tokens[i] == tok!"*")
176 			{
177 				writeToken(output, tokens[i]);
178 				i++;
179 			}
180 			else if (tokens[i] == tok!"[")
181 				skipAndWrite!("[", "]")(output, tokens, i);
182 		}
183 	}
184 
185 	for (size_t i = 0; i < tokens.length; i++)
186 	{
187 		markerLoop: foreach (marker; markers)
188 		{
189 			with (SpecialMarkerType) final switch (marker.type)
190 			{
191 			case bodyEnd:
192 				if (tokens[i].index != marker.index)
193 					break;
194 				assert (tokens[i].type == tok!"}", format("%d %s", tokens[i].line, str(tokens[i].type)));
195 				writeToken(output, tokens[i]);
196 				i++;
197 				if (i < tokens.length && tokens[i] == tok!";")
198 					i++;
199 				markers = markers[1 .. $];
200 				break markerLoop;
201 			case functionAttributePrefix:
202 				if (tokens[i].index != marker.index)
203 					break;
204 				// skip over token to be moved
205 				i++;
206 				skipWhitespace(output, tokens, i, false);
207 
208 				// skip over function return type
209 				writeType(output, tokens, i);
210 				skipWhitespace(output, tokens, i);
211 
212 				// skip over function name
213 				skipIdentifierChain(output, tokens, i, true);
214 				skipWhitespace(output, tokens, i, false);
215 
216 				// skip first paramters
217 				skipAndWrite!("(", ")")(output, tokens, i);
218 
219 				immutable bookmark = i;
220 				skipWhitespace(output, tokens, i, false);
221 
222 				// If there is a second set of parameters, go back to the bookmark
223 				// and print out the whitespace
224 				if (i < tokens.length && tokens[i] == tok!"(")
225 				{
226 					i = bookmark;
227 					skipWhitespace(output, tokens, i);
228 					skipAndWrite!("(", ")")(output, tokens, i);
229 					skipWhitespace(output, tokens, i, false);
230 				}
231 				else
232 					i = bookmark;
233 
234 				// write out the attribute being moved
235 				output.write(" ", marker.functionAttribute);
236 
237 				// if there was no whitespace, add it after the moved attribute
238 				if (i < tokens.length && tokens[i] != tok!"whitespace" && tokens[i] != tok!";")
239 					output.write(" ");
240 
241 				markers = markers[1 .. $];
242 				break markerLoop;
243 			case cStyleArray:
244 				if (i != marker.index)
245 					break;
246 				formatter.sink = output.lockingTextWriter();
247 				foreach (node; retro(marker.nodes))
248 					formatter.format(node);
249 				formatter.sink = File.LockingTextWriter.init;
250 				skipWhitespace(output, tokens, i);
251 				writeToken(output, tokens[i]);
252 				i++;
253 				suffixLoop: while (i < tokens.length) switch (tokens[i].type)
254 				{
255 					case tok!"(": skipAndWrite!("(", ")")(output, tokens, i); break;
256 					case tok!"[": skip!("[", "]")(tokens, i); break;
257 					case tok!"*": i++; break;
258 					default: break suffixLoop;
259 				}
260 				markers = markers[1 .. $];
261 				break markerLoop;
262 			}
263 		}
264 
265 		if (i >= tokens.length)
266 			break;
267 
268 		switch (tokens[i].type)
269 		{
270 		case tok!"asm":
271 			skipAsmBlock(output, tokens, i);
272 			goto default;
273 		case tok!"catch":
274 			if (!dip65)
275 				goto default;
276 			size_t j = i + 1;
277 			while (j < tokens.length && (tokens[j] == tok!"whitespace" || tokens[j] == tok!"comment"))
278 				j++;
279 			if (j < tokens.length && tokens[j].type != tok!"(")
280 			{
281 				output.write("catch (Throwable)");
282 				break;
283 			}
284 			else
285 				goto default;
286 		case tok!"deprecated":
287 			if (dip64)
288 				output.write("@");
289 			output.writeToken(tokens[i]);
290 			i++;
291 			if (i < tokens.length && tokens[i] == tok!"(")
292 				skipAndWrite!("(", ")")(output, tokens, i);
293 			if (i < tokens.length)
294 				goto default;
295 			else
296 				break;
297 		case tok!"stringLiteral":
298 			immutable size_t stringBookmark = i;
299 			while (tokens[i] == tok!"stringLiteral")
300 			{
301 				i++;
302 				skipWhitespace(output, tokens, i, false);
303 			}
304 			immutable bool parensNeeded = stringBookmark + 1 != i && tokens[i] == tok!".";
305 			i = stringBookmark;
306 			if (parensNeeded)
307 				output.write("(");
308 			output.writeToken(tokens[i]);
309 			i++;
310 			skipWhitespace(output, tokens, i);
311 			while (tokens[i] == tok!"stringLiteral")
312 			{
313 				output.write("~ ");
314 				output.writeToken(tokens[i]);
315 				i++;
316 				skipWhitespace(output, tokens, i);
317 			}
318 			if (parensNeeded)
319 				output.write(")");
320 			if (i < tokens.length)
321 				goto default;
322 			else
323 				break;
324 		case tok!"override":
325 		case tok!"final":
326 		case tok!"abstract":
327 		case tok!"align":
328 		case tok!"pure":
329 		case tok!"nothrow":
330 			if (!dip64)
331 				goto default;
332 			output.write("@");
333 			output.write(str(tokens[i].type));
334 			break;
335 		case tok!"alias":
336 			bool multipleAliases = false;
337 			bool oldStyle = true;
338 			output.writeToken(tokens[i]); // alias
339 				i++;
340 			size_t j = i + 1;
341 
342 			int depth;
343 			loop: while (j < tokens.length) switch (tokens[j].type)
344 			{
345 			case tok!"(":
346 				depth++;
347 				j++;
348 				break;
349 			case tok!")":
350 				depth--;
351 				if (depth < 0)
352 				{
353 					oldStyle = false;
354 					break loop;
355 				}
356 				j++;
357 				break;
358 			case tok!"=":
359 			case tok!"this":
360 				j++;
361 				oldStyle = false;
362 				break;
363 			case tok!",":
364 				j++;
365 				if (depth == 0)
366 					multipleAliases = true;
367 				break;
368 			case tok!";":
369 				break loop;
370 			default:
371 				j++;
372 				break;
373 			}
374 
375 			if (!oldStyle) foreach (k; i .. j + 1)
376 			{
377 				output.writeToken(tokens[k]);
378 				i = k;
379 			}
380 			else
381 			{
382 				skipWhitespace(output, tokens, i);
383 
384 				size_t beforeStart = i;
385 				size_t beforeEnd = beforeStart;
386 
387 				loop2: while (beforeEnd < tokens.length) switch (tokens[beforeEnd].type)
388 				{
389 				case tok!"bool":
390 				case tok!"byte":
391 				case tok!"ubyte":
392 				case tok!"short":
393 				case tok!"ushort":
394 				case tok!"int":
395 				case tok!"uint":
396 				case tok!"long":
397 				case tok!"ulong":
398 				case tok!"char":
399 				case tok!"wchar":
400 				case tok!"dchar":
401 				case tok!"float":
402 				case tok!"double":
403 				case tok!"real":
404 				case tok!"ifloat":
405 				case tok!"idouble":
406 				case tok!"ireal":
407 				case tok!"cfloat":
408 				case tok!"cdouble":
409 				case tok!"creal":
410 				case tok!"void":
411 					beforeEnd++;
412 					break loop2;
413 				case tok!".":
414 					beforeEnd++;
415 					goto case;
416 				case tok!"identifier":
417 					skipIdentifierChain(output, tokens, beforeEnd);
418 					break loop2;
419 				case tok!"typeof":
420 					beforeEnd++;
421 					skip!("(", ")")(tokens, beforeEnd);
422 					skipWhitespace(output, tokens, beforeEnd, false);
423 					if (tokens[beforeEnd] == tok!".")
424 						skipIdentifierChain(output, tokens, beforeEnd);
425 					break loop2;
426 				case tok!"@":
427 					beforeEnd++;
428 					if (tokens[beforeEnd] == tok!"identifier")
429 						beforeEnd++;
430 					if (tokens[beforeEnd] == tok!"(")
431 						skip!("(", ")")(tokens, beforeEnd);
432 					skipWhitespace(output, tokens, beforeEnd, false);
433 					break;
434 				case tok!"static":
435 				case tok!"const":
436 				case tok!"immutable":
437 				case tok!"inout":
438 				case tok!"shared":
439 				case tok!"extern":
440 				case tok!"nothrow":
441 				case tok!"pure":
442 				case tok!"__vector":
443 					beforeEnd++;
444 					skipWhitespace(output, tokens, beforeEnd, false);
445 					if (tokens[beforeEnd] == tok!"(")
446 						skip!("(", ")")(tokens, beforeEnd);
447 					if (beforeEnd >= tokens.length)
448 						break loop2;
449 					size_t k = beforeEnd;
450 					skipWhitespace(output, tokens, k, false);
451 					if (k + 1 < tokens.length && tokens[k + 1].type == tok!";")
452 						break loop2;
453 					else
454 						beforeEnd = k;
455 					break;
456 				default:
457 					break loop2;
458 				}
459 
460 				i = beforeEnd;
461 
462 				skipWhitespace(output, tokens, i, false);
463 
464 				if (tokens[i] == tok!"*" || tokens[i] == tok!"["
465 					|| tokens[i] == tok!"function" || tokens[i] == tok!"delegate")
466 				{
467 					beforeEnd = i;
468 				}
469 
470 				loop3: while (beforeEnd < tokens.length) switch (tokens[beforeEnd].type)
471 				{
472 				case tok!"*":
473 					beforeEnd++;
474 					size_t m = beforeEnd;
475 					skipWhitespace(output, tokens, m, false);
476 					if (m < tokens.length && (tokens[m] == tok!"*"
477 						|| tokens[m] == tok!"[" || tokens[m] == tok!"function"
478 						|| tokens[m] == tok!"delegate"))
479 					{
480 						beforeEnd = m;
481 					}
482 					break;
483 				case tok!"[":
484 					skip!("[", "]")(tokens, beforeEnd);
485 					size_t m = beforeEnd;
486 					skipWhitespace(output, tokens, m, false);
487 					if (m < tokens.length && (tokens[m] == tok!"*"
488 						|| tokens[m] == tok!"[" || tokens[m] == tok!"function"
489 						|| tokens[m] == tok!"delegate"))
490 					{
491 						beforeEnd = m;
492 					}
493 					break;
494 				case tok!"function":
495 				case tok!"delegate":
496 					beforeEnd++;
497 					skipWhitespace(output, tokens, beforeEnd, false);
498 					skip!("(", ")")(tokens, beforeEnd);
499 					size_t l = beforeEnd;
500 					skipWhitespace(output, tokens, l, false);
501 					loop4: while (l < tokens.length) switch (tokens[l].type)
502 					{
503 					case tok!"const":
504 					case tok!"nothrow":
505 					case tok!"pure":
506 					case tok!"immutable":
507 					case tok!"inout":
508 					case tok!"shared":
509 						beforeEnd = l + 1;
510 						l = beforeEnd;
511 						skipWhitespace(output, tokens, l, false);
512 						if (l < tokens.length && tokens[l].type == tok!"identifier")
513 						{
514 							beforeEnd = l - 1;
515 							break loop4;
516 						}
517 						break;
518 					case tok!"@":
519 						beforeEnd = l + 1;
520 						skipWhitespace(output, tokens, beforeEnd, false);
521 						if (tokens[beforeEnd] == tok!"(")
522 							skip!("(", ")")(tokens, beforeEnd);
523 						else
524 						{
525 							beforeEnd++; // identifier
526 							skipWhitespace(output, tokens, beforeEnd, false);
527 							if (tokens[beforeEnd] == tok!"(")
528 								skip!("(", ")")(tokens, beforeEnd);
529 						}
530 						l = beforeEnd;
531 						skipWhitespace(output, tokens, l, false);
532 						if (l < tokens.length && tokens[l].type == tok!"identifier")
533 						{
534 							beforeEnd = l - 1;
535 							break loop4;
536 						}
537 						break;
538 					default:
539 						break loop4;
540 					}
541 					break;
542 				default:
543 					break loop3;
544 				}
545 
546 				i = beforeEnd;
547 				skipWhitespace(output, tokens, i, false);
548 
549 				output.writeToken(tokens[i]);
550 				output.write(" = ");
551 				foreach (l; beforeStart .. beforeEnd)
552 					output.writeToken(tokens[l]);
553 
554 				if (multipleAliases)
555 				{
556 					i++;
557 					skipWhitespace(output, tokens, i, false);
558 					while (tokens[i] == tok!",")
559 					{
560 						i++; // ,
561 						output.write(", ");
562 						skipWhitespace(output, tokens, i, false);
563 						output.writeToken(tokens[i]);
564 						output.write(" = ");
565 						foreach (l; beforeStart .. beforeEnd)
566 							output.writeToken(tokens[l]);
567 					}
568 				}
569 			}
570 			break;
571 		default:
572 			output.writeToken(tokens[i]);
573 			break;
574 		}
575 	}
576 }
577 
578 /**
579  * The types of special token ranges identified by the parsing pass
580  */
581 enum SpecialMarkerType
582 {
583 	/// Function declarations such as "const int foo();"
584 	functionAttributePrefix,
585 	/// Variable and parameter declarations such as "int bar[]"
586 	cStyleArray,
587 	/// The location of a closing brace for an interface, class, struct, union,
588 	/// or enum.
589 	bodyEnd
590 }
591 
592 /**
593  * Identifies ranges of tokens in the source tokens that need to be rewritten
594  */
595 struct SpecialMarker
596 {
597 	/// Range type
598 	SpecialMarkerType type;
599 
600 	/// Begin byte position (before relocateMarkers) or token index
601 	/// (after relocateMarkers)
602 	size_t index;
603 
604 	/// The type suffix AST nodes that should be moved
605 	const(TypeSuffix[]) nodes;
606 
607 	/// The function attribute such as const, immutable, or inout to move
608 	string functionAttribute;
609 }
610 
611 /**
612  * Scans a module's parsed AST and looks for C-style array variables and
613  * parameters, storing the locations in the markers array.
614  */
615 class DFixVisitor : ASTVisitor
616 {
617 	// C-style arrays variables
618 	override void visit(const VariableDeclaration varDec)
619 	{
620 		if (varDec.declarators.length == 0)
621 			return;
622 		markers ~= SpecialMarker(SpecialMarkerType.cStyleArray,
623 			varDec.declarators[0].name.index, varDec.declarators[0].cstyle);
624 	}
625 
626 	// C-style array parameters
627 	override void visit(const Parameter param)
628 	{
629 		if (param.cstyle.length > 0)
630 			markers ~= SpecialMarker(SpecialMarkerType.cStyleArray, param.name.index,
631 				param.cstyle);
632 		param.accept(this);
633 	}
634 
635 	// interface, union, class, struct body closing braces
636 	override void visit(const StructBody structBody)
637 	{
638 		structBody.accept(this);
639 		markers ~= SpecialMarker(SpecialMarkerType.bodyEnd, structBody.endLocation);
640 	}
641 
642 	// enum body closing braces
643 	override void visit(const EnumBody enumBody)
644 	{
645 		enumBody.accept(this);
646 		// skip over enums whose body is a single semicolon
647 		if (enumBody.endLocation == 0 && enumBody.startLocation == 0)
648 			return;
649 		markers ~= SpecialMarker(SpecialMarkerType.bodyEnd, enumBody.endLocation);
650 	}
651 
652 	// Confusing placement of function attributes
653 	override void visit(const Declaration dec)
654 	{
655 		if (dec.functionDeclaration is null)
656 			goto end;
657 		if (dec.attributes.length == 0)
658 			goto end;
659 		foreach (attr; dec.attributes)
660 		{
661 			if (attr.attribute == tok!"")
662 				continue;
663 			if (attr.attribute == tok!"const"
664 				|| attr.attribute == tok!"inout"
665 				|| attr.attribute == tok!"immutable")
666 			{
667 				markers ~= SpecialMarker(SpecialMarkerType.functionAttributePrefix,
668 					attr.attribute.index, null, str(attr.attribute.type));
669 			}
670 		}
671 	end:
672 		dec.accept(this);
673 	}
674 
675 	alias visit = ASTVisitor.visit;
676 
677 	/// Parts of the source file identified as needing a rewrite
678 	SpecialMarker[] markers;
679 }
680 
681 /**
682  * Converts the marker index from a byte index into the source code to an index
683  * into the tokens array.
684  */
685 void relocateMarkers(SpecialMarker[] markers, const(Token)[] tokens) pure nothrow @nogc
686 {
687 	foreach (ref marker; markers)
688 	{
689 		if (marker.type != SpecialMarkerType.cStyleArray)
690 			continue;
691 		size_t index = 0;
692 		while (tokens[index].index != marker.index)
693 			index++;
694 		marker.index = index - 1;
695 	}
696 }
697 
698 /**
699  * Writes a token to the output file.
700  */
701 void writeToken(File output, ref const(Token) token)
702 {
703 	output.write(token.text is null ? str(token.type) : token.text);
704 }
705 
706 void skipAndWrite(alias Open, alias Close)(File output, const(Token)[] tokens, ref size_t index)
707 {
708 	int depth = 1;
709 	writeToken(output, tokens[index]);
710 	index++;
711 	while (index < tokens.length && depth > 0) switch (tokens[index].type)
712 	{
713 	case tok!Open:
714 		depth++;
715 		writeToken(output, tokens[index]);
716 		index++;
717 		break;
718 	case tok!Close:
719 		depth--;
720 		writeToken(output, tokens[index]);
721 		index++;
722 		break;
723 	default:
724 		writeToken(output, tokens[index]);
725 		index++;
726 		break;
727 	}
728 }
729 
730 /**
731  * Skips balanced parens, braces, or brackets. index will be incremented to
732  * index tokens just after the balanced closing token.
733  */
734 void skip(alias Open, alias Close)(const(Token)[] tokens, ref size_t index)
735 {
736 	int depth = 1;
737 	index++;
738 	while (index < tokens.length && depth > 0) switch (tokens[index].type)
739 	{
740 	case tok!Open: depth++;  index++; break;
741 	case tok!Close: depth--; index++; break;
742 	default:                 index++; break;
743 	}
744 }
745 
746 /**
747  * Skips whitespace tokens, incrementing index until it indexes tokens at a
748  * non-whitespace token.
749  */
750 void skipWhitespace(File output, const(Token)[] tokens, ref size_t index, bool print = true)
751 {
752 	while (index < tokens.length && (tokens[index] == tok!"whitespace" || tokens[index] == tok!"comment"))
753 	{
754 		if (print) output.writeToken(tokens[index]);
755 		index++;
756 	}
757 }
758 
759 /**
760  * Advances index until it indexs the token just after an identifier or template
761  * chain.
762  */
763 void skipIdentifierChain(File output, const(Token)[] tokens, ref size_t index, bool print = false)
764 {
765 	loop: while (index < tokens.length) switch (tokens[index].type)
766 	{
767 	case tok!".":
768 		if (print)
769 			writeToken(output, tokens[index]);
770 		index++;
771 		skipWhitespace(output, tokens, index, false);
772 		break;
773 	case tok!"identifier":
774 		if (print)
775 			writeToken(output, tokens[index]);
776 		index++;
777 		size_t i = index;
778 		skipWhitespace(output, tokens, i, false);
779 		if (tokens[i] == tok!"!")
780 		{
781 			i++;
782 			if (print)
783 				writeToken(output, tokens[index]);
784 			index++;
785 			skipWhitespace(output, tokens, i, false);
786 			if (tokens[i] == tok!"(")
787 			{
788 				if (print)
789 					skipAndWrite!("(", ")")(output, tokens, i);
790 				else
791 					skip!("(", ")")(tokens, i);
792 				index = i;
793 			}
794 			else
795 			{
796 				i++;
797 				if (print)
798 					writeToken(output, tokens[index]);
799 				index++;
800 			}
801 		}
802 		if (tokens[i] != tok!".")
803 			break loop;
804 		break;
805 	case tok!"whitespace":
806 		index++;
807 		break;
808 	default:
809 		break loop;
810 	}
811 }
812 
813 /**
814  * Skips over an attribute
815  */
816 void skipAttribute(File output, const(Token)[] tokens, ref size_t i)
817 {
818 	switch (tokens[i].type)
819 	{
820 	case tok!"@":
821 		output.writeToken(tokens[i]);
822 		i++; // @
823 		skipWhitespace(output, tokens, i, true);
824 		switch (tokens[i].type)
825 		{
826 		case tok!"identifier":
827 			output.writeToken(tokens[i]);
828 			i++; // identifier
829 			skipWhitespace(output, tokens, i, true);
830 			if (tokens[i].type == tok!"(")
831 				goto case tok!"(";
832 			break;
833 		case tok!"(":
834 			int depth = 1;
835 			output.writeToken(tokens[i]);
836 			i++;
837 			while (i < tokens.length && depth > 0) switch (tokens[i].type)
838 			{
839 			case tok!"(": depth++; output.writeToken(tokens[i]); i++; break;
840 			case tok!")": depth--; output.writeToken(tokens[i]); i++; break;
841 			default:               output.writeToken(tokens[i]); i++; break;
842 			}
843 			break;
844 		default:
845 			break;
846 		}
847 		break;
848 	case tok!"nothrow":
849 	case tok!"pure":
850 		output.writeToken(tokens[i]);
851 		i++;
852 		break;
853 	default:
854 		break;
855 	}
856 }
857 
858 /**
859  * Skips over (and prints) an asm block
860  */
861 void skipAsmBlock(File output, const(Token)[] tokens, ref size_t i)
862 {
863 	import std.exception : enforce;
864 
865 	output.write("asm");
866 	i++; // asm
867 	skipWhitespace(output, tokens, i);
868 	loop: while (true) switch (tokens[i].type)
869 	{
870 	case tok!"@":
871 	case tok!"nothrow":
872 	case tok!"pure":
873 		skipAttribute(output, tokens, i);
874 		skipWhitespace(output, tokens, i);
875 		break;
876 	case tok!"{":
877 		break loop;
878 	default:
879 		break loop;
880 	}
881 	enforce(tokens[i].type == tok!"{");
882 	output.write("{");
883 	i++; // {
884 	int depth = 1;
885 	while (depth > 0 && i < tokens.length) switch (tokens[i].type)
886 	{
887 	case tok!"{": depth++; goto default;
888 	case tok!"}": depth--; goto default;
889 	default: writeToken(output, tokens[i]); i++; break;
890 	}
891 }
892 
893 /**
894  * Dummy message output function for the lexer/parser
895  */
896 void reportErrors(string fileName, size_t lineNumber, size_t columnNumber,
897 	string message, bool isError)
898 {
899 	import std.stdio : stderr;
900 
901 	if (!isError)
902 		return;
903 	stderr.writefln("%s(%d:%d)[error]: %s", fileName, lineNumber, columnNumber, message);
904 }