Mercurial > projects > dil
view 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 |
line wrap: on
line source
/++ Author: Aziz Köksal License: GPL3 +/ module dil.doc.Macro; import dil.doc.Parser; import dil.lexer.Funcs; import dil.Unicode; import dil.Information; import dil.Messages; import common; /// The DDoc macro class. class Macro { string name; /// The name of the macro. string text; /// The substitution text. uint callLevel; /// Recursive call level. this (string name, string text) { this.name = name; this.text = text; } } /// Maps macro names to Macro objects. /// /// MacroTables can be chained so that they build a linear hierarchy. /// Macro definitions in the current table override the ones in the parent tables. class MacroTable { /// The parent in the hierarchy. Or null if this is the root. MacroTable parent; Macro[string] table; /// The associative array that holds the macro definitions. /// Constructs a MacroTable instance. this(MacroTable parent = null) { this.parent = parent; } /// Inserts the macro m into the table. /// Overwrites the current macro if one exists. void insert(Macro m) { table[m.name] = m; } /// Inserts an array of macros into the table. void insert(Macro[] macros) { foreach (m; macros) insert(m); } /// Creates a macro using name and text and inserts that into the table. void insert(string name, string text) { insert(new Macro(name, text)); } /// Creates a macro using name[n] and text[n] and inserts that into the table. void insert(string[] names, string[] texts) { assert(names.length == texts.length); foreach (i, name; names) insert(name, texts[i]); } /// Searches for a macro. /// /// If the macro isn't found in this table the search /// continues upwards in the table hierarchy. /// Returns: the macro if found, or null if not. Macro search(string name) { auto pmacro = name in table; if (pmacro) return *pmacro; if (!isRoot()) return parent.search(name); return null; } /// Returns: true if this is the root of the hierarchy. bool isRoot() { return parent is null; } } /// Parses a text with macro definitions. struct MacroParser { Macro[] parse(string text) { IdentValueParser parser; auto idvalues = parser.parse(text); auto macros = new Macro[idvalues.length]; foreach (i, idvalue; idvalues) macros[i] = new Macro(idvalue.ident, idvalue.value); return macros; } /// Scans for a macro invocation. E.g.: $(DDOC) /// Returns: a pointer set to one char past the closing parenthesis, /// or null if this isn't a macro invocation. static char* scanMacro(char* p, char* textEnd) { assert(*p == '$'); if (p+2 < textEnd && p[1] == '(') { p += 2; if (isidbeg(*p) || isUnicodeAlpha(p, textEnd)) // IdStart { do // IdChar* p++; while (p < textEnd && (isident(*p) || isUnicodeAlpha(p, textEnd))) MacroExpander.scanArguments(p, textEnd); p != textEnd && p++; // Skip ')'. return p; } } return null; } } /// Expands DDoc macros in a text. struct MacroExpander { MacroTable mtable; /// Used to look up macros. InfoManager infoMan; /// Collects warning messages. char[] filePath; /// Used in warning messages. /// Starts expanding the macros. static char[] expand(MacroTable mtable, char[] text, char[] filePath, InfoManager infoMan = null) { MacroExpander me; me.mtable = mtable; me.infoMan = infoMan; me.filePath = filePath; return me.expandMacros(text); } /// Reports a warning message. void warning(char[] msg, char[] macroName) { msg = Format(msg, macroName); if (infoMan) infoMan ~= new Warning(new Location(filePath, 0), msg); } /// Expands the macros from the table in the text. char[] expandMacros(char[] text, char[] prevArg0 = null/+, uint depth = 1000+/) { // if (depth == 0) // return text; // depth--; char[] result; char* p = text.ptr; char* textEnd = p + text.length; char* macroEnd = p; while (p+3 < textEnd) // minimum 4 chars: $(x) { if (*p == '$' && p[1] == '(') { // Copy string between macros. if (macroEnd != p) result ~= makeString(macroEnd, p); p += 2; auto idBegin = p; if (isidbeg(*p) || isUnicodeAlpha(p, textEnd)) // IdStart { do // IdChar* p++; while (p < textEnd && (isident(*p) || isUnicodeAlpha(p, textEnd))) // Create macro name. auto macroName = makeString(idBegin, p); // Get arguments. auto macroArgs = scanArguments(p, textEnd); if (p == textEnd) { warning(MSG.UnterminatedDDocMacro, macroName); result ~= "$(" ~ macroName ~ " "; } else p++; macroEnd = p; // Point past ')'. auto macro_ = mtable.search(macroName); if (macro_) { // Ignore recursive macro if: auto macroArg0 = macroArgs.length ? macroArgs[0] : null; if (macro_.callLevel != 0 && (macroArgs.length == 0/+ || // Macro has no arguments. prevArg0 == macroArg0+/)) // macroArg0 equals previous arg0. { continue; } macro_.callLevel++; // Expand the arguments in the macro text. auto expandedText = expandArguments(macro_.text, macroArgs); result ~= expandMacros(expandedText, macroArg0/+, depth+/); macro_.callLevel--; } else { warning(MSG.UndefinedDDocMacro, macroName); //result ~= makeString(macroName.ptr-2, macroEnd); } continue; } } p++; } if (macroEnd == text.ptr) return text; // No macros found. Return original text. if (macroEnd < textEnd) result ~= makeString(macroEnd, textEnd); return result; } /// Scans until the closing parenthesis is found. Sets p to one char past it. /// Returns: [arg0, arg1, arg2 ...]. static char[][] scanArguments(ref char* p, char* textEnd) out(args) { assert(args.length != 1); } body { // D specs: "The argument text can contain nested parentheses, // "" or '' strings, comments, or tags." uint level = 1; // Nesting level of the parentheses. char[][] args; // Skip leading spaces. while (p < textEnd && isspace(*p)) p++; char* arg0Begin = p; // Whole argument list. char* argBegin = p; MainLoop: while (p < textEnd) { switch (*p) { case ',': if (level != 1) // Ignore comma if inside (). break; // Add a new argument. args ~= makeString(argBegin, p); while (++p < textEnd && isspace(*p)) // Skip spaces. {} argBegin = p; continue; case '(': level++; break; case ')': if (--level == 0) break MainLoop; break; // Commented out: causes too many problems in the expansion pass. // case '"', '\'': // auto c = *p; // while (++p < textEnd && *p != c) // Scan to next " or '. // {} // assert(*p == c || p == textEnd); // if (p == textEnd) // break MainLoop; // break; case '<': p++; if (p+2 < textEnd && *p == '!' && p[1] == '-' && p[2] == '-') // <!-- { p += 2; // Point to 2nd '-'. // Scan to closing "-->". while (++p < textEnd) if (p+2 < textEnd && *p == '-' && p[1] == '-' && p[2] == '>') p += 2; // Point to '>'. } // <tag ...> or </tag> else if (p < textEnd && (isalpha(*p) || *p == '/')) while (++p < textEnd && *p != '>') // Skip to closing '>'. {} else continue MainLoop; if (p == textEnd) break MainLoop; assert(*p == '>'); break; default: } p++; } assert(*p == ')' && level == 0 || p == textEnd); if (arg0Begin == p) return null; // arg0 spans the whole argument list. auto arg0 = makeString(arg0Begin, p); // Add last argument. args ~= makeString(argBegin, p); return arg0 ~ args; } /// Expands "$+", "$0" - "$9" with args[n] in text. /// Params: /// text = the text to scan for argument placeholders. /// args = the first element, args[0], is the whole argument string and /// the following elements are slices into it.$(BR) /// The array is empty if there are no arguments. char[] expandArguments(char[] text, char[][] args) in { assert(args.length != 1, "zero or more than 1 args expected"); } body { char[] result; char* p = text.ptr; char* textEnd = p + text.length; char* placeholderEnd = p; while (p+1 < textEnd) { if (*p == '$' && (*++p == '+' || isdigit(*p))) { // Copy string between argument placeholders. if (placeholderEnd != p-1) result ~= makeString(placeholderEnd, p-1); placeholderEnd = p+1; // Set new placeholder end. if (args.length == 0) continue; if (*p == '+') { // $+ = $2 to $n if (args.length > 2) result ~= makeString(args[2].ptr, args[0].ptr + args[0].length); } else { // 0 - 9 uint nthArg = *p - '0'; if (nthArg < args.length) result ~= args[nthArg]; } } p++; } if (placeholderEnd == text.ptr) return text; // No placeholders found. Return original text. if (placeholderEnd < textEnd) result ~= makeString(placeholderEnd, textEnd); return result; } }