Mercurial > projects > dil
comparison src/dil/doc/Macro.d @ 806:bcb74c9b895c
Moved out files in the trunk folder to the root.
author | Aziz K?ksal <aziz.koeksal@gmail.com> |
---|---|
date | Sun, 09 Mar 2008 00:12:19 +0100 |
parents | trunk/src/dil/doc/Macro.d@3b34f6a95a27 |
children |
comparison
equal
deleted
inserted
replaced
805:a3fab8b74a7d | 806:bcb74c9b895c |
---|---|
1 /++ | |
2 Author: Aziz Köksal | |
3 License: GPL3 | |
4 +/ | |
5 module dil.doc.Macro; | |
6 | |
7 import dil.doc.Parser; | |
8 import dil.lexer.Funcs; | |
9 import dil.Unicode; | |
10 import dil.Information; | |
11 import dil.Messages; | |
12 import common; | |
13 | |
14 /// The DDoc macro class. | |
15 class Macro | |
16 { | |
17 string name; /// The name of the macro. | |
18 string text; /// The substitution text. | |
19 uint callLevel; /// Recursive call level. | |
20 this (string name, string text) | |
21 { | |
22 this.name = name; | |
23 this.text = text; | |
24 } | |
25 } | |
26 | |
27 /// Maps macro names to Macro objects. | |
28 /// | |
29 /// MacroTables can be chained so that they build a linear hierarchy. | |
30 /// Macro definitions in the current table override the ones in the parent tables. | |
31 class MacroTable | |
32 { | |
33 /// The parent in the hierarchy. Or null if this is the root. | |
34 MacroTable parent; | |
35 Macro[string] table; /// The associative array that holds the macro definitions. | |
36 | |
37 /// Constructs a MacroTable instance. | |
38 this(MacroTable parent = null) | |
39 { | |
40 this.parent = parent; | |
41 } | |
42 | |
43 /// Inserts the macro m into the table. | |
44 /// Overwrites the current macro if one exists. | |
45 void insert(Macro m) | |
46 { | |
47 table[m.name] = m; | |
48 } | |
49 | |
50 /// Inserts an array of macros into the table. | |
51 void insert(Macro[] macros) | |
52 { | |
53 foreach (m; macros) | |
54 insert(m); | |
55 } | |
56 | |
57 /// Creates a macro using name and text and inserts that into the table. | |
58 void insert(string name, string text) | |
59 { | |
60 insert(new Macro(name, text)); | |
61 } | |
62 | |
63 /// Creates a macro using name[n] and text[n] and inserts that into the table. | |
64 void insert(string[] names, string[] texts) | |
65 { | |
66 assert(names.length == texts.length); | |
67 foreach (i, name; names) | |
68 insert(name, texts[i]); | |
69 } | |
70 | |
71 /// Searches for a macro. | |
72 /// | |
73 /// If the macro isn't found in this table the search | |
74 /// continues upwards in the table hierarchy. | |
75 /// Returns: the macro if found, or null if not. | |
76 Macro search(string name) | |
77 { | |
78 auto pmacro = name in table; | |
79 if (pmacro) | |
80 return *pmacro; | |
81 if (!isRoot()) | |
82 return parent.search(name); | |
83 return null; | |
84 } | |
85 | |
86 /// Returns: true if this is the root of the hierarchy. | |
87 bool isRoot() | |
88 { return parent is null; } | |
89 } | |
90 | |
91 /// Parses a text with macro definitions. | |
92 struct MacroParser | |
93 { | |
94 Macro[] parse(string text) | |
95 { | |
96 IdentValueParser parser; | |
97 auto idvalues = parser.parse(text); | |
98 auto macros = new Macro[idvalues.length]; | |
99 foreach (i, idvalue; idvalues) | |
100 macros[i] = new Macro(idvalue.ident, idvalue.value); | |
101 return macros; | |
102 } | |
103 | |
104 /// Scans for a macro invocation. E.g.: $(DDOC) | |
105 /// Returns: a pointer set to one char past the closing parenthesis, | |
106 /// or null if this isn't a macro invocation. | |
107 static char* scanMacro(char* p, char* textEnd) | |
108 { | |
109 assert(*p == '$'); | |
110 if (p+2 < textEnd && p[1] == '(') | |
111 { | |
112 p += 2; | |
113 if (isidbeg(*p) || isUnicodeAlpha(p, textEnd)) // IdStart | |
114 { | |
115 do // IdChar* | |
116 p++; | |
117 while (p < textEnd && (isident(*p) || isUnicodeAlpha(p, textEnd))) | |
118 MacroExpander.scanArguments(p, textEnd); | |
119 p != textEnd && p++; // Skip ')'. | |
120 return p; | |
121 } | |
122 } | |
123 return null; | |
124 } | |
125 } | |
126 | |
127 /// Expands DDoc macros in a text. | |
128 struct MacroExpander | |
129 { | |
130 MacroTable mtable; /// Used to look up macros. | |
131 InfoManager infoMan; /// Collects warning messages. | |
132 char[] filePath; /// Used in warning messages. | |
133 | |
134 /// Starts expanding the macros. | |
135 static char[] expand(MacroTable mtable, char[] text, char[] filePath, | |
136 InfoManager infoMan = null) | |
137 { | |
138 MacroExpander me; | |
139 me.mtable = mtable; | |
140 me.infoMan = infoMan; | |
141 me.filePath = filePath; | |
142 return me.expandMacros(text); | |
143 } | |
144 | |
145 /// Reports a warning message. | |
146 void warning(char[] msg, char[] macroName) | |
147 { | |
148 msg = Format(msg, macroName); | |
149 if (infoMan) | |
150 infoMan ~= new Warning(new Location(filePath, 0), msg); | |
151 } | |
152 | |
153 /// Expands the macros from the table in the text. | |
154 char[] expandMacros(char[] text, char[] prevArg0 = null/+, uint depth = 1000+/) | |
155 { | |
156 // if (depth == 0) | |
157 // return text; | |
158 // depth--; | |
159 char[] result; | |
160 char* p = text.ptr; | |
161 char* textEnd = p + text.length; | |
162 char* macroEnd = p; | |
163 while (p+3 < textEnd) // minimum 4 chars: $(x) | |
164 { | |
165 if (*p == '$' && p[1] == '(') | |
166 { | |
167 // Copy string between macros. | |
168 if (macroEnd != p) | |
169 result ~= makeString(macroEnd, p); | |
170 p += 2; | |
171 auto idBegin = p; | |
172 if (isidbeg(*p) || isUnicodeAlpha(p, textEnd)) // IdStart | |
173 { | |
174 do // IdChar* | |
175 p++; | |
176 while (p < textEnd && (isident(*p) || isUnicodeAlpha(p, textEnd))) | |
177 // Create macro name. | |
178 auto macroName = makeString(idBegin, p); | |
179 // Get arguments. | |
180 auto macroArgs = scanArguments(p, textEnd); | |
181 if (p == textEnd) | |
182 { | |
183 warning(MSG.UnterminatedDDocMacro, macroName); | |
184 result ~= "$(" ~ macroName ~ " "; | |
185 } | |
186 else | |
187 p++; | |
188 macroEnd = p; // Point past ')'. | |
189 | |
190 auto macro_ = mtable.search(macroName); | |
191 if (macro_) | |
192 { // Ignore recursive macro if: | |
193 auto macroArg0 = macroArgs.length ? macroArgs[0] : null; | |
194 if (macro_.callLevel != 0 && | |
195 (macroArgs.length == 0/+ || // Macro has no arguments. | |
196 prevArg0 == macroArg0+/)) // macroArg0 equals previous arg0. | |
197 { continue; } | |
198 macro_.callLevel++; | |
199 // Expand the arguments in the macro text. | |
200 auto expandedText = expandArguments(macro_.text, macroArgs); | |
201 result ~= expandMacros(expandedText, macroArg0/+, depth+/); | |
202 macro_.callLevel--; | |
203 } | |
204 else | |
205 { | |
206 warning(MSG.UndefinedDDocMacro, macroName); | |
207 //result ~= makeString(macroName.ptr-2, macroEnd); | |
208 } | |
209 continue; | |
210 } | |
211 } | |
212 p++; | |
213 } | |
214 if (macroEnd == text.ptr) | |
215 return text; // No macros found. Return original text. | |
216 if (macroEnd < textEnd) | |
217 result ~= makeString(macroEnd, textEnd); | |
218 return result; | |
219 } | |
220 | |
221 /// Scans until the closing parenthesis is found. Sets p to one char past it. | |
222 /// Returns: [arg0, arg1, arg2 ...]. | |
223 static char[][] scanArguments(ref char* p, char* textEnd) | |
224 out(args) { assert(args.length != 1); } | |
225 body | |
226 { | |
227 // D specs: "The argument text can contain nested parentheses, | |
228 // "" or '' strings, comments, or tags." | |
229 uint level = 1; // Nesting level of the parentheses. | |
230 char[][] args; | |
231 | |
232 // Skip leading spaces. | |
233 while (p < textEnd && isspace(*p)) | |
234 p++; | |
235 | |
236 char* arg0Begin = p; // Whole argument list. | |
237 char* argBegin = p; | |
238 MainLoop: | |
239 while (p < textEnd) | |
240 { | |
241 switch (*p) | |
242 { | |
243 case ',': | |
244 if (level != 1) // Ignore comma if inside (). | |
245 break; | |
246 // Add a new argument. | |
247 args ~= makeString(argBegin, p); | |
248 while (++p < textEnd && isspace(*p)) // Skip spaces. | |
249 {} | |
250 argBegin = p; | |
251 continue; | |
252 case '(': | |
253 level++; | |
254 break; | |
255 case ')': | |
256 if (--level == 0) | |
257 break MainLoop; | |
258 break; | |
259 // Commented out: causes too many problems in the expansion pass. | |
260 // case '"', '\'': | |
261 // auto c = *p; | |
262 // while (++p < textEnd && *p != c) // Scan to next " or '. | |
263 // {} | |
264 // assert(*p == c || p == textEnd); | |
265 // if (p == textEnd) | |
266 // break MainLoop; | |
267 // break; | |
268 case '<': | |
269 p++; | |
270 if (p+2 < textEnd && *p == '!' && p[1] == '-' && p[2] == '-') // <!-- | |
271 { | |
272 p += 2; // Point to 2nd '-'. | |
273 // Scan to closing "-->". | |
274 while (++p < textEnd) | |
275 if (p+2 < textEnd && *p == '-' && p[1] == '-' && p[2] == '>') | |
276 p += 2; // Point to '>'. | |
277 } // <tag ...> or </tag> | |
278 else if (p < textEnd && (isalpha(*p) || *p == '/')) | |
279 while (++p < textEnd && *p != '>') // Skip to closing '>'. | |
280 {} | |
281 else | |
282 continue MainLoop; | |
283 if (p == textEnd) | |
284 break MainLoop; | |
285 assert(*p == '>'); | |
286 break; | |
287 default: | |
288 } | |
289 p++; | |
290 } | |
291 assert(*p == ')' && level == 0 || p == textEnd); | |
292 if (arg0Begin == p) | |
293 return null; | |
294 // arg0 spans the whole argument list. | |
295 auto arg0 = makeString(arg0Begin, p); | |
296 // Add last argument. | |
297 args ~= makeString(argBegin, p); | |
298 return arg0 ~ args; | |
299 } | |
300 | |
301 /// Expands "$+", "$0" - "$9" with args[n] in text. | |
302 /// Params: | |
303 /// text = the text to scan for argument placeholders. | |
304 /// args = the first element, args[0], is the whole argument string and | |
305 /// the following elements are slices into it.$(BR) | |
306 /// The array is empty if there are no arguments. | |
307 char[] expandArguments(char[] text, char[][] args) | |
308 in { assert(args.length != 1, "zero or more than 1 args expected"); } | |
309 body | |
310 { | |
311 char[] result; | |
312 char* p = text.ptr; | |
313 char* textEnd = p + text.length; | |
314 char* placeholderEnd = p; | |
315 | |
316 while (p+1 < textEnd) | |
317 { | |
318 if (*p == '$' && (*++p == '+' || isdigit(*p))) | |
319 { | |
320 // Copy string between argument placeholders. | |
321 if (placeholderEnd != p-1) | |
322 result ~= makeString(placeholderEnd, p-1); | |
323 placeholderEnd = p+1; // Set new placeholder end. | |
324 | |
325 if (args.length == 0) | |
326 continue; | |
327 | |
328 if (*p == '+') | |
329 { // $+ = $2 to $n | |
330 if (args.length > 2) | |
331 result ~= makeString(args[2].ptr, args[0].ptr + args[0].length); | |
332 } | |
333 else | |
334 { // 0 - 9 | |
335 uint nthArg = *p - '0'; | |
336 if (nthArg < args.length) | |
337 result ~= args[nthArg]; | |
338 } | |
339 } | |
340 p++; | |
341 } | |
342 if (placeholderEnd == text.ptr) | |
343 return text; // No placeholders found. Return original text. | |
344 if (placeholderEnd < textEnd) | |
345 result ~= makeString(placeholderEnd, textEnd); | |
346 return result; | |
347 } | |
348 } |