| // 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); | |
| } |