| // Written in the D programming language. |
| |
| /* |
| 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/internal/read.d) |
| */ |
| module std.format.internal.read; |
| |
| import std.range.primitives : ElementEncodingType, ElementType, isInputRange; |
| |
| import std.traits : isAggregateType, isArray, isAssociativeArray, |
| isDynamicArray, isFloatingPoint, isIntegral, isSomeChar, isSomeString, |
| isStaticArray, StringTypeOf; |
| |
| import std.format.spec : FormatSpec; |
| |
| package(std.format): |
| |
| void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| { |
| import std.ascii : isDigit, isWhite; |
| import std.range.primitives : empty, front, popFront; |
| |
| switch (spec.spec) |
| { |
| case 'c': input.popFront(); break; |
| case 'd': |
| if (input.front == '+' || input.front == '-') input.popFront(); |
| goto case 'u'; |
| case 's': |
| while (!input.empty && !isWhite(input.front)) input.popFront(); |
| break; |
| case 'u': |
| while (!input.empty && isDigit(input.front)) input.popFront(); |
| break; |
| default: |
| assert(0, "Format specifier not understood: %" ~ spec.spec); |
| } |
| } |
| |
| private template acceptedSpecs(T) |
| { |
| static if (isIntegral!T) |
| enum acceptedSpecs = "bdosuxX"; |
| else static if (isFloatingPoint!T) |
| enum acceptedSpecs = "seEfgG"; |
| else static if (isSomeChar!T) |
| enum acceptedSpecs = "bcdosuxX"; // integral + 'c' |
| else |
| enum acceptedSpecs = ""; |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isInputRange!Range && is(immutable T == immutable bool)) |
| { |
| import std.algorithm.searching : find; |
| import std.conv : parse, text; |
| import std.format : enforceFmt, unformatValue; |
| |
| if (spec.spec == 's') return parse!T(input); |
| |
| enforceFmt(find(acceptedSpecs!long, spec.spec).length, |
| text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); |
| |
| return unformatValue!long(input, spec) != 0; |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isInputRange!Range && is(T == typeof(null))) |
| { |
| import std.conv : parse, text; |
| import std.format : enforceFmt; |
| |
| enforceFmt(spec.spec == 's', |
| text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); |
| |
| return parse!T(input); |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isInputRange!Range && isIntegral!T && !is(T == enum) && isSomeChar!(ElementType!Range)) |
| { |
| import std.algorithm.searching : find; |
| import std.conv : parse, text; |
| import std.format : enforceFmt, FormatException; |
| |
| if (spec.spec == 'r') |
| { |
| static if (is(immutable ElementEncodingType!Range == immutable char) |
| || is(immutable ElementEncodingType!Range == immutable byte) |
| || is(immutable ElementEncodingType!Range == immutable ubyte)) |
| return rawRead!T(input); |
| else |
| throw new FormatException( |
| "The raw read specifier %r may only be used with narrow strings and ranges of bytes." |
| ); |
| } |
| |
| enforceFmt(find(acceptedSpecs!T, spec.spec).length, |
| text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); |
| |
| enforceFmt(spec.width == 0, "Parsing integers with a width specification is not implemented"); // TODO |
| |
| immutable uint base = |
| spec.spec == 'x' || spec.spec == 'X' ? 16 : |
| spec.spec == 'o' ? 8 : |
| spec.spec == 'b' ? 2 : |
| spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0; |
| assert(base != 0, "base must be not equal to zero"); |
| |
| return parse!T(input, base); |
| |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isFloatingPoint!T && !is(T == enum) && isInputRange!Range |
| && isSomeChar!(ElementType!Range)&& !is(Range == enum)) |
| { |
| import std.algorithm.searching : find; |
| import std.conv : parse, text; |
| import std.format : enforceFmt, FormatException; |
| |
| if (spec.spec == 'r') |
| { |
| static if (is(immutable ElementEncodingType!Range == immutable char) |
| || is(immutable ElementEncodingType!Range == immutable byte) |
| || is(immutable ElementEncodingType!Range == immutable ubyte)) |
| return rawRead!T(input); |
| else |
| throw new FormatException( |
| "The raw read specifier %r may only be used with narrow strings and ranges of bytes." |
| ); |
| } |
| |
| enforceFmt(find(acceptedSpecs!T, spec.spec).length, |
| text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); |
| |
| return parse!T(input); |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isInputRange!Range && isSomeChar!T && !is(T == enum) && isSomeChar!(ElementType!Range)) |
| { |
| import std.algorithm.searching : find; |
| import std.conv : to, text; |
| import std.range.primitives : empty, front, popFront; |
| import std.format : enforceFmt, unformatValue; |
| |
| if (spec.spec == 's' || spec.spec == 'c') |
| { |
| auto result = to!T(input.front); |
| input.popFront(); |
| return result; |
| } |
| |
| enforceFmt(find(acceptedSpecs!T, spec.spec).length, |
| text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); |
| |
| enum int size = T.sizeof; |
| static if (size == 1) |
| return unformatValue!ubyte(input, spec); |
| else static if (size == 2) |
| return unformatValue!ushort(input, spec); |
| else static if (size == 4) |
| return unformatValue!uint(input, spec); |
| else |
| static assert(false, T.stringof ~ ".sizeof must be 1, 2, or 4 not " ~ |
| size.stringof); |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) |
| if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) |
| { |
| import std.conv : text; |
| import std.range.primitives : empty, front, popFront, put; |
| import std.format : enforceFmt; |
| |
| const spec = fmt.spec; |
| if (spec == '(') |
| { |
| return unformatRange!T(input, fmt); |
| } |
| enforceFmt(spec == 's', |
| text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); |
| |
| static if (isStaticArray!T) |
| { |
| T result; |
| auto app = result[]; |
| } |
| else |
| { |
| import std.array : appender; |
| auto app = appender!T(); |
| } |
| if (fmt.trailing.empty) |
| { |
| for (; !input.empty; input.popFront()) |
| { |
| static if (isStaticArray!T) |
| if (app.empty) |
| break; |
| app.put(input.front); |
| } |
| } |
| else |
| { |
| immutable end = fmt.trailing.front; |
| for (; !input.empty && input.front != end; input.popFront()) |
| { |
| static if (isStaticArray!T) |
| if (app.empty) |
| break; |
| app.put(input.front); |
| } |
| } |
| static if (isStaticArray!T) |
| { |
| enforceFmt(app.empty, "need more input"); |
| return result; |
| } |
| else |
| return app.data; |
| } |
| |
| T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) |
| if (isInputRange!Range && !is(StringTypeOf!T) && !isAggregateType!T |
| && (isArray!T || isAssociativeArray!T || is(T == enum))) |
| { |
| import std.conv : parse, text; |
| import std.format : enforceFmt; |
| |
| const spec = fmt.spec; |
| if (spec == '(') |
| { |
| return unformatRange!T(input, fmt); |
| } |
| |
| enforceFmt(spec == 's', |
| text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); |
| |
| return parse!T(input); |
| } |
| |
| /* |
| * Function that performs raw reading. Used by unformatValue |
| * for integral and float types. |
| */ |
| private T rawRead(T, Range)(ref Range input) |
| if (is(immutable ElementEncodingType!Range == immutable char) |
| || is(immutable ElementEncodingType!Range == immutable byte) |
| || is(immutable ElementEncodingType!Range == immutable ubyte)) |
| { |
| import std.range.primitives : popFront; |
| |
| union X |
| { |
| ubyte[T.sizeof] raw; |
| T typed; |
| } |
| X x; |
| foreach (i; 0 .. T.sizeof) |
| { |
| static if (isSomeString!Range) |
| { |
| x.raw[i] = input[0]; |
| input = input[1 .. $]; |
| } |
| else |
| { |
| // TODO: recheck this |
| x.raw[i] = input.front; |
| input.popFront(); |
| } |
| } |
| return x.typed; |
| } |
| |
| private T unformatRange(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| in (spec.spec == '(', "spec.spec must be '(' not " ~ spec.spec) |
| { |
| import std.range.primitives : empty, front, popFront; |
| import std.format : enforceFmt, format; |
| |
| T result; |
| static if (isStaticArray!T) |
| { |
| size_t i; |
| } |
| |
| const(Char)[] cont = spec.trailing; |
| for (size_t j = 0; j < spec.trailing.length; ++j) |
| { |
| if (spec.trailing[j] == '%') |
| { |
| cont = spec.trailing[0 .. j]; |
| break; |
| } |
| } |
| |
| bool checkEnd() |
| { |
| return input.empty || !cont.empty && input.front == cont.front; |
| } |
| |
| if (!checkEnd()) |
| { |
| for (;;) |
| { |
| auto fmt = FormatSpec!Char(spec.nested); |
| fmt.readUpToNextSpec(input); |
| enforceFmt(!input.empty, "Unexpected end of input when parsing range"); |
| |
| static if (isStaticArray!T) |
| { |
| result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt); |
| } |
| else static if (isDynamicArray!T) |
| { |
| import std.conv : WideElementType; |
| result ~= unformatElement!(WideElementType!T)(input, fmt); |
| } |
| else static if (isAssociativeArray!T) |
| { |
| auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt); |
| fmt.readUpToNextSpec(input); // eat key separator |
| |
| result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt); |
| } |
| |
| static if (isStaticArray!T) |
| { |
| enforceFmt(i <= T.length, |
| "Too many format specifiers for static array of length %d".format(T.length)); |
| } |
| |
| if (spec.sep !is null) |
| fmt.readUpToNextSpec(input); |
| auto sep = spec.sep !is null ? spec.sep : fmt.trailing; |
| |
| if (checkEnd()) |
| break; |
| |
| if (!sep.empty && input.front == sep.front) |
| { |
| while (!sep.empty) |
| { |
| enforceFmt(!input.empty, |
| "Unexpected end of input when parsing range separator"); |
| enforceFmt(input.front == sep.front, |
| "Unexpected character when parsing range separator"); |
| input.popFront(); |
| sep.popFront(); |
| } |
| } |
| } |
| } |
| static if (isStaticArray!T) |
| { |
| enforceFmt(i == T.length, |
| "Too few (%d) format specifiers for static array of length %d".format(i, T.length)); |
| } |
| return result; |
| } |
| |
| T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isInputRange!Range) |
| { |
| import std.conv : parseElement; |
| import std.format.read : unformatValue; |
| |
| static if (isSomeString!T) |
| { |
| if (spec.spec == 's') |
| { |
| return parseElement!T(input); |
| } |
| } |
| else static if (isSomeChar!T) |
| { |
| if (spec.spec == 's') |
| { |
| return parseElement!T(input); |
| } |
| } |
| |
| return unformatValue!T(input, spec); |
| } |