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.: &#36;(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 "&#36;+", "&#36;0" - "&#36;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 }