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