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