1 /**
2  * Functions to work with DDOC macros.
3  *
4  * Provide functionalities to perform various macro-related operations, including:
5  * - Expand a text, with $(D expand).
6  * - Expand a macro, with $(D expandMacro);
7  * - Parse macro files (.ddoc), with $(D parseMacrosFile);
8  * - Parse a "Macros:" section, with $(D parseKeyValuePair);
9  * To work with embedded documentation ('.dd' files), see $(D ddoc.standalone).
10  *
11  * Most functions provide two interfaces.
12  * One takes an $(D OutputRange) to write to, and the other one is
13  * a convenience wrapper around it, which returns a string.
14  * It uses an $(D std.array.Appender) as the output range.
15  *
16  * Most functions take a 'macros' parameter. The user is not required to pass
17  * the standard D macros in it if he wants HTML output, the same macros that
18  * are hardwired into DDOC are hardwired into libddoc (B, I, D_CODE, etc...).
19  *
20  * Note:
21  * The code can contains embedded code, which will be highlighted by
22  * macros substitution (see corresponding DDOC macros).
23  * However, the substitution is *NOT* performed by this module, you should
24  * call $(D ddoc.highlight.highlight) first.
25  * If you forget to do so, libddoc will consider this as a developper
26  * mistake, and will kindly inform you with an assertion error.
27  *
28  * Copyright: © 2014 Economic Modeling Specialists, Intl.
29  * Authors: Brian Schott, Mathias 'Geod24' Lang
30  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
31  */
32 module ddoc.macros;
33 
34 ///
35 unittest
36 {
37 	import std.conv : text;
38 	import ddoc.lexer : Lexer;
39 
40 	// Ddoc has some hardwired macros, which will be automatically searched.
41 	// List here: dlang.org/ddoc.html
42 	auto l1 = Lexer(`A simple $(B Hello $(I world))`);
43 	immutable r1 = expand(l1, null);
44 	assert(r1 == `A simple <b>Hello <i>world</i></b>`, r1);
45 
46 	// Example on how to parse ddoc file / macros sections.
47 	KeyValuePair[] pairs;
48 	auto lm2 = Lexer(`GREETINGS  =  Hello $(B $0)
49 			  IDENTITY = $0`);
50 	// Acts as we are parsing a ddoc file.
51 	assert(parseKeyValuePair(lm2, pairs));
52 	// parseKeyValuePair parses up to the first invalid token, or until
53 	// a section is reached. It returns false on parsing failure.
54 	assert(lm2.empty, lm2.front.text);
55 	assert(pairs.length == 2, text("Expected length 2, got: ", pairs.length));
56 	string[string] m2;
57 	foreach (kv; pairs)
58 		m2[kv[0]] = kv[1];
59 	// Macros are not expanded until the final call site.
60 	// This allow for forward reference of macro and recursive macros.
61 	assert(m2.get(`GREETINGS`, null) == `Hello $(B $0)`, m2.get(`GREETINGS`, null));
62 	assert(m2.get(`IDENTITY`, null) == `$0`, m2.get(`IDENTITY`, null));
63 
64 	// There are some more specialized functions in this module, such as
65 	// expandMacro which expects the lexer to be placed on a macro, and
66 	// will consume the input (unlike expand, which exhaust a copy).
67 	auto l2 = Lexer(`$(GREETINGS $(IDENTITY John Doe))`);
68 	immutable r2 = expand(l2, m2);
69 	assert(r2 == `Hello <b>John Doe</b>`, r2);
70 
71 	// Note that the expansions are not processed recursively.
72 	// Hence, it's possible to have DDOC-formatted code inside DDOC.
73 	auto l3 = Lexer(`This $(DOLLAR)(MACRO) do not expand recursively.`);
74 	immutable r3 = expand(l3, null);
75 	immutable e3 = `This $(MACRO) do not expand recursively.`;
76 	assert(e3 == r3, r3);
77 }
78 
79 import ddoc.lexer;
80 import std.exception;
81 import std.range;
82 import std.algorithm;
83 import std.stdio;
84 import std.typecons : Tuple;
85 
86 alias KeyValuePair = Tuple!(string, string);
87 
88 /// The set of ddoc's predefined macros.
89 immutable string[string] DEFAULT_MACROS;
90 
91 shared static this()
92 {
93 	DEFAULT_MACROS = [`B` : `<b>$0</b>`, `I` : `<i>$0</i>`, `U` : `<u>$0</u>`,
94 		`P` : `<p>$0</p>`, `DL` : `<dl>$0</dl>`, `DT` : `<dt>$0</dt>`,
95 		`DD` : `<dd>$0</dd>`, `TABLE` : `<table>$0</table>`, `TR` : `<tr>$0</tr>`,
96 		`TH` : `<th>$0</th>`, `TD` : `<td>$0</td>`, `OL` : `<ol>$0</ol>`,
97 		`UL` : `<ul>$0</ul>`, `LI` : `<li>$0</li>`,
98 		`LINK` : `<a href="$0">$0</a>`, `LINK2` : `<a href="$1">$+</a>`,
99 		`LPAREN` : `(`, `RPAREN` : `)`, `DOLLAR` : `$`, `BACKTICK` : "`",
100 		`DEPRECATED` : `$0`, `RED` : `<font color=red>$0</font>`,
101 		`BLUE` : `<font color=blue>$0</font>`,
102 		`GREEN` : `<font color=green>$0</font>`,
103 		`YELLOW` : `<font color=yellow>$0</font>`,
104 		`BLACK` : `<font color=black>$0</font>`,
105 		`WHITE` : `<font color=white>$0</font>`,
106 
107 		`D_CODE` : `<pre class="d_code">$0</pre>`,
108 		`D_INLINECODE` : `<pre style="display:inline;" class="d_inline_code">$0</pre>`,
109 		`D_COMMENT` : `$(GREEN $0)`, `D_STRING` : `$(RED $0)`,
110 		`D_KEYWORD` : `$(BLUE $0)`, `D_PSYMBOL` : `$(U $0)`,
111 		`D_PARAM` : `$(I $0)`, `DDOC` : `<html>
112   <head>
113     <META http-equiv="content-type" content="text/html; charset=utf-8">
114     <title>$(TITLE)</title>
115   </head>
116   <body>
117   <h1>$(TITLE)</h1>
118   $(BODY)
119   <hr>$(SMALL Page generated by $(LINK2 https://github.com/economicmodeling/libddoc, libddoc). $(COPYRIGHT))
120   </body>
121 </html>`,
122 
123 		`DDOC_BACKQUOTED` : `$(D_INLINECODE $0)`, `DDOC_COMMENT` : `<!-- $0 -->`,
124 		`DDOC_DECL` : `$(DT $(BIG $0))`, `DDOC_DECL_DD` : `$(DD $0)`,
125 		`DDOC_DITTO` : `$(BR)$0`, `DDOC_SECTIONS` : `$0`,
126 		`DDOC_SUMMARY` : `$0$(BR)$(BR)`, `DDOC_DESCRIPTION` : `$0$(BR)$(BR)`,
127 		`DDOC_AUTHORS` : "$(B Authors:)$(BR)\n$0$(BR)$(BR)",
128 		`DDOC_BUGS` : "$(RED BUGS:)$(BR)\n$0$(BR)$(BR)",
129 		`DDOC_COPYRIGHT` : "$(B Copyright:)$(BR)\n$0$(BR)$(BR)",
130 		`DDOC_DATE` : "$(B Date:)$(BR)\n$0$(BR)$(BR)",
131 		`DDOC_DEPRECATED` : "$(RED Deprecated:)$(BR)\n$0$(BR)$(BR)",
132 		`DDOC_EXAMPLES` : "$(B Examples:)$(BR)\n$0$(BR)$(BR)",
133 		`DDOC_HISTORY` : "$(B History:)$(BR)\n$0$(BR)$(BR)",
134 		`DDOC_LICENSE` : "$(B License:)$(BR)\n$0$(BR)$(BR)",
135 		`DDOC_RETURNS` : "$(B Returns:)$(BR)\n$0$(BR)$(BR)",
136 		`DDOC_SEE_ALSO` : "$(B See Also:)$(BR)\n$0$(BR)$(BR)",
137 		`DDOC_STANDARDS` : "$(B Standards:)$(BR)\n$0$(BR)$(BR)",
138 		`DDOC_THROWS` : "$(B Throws:)$(BR)\n$0$(BR)$(BR)",
139 		`DDOC_VERSION` : "$(B Version:)$(BR)\n$0$(BR)$(BR)",
140 		`DDOC_SECTION_H` : `$(B $0)$(BR)$(BR)`, `DDOC_SECTION` : `$0$(BR)$(BR)`,
141 		`DDOC_MEMBERS` : `$(DL $0)`,
142 		`DDOC_MODULE_MEMBERS` : `$(DDOC_MEMBERS $0)`,
143 		`DDOC_CLASS_MEMBERS` : `$(DDOC_MEMBERS $0)`,
144 		`DDOC_STRUCT_MEMBERS` : `$(DDOC_MEMBERS $0)`,
145 		`DDOC_ENUM_MEMBERS` : `$(DDOC_MEMBERS $0)`,
146 		`DDOC_TEMPLATE_MEMBERS` : `$(DDOC_MEMBERS $0)`,
147 		`DDOC_ENUM_BASETYPE` : `$0`,
148 		`DDOC_PARAMS` : "$(B Params:)$(BR)\n$(TABLE $0)$(BR)",
149 		`DDOC_PARAM_ROW` : `$(TR $0)`, `DDOC_PARAM_ID` : `$(TD $0)`,
150 		`DDOC_PARAM_DESC` : `$(TD $0)`, `DDOC_BLANKLINE` : `$(BR)$(BR)`,
151 
152 		`DDOC_ANCHOR` : `<a name="$1"></a>`, `DDOC_PSYMBOL` : `$(U $0)`,
153 		`DDOC_PSUPER_SYMBOL` : `$(U $0)`, `DDOC_KEYWORD` : `$(B $0)`,
154 		`DDOC_PARAM` : `$(I $0)`, `ESCAPES` : `/</&lt;/
155 />/&gt;/
156 &/&amp;/`,];
157 }
158 
159 /**
160  * Write the text from the lexer to the $(D OutputRange), and expand any macro in it..
161  *
162  * expand takes a $(D ddoc.Lexer), and will, until it's empty, write it's expanded version to $(D output).
163  *
164  * Params:
165  * input = A reference to the lexer to use. When expandMacros successfully returns, it will be empty.
166  * macros = A list of DDOC macros to use for expansion. This override the previous definitions, hardwired in
167  *		DDOC. Which means if an user provides a macro such as $(D macros["B"] = "<h1>$0</h1>";),
168  *		it will be used, otherwise the default $(D macros["B"] = "<b>$0</b>";) will be used.
169  *		To undefine hardwired macros, just set them to an empty string: $(D macros["B"] = "";).
170  * removeUnknown = Set to true to make unknown macros disappear from the output or false to make them output unprocessed.
171  * output = An object satisfying $(D std.range.isOutputRange), usually a $(D std.array.Appender).
172  */
173 void expand(O)(Lexer input, in string[string] macros, O output, bool removeUnknown = true) if (isOutputRange!(O,
174 		string))
175 {
176 	// First, we need to turn every embedded code into a $(D_CODE)
177 	while (!input.empty)
178 	{
179 		assert(input.front.type != Type.embedded, callHighlightMsg);
180 		if (input.front.type == Type.dollar)
181 		{
182 			input.popFront();
183 			if (input.front.type == Type.lParen)
184 			{
185 				auto mac = Lexer(matchParenthesis(input), true);
186 				if (!mac.empty)
187 				{
188 					if (!expandMacroImpl(mac, macros, output) && !removeUnknown)
189 					{
190 						output.put("$");
191 						output.put("(");
192 						foreach (val; mac)
193 							output.put(val.text);
194 						output.put(")");
195 					}
196 				}
197 			}
198 			else
199 				output.put("$");
200 		}
201 		else
202 		{
203 			output.put(input.front.text);
204 			input.popFront();
205 		}
206 	}
207 }
208 
209 /// Ditto
210 string expand(Lexer input, string[string] macros, bool removeUnknown = true)
211 {
212 	import std.array : appender;
213 
214 	auto app = appender!string();
215 	expand(input, macros, app, removeUnknown);
216 	return app.data;
217 }
218 
219 unittest
220 {
221 	auto lex = Lexer(`Dat logo: $(LOGO dlang, Beautiful dlang logo)`);
222 	immutable r = expand(lex, [`LOGO` : `<img src="images/$1_logo.png" alt="$2">`]);
223 	immutable exp = `Dat logo: <img src="images/dlang_logo.png" alt="Beautiful dlang logo">`;
224 	assert(r == exp, r);
225 }
226 
227 unittest
228 {
229 	auto lex = Lexer(`$(DIV, Evil)`);
230 	immutable r = expand(lex, [`DIV` : `<div $1>$+</div>`]);
231 	immutable exp = `<div >Evil</div>`;
232 	assert(r == exp, r);
233 }
234 
235 unittest
236 {
237 	auto lex = Lexer(`$(B this) $(UNKN $(B is)) unknown!`);
238 	immutable r = expand(lex, [`B` : `<b>$0</b>`], false);
239 	immutable exp = `<b>this</b> $(UNKN $(B is)) unknown!`;
240 	assert(r == exp, r);
241 }
242 
243 /**
244  * Expand a macro, and write the result to an $(D OutputRange).
245  *
246  * It's the responsability of the caller to ensure that the lexer contains the
247  * beginning of a macro. The front of the input should be either a dollar
248  * followed an opening parenthesis, or an opening parenthesis.
249  *
250  * If the macro does not have a closing parenthesis, input will be exhausted
251  * and a $(D DdocException) will be thrown.
252  *
253  * Params:
254  * input = A reference to a lexer with front pointing to the macro.
255  * macros = Additional macros to use, in addition of DDOC's ones.
256  * output = An $(D OutputRange) to write to.
257  */
258 void expandMacro(O)(ref Lexer input, in string[string] macros, O output) if (
259 		isOutputRange!(O, string))
260 	in
261 {
262 	import std.conv : text;
263 
264 	assert(input.front.type == Type.dollar || input.front.type == Type.lParen,
265 		text("$ or ( expected, not ", input.front.type));
266 }
267 do
268 {
269 	import std.conv : text;
270 
271 	if (input.front.type == Type.dollar)
272 		input.popFront();
273 	assert(input.front.type == Type.lParen, text(input.front.type));
274 	auto l = Lexer(matchParenthesis(input), true);
275 	expandMacroImpl(l, macros, output);
276 }
277 
278 /// Ditto
279 string expandMacro(ref Lexer input, in string[string] macros)
280 in
281 {
282 	import std.conv : text;
283 
284 	assert(input.front.type == Type.dollar || input.front.type == Type.lParen,
285 		text("$ or ( expected, not ", input.front.type));
286 }
287 do
288 {
289 	import std.array : appender;
290 
291 	auto app = appender!string();
292 	expandMacro(input, macros, app);
293 	return app.data;
294 }
295 
296 ///
297 unittest
298 {
299 	import ddoc.lexer : Lexer;
300 	import std.array : appender;
301 
302 	auto macros = [
303 		"IDENTITY" : "$0", "HWORLD" : "$(IDENTITY Hello world!)",
304 		"ARGS" : "$(IDENTITY $1 $+)", "GREETINGS" : "$(IDENTITY $(ARGS Hello,$0))",
305 	];
306 
307 	auto l1 = Lexer(`$(HWORLD)`);
308 	immutable r1 = expandMacro(l1, macros);
309 	assert(r1 == "Hello world!", r1);
310 
311 	auto l2 = Lexer(`$(B $(IDENTITY $(GREETINGS John Malkovich)))`);
312 	immutable r2 = expandMacro(l2, macros);
313 	assert(r2 == "<b>Hello John Malkovich</b>", r2);
314 }
315 
316 /// A simple example, with recursive macros:
317 unittest
318 {
319 	import ddoc.lexer : Lexer;
320 
321 	auto lex = Lexer(`$(MYTEST Un,jour,mon,prince,viendra)`);
322 	auto macros = [`MYTEST` : `$1 $(MYTEST $+)`];
323 	// Note: There's also a version of expand that takes an OutputRange.
324 	immutable result = expand(lex, macros);
325 	assert(result == `Un jour mon prince viendra `, result);
326 }
327 
328 unittest
329 {
330 	auto macros = [
331 		"D" : "<b>$0</b>", "P" : "<p>$(D $0)</p>", "KP" : "<b>$1</b><i>$+</i>",
332 		"LREF" : `<a href="#$1">$(D $1)</a>`
333 	];
334 	auto l = Lexer(`$(D something $(KP a, b) $(P else), abcd) $(LREF byLineAsync)`c);
335 	immutable expected = `<b>something <b>a</b><i>b</i> <p><b>else</b></p>, abcd</b> <a href="#byLineAsync"><b>byLineAsync</b></a>`;
336 	auto result = appender!string();
337 	expand(l, macros, result);
338 	assert(result.data == expected, result.data);
339 }
340 
341 unittest
342 {
343 	auto l1 = Lexer("Do you have a $(RPAREN) problem with $(LPAREN) me?");
344 	immutable r1 = expand(l1, null);
345 	assert(r1 == "Do you have a ) problem with ( me?", r1);
346 
347 	auto l2 = Lexer("And (with $(LPAREN) me) ?");
348 	immutable r2 = expand(l2, null);
349 	assert(r2 == "And (with ( me) ?", r2);
350 
351 	auto l3 = Lexer("What about $(TEST me) ?");
352 	immutable r3 = expand(l3, ["TEST" : "($0"]);
353 	assert(r3 == "What about (me ?", r3);
354 }
355 
356 /**
357  * Parses macros files, usually with extension .ddoc.
358  *
359  * Macros files are files that only contains macros definitions.
360  * Newline after a macro is part of this macro, so a blank line between
361  * macro A and macro B will lead to macro A having a trailing newline.
362  * If you wish to split your file in blocks, terminate each block with
363  * a dummy macro, e.g: '_' (underscore).
364  *
365  * Params:
366  * paths = A variadic array with paths to ddoc files.
367  *
368  * Returns:
369  * An associative array containing all the macros parsed from the files.
370  * In case of multiple definitions, macros are overriden.
371  */
372 string[string] parseMacrosFile(R)(R paths) if (isInputRange!(R))
373 {
374 	import std.exception : enforceEx;
375 	import std.file : readText;
376 	import std.conv : text;
377 
378 	string[string] ret;
379 	foreach (file; paths)
380 	{
381 		KeyValuePair[] pairs;
382 		auto txt = readText(file);
383 		auto lexer = Lexer(txt, true);
384 		parseKeyValuePair(lexer, pairs);
385 		enforceEx!DdocException(lexer.empty, text("Unparsed data (",
386 			lexer.offset, "): ", lexer.text[lexer.offset .. $]));
387 		foreach (kv; pairs)
388 			ret[kv[0]] = kv[1];
389 	}
390 	return ret;
391 }
392 
393 /**
394  * Parses macros (or params) declaration list until the lexer is empty.
395  *
396  * Macros are simple Key/Value pair. So, a macro is declared as: NAME=VALUE.
397  * Any number of whitespace (space / tab) can precede and follow the equal sign.
398  *
399  * Params:
400  * lexer = A reference to lexer consisting solely of macros definition (if $(D stopAtSection) is false),
401  *	   or consisting of a macro followed by other sections.
402  *	   Consequently, at the end of the parsing, the lexer will be empty or may point to a section.
403  * pairs = A reference to an array of $(D KeyValuePair), where the macros will be stored.
404  *
405  * Returns: true if the parsing succeeded.
406  */
407 bool parseKeyValuePair(ref Lexer lexer, ref KeyValuePair[] pairs)
408 {
409 	import std.array : appender;
410 	import std.conv : text;
411 
412 	string prevKey, key;
413 	string prevValue, value;
414 	size_t start;
415 	while (!lexer.empty)
416 	{
417 		// If parseAsKeyValuePair returns true, we stopped on a newline.
418 		// If it returns false, we're either on a section (header),
419 		// or the continuation of a macro.
420 		if (!parseAsKeyValuePair(lexer, key, value))
421 		{
422 			if (prevKey == null) // First pass and invalid data
423 				return false;
424 			if (lexer.front.type == Type.header)
425 				break;
426 			assert(lexer.offset >= prevValue.length);
427 			if (prevValue.length == 0)
428 				start = tokOffset(lexer);
429 			while (!lexer.empty && lexer.front.type != Type.newline)
430 				lexer.popFront();
431 			prevValue = lexer.text[start .. lexer.offset];
432 		}
433 		else
434 		{
435 			// New macro, we can save the previous one.
436 			// The only case when key would not be defined is on first pass.
437 			if (prevKey)
438 				pairs ~= KeyValuePair(prevKey, prevValue);
439 			prevKey = key;
440 			prevValue = value;
441 			key = value = null;
442 			start = tokOffset(lexer) - prevValue.length;
443 		}
444 		if (!lexer.empty)
445 		{
446 			assert(lexer.front.type == Type.newline, text("Front: ",
447 				lexer.front.type, ", text: ", lexer.text[lexer.offset .. $]));
448 			lexer.popFront();
449 		}
450 	}
451 
452 	if (prevKey)
453 		pairs ~= KeyValuePair(prevKey, prevValue);
454 
455 	return true;
456 }
457 
458 private:
459 // upperArgs is a string[11] actually, or null.
460 bool expandMacroImpl(O)(Lexer input, in string[string] macros, O output)
461 {
462 	import std.conv : text;
463 
464 	//debug writeln("Expanding: ", input.text);
465 	// Check if the macro exist and get it's value.
466 	if (input.front.type != Type.word)
467 		return false;
468 	string macroName = input.front.text;
469 	//debug writeln("[EXPAND] Macro name: ", input.front.text);
470 	string macroValue = lookup(macroName, macros);
471 	// No point loosing time if the macro is undefined.
472 	if (macroValue is null)
473 		return false;
474 	//debug writeln("[EXPAND] Macro value: ", macroValue);
475 	input.popFront();
476 
477 	// Special case for $(DDOC). It's ugly, but it gets the job done.
478 	if (input.empty && macroName == "BODY")
479 	{
480 		output.put(lookup("BODY", macros));
481 		return true;
482 	}
483 
484 	// Collect the arguments
485 	if (!input.empty && (input.front.type == Type.whitespace || input.front.type == Type.newline))
486 		input.popFront();
487 	string[11] arguments;
488 	collectMacroArguments(input, arguments);
489 
490 	// First pass
491 	auto argOutput = appender!string();
492 	if (!replaceArgs(macroValue, arguments, argOutput))
493 		return true;
494 
495 	// Second pass
496 	replaceMacs(argOutput.data, macros, output);
497 	return true;
498 }
499 
500 unittest
501 {
502 	auto a1 = appender!string();
503 	expandMacroImpl(Lexer(`B value`), null, a1);
504 	assert(a1.data == `<b>value</b>`, a1.data);
505 
506 	auto a2 = appender!string();
507 	expandMacroImpl(Lexer(`IDENTITY $(B value)`), ["IDENTITY" : "$0"], a2);
508 	assert(a2.data == `<b>value</b>`, a2.data);
509 }
510 
511 // Try to parse a line as a KeyValuePair, returns false if it fails
512 private bool parseAsKeyValuePair(ref Lexer olexer, ref string key, ref string value)
513 {
514 	auto lexer = olexer;
515 	while (!lexer.empty && (lexer.front.type == Type.whitespace || lexer.front.type == Type.newline))
516 		lexer.popFront();
517 	if (!lexer.empty && lexer.front.type == Type.word)
518 	{
519 		key = lexer.front.text;
520 		lexer.popFront();
521 	}
522 	else
523 		return false;
524 	while (!lexer.empty && lexer.front.type == Type.whitespace)
525 		lexer.popFront();
526 	if (!lexer.empty && lexer.front.type == Type.equals)
527 		lexer.popFront();
528 	else
529 		return false;
530 	while (!lexer.empty && lexer.front.type == Type.whitespace)
531 		lexer.popFront();
532 	assert(lexer.offset > 0, "Something is wrong with the lexer");
533 	// Offset points to the END of the token, not the beginning.
534 	immutable size_t start = tokOffset(lexer);
535 	while (!lexer.empty && lexer.front.type != Type.newline)
536 	{
537 		assert(lexer.front.type != Type.header);
538 		lexer.popFront();
539 	}
540 	immutable size_t end = lexer.offset - ((start != lexer.offset
541 		&& lexer.offset != lexer.text.length) ? 1 : 0);
542 	value = lexer.text[start .. end];
543 	olexer = lexer;
544 	return true;
545 }
546 
547 // Note: For macro $(NAME arg1,arg2), collectMacroArguments receive "arg1,arg2".
548 size_t collectMacroArguments(Lexer input, ref string[11] args)
549 {
550 	import std.conv : text;
551 
552 	size_t argPos = 1;
553 	size_t argStart = tokOffset(input);
554 	args[] = null;
555 	if (input.empty)
556 		return 0;
557 	args[0] = input.text[tokOffset(input) .. $];
558 	while (!input.empty)
559 	{
560 		assert(input.front.type != Type.embedded, callHighlightMsg);
561 		switch (input.front.type)
562 		{
563 		case Type.comma:
564 			if (argPos <= 9)
565 				args[argPos++] = input.text[argStart .. (input.offset - 1)];
566 			input.popFront();
567 			stripWhitespace(input);
568 			argStart = tokOffset(input);
569 			// Set the $+ parameter.
570 			if (argPos == 2)
571 				args[10] = input.text[tokOffset(input) .. $];
572 			break;
573 		case Type.lParen:
574 			// Advance the lexer to the matching parenthesis.
575 			matchParenthesis(input);
576 			break;
577 			// TODO: Implement ", ' and <-- pairing.
578 		default:
579 			input.popFront();
580 		}
581 	}
582 	assert(argPos >= 1 && argPos <= 10, text(argPos));
583 	if (argPos <= 9)
584 		args[argPos] = input.text[argStart .. input.offset];
585 	return argPos;
586 }
587 
588 unittest
589 {
590 	import std.conv : text;
591 
592 	string[11] args;
593 
594 	auto l1 = Lexer(`Hello, world`);
595 	auto c1 = collectMacroArguments(l1, args);
596 	assert(c1 == 2, text(c1));
597 	assert(args[0] == `Hello, world`, args[0]);
598 	assert(args[1] == `Hello`, args[1]);
599 	assert(args[2] == `world`, args[2]);
600 	for (size_t i = 3; i < 10; ++i)
601 		assert(args[i] is null, args[i]);
602 	assert(args[10] == `world`, args[10]);
603 
604 	auto l2 = Lexer(`goodbye,cruel,world,I,will,happily,return,home`);
605 	auto c2 = collectMacroArguments(l2, args);
606 	assert(c2 == 8, text(c2));
607 	assert(args[0] == `goodbye,cruel,world,I,will,happily,return,home`, args[0]);
608 	assert(args[1] == `goodbye`, args[1]);
609 	assert(args[2] == `cruel`, args[2]);
610 	assert(args[3] == `world`, args[3]);
611 	assert(args[4] == `I`, args[4]);
612 	assert(args[5] == `will`, args[5]);
613 	assert(args[6] == `happily`, args[6]);
614 	assert(args[7] == `return`, args[7]);
615 	assert(args[8] == `home`, args[8]);
616 	assert(args[9] is null, args[9]);
617 	assert(args[10] == `cruel,world,I,will,happily,return,home`, args[10]);
618 
619 	// It's not as easy as a split !
620 	auto l3 = Lexer(`this,(is,(just,two),args)`);
621 	auto c3 = collectMacroArguments(l3, args);
622 	assert(c3 == 2, text(c3));
623 	assert(args[0] == `this,(is,(just,two),args)`, args[0]);
624 	assert(args[1] == `this`, args[1]);
625 	assert(args[2] == `(is,(just,two),args)`, args[2]);
626 	for (size_t i = 3; i < 10; ++i)
627 		assert(args[i] is null, args[i]);
628 	assert(args[10] == `(is,(just,two),args)`, args[10]);
629 
630 	auto l4 = Lexer(``);
631 	auto c4 = collectMacroArguments(l4, args);
632 	assert(c4 == 0, text(c4));
633 	for (size_t i = 0; i < 11; ++i)
634 		assert(args[i] is null, args[i]);
635 
636 	import std.string : split;
637 
638 	enum first = `I,am,happy,to,join,with,you,today,in,what,will,go,down,in,history,as,the,greatest,demonstration,for,freedom,in,the,history,of,our,nation.`;
639 	auto l5 = Lexer(first);
640 	auto c5 = collectMacroArguments(l5, args);
641 	assert(c5 == 10, text(c5));
642 	assert(args[0] == first, args[0]);
643 	foreach (idx, word; first.split(",")[0 .. 9])
644 		assert(args[idx + 1] == word, text(word, " != ", args[idx + 1]));
645 	assert(args[10] == first[2 .. $], args[10]);
646 
647 	// TODO: ", ', {, <--, matched and unmatched.
648 }
649 
650 // Where the grunt work is done...
651 
652 bool replaceArgs(O)(string val, in string[11] args, O output)
653 {
654 	import std.conv : text;
655 	import std.ascii : isDigit;
656 
657 	bool hasEnd;
658 	auto lex = Lexer(val, true);
659 	while (!lex.empty)
660 	{
661 		assert(lex.front.type != Type.embedded, callHighlightMsg);
662 		switch (lex.front.type)
663 		{
664 		case Type.dollar:
665 			lex.popFront();
666 			// It could be $1_test
667 			if (isDigit(lex.front.text[0]))
668 			{
669 				auto idx = lex.front.text[0] - '0';
670 				assert(idx >= 0 && idx <= 9, text(idx));
671 				// Missing argument
672 				if (args[idx] is null)
673 				{
674 					lex.popFront();
675 					continue;
676 				}
677 				output.put(args[idx]);
678 				output.put(lex.front.text[1 .. $]);
679 				lex.popFront();
680 			}
681 			else if (lex.front.text == "+")
682 			{
683 				if (args == string[11].init)
684 					return false;
685 
686 				lex.popFront();
687 				output.put(args[10]);
688 			}
689 			else
690 			{
691 				output.put("$");
692 			}
693 			break;
694 		case Type.lParen:
695 			output.put("(");
696 			if (!replaceArgs(matchParenthesis(lex, &hasEnd), args, output))
697 				return false;
698 			if (hasEnd)
699 				output.put(")");
700 			break;
701 		default:
702 			output.put(lex.front.text);
703 			lex.popFront();
704 		}
705 	}
706 	return true;
707 }
708 
709 unittest
710 {
711 	string[11] args;
712 
713 	auto a1 = appender!string;
714 	args[0] = "Some kind of test, I guess";
715 	args[1] = "Some kind of test";
716 	args[2] = " I guess";
717 	assert(replaceArgs("$(MY $(SUPER $(MACRO $0)))", args, a1));
718 	assert(a1.data == "$(MY $(SUPER $(MACRO Some kind of test, I guess)))", a1.data);
719 
720 	auto a2 = appender!string;
721 	args[] = null;
722 	args[0] = "Some,kind,of,test";
723 	args[1] = "Some";
724 	args[2] = "kind";
725 	args[3] = "of";
726 	args[4] = "test";
727 	args[10] = "kind,of,test";
728 	assert(replaceArgs("$(SOME $(MACRO $1 $+))", args, a2));
729 	assert(a2.data == "$(SOME $(MACRO Some kind,of,test))", a2.data);
730 
731 	auto a3 = appender!string;
732 	args[] = null;
733 	args[0] = "Some,kind";
734 	args[1] = "Some";
735 	args[2] = "kind";
736 	args[10] = "kind";
737 	assert(replaceArgs("$(SOME $(MACRO $1 $2 $3))", args, a3), a3.data);
738 
739 	auto a4 = appender!string;
740 	args[] = null;
741 	args[0] = "Some kind of test, I guess";
742 	assert(replaceArgs("$(MACRO $0 $1)", args, a4));
743 	assert(a4.data == "$(MACRO Some kind of test, I guess )", a4.data);
744 }
745 
746 void replaceMacs(O)(string val, in string[string] macros, O output)
747 {
748 	//debug writeln("[REPLACE] Arguments replaced: ", val);
749 	bool hasEnd;
750 	auto lex = Lexer(val, true);
751 	while (!lex.empty)
752 	{
753 		assert(lex.front.type != Type.embedded, callHighlightMsg);
754 		switch (lex.front.type)
755 		{
756 		case Type.dollar:
757 			lex.popFront();
758 			if (lex.front.type == Type.lParen)
759 				expandMacro(lex, macros, output);
760 			else
761 				output.put("$");
762 			break;
763 		case Type.lParen:
764 			output.put("(");
765 			auto par = matchParenthesis(lex, &hasEnd);
766 			expand(Lexer(par), macros, output);
767 			if (hasEnd)
768 				output.put(")");
769 			break;
770 		default:
771 			output.put(lex.front.text);
772 			lex.popFront();
773 		}
774 	}
775 }
776 
777 // Some utilities functions
778 
779 /**
780  * Must be called with a parenthesis as the front item of $(D lexer).
781  * Will move the lexer forward until a matching parenthesis is met,
782  * taking nesting into account.
783  * If no matching parenthesis is met, returns null (and $(D lexer) will be empty).
784  */
785 string matchParenthesis(ref Lexer lexer, bool* hasEnd = null)
786 in
787 {
788 	import std.conv : text;
789 
790 	assert(lexer.front.type == Type.lParen, text(lexer.front));
791 	assert(lexer.offset);
792 }
793 do
794 {
795 	size_t count;
796 	size_t start = lexer.offset;
797 	do
798 	{
799 		if (lexer.front.type == Type.rParen)
800 			--count;
801 		else if (lexer.front.type == Type.lParen)
802 			++count;
803 		lexer.popFront();
804 	}
805 	while (count > 0 && !lexer.empty);
806 	size_t end = (lexer.empty) ? lexer.text.length : tokOffset(lexer);
807 	if (hasEnd !is null)
808 		*hasEnd = (count == 0);
809 	if (count == 0)
810 		end -= 1;
811 	return lexer.text[start .. end];
812 }
813 
814 unittest
815 {
816 	auto l1 = Lexer(`(Hello) World`);
817 	immutable r1 = matchParenthesis(l1);
818 	assert(r1 == "Hello", r1);
819 	assert(!l1.empty);
820 
821 	auto l2 = Lexer(`()`);
822 	immutable r2 = matchParenthesis(l2);
823 	assert(r2 == "", r2);
824 	assert(l2.empty);
825 
826 	auto l3 = Lexer(`(())`);
827 	immutable r3 = matchParenthesis(l3);
828 	assert(r3 == "()", r3);
829 	assert(l3.empty);
830 
831 	auto l4 = Lexer(`W (He(l)lo)`);
832 	l4.popFront();
833 	l4.popFront();
834 	immutable r4 = matchParenthesis(l4);
835 	assert(r4 == "He(l)lo", r4);
836 	assert(l4.empty);
837 
838 	auto l5 = Lexer(` @(Hello())   ()`);
839 	l5.popFront();
840 	l5.popFront();
841 	immutable r5 = matchParenthesis(l5);
842 	assert(r5 == "Hello()", r5);
843 	assert(!l5.empty);
844 
845 	auto l6 = Lexer(`(Hello()   (`);
846 	immutable r6 = matchParenthesis(l6);
847 	assert(r6 == "Hello()   (", r6);
848 	assert(l6.empty);
849 }
850 
851 package size_t tokOffset(in Lexer lex)
852 {
853 	return lex.offset - lex.front.text.length;
854 }
855 
856 unittest
857 {
858 	import std.conv : text;
859 
860 	auto lex = Lexer(`My  (friend) $ lives abroad`);
861 	auto expected = [0, 2, 4, 5, 11, 12, 13, 14, 15, 20, 21];
862 	while (!lex.empty)
863 	{
864 		assert(expected.length > 0, "Test and results are not in sync");
865 		assert(tokOffset(lex) == expected[0], text(lex.front, " : ",
866 			tokOffset(lex), " -- ", expected[0]));
867 		lex.popFront();
868 		expected = expected[1 .. $];
869 	}
870 }
871 
872 string lookup(in string name, in string[string] macros, string defVal = null)
873 {
874 	auto p = name in macros;
875 	if (p is null)
876 		return DEFAULT_MACROS.get(name, defVal);
877 	return *p;
878 }
879 
880 /// Returns: The number of offset skipped.
881 package size_t stripWhitespace(ref Lexer lexer)
882 {
883 	size_t start = lexer.offset;
884 	while (!lexer.empty && (lexer.front.type == Type.whitespace || lexer.front.type == Type.newline))
885 	{
886 		start = lexer.offset;
887 		lexer.popFront();
888 	}
889 	return start;
890 }
891 
892 enum callHighlightMsg = "You should call ddoc.hightlight.hightlight(string) or ddoc.unhighlight.unhighlight(string) first.";