1 // This file is part of Visual D
2 //
3 // Visual D integrates the D programming language into Visual Studio
4 // Copyright (c) 2010 by Rainer Schuetze, All Rights Reserved
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt
8 
9 module c2d.tokutil;
10 
11 import c2d.tokenizer;
12 import c2d.dlist;
13 import c2d.dgutil;
14 
15 import std.string;
16 import std.ascii;
17 import std.array;
18 //static import std.regexp;
19 static import std.regex;
20 static import std.conv;
21 
22 //////////////////////////////////////////////////////////////////////////////
23 alias DList!(c2d.tokenizer.Token) TokenList;
24 alias DListIterator!(c2d.tokenizer.Token) TokenIterator;
25 
26 alias object.AssociativeArray!(string, const(TokenList)) _wa2; // fully instantiate type info for TokenList[string]
27 
28 struct TokenRange
29 {
30 	TokenIterator start;
31 	TokenIterator end;
32 }
33 
34 struct SubMatch
35 {
36 	string ident;
37 	TokenIterator start;
38 	TokenIterator end;
39 }
40 
41 //////////////////////////////////////////////////////////////////////////////
42 Token createToken(string pretext, string text, int type, int lineno)
43 {
44 	Token tok = new Token();
45 	tok.pretext = pretext;
46 	tok.text = text;
47 	tok.type = type;
48 	tok.lineno = lineno;
49 	return tok;
50 }
51 
52 Token createToken(Token tok)
53 {
54 	Token ntok = new Token();
55 	ntok.pretext = tok.pretext;
56 	ntok.text = tok.text;
57 	ntok.type = tok.type;
58 	ntok.lineno = tok.lineno;
59 	return ntok;
60 }
61 
62 bool isCommentToken(Token tok, bool checkPP = true)
63 {
64 	return tok.type == Token.Comment || tok.type == Token.Newline || (checkPP && Token.isPPToken(tok.type));
65 }
66 
67 void skipComments(ref TokenIterator tokIt, bool skipPP = true)
68 {
69 	while (!tokIt.atEnd() && isCommentToken(*tokIt, skipPP))
70 		tokIt.advance();
71 }
72 
73 void comment_line(ref TokenIterator tokIt)
74 {
75 	TokenIterator it = tokIt + 1;
76 	string txt = tokIt.pretext ~ "// " ~ tokIt.text;
77 	while(!it.atEnd() && it.pretext.indexOf('\n') < 0 && it.type != Token.EOF)
78 	{
79 		txt ~= it.pretext ~ it.text;
80 		it.advance();
81 	}
82 	if(!it.atEnd())
83 	{
84 		tokIt.eraseUntil(it);
85 		tokIt.pretext = txt ~ tokIt.pretext;
86 	}
87 	else
88 		tokIt.text = "// " ~ tokIt.text;
89 }
90 
91 void nextToken(ref TokenIterator tokIt, bool skipPP = true)
92 {
93 	tokIt.advance();
94 	skipComments(tokIt, skipPP);
95 }
96 
97 void checkToken(ref TokenIterator tokIt, int type, bool skipPP = true)
98 {
99 	skipComments(tokIt, skipPP);
100 	
101 	if(tokIt.atEnd() ||tokIt.type != type)
102 	{
103 		string txt = tokIt.atEnd() ? "EOF" : tokIt.text;
104 		int lineno = tokIt.atEnd() ? (tokIt-1).atEnd() ? -1 : tokIt[-1].lineno : tokIt.lineno;
105 		throwException(lineno, "expected " ~ Token.toString(type) ~ " instead of " ~ txt);
106 	}
107 	nextToken(tokIt, skipPP);
108 }
109 
110 void checkOperator(ref TokenIterator tokIt)
111 {
112 	// TODO: allows any token 
113 	if(tokIt.type == Token.BracketL)
114 	{
115 		nextToken(tokIt);
116 		checkToken(tokIt, Token.BracketR);
117 	}
118 	else
119 		nextToken(tokIt);
120 }
121 
122 string tokensToIdentifier(TokenIterator start, TokenIterator end)
123 {
124 	string ident;
125 	while(!start.atEnd() && start != end)
126 	{
127 		if(ident.length > 0 && start.text.length > 0)
128 			if(isAlphaNum(ident[$-1]) && isAlphaNum(start.text[0]))
129 				ident ~= " ";
130 		ident ~= start.text;
131 		++start;
132 	}
133 	return ident;
134 }
135 
136 void identifierToKeywords(TokenIterator start, TokenIterator end)
137 {
138 	while(!start.atEnd() && start != end)
139 	{
140 		if(start.type == Token.Identifier)
141 			start.type = Tokenizer.identifierToKeyword(start.text);
142 		++start;
143 	}
144 }
145 
146 void identifierToKeywords(TokenList list)
147 {
148 	return identifierToKeywords(list.begin(), list.end());
149 }
150 
151 //////////////////////////////////////////////////////////////////////////////
152 TokenList copyTokenList(TokenIterator start, TokenIterator end, bool cloneTokens = true)
153 {
154 	TokenList tokenList = new TokenList;
155 	for(TokenIterator it = start; it != end; ++it)
156 	{
157 		Token tok = cloneTokens ? createToken(*it) : *it;
158 		tokenList.append(tok);
159 	}
160 	return tokenList;
161 }
162 
163 TokenList copyTokenList(TokenRange range, bool cloneTokens = true)
164 {
165 	return copyTokenList(range.start, range.end, cloneTokens);
166 }
167 
168 TokenList copyTokenList(TokenList tokenList, bool cloneTokens = true)
169 {
170 	return copyTokenList(tokenList.begin(), tokenList.end(), cloneTokens);
171 }
172 
173 TokenIterator insertTokenList(TokenIterator insBefore, TokenList tokenList)
174 {
175 	if(tokenList.empty())
176 		return insBefore;
177 	TokenIterator endit = tokenList.end() - 1;
178 	if(endit.type == Token.EOF && !insBefore.atEnd())
179 	{
180 		insBefore.pretext = endit.pretext ~ insBefore.pretext;
181 		endit.erase;
182 	}
183 	return insBefore.insertListBefore(tokenList);
184 }
185 
186 string tokenListToString(TokenIterator start, TokenIterator end, bool checkSpaceBetweenIdentifiers = false,
187 			 bool normalizePreText = false)
188 {
189 	string text;
190 	string prevtext;
191 	for(TokenIterator tokIt = start; tokIt != end; ++tokIt)
192 	{
193 		Token tok = *tokIt;
194 		string txt = normalizePreText ? tok.text : tok.pretext ~ tok.text;
195 		if(checkSpaceBetweenIdentifiers || normalizePreText)
196 		{
197 			if (prevtext == "__")
198 				txt = tok.text;
199 			else if (tok.text == "__")
200 				txt = "";
201 			else if (txt.length && prevtext.length)
202 			{
203 				char prevch = prevtext[$-1];
204 				char ch = txt[0];
205 				if((isAlphaNum(ch) || ch == '_') && (isAlphaNum(prevch) || prevch == '_'))
206 					txt = " " ~ txt;
207 			}
208 			prevtext = tok.text;
209 		}
210 		text ~= txt;
211 	}
212 	return text;
213 }
214 
215 string tokenListToString(TokenList tokenList, bool checkSpaceBetweenIdentifiers = false)
216 {
217 	return tokenListToString(tokenList.begin(), tokenList.end(), checkSpaceBetweenIdentifiers);
218 }
219 
220 bool compareTokenList(TokenIterator start1, TokenIterator end1, TokenIterator start2, TokenIterator end2)
221 {
222 	TokenIterator it1 = start1;
223 	TokenIterator it2 = start2;
224 	for( ; it1 != end1 && it2 != end2; ++it1, ++it2)
225 		if(it1.text != it2.text)
226 			return false;
227 
228 	return it1 == end1 && it2 == end2;
229 }
230 
231 //////////////////////////////////////////////////////////////////////////////
232 void reindentList(TokenIterator start, TokenIterator end, int indent, int tabsize)
233 {
234 	for(TokenIterator tokIt = start; tokIt != end; ++tokIt)
235 		tokIt.pretext = reindent(tokIt.pretext, indent, tabsize);
236 }
237 
238 void reindentList(TokenList tokenList, int indent, int tabsize)
239 {
240 	return reindentList(tokenList.begin(), tokenList.end(), indent, tabsize);
241 }
242 
243 //////////////////////////////////////////////////////////////////////////////
244 bool isClosingBracket(int type)
245 {
246 	return (type == Token.BraceR || type == Token.BracketR || type == Token.ParenR);
247 }
248 
249 bool isOpeningBracket(int type)
250 {
251 	return (type == Token.BraceL || type == Token.BracketL || type == Token.ParenL);
252 }
253 
254 bool isBracketPair(dchar ch1, dchar ch2)
255 {
256 	switch(ch1)
257 	{
258 		case '{': return ch2 == '}';
259 		case '}': return ch2 == '{';
260 		case '(': return ch2 == ')';
261 		case ')': return ch2 == ')';
262 		case '[': return ch2 == ']';
263 		case ']': return ch2 == '[';
264 		default:  return false;
265 	}
266 }
267 
268 //////////////////////////////////////////////////////////////////////////////
269 // iterator on token after closing bracket
270 bool advanceToClosingBracket(ref TokenIterator it, TokenIterator stopIt)
271 {
272 	TokenIterator prevIt = it; // for debugging
273 	int lineno = it.lineno;
274 	int open = it.type;
275 	int close;
276 	switch(open)
277 	{
278 	case Token.ParenL:
279 		close = Token.ParenR;
280 		break;
281 	case Token.BraceL:
282 		close = Token.BraceR;
283 		break;
284 	case Token.BracketL:
285 		close = Token.BracketR;
286 		break;
287 	default:
288 		throwException(lineno, "opening bracket expected instead of " ~ it.text);
289 	}
290 
291 	int level = 1;
292 	++it;
293 	while (level > 0)
294 	{
295 		if(it == stopIt)
296 			return false;
297 		if(it.atEnd())
298 			throwException(lineno, "end of file while looking for closing bracket");
299 		if(it.type == open)
300 			level++;
301 		else if(it.type == close)
302 			level--;
303 		++it;
304 	}
305 	return true;
306 }
307 
308 bool advanceToClosingBracket(ref TokenIterator it)
309 {
310 	TokenIterator noStop;
311 	return advanceToClosingBracket(it, noStop);
312 }
313 
314 // iterator on token with opening bracket
315 bool retreatToOpeningBracket(ref TokenIterator it, TokenIterator stopIt)
316 {
317 	int lineno = it.lineno;
318 	int open;
319 	int close = it.type;
320 	switch(close)
321 	{
322 	case Token.ParenR:
323 		open = Token.ParenL;
324 		break;
325 	case Token.BraceR:
326 		open = Token.BraceL;
327 		break;
328 	case Token.BracketR:
329 		open = Token.BracketL;
330 		break;
331 	default:
332 		throwException(lineno, "closing bracket expected instead of " ~ it.text);
333 	}
334 
335 	int level = 1;
336 	while (level > 0)
337 	{
338 		--it;
339 		if(it == stopIt)
340 			return false;
341 		if(it.atEnd())
342 			throwException(lineno, "beginnig of file while looking for opening bracket");
343 		if(it.type == close)
344 			level++;
345 		else if(it.type == open)
346 			level--;
347 	}
348 	return true;
349 }
350 
351 bool retreatToOpeningBracket(ref TokenIterator it)
352 {
353 	TokenIterator noStop;
354 	return retreatToOpeningBracket(it, noStop);
355 }
356 
357 //////////////////////////////////////////////////////////////////////////////
358 static void scanAny(TL)(ref TL tokenList, string text, int lineno = 1, bool combinePP = true)
359 {
360 	Tokenizer tokenizer = new Tokenizer(text);
361 	tokenizer.keepBackSlashAtEOL = true;
362 	tokenizer.lineno = lineno;
363 
364 	try
365 	{
366 		string pretext;
367 		Token pptok = new Token;
368 		Token tok;
369 		do
370 		{
371 			tok = new Token;
372 			tokenizer.next(tok);
373 
374 			if(combinePP && Token.isPPToken(tok.type))
375 			{
376 				tokenizer.skipNewline = false;
377 				while(tokenizer.next(pptok) && pptok.type != Token.Newline)
378 					tok.text ~= pptok.pretext ~ pptok.text;
379 				tokenizer.skipNewline = true;
380 				tok.text ~= pptok.pretext;
381 				if(pptok.type == Token.Newline)
382 					tok.text ~= "\n";
383 			}
384 			switch(tok.type)
385 			{
386 			case Token.Comment:
387 				if(startsWith(tok.text, ";")) // aasm comment?
388 					pretext ~= tok.pretext ~ "//" ~ tok.text;
389 				else
390 					pretext ~= tok.pretext ~ tok.text;
391 				break;
392 
393 			case Token.__Asm:
394 				tokenizer.enableASMComment = true;
395 				tokenizer.skipNewline = false;
396 				goto default;
397 
398 			case Token.BraceR:
399 				if(tokenizer.enableASMComment)
400 				{
401 					tokenizer.enableASMComment = false;
402 					tokenizer.skipNewline = true;
403 				}
404 				goto default;
405 
406 			default:
407 				tok.pretext = pretext ~ tok.pretext;
408 				static if(is(TL == Token[]))
409 					tokenList ~= tok;
410 				else static if(is(TL == string[]))
411 					tokenList ~= tok.text;
412 				else
413 					tokenList.append(tok);
414 				pretext = "";
415 				break;
416 			}
417 		} 
418 		while (tok.type != Token.EOF);
419 
420 	}
421 	catch(Exception e)
422 	{
423 		e.msg = "(" ~ std.conv.text(tokenizer.lineno) ~ "):" ~ e.msg;
424 		throw e;
425 	}
426 }
427 
428 TokenList scanText(string text, int lineno = 1, bool combinePP = true)
429 {
430 	TokenList tokenList = new TokenList;
431 	scanAny(tokenList, text, lineno, combinePP);
432 	return tokenList;
433 }
434 
435 void scanTextArray(TYPE)(ref TYPE[] tokens, string text, int lineno = 1, bool combinePP = true)
436 {
437 	scanAny(tokens, text, lineno, combinePP);
438 
439 	static if(is(TYPE == string))
440 	{
441 		while(tokens.length > 0 && tokens[$-1].length == 0)
442 			tokens = tokens[0..$-1];
443 	}
444 	else
445 	{
446 		while(tokens.length > 0 && tokens[$-1].text.length == 0)
447 			tokens = tokens[0..$-1];
448 	}
449 }
450 
451 ///////////////////////////////////////////////////////////////////////
452 int findSubmatch(ref SubMatch[] submatch, string ident)
453 {
454 	for(int i = 0; i < submatch.length; i++)
455 		if(submatch[i].ident == ident)
456 			return i;
457 	return -1;
458 }
459 
460 ///////////////////////////////////////////////////////////////////////
461 bool findTokenSequence(TokenIterator it, string[] search, bool checkBracketsSearch, bool checkBracketsMatch,
462                        string stopText, ref TokenRange match, ref SubMatch[] submatch)
463 {
464 	if(search.length == 0)
465 	{
466 		match.start = it;
467 		match.end = it;
468 		return true;
469 	}
470 
471 	void addSubmatch(string search, TokenIterator start, TokenIterator end)
472 	{
473 		SubMatch smatch;
474 		smatch.ident = search;
475 		smatch.start = start;
476 		smatch.end = end;
477 		submatch ~= smatch;
478 	}
479 
480 	bool compareTokens(TokenIterator start, TokenIterator end, ref TokenIterator it)
481 	{
482 		for(TokenIterator sit = start; !sit.atEnd() && sit != end; ++sit)
483 		{
484 			string sittext = strip(sit.text);
485 			if(sittext.length == 0)
486 				continue;
487 			while(!it.atEnd() && strip(it.text).length == 0)
488 				++it;
489 			if(it.atEnd())
490 				return false;
491 			if(strip(it.text) != sittext)
492 				return false;
493 			++it;
494 		}
495 		return true;
496 	}
497 	bool compareSubmatch(ref SubMatch sm, string txt)
498 	{
499 		string s = tokenListToString(sm.start, sm.end);
500 		return strip(s) == strip(txt);
501 	}
502 
503 	size_t p = 0;
504 	while(p < search.length && search[p].length == 0)
505 		p++;
506 	if(p >= search.length)
507 		return false;
508 
509 	size_t prevsubmatchLength = submatch.length;
510 
511 	while(!it.atEnd() && (stopText.length == 0 || it.text != stopText || search[p] == stopText))
512 	{
513 		bool dollar = indexOf(search[p], '$') >= 0;
514 		if(strip(it.text) == search[p] || dollar)
515 		{
516 			TokenIterator mit = it + (dollar ? 0 : 1);
517 			size_t i = p + (dollar ? 0 : 1);
518 			while(i < search.length && search[i].length == 0)
519 				i++;
520 			while(!mit.atEnd() && i < search.length)
521 			{
522 				string mittext = strip(mit.text);
523 				if(mittext.length == 0)
524 				{
525 					++mit;
526 					continue;
527 				}
528 				if(startsWith(search[i], "$"))
529 				{
530 					int idx = findSubmatch(submatch, search[i]);
531 					if(idx >= 0)
532 					{
533 						if(!compareTokens(submatch[idx].start, submatch[idx].end, mit))
534 							goto Lnomatch;
535 						goto LnoAdvance;
536 					}
537 					else if(startsWith(search[i], "$_num"))
538 					{
539 						if(mit.type != Token.Number)
540 							break;
541 						addSubmatch(search[i], mit, mit + 1);
542 					}
543 					else if(startsWith(search[i], "$_string"))
544 					{
545 						if(mit.type != Token.String)
546 							break;
547 						addSubmatch(search[i], mit, mit + 1);
548 					}
549 					else if(startsWith(search[i], "$_ident"))
550 					{
551 						if(mit.type != Token.Identifier)
552 							break;
553 						addSubmatch(search[i], mit, mit + 1);
554 					}
555 					else if(startsWith(search[i], "$_dotident"))
556 					{
557 						if(mit.type != Token.Identifier)
558 							break;
559 
560 						TokenIterator start = mit;
561 						while(!(mit + 1).atEnd() && !(mit + 2).atEnd() && 
562 						       mit[1].type == Token.Dot && mit[2].type == Token.Identifier)
563 						{
564 							mit.advance();
565 							mit.advance();
566 						}
567 						addSubmatch(search[i], start, mit + 1);
568 					}
569 					else if(startsWith(search[i], "$_expr"))
570 					{
571 						// ok to allow empty expression?
572 						TokenRange tailmatch;
573 						if (!findTokenSequence(mit, search[i+1 .. $], true, true, ";",
574 								       tailmatch, submatch))
575 						       break;
576 						addSubmatch(search[i], mit, tailmatch.start);
577 						mit = tailmatch.end;
578 						i = search.length;
579 						break;
580 					}
581 					else if(startsWith(search[i], "$_not") && i + 1 < search.length)
582 					{
583 						if(startsWith(search[i + 1], "$_ident"))
584 						{
585 							if(mit.type == Token.Identifier)
586 								break;
587 						}
588 						else if(startsWith(search[i + 1], "$_num"))
589 						{
590 							if(mit.type == Token.Number)
591 								break;
592 						}
593 						else if(startsWith(search[i], "$_string"))
594 						{
595 							if(mit.type != Token.String)
596 								break;
597 						}
598 						else if(mittext == search[i + 1])
599 							break;
600 						addSubmatch(search[i], mit, mit + 1);
601 						i++;
602 					}
603 					else if(startsWith(search[i], "$_opt"))
604 					{
605 						i++;
606 						if(i < search.length && mittext == search[i])
607 							addSubmatch(search[i-1], mit, mit + 1);
608 						else
609 						{
610 							addSubmatch(search[i-1], mit, mit);
611 							goto LnoAdvance; // nothing matched
612 						}
613 					}
614 					else
615 					{
616 						TokenRange tailmatch;
617 						if (!findTokenSequence(mit, search[i+1 .. $], checkBracketsMatch, checkBracketsMatch, 
618 								       stopText, tailmatch, submatch))
619 							break;
620 						addSubmatch(search[i], mit, tailmatch.start);
621 						mit = tailmatch.end;
622 						i = search.length;
623 						break;
624 					}
625 				}
626 				else
627 				{
628 					ptrdiff_t idx = indexOf(search[i], '$');
629 					if(idx < 0)
630 					{
631 						if (mittext != search[i])
632 							break;
633 					}
634 					else if(mittext.length < idx)
635 						break;
636 					else if(mittext[0 .. idx] != search[i][0 .. idx])
637 						break;
638 					else
639 					{
640 						int sidx = findSubmatch(submatch, search[i][idx .. $]);
641 						if(sidx < 0)
642 						{
643 							// create dummy token and list to add a submatch
644 							Token subtok = createToken("", mittext[idx .. $], Token.Identifier, mit.lineno);
645 							TokenList sublist = new TokenList;
646 							sublist.append(subtok);
647 							addSubmatch(search[i][idx .. $], sublist.begin(), sublist.end());
648 						}
649 						else if(!compareSubmatch(submatch[sidx], mittext[idx .. $]))
650 							break;
651 					}
652 				}
653 				++mit;
654 			LnoAdvance:
655 				i++;
656 				while(i < search.length && search[i].length == 0)
657 					i++;
658 			}
659 			if(i >= search.length)
660 			{
661 				match.start = it;
662 				match.end = mit;
663 				return true;
664 			}
665 		Lnomatch:
666 			submatch.length = prevsubmatchLength;
667 		}
668 		if(checkBracketsSearch && isOpeningBracket(it.type))
669 			advanceToClosingBracket(it);
670 		else if(checkBracketsSearch && isClosingBracket(it.type))
671 			break;
672 		else 
673 			it.advance();
674 	}
675 	return false;
676 }
677 
678 TokenList createReplacementTokenList(RTYPE) (RTYPE[] replace, TokenRange match, ref SubMatch[] submatch)
679 {
680 	TokenList tokenList = new TokenList;
681 	for(int i = 0; i < replace.length; i++)
682 	{
683 		string reptext;
684 		string pretext;
685 		int type = Token.PPinsert;
686 		static if (is(RTYPE == Token))
687 		{
688 			reptext = replace[i].text;
689 			pretext = replace[i].pretext;
690 			type = replace[i].type;
691 			if(reptext == "$" && i + 1 < replace.length && replace[i+1].pretext == "")
692 			{
693 				reptext ~= replace[i + 1].text;
694 				i++;
695 			}
696 		}
697 		else
698 		{
699 			reptext = replace[i];
700 		}
701 
702 		if(reptext == "$*")
703 			tokenList.appendList(copyTokenList(match));
704 
705 		else if(startsWith(reptext, "$"))
706 		{
707 			int idx = findSubmatch(submatch, reptext);
708 			if(idx < 0)
709 				throwException("no submatch for " ~ reptext);
710 
711 			TokenList list = copyTokenList(submatch[idx].start, submatch[idx].end);
712 			if(!list.empty && !list.begin().pretext.length) //&& pretext.length)
713 				list.begin().pretext = pretext; // ~ list.begin().pretext;
714 			tokenList.appendList(list);
715 		}
716 		else
717 		{
718 			Token tok = createToken(pretext, reptext, type, 0);
719 			tokenList.append(tok);
720 		}
721 	}
722 	return tokenList;
723 }
724 
725 
726 int _replaceTokenSequence(RTYPE)(TokenList srctoken, string[] search, RTYPE[] replace, bool checkBrackets)
727 {
728 	if(search.length == 0)
729 		return 0;
730 
731 	for(int i = 0; i < search.length; i++)
732 		search[i] = strip(search[i]);
733 
734 	int cntReplacements = 0;
735 	TokenIterator it = srctoken.begin();
736 	for( ; ; )
737 	{
738 		TokenRange match;
739 		SubMatch[] submatch;
740 		if(!findTokenSequence(it, search, false, checkBrackets, "", match, submatch))
741 			break;
742 
743 		string pretext = match.start.pretext;
744 		match.start.pretext = "";
745 		TokenList tokenList = createReplacementTokenList(replace, match, submatch);
746 
747 		if(!tokenList.empty())
748 			tokenList.begin().pretext = pretext ~ tokenList.begin().pretext;
749 
750 		srctoken.remove(match.start, match.end);
751 		srctoken.insertListBefore(match.end, tokenList);
752 		
753 		it = match.end;
754 		// avoid recursing into the replacement?
755 		cntReplacements++;
756 	}
757 	return cntReplacements;
758 }
759 
760 int replaceTokenSequence(TokenList srctoken, string[] search, string[] replace, bool checkBrackets)
761 {
762 	return _replaceTokenSequence(srctoken, search, replace, checkBrackets);
763 }
764 
765 int replaceTokenSequence(TokenList srctoken, string search, string replace, bool checkBrackets)
766 {
767 	string[] searchTokens;
768 	scanTextArray!(string)(searchTokens, search);
769 	Token[] replaceTokens;
770 	scanTextArray!(Token)(replaceTokens, replace);
771 
772 	return _replaceTokenSequence(srctoken, searchTokens, replaceTokens, checkBrackets);
773 }
774 
775 ///////////////////////////////////////////////////////////////////////
776 
777 TokenList scanArgument(ref TokenIterator it)
778 {
779 	TokenIterator start = it;
780 	
781 	while(!it.atEnd() && it.type != Token.Comma && it.type != Token.ParenR)
782 	{
783 		if(it.type == Token.ParenL)
784 			advanceToClosingBracket(it);
785 		else
786 			it.advance();
787 
788 		if(it.atEnd())
789 			throwException(start.lineno, "unterminated macro invocation");
790 	}
791 
792 	TokenList tokenList = new TokenList;
793 	for( ; start != it; ++start)
794 		tokenList.append(*start);
795 
796 	return tokenList;
797 }
798 
799 void replaceArgument(ref TokenIterator defIt, TokenList list, void delegate(bool, TokenList) expandList)
800 {
801 	// defIt on identifer to replace
802 	string pretext = defIt.pretext;
803 	int lineno = 0;
804 	if(!list.empty())
805 		lineno = list.begin().lineno;
806 
807 	if(pretext.length > 0)
808 	{
809 		defIt.insertBefore(createToken(pretext, "", Token.Comment, defIt.lineno));
810 	}
811 	defIt.erase();
812 	if(!defIt.atBegin() && defIt[-1].type == Token.Fis)
813 	{
814 		if(expandList)
815 		{
816 			list = copyTokenList(list);
817 			expandList(true, list);
818 		}
819 		// TODO: should create escape sequences?
820 		string insText = "\"" ~ strip(tokenListToString(list)) ~ "\"";
821 		Token tok = createToken("", insText, Token.String, defIt[-1].lineno);
822 		defIt.retreat();
823 		defIt.insertAfter(tok);
824 		defIt.erase(); // remove '#'
825 	}
826 	else
827 	{
828 		bool org = ((!defIt.atBegin() && defIt[-1].type == Token.FisFis) || (!defIt.atEnd() && defIt.type == Token.FisFis));
829 		TokenList insList = copyTokenList(list);
830 		if(!org && expandList)
831 			expandList(true, insList);
832 
833 		TokenIterator ins = defIt;
834 		insertTokenList(ins, insList);
835 	}
836 }
837 
838 TokenList removeFisFis(TokenList tokens)
839 {
840 	int cntFisFis = 0;
841 	TokenIterator it = tokens.begin();
842 	while(!it.atEnd())
843 	{
844 		if(it.type == Token.FisFis)
845 		{
846 			it.erase();
847 			if(!it.atEnd())
848 				it.pretext = "";
849 			cntFisFis++;
850 		}
851 		it.advance();
852 	}
853 	if(cntFisFis == 0)
854 		return tokens;
855 	
856 	string text = strip(tokenListToString(tokens));
857 	TokenList newList = scanText(text, tokens.begin().lineno);
858 	return newList;
859 }
860 
861 // returns iterator after insertion, it is set to iterator at beginning of insertion
862 TokenIterator expandDefine(ref TokenIterator it, TokenList define, void delegate(bool, TokenList) expandList)
863 {
864 	define = copyTokenList(define, true);
865 	TokenIterator srcIt = it;
866 	TokenIterator defIt = define.begin() + 2;
867 	string pretext = srcIt.pretext;
868 	
869 	TokenList[string] args;
870 	checkToken(it, Token.Identifier, false);
871 	if(!defIt.atEnd() && defIt.type == Token.ParenL && defIt.pretext.length == 0)
872 	{
873 		nextToken(defIt, false);
874 		checkToken(it, Token.ParenL, false);
875 		if(defIt.type != Token.ParenR)
876 		{
877 			for( ; ; )
878 			{
879 				string ident = defIt.text;
880 				checkToken(defIt, Token.Identifier, false);
881 				args[ident] = scanArgument(it);
882 				if(defIt.type == Token.ParenR)
883 					break;
884 				checkToken(defIt, Token.Comma, false);
885 				checkToken(it, Token.Comma, false);
886 			}
887 		}
888 		checkToken(defIt, Token.ParenR, false);
889 		checkToken(it, Token.ParenR, false);
890 	}
891 
892 	if(!defIt.atEnd())
893 		defIt.pretext = stripLeft(defIt.pretext);
894 
895 	define.begin().eraseUntil(defIt);
896 	while(!defIt.atEnd())
897 	{
898 		defIt.pretext = replace(defIt.pretext, "\\\n", "\n");
899 		if(defIt.type == Token.Identifier)
900 			if(TokenList* list = defIt.text in args)
901 			{
902 				replaceArgument(defIt, *list, expandList);
903 				continue;
904 			}
905 		defIt.advance();
906 	}
907 
908 	if(!define.empty())
909 	{
910 		define = removeFisFis(define);
911 		srcIt.eraseUntil(it);  // makes srcIt invalid, but it stays valid
912 		srcIt = it;
913 		if(expandList)
914 		{
915 			expandList(false, define);
916 			it = insertTokenList(srcIt, define); // it is after insertion now
917 		}
918 		else
919 			it = insertTokenList(srcIt, define);
920 	}
921 	else
922 	{
923 		srcIt.eraseUntil(it);  // makes srcIt invalid, but it stays valid
924 		srcIt = it;
925 	}
926 	if(!it.atEnd())
927 		it.pretext = pretext ~ it.pretext;
928 	return srcIt;
929 }
930 
931 enum MixinMode
932 {
933 	ExpandDefine,
934 	ExpressionMixin,
935 	StatementMixin,
936 	LabelMixin
937 }
938 
939 // if createMixins === 0: 
940 void expandPPdefines(TokenList srctokens, TokenList[string] defines, MixinMode mixinMode)
941 {
942 	for(TokenIterator it = srctokens.begin(); !it.atEnd(); )
943 	{
944 		if(it.type == Token.PPdefine)
945 		{
946 			string text = strip(it.text);
947 			TokenList defList = scanText(text, it.lineno, false);
948 			TokenIterator tokIt = defList.begin();
949 			assume(tokIt[0].type == Token.PPdefine);
950 			assume(tokIt[1].type == Token.Identifier);
951 
952 			if(TokenList* list = tokIt[1].text in defines)
953 			{
954 				// remove trailing comments
955 				while((defList.end()-1).text.empty())
956 					(defList.end()-1).erase();
957 
958 				*list = defList;
959 				if(mixinMode != MixinMode.ExpandDefine)
960 				{
961 					it.text = createMixinFunction(defList, mixinMode);
962 					it.type = Token.PPinsert;
963 				}
964 				else
965 				{
966 					string pretext = it.pretext;
967 					it.erase();
968 					it.pretext = pretext ~ it.pretext;
969 					continue;
970 				}
971 			}
972 			else
973 			{
974 				// expand content of define
975 				tokIt = tokIt + 2;
976 				if(tokIt.text == "(" && tokIt.pretext == "")
977 					advanceToClosingBracket(tokIt);
978 				bool changed = false;
979 				while(!tokIt.atEnd())
980 				{
981 					if(tokIt.type == Token.Identifier)
982 						if(TokenList* list = tokIt.text in defines)
983 						{
984 							if(*list !is null)
985 							{
986 								if(mixinMode != MixinMode.ExpandDefine)
987 									invokeMixin(tokIt, mixinMode);
988 								else
989 									expandDefine(tokIt, *list, null);
990 								changed = true;
991 								continue;
992 							}
993 						}
994 					tokIt.advance();
995 				}
996 				if(changed)
997 					it.text = tokenListToString(defList) ~ "\n";
998 			}
999 		}
1000 		else if(it.type == Token.PPundef)
1001 		{
1002 			TokenList undefList = scanText(it.text, it.lineno, false);
1003 			TokenIterator tokIt = undefList.begin();
1004 			assume(tokIt[0].type == Token.PPundef);
1005 			assume(tokIt[1].type == Token.Identifier);
1006 	
1007 			if(TokenList* list = tokIt[1].text in defines)
1008 			{
1009 				string pretext = it.pretext;
1010 				*list = null;
1011 				it.erase();
1012 				it.pretext = pretext ~ it.pretext;
1013 				continue;
1014 			}
1015 		}
1016 		else if(it.type == Token.Identifier)
1017 		{
1018 			if(TokenList* list = it.text in defines)
1019 			{
1020 				if(*list !is null)
1021 				{
1022 					if(mixinMode != MixinMode.ExpandDefine)
1023 						invokeMixin(it, mixinMode);
1024 					else
1025 						expandDefine(it, *list, null);
1026 					continue;
1027 				}
1028 			}
1029 		}
1030 		it.advance();
1031 	}
1032 }
1033 
1034 void insertTokenBefore(ref TokenIterator it, Token tok, string tokpretext = "")
1035 {
1036 	it.pretext ~= tok.pretext;
1037 	tok.pretext = tokpretext;
1038 	
1039 	it.insertBefore(tok);
1040 }
1041 
1042 void invokeMixin(ref TokenIterator it, MixinMode mixinMode)
1043 {
1044 	TokenIterator start = it;
1045 	assume(it.type == Token.Identifier);
1046 	string text = "mixin(" ~ it.text;
1047 
1048 	nextToken(it);
1049 	if(it.type == Token.ParenL && it.pretext.length == 0)
1050 	{
1051 		nextToken(it, false);
1052 		text ~= "(";
1053 		if(it.type != Token.ParenR)
1054 		{
1055 			string sep;
1056 			for( ; ; )
1057 			{
1058 				TokenList arg = scanArgument(it);
1059 				string argtext = strip(tokenListToString(arg));
1060 				//text ~= sep ~ "\"" ~ argtext ~ "\"";
1061 				text ~= sep ~ argtext;
1062 				sep = ", ";
1063 
1064 				if(it.type == Token.ParenR)
1065 					break;
1066 				checkToken(it, Token.Comma, false);
1067 			}
1068 		}
1069 		text ~= ")";
1070 		nextToken(it, false);
1071 	}
1072 	text ~= ")";
1073 	if(mixinMode == MixinMode.StatementMixin && it.type != Token.Semicolon)
1074 		text ~= ";";
1075 	if(mixinMode == MixinMode.LabelMixin && it.type == Token.Colon)
1076 	{
1077 		text ~= ";";
1078 		it.erase();
1079 	}
1080 
1081 	start.insertBefore(createToken(start.pretext, text, Token.PPinsert, it.lineno));
1082 	start.eraseUntil(it);
1083 }
1084 
1085 string createMixinFunction(TokenList tokList, MixinMode mixinMode)
1086 {
1087 	TokenIterator it = tokList.begin();
1088 	checkToken(it, Token.PPdefine, false);
1089 	string ident = it.text;
1090 	checkToken(it, Token.Identifier, false);
1091 
1092 	string text = "static __string " ~ ident ~ "(";
1093 
1094 	int[string] argsUsage;
1095 	if(it.type == Token.ParenL && it.pretext.length == 0)
1096 	{
1097 		nextToken(it);
1098 		if(it.type != Token.ParenR)
1099 		{
1100 			string sep;
1101 			for( ; ; )
1102 			{
1103 				string arg = it.text;
1104 				checkToken(it, Token.Identifier, false);
1105 				argsUsage[arg] = 0;
1106 
1107 				text ~= sep ~ "__string " ~ arg;
1108 				sep = ", ";
1109 
1110 				if(it.type == Token.ParenR)
1111 					break;
1112 				checkToken(it, Token.Comma, false);
1113 			}
1114 		}
1115 		nextToken(it);
1116 	}
1117 	text ~= ") { return \"";
1118 
1119 	if(!it.atEnd())
1120 		it.pretext = stripLeft(it.pretext);
1121 
1122 	while(!it.atEnd())
1123 	{
1124 		if(it.type == Token.Identifier && (it.text in argsUsage))
1125 		{
1126 			text ~= it.pretext ~ "\" ~ " ~ it.text ~ " ~ \"";
1127 			argsUsage[it.text]++;
1128 		}
1129 		else
1130 			text ~= replace(it.pretext ~ it.text, "\"", "\\\"");
1131 		it.advance();
1132 	}
1133 
1134 	if(mixinMode == MixinMode.StatementMixin && !endsWith(text, ";"))
1135 		text ~= ";";
1136 	if(mixinMode == MixinMode.LabelMixin && !endsWith(text, ";"))
1137 	{
1138 		if (!endsWith(text, ":"))
1139 			text ~= ":";
1140 		text ~= ";";
1141 	}
1142 
1143 	text = replace(text, "##", "");
1144 	text ~= "\"; }\n";
1145 	return text;
1146 }
1147 
1148 void regexReplacePPdefines(TokenList srctokens, string[string] defines)
1149 {
1150 	for(TokenIterator it = srctokens.begin(); !it.atEnd(); )
1151 	{
1152 		if(it.type == Token.PPdefine)
1153 		{
1154 			string text = strip(it.text);
1155 			TokenList defList = scanText(text, it.lineno, false);
1156 			TokenIterator tokIt = defList.begin();
1157 			assume(tokIt[0].type == Token.PPdefine);
1158 			assume(tokIt[1].type == Token.Identifier);
1159 
1160 			string ident = tokIt[1].text;
1161 			foreach(re, s; defines)
1162 			{
1163 				//if(std.regexp.find(ident, re) >= 0)
1164 				auto rex = std.regex.regex(re);
1165 				if(!std.regex.match(ident, rex).empty())
1166 				{
1167 					// no arguments supported so far
1168 					string posttext = "\n";
1169 					TokenIterator endIt = defList.end();
1170 					while(endIt[-1].type == Token.Newline || endIt[-1].type == Token.EOF || endIt[-1].type == Token.Comment)
1171 					{
1172 						endIt.retreat();
1173 						posttext = endIt.pretext ~ endIt.text ~ posttext;
1174 					}
1175 					string toktext = tokenListToString(tokIt + 2, endIt);
1176 					string txt = s;
1177 					txt = replace(txt, "$id", ident);
1178 					txt = replace(txt, "$text", toktext);
1179 					it.pretext ~= tokIt.pretext;
1180 					it.text = txt ~ posttext;
1181 					it.type = Token.PPinsert;
1182 					break;
1183 				}
1184 			}
1185 		}
1186 		it.advance();
1187 	}
1188 }
1189 
1190 ///////////////////////////////////////////////////////////////////////
1191 
1192 string testDefine(string txt, TokenList[string] defines)
1193 {
1194 	TokenList list = scanText(txt);
1195 	expandPPdefines(list, defines, MixinMode.ExpandDefine);
1196 	// src.fixConditionalCompilation();
1197 	string res = tokenListToString(list);
1198 	return res;
1199 }
1200 
1201 unittest
1202 {
1203 	string txt = 
1204 		  "#define X(a) a\n"
1205 		~ "before X(1) after\n"
1206 		~ "#undef X\n"
1207 		~ "X(2)\n"
1208 		~ "#define X(a)\n"
1209 		~ "X(3)\n"
1210 		;
1211 
1212 	string exp = 
1213 		  "before 1 after\n"
1214 		~ "X(2)\n"
1215 		~ "\n"
1216 		;
1217 
1218 	TokenList[string] defines = [ "X" : null ];
1219 	string res = testDefine(txt, defines);
1220 	assume(res == exp);
1221 }
1222 
1223 unittest
1224 {
1225 	string txt = 
1226 		  "#define X(a) #a\n"
1227 		~ "X(1)\n"
1228 		~ "#undef X\n"
1229 		~ "#define X(a) x(#a)\n"
1230 		~ "X(1+2+3)\n"
1231 		;
1232 
1233 	string exp = 
1234 		  "\"1\"\n"
1235 		~ "x(\"1+2+3\")\n"
1236 		;
1237 
1238 	TokenList[string] defines = [ "X" : null ];
1239 	string res = testDefine(txt, defines);
1240 	assume(res == exp);
1241 }
1242 
1243 unittest
1244 {
1245 	string txt = 
1246 		  "#define X(a) a##1\n"
1247 		~ "X(2)\n"
1248 		;
1249 
1250 	string exp = 
1251 		"21\n"
1252 		;
1253 
1254 	TokenList[string] defines = [ "X" : null ];
1255 	string res = testDefine(txt, defines);
1256 	assume(res == exp);
1257 }
1258 
1259 ///////////////////////////////////////////////////////////////////////
1260 
1261 string testMixin(string txt, TokenList[string] mixins)
1262 {
1263 	TokenList list = scanText(txt);
1264 	expandPPdefines(list, mixins, MixinMode.StatementMixin);
1265 	// src.fixConditionalCompilation();
1266 	string res = tokenListToString(list);
1267 	return res;
1268 }
1269 
1270 unittest
1271 {
1272 	string txt = 
1273 		  "#define X(a) x = a;\n"
1274 		~ "X(b);\n"
1275 		;
1276 
1277 	string exp = 
1278 		  "static __string X(__string a) { return \"x = \" ~ a ~ \";\"; }\n"
1279 		~ "mixin(X(b));\n"
1280 		;
1281 
1282 	TokenList[string] mixins = [ "X" : null ];
1283 	string res = testMixin(txt, mixins);
1284 	assume(res == exp);
1285 }
1286 
1287 ///////////////////////////////////////////////////////////////////////
1288 
1289 string testReplace(string txt, TokenList[string] defines)
1290 {
1291 	TokenList list = scanText(txt);
1292 	expandPPdefines(list, defines, MixinMode.ExpandDefine);
1293 	// src.fixConditionalCompilation();
1294 	string res = tokenListToString(list);
1295 	return res;
1296 }
1297 
1298 unittest
1299 {
1300 	string txt = 
1301 		  "  if (list_freelist) {\n"
1302 		~ "    list--;\n"
1303 		~ "__static_if(MEM_DEBUG) {\n"
1304 		~ "    mem_setnewfileline(list,file,line);\n"
1305 		~ "}\n"
1306 		~ "  } else {\n"
1307 		~ "    list++;\n"
1308 		~ "  }\n"
1309 		;
1310 
1311 	string exp = 
1312 		  "  if (list_freelist) {\n"
1313 		~ "    list--;\n"
1314 		~ "  } else {\n"
1315 		~ "    list++;\n"
1316 		~ "  }\n"
1317 		;
1318 
1319 	TokenList list = scanText(txt);
1320 
1321 	replaceTokenSequence(list, "__static_if(MEM_DEBUG) { $1 } else { $2 }", "$2", true);
1322 	replaceTokenSequence(list, "__static_if(MEM_DEBUG) { $1 }", "", true);
1323 
1324 	string res = tokenListToString(list);
1325 	assume(res == exp);
1326 }
1327 
1328 unittest
1329 {
1330 	string txt = 
1331 		  "#define X(p) \\\n"
1332 		~ "    int p##1(); \\\n"
1333 		~ "    int p##2(); \\\n"
1334 		~ "    int p##3();\n"
1335 		~ "X(a)\n"
1336 		~ "X(b)\n"
1337 		~ "X(c)\n";
1338 
1339 	string exp = 
1340 		  "int a1(); \n"
1341 		~ "    int a2(); \n"
1342 		~ "    int a3();\n"
1343 		~ "int b1(); \n"
1344 		~ "    int b2(); \n"
1345 		~ "    int b3();\n"
1346 		~ "int c1(); \n"
1347 		~ "    int c2(); \n"
1348 		~ "    int c3();\n";
1349 
1350 	TokenList list = scanText(txt);
1351 
1352 	TokenList[string] defines = [ "X" : null ];
1353 	expandPPdefines(list, defines, MixinMode.ExpandDefine);
1354 
1355 	string res = tokenListToString(list);
1356 	assume(res == exp);
1357 }
1358 
1359 unittest 
1360 {
1361 	string txt = "0 a __ b c";
1362 	TokenList list = scanText(txt);
1363 	string ntxt = tokenListToString(list, true);
1364 	assume(ntxt == "0 ab c");
1365 }