// Written in the D programming language. /**This is a submodule of $(MREF std, format). It centers around a struct called $(LREF FormatSpec), which takes a$(MREF_ALTTEXT format string, std,format) and provides tools forparsing this string. Additionally this module contains a function$(LREF singleSpec) which helps treating a single format specifier. Copyright: Copyright The D Language Foundation 2000-2013. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,Andrei Alexandrescu), and Kenji Hara Source: $(PHOBOSSRC std/format/spec.d) */module std.format.spec; import std.traits : Unqual; template FormatSpec(Char)if (!is(Unqual!Char == Char)){ alias FormatSpec = FormatSpec!(Unqual!Char);} /**A general handler for format strings. This handler centers around the function $(LREF writeUpToNextSpec),which parses the $(MREF_ALTTEXT format string, std,format) until thenext format specifier is found. After the call, it providesinformation about this format specifier in its numerous variables. Params: Char = the character type of the format string */struct FormatSpec(Char)if (is(Unqual!Char == Char)){ import std.algorithm.searching : startsWith; import std.ascii : isDigit; import std.conv : parse, text, to; import std.range.primitives; /** Minimum width. _Default: `0`. */ int width = 0; /** Precision. Its semantic depends on the format character. See $(MREF_ALTTEXT format string, std,format) for more details. _Default: `UNSPECIFIED`. */ int precision = UNSPECIFIED; /** Number of elements between separators. _Default: `UNSPECIFIED`. */ int separators = UNSPECIFIED; /** The separator charactar is supplied at runtime. _Default: false. */ bool dynamicSeparatorChar = false; /** Set to `DYNAMIC` when the separator character is supplied at runtime. _Default: `UNSPECIFIED`. $(RED Warning: `separatorCharPos` is deprecated. It will be removed in 2.107.0. Please use `dynamicSeparatorChar` instead.) */ // @@@DEPRECATED_[2.107.0]@@@ deprecated("separatorCharPos will be removed in 2.107.0. Please use dynamicSeparatorChar instead.") int separatorCharPos() { return dynamicSeparatorChar ? DYNAMIC : UNSPECIFIED; } /// ditto // @@@DEPRECATED_[2.107.0]@@@ deprecated("separatorCharPos will be removed in 2.107.0. Please use dynamicSeparatorChar instead.") void separatorCharPos(int value) { dynamicSeparatorChar = value == DYNAMIC; } /** Character to use as separator. _Default: `','`. */ dchar separatorChar = ','; /** Special value for `width`, `precision` and `separators`. It flags that these values will be passed at runtime through variadic arguments. */ enum int DYNAMIC = int.max; /** Special value for `precision` and `separators`. It flags that these values have not been specified. */ enum int UNSPECIFIED = DYNAMIC - 1; /** The format character. _Default: `'s'`. */ char spec = 's'; /** Index of the argument for positional parameters. Counting starts with `1`. Set to `0` if not used. Default: `0`. */ ushort indexStart; /** Index of the last argument for positional parameter ranges. Counting starts with `1`. Set to `0` if not used. Default: `0`. The maximum value of this field is used as a sentinel to indicate the arguments' length. */ ushort indexEnd; version (StdDdoc) { /// The format specifier contained a `'-'`. bool flDash; /// The format specifier contained a `'0'`. bool flZero; /// The format specifier contained a space. bool flSpace; /// The format specifier contained a `'+'`. bool flPlus; /// The format specifier contained a `'#'`. bool flHash; /// The format specifier contained a `'='`. bool flEqual; /// The format specifier contained a `','`. bool flSeparator; // Fake field to allow compilation ubyte allFlags; } else { union { import std.bitmanip : bitfields; mixin(bitfields!( bool, "flDash", 1, bool, "flZero", 1, bool, "flSpace", 1, bool, "flPlus", 1, bool, "flHash", 1, bool, "flEqual", 1, bool, "flSeparator", 1, ubyte, "", 1)); ubyte allFlags; } } /// The inner format string of a nested format specifier. const(Char)[] nested; /** The separator of a nested format specifier. `null` means, there is no separator. `empty`, but not `null`, means zero length separator. */ const(Char)[] sep; /// Contains the part of the format string, that has not yet been parsed. const(Char)[] trailing; /// Sequence `"["` inserted before each range or range like structure. enum immutable(Char)[] seqBefore = "["; /// Sequence `"]"` inserted after each range or range like structure. enum immutable(Char)[] seqAfter = "]"; /** Sequence `":"` inserted between element key and element value of an associative array. */ enum immutable(Char)[] keySeparator = ":"; /** Sequence `", "` inserted between elements of a range, a range like structure or the elements of an associative array. */ enum immutable(Char)[] seqSeparator = ", "; /** Creates a new `FormatSpec`. The string is lazily evaluated. That means, nothing is done, until $(LREF writeUpToNextSpec) is called. Params: fmt = a $(MREF_ALTTEXT format string, std,format) */ this(in Char[] fmt) @safe pure { trailing = fmt; } /** Writes the format string to an output range until the next format specifier is found and parse that format specifier. See the $(MREF_ALTTEXT description of format strings, std,format) for more details about the format specifier. Params: writer = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives), where the format string is written to OutputRange = type of the output range Returns: True, if a format specifier is found and false, if the end of the format string has been reached. Throws: A $(REF_ALTTEXT FormatException, FormatException, std,format) when parsing the format specifier did not succeed. */ bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) scope { import std.format : enforceFmt; if (trailing.empty) return false; for (size_t i = 0; i < trailing.length; ++i) { if (trailing[i] != '%') continue; put(writer, trailing[0 .. i]); trailing = trailing[i .. $]; enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`); trailing = trailing[1 .. $]; if (trailing[0] != '%') { // Spec found. Fill up the spec, and bailout fillUp(); return true; } // Doubled! Reset and Keep going i = 0; } // no format spec found put(writer, trailing); trailing = null; return false; } private void fillUp() scope { import std.format : enforceFmt, FormatException; // Reset content if (__ctfe) { flDash = false; flZero = false; flSpace = false; flPlus = false; flEqual = false; flHash = false; flSeparator = false; } else { allFlags = 0; } width = 0; indexStart = 0; indexEnd = 0; precision = UNSPECIFIED; nested = null; // Parse the spec (we assume we're past '%' already) for (size_t i = 0; i < trailing.length; ) { switch (trailing[i]) { case '(': // Embedded format specifier. auto j = i + 1; // Get the matching balanced paren for (uint innerParens;;) { enforceFmt(j + 1 < trailing.length, text("Incorrect format specifier: %", trailing[i .. $])); if (trailing[j++] != '%') { // skip, we're waiting for %( and %) continue; } if (trailing[j] == '-') // for %-( { ++j; // skip enforceFmt(j < trailing.length, text("Incorrect format specifier: %", trailing[i .. $])); } if (trailing[j] == ')') { if (innerParens-- == 0) break; } else if (trailing[j] == '|') { if (innerParens == 0) break; } else if (trailing[j] == '(') { ++innerParens; } } if (trailing[j] == '|') { auto k = j; for (++j;;) { if (trailing[j++] != '%') continue; if (trailing[j] == '%') ++j; else if (trailing[j] == ')') break; else throw new FormatException( text("Incorrect format specifier: %", trailing[j .. $])); } nested = trailing[i + 1 .. k - 1]; sep = trailing[k + 1 .. j - 1]; } else { nested = trailing[i + 1 .. j - 1]; sep = null; // no separator } //this = FormatSpec(innerTrailingSpec); spec = '('; // We practically found the format specifier trailing = trailing[j + 1 .. $]; return; case '-': flDash = true; ++i; break; case '+': flPlus = true; ++i; break; case '=': flEqual = true; ++i; break; case '#': flHash = true; ++i; break; case '0': flZero = true; ++i; break; case ' ': flSpace = true; ++i; break; case '*': ++i; if (i < trailing.length && isDigit(trailing[i])) { // a '*' followed by digits and '$' is a // positional format trailing = trailing[1 .. $]; width = -parse!(typeof(width))(trailing); i = 0; enforceFmt(trailing[i++] == '$', text("$ expected after '*", -width, "' in format string")); } else { // read result width = DYNAMIC; } break; case '1': .. case '9': auto tmp = trailing[i .. $]; const widthOrArgIndex = parse!uint(tmp); enforceFmt(tmp.length, text("Incorrect format specifier %", trailing[i .. $])); i = trailing.length - tmp.length; if (tmp.startsWith('$')) { // index of the form %n$ indexEnd = indexStart = to!ubyte(widthOrArgIndex); ++i; } else if (tmp.startsWith(':')) { // two indexes of the form %m:n$, or one index of the form %m:$ indexStart = to!ubyte(widthOrArgIndex); tmp = tmp[1 .. $]; if (tmp.startsWith('$')) { indexEnd = indexEnd.max; } else { indexEnd = parse!(typeof(indexEnd))(tmp); } i = trailing.length - tmp.length; enforceFmt(trailing[i++] == '$', "$ expected"); } else { // width width = to!int(widthOrArgIndex); } break; case ',': // Separator (default 3 digits) ++i; flSeparator = true; if (i < trailing.length && trailing[i] == '*') { ++i; // read result separators = DYNAMIC; } else if (i < trailing.length && isDigit(trailing[i])) { auto tmp = trailing[i .. $]; separators = parse!int(tmp); i = trailing.length - tmp.length; } else { // "," was specified, but nothing after it separators = 3; } if (i < trailing.length && trailing[i] == '?') { dynamicSeparatorChar = true; ++i; } break; case '.': // Precision ++i; if (i < trailing.length && trailing[i] == '*') { ++i; if (i < trailing.length && isDigit(trailing[i])) { // a '.*' followed by digits and '$' is a // positional precision trailing = trailing[i .. $]; i = 0; precision = -parse!int(trailing); enforceFmt(trailing[i++] == '$', "$ expected"); } else { // read result precision = DYNAMIC; } } else if (i < trailing.length && trailing[i] == '-') { // negative precision, as good as 0 precision = 0; auto tmp = trailing[i .. $]; parse!int(tmp); // skip digits i = trailing.length - tmp.length; } else if (i < trailing.length && isDigit(trailing[i])) { auto tmp = trailing[i .. $]; precision = parse!int(tmp); i = trailing.length - tmp.length; } else { // "." was specified, but nothing after it precision = 0; }Lines 1–500 of 988. Load more
// Written in the D programming language. /** This is a submodule of $(MREF std, format). It centers around a struct called $(LREF FormatSpec), which takes a $(MREF_ALTTEXT format string, std,format) and provides tools for parsing this string. Additionally this module contains a function $(LREF singleSpec) which helps treating a single format specifier. Copyright: Copyright The D Language Foundation 2000-2013. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, Andrei Alexandrescu), and Kenji Hara Source: $(PHOBOSSRC std/format/spec.d) */ module std.format.spec; import std.traits : Unqual; template FormatSpec(Char) if (!is(Unqual!Char == Char)) { alias FormatSpec = FormatSpec!(Unqual!Char); } /** A general handler for format strings. This handler centers around the function $(LREF writeUpToNextSpec), which parses the $(MREF_ALTTEXT format string, std,format) until the next format specifier is found. After the call, it provides information about this format specifier in its numerous variables. Params: Char = the character type of the format string */ struct FormatSpec(Char) if (is(Unqual!Char == Char)) { import std.algorithm.searching : startsWith; import std.ascii : isDigit; import std.conv : parse, text, to; import std.range.primitives; /** Minimum width. _Default: `0`. */ int width = 0; /** Precision. Its semantic depends on the format character. See $(MREF_ALTTEXT format string, std,format) for more details. _Default: `UNSPECIFIED`. */ int precision = UNSPECIFIED; /** Number of elements between separators. _Default: `UNSPECIFIED`. */ int separators = UNSPECIFIED; /** The separator charactar is supplied at runtime. _Default: false. */ bool dynamicSeparatorChar = false; /** Set to `DYNAMIC` when the separator character is supplied at runtime. _Default: `UNSPECIFIED`. $(RED Warning: `separatorCharPos` is deprecated. It will be removed in 2.107.0. Please use `dynamicSeparatorChar` instead.) */ // @@@DEPRECATED_[2.107.0]@@@ deprecated("separatorCharPos will be removed in 2.107.0. Please use dynamicSeparatorChar instead.") int separatorCharPos() { return dynamicSeparatorChar ? DYNAMIC : UNSPECIFIED; } /// ditto // @@@DEPRECATED_[2.107.0]@@@ deprecated("separatorCharPos will be removed in 2.107.0. Please use dynamicSeparatorChar instead.") void separatorCharPos(int value) { dynamicSeparatorChar = value == DYNAMIC; } /** Character to use as separator. _Default: `','`. */ dchar separatorChar = ','; /** Special value for `width`, `precision` and `separators`. It flags that these values will be passed at runtime through variadic arguments. */ enum int DYNAMIC = int.max; /** Special value for `precision` and `separators`. It flags that these values have not been specified. */ enum int UNSPECIFIED = DYNAMIC - 1; /** The format character. _Default: `'s'`. */ char spec = 's'; /** Index of the argument for positional parameters. Counting starts with `1`. Set to `0` if not used. Default: `0`. */ ushort indexStart; /** Index of the last argument for positional parameter ranges. Counting starts with `1`. Set to `0` if not used. Default: `0`. The maximum value of this field is used as a sentinel to indicate the arguments' length. */ ushort indexEnd; version (StdDdoc) { /// The format specifier contained a `'-'`. bool flDash; /// The format specifier contained a `'0'`. bool flZero; /// The format specifier contained a space. bool flSpace; /// The format specifier contained a `'+'`. bool flPlus; /// The format specifier contained a `'#'`. bool flHash; /// The format specifier contained a `'='`. bool flEqual; /// The format specifier contained a `','`. bool flSeparator; // Fake field to allow compilation ubyte allFlags; } else { union { import std.bitmanip : bitfields; mixin(bitfields!( bool, "flDash", 1, bool, "flZero", 1, bool, "flSpace", 1, bool, "flPlus", 1, bool, "flHash", 1, bool, "flEqual", 1, bool, "flSeparator", 1, ubyte, "", 1)); ubyte allFlags; } } /// The inner format string of a nested format specifier. const(Char)[] nested; /** The separator of a nested format specifier. `null` means, there is no separator. `empty`, but not `null`, means zero length separator. */ const(Char)[] sep; /// Contains the part of the format string, that has not yet been parsed. const(Char)[] trailing; /// Sequence `"["` inserted before each range or range like structure. enum immutable(Char)[] seqBefore = "["; /// Sequence `"]"` inserted after each range or range like structure. enum immutable(Char)[] seqAfter = "]"; /** Sequence `":"` inserted between element key and element value of an associative array. */ enum immutable(Char)[] keySeparator = ":"; /** Sequence `", "` inserted between elements of a range, a range like structure or the elements of an associative array. */ enum immutable(Char)[] seqSeparator = ", "; /** Creates a new `FormatSpec`. The string is lazily evaluated. That means, nothing is done, until $(LREF writeUpToNextSpec) is called. Params: fmt = a $(MREF_ALTTEXT format string, std,format) */ this(in Char[] fmt) @safe pure { trailing = fmt; } /** Writes the format string to an output range until the next format specifier is found and parse that format specifier. See the $(MREF_ALTTEXT description of format strings, std,format) for more details about the format specifier. Params: writer = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives), where the format string is written to OutputRange = type of the output range Returns: True, if a format specifier is found and false, if the end of the format string has been reached. Throws: A $(REF_ALTTEXT FormatException, FormatException, std,format) when parsing the format specifier did not succeed. */ bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) scope { import std.format : enforceFmt; if (trailing.empty) return false; for (size_t i = 0; i < trailing.length; ++i) { if (trailing[i] != '%') continue; put(writer, trailing[0 .. i]); trailing = trailing[i .. $]; enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`); trailing = trailing[1 .. $]; if (trailing[0] != '%') { // Spec found. Fill up the spec, and bailout fillUp(); return true; } // Doubled! Reset and Keep going i = 0; } // no format spec found put(writer, trailing); trailing = null; return false; } private void fillUp() scope { import std.format : enforceFmt, FormatException; // Reset content if (__ctfe) { flDash = false; flZero = false; flSpace = false; flPlus = false; flEqual = false; flHash = false; flSeparator = false; } else { allFlags = 0; } width = 0; indexStart = 0; indexEnd = 0; precision = UNSPECIFIED; nested = null; // Parse the spec (we assume we're past '%' already) for (size_t i = 0; i < trailing.length; ) { switch (trailing[i]) { case '(': // Embedded format specifier. auto j = i + 1; // Get the matching balanced paren for (uint innerParens;;) { enforceFmt(j + 1 < trailing.length, text("Incorrect format specifier: %", trailing[i .. $])); if (trailing[j++] != '%') { // skip, we're waiting for %( and %) continue; } if (trailing[j] == '-') // for %-( { ++j; // skip enforceFmt(j < trailing.length, text("Incorrect format specifier: %", trailing[i .. $])); } if (trailing[j] == ')') { if (innerParens-- == 0) break; } else if (trailing[j] == '|') { if (innerParens == 0) break; } else if (trailing[j] == '(') { ++innerParens; } } if (trailing[j] == '|') { auto k = j; for (++j;;) { if (trailing[j++] != '%') continue; if (trailing[j] == '%') ++j; else if (trailing[j] == ')') break; else throw new FormatException( text("Incorrect format specifier: %", trailing[j .. $])); } nested = trailing[i + 1 .. k - 1]; sep = trailing[k + 1 .. j - 1]; } else { nested = trailing[i + 1 .. j - 1]; sep = null; // no separator } //this = FormatSpec(innerTrailingSpec); spec = '('; // We practically found the format specifier trailing = trailing[j + 1 .. $]; return; case '-': flDash = true; ++i; break; case '+': flPlus = true; ++i; break; case '=': flEqual = true; ++i; break; case '#': flHash = true; ++i; break; case '0': flZero = true; ++i; break; case ' ': flSpace = true; ++i; break; case '*': ++i; if (i < trailing.length && isDigit(trailing[i])) { // a '*' followed by digits and '$' is a // positional format trailing = trailing[1 .. $]; width = -parse!(typeof(width))(trailing); i = 0; enforceFmt(trailing[i++] == '$', text("$ expected after '*", -width, "' in format string")); } else { // read result width = DYNAMIC; } break; case '1': .. case '9': auto tmp = trailing[i .. $]; const widthOrArgIndex = parse!uint(tmp); enforceFmt(tmp.length, text("Incorrect format specifier %", trailing[i .. $])); i = trailing.length - tmp.length; if (tmp.startsWith('$')) { // index of the form %n$ indexEnd = indexStart = to!ubyte(widthOrArgIndex); ++i; } else if (tmp.startsWith(':')) { // two indexes of the form %m:n$, or one index of the form %m:$ indexStart = to!ubyte(widthOrArgIndex); tmp = tmp[1 .. $]; if (tmp.startsWith('$')) { indexEnd = indexEnd.max; } else { indexEnd = parse!(typeof(indexEnd))(tmp); } i = trailing.length - tmp.length; enforceFmt(trailing[i++] == '$', "$ expected"); } else { // width width = to!int(widthOrArgIndex); } break; case ',': // Separator (default 3 digits) ++i; flSeparator = true; if (i < trailing.length && trailing[i] == '*') { ++i; // read result separators = DYNAMIC; } else if (i < trailing.length && isDigit(trailing[i])) { auto tmp = trailing[i .. $]; separators = parse!int(tmp); i = trailing.length - tmp.length; } else { // "," was specified, but nothing after it separators = 3; } if (i < trailing.length && trailing[i] == '?') { dynamicSeparatorChar = true; ++i; } break; case '.': // Precision ++i; if (i < trailing.length && trailing[i] == '*') { ++i; if (i < trailing.length && isDigit(trailing[i])) { // a '.*' followed by digits and '$' is a // positional precision trailing = trailing[i .. $]; i = 0; precision = -parse!int(trailing); enforceFmt(trailing[i++] == '$', "$ expected"); } else { // read result precision = DYNAMIC; } } else if (i < trailing.length && trailing[i] == '-') { // negative precision, as good as 0 precision = 0; auto tmp = trailing[i .. $]; parse!int(tmp); // skip digits i = trailing.length - tmp.length; } else if (i < trailing.length && isDigit(trailing[i])) { auto tmp = trailing[i .. $]; precision = parse!int(tmp); i = trailing.length - tmp.length; } else { // "." was specified, but nothing after it precision = 0; }