Mercurial > projects > dil
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dil/doc/Macro.d Sun Mar 09 00:12:19 2008 +0100 @@ -0,0 +1,348 @@ +/++ + 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; + } +}