From 7bcb86b90d6b2bc2def483851c9c8661acefbb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Louren=C3=A7o?= Date: Sat, 10 Dec 2022 14:54:55 +0000 Subject: [PATCH 1/3] refactor(formattedRead): add not is type validation to template argument 'fmt' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Lourenço --- std/format/read.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/format/read.d b/std/format/read.d index da9d0dc14db..f1f37c7a541 100644 --- a/std/format/read.d +++ b/std/format/read.d @@ -198,7 +198,7 @@ module std.format.read; import std.format.spec : FormatSpec; import std.format.internal.read; -import std.traits : isSomeString; +import std.traits : isSomeString, isType; /** Reads an input range according to a format string and stores the read @@ -300,7 +300,7 @@ uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, au /// ditto uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args) -if (isSomeString!(typeof(fmt))) +if (!isType!fmt && isSomeString!(typeof(fmt))) { import std.format : checkFormatException; import std.meta : staticMap; From 76fc1858819b32b177253eb06882f0b0d4215642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Louren=C3=A7o?= Date: Sat, 10 Dec 2022 14:57:33 +0000 Subject: [PATCH 2/3] feat(formattedRead): add overloads to return a tuple with the read values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Lourenço --- std/format/read.d | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/std/format/read.d b/std/format/read.d index f1f37c7a541..093b381bb76 100644 --- a/std/format/read.d +++ b/std/format/read.d @@ -198,6 +198,7 @@ module std.format.read; import std.format.spec : FormatSpec; import std.format.internal.read; +import std.meta : allSatisfy; import std.traits : isSomeString, isType; /** @@ -692,6 +693,72 @@ if (!isType!fmt && isSomeString!(typeof(fmt))) assert(aa2 == ["hello":1, "world":2]); } +/** +Reads an input range according to a format string and returns a tuple with the +read values. + +Format specifiers with format character $(B 'd'), $(B 'u') and $(B +'c') can take a $(B '*') parameter for skipping values. + +The second version of `formattedRead` takes the format string as +template argument. In this case, it is checked for consistency at +compile-time. + +Params: + Args = a variadic list of types of the arguments + */ +template formattedRead(Args...) +if (Args.length && allSatisfy!(isType, Args)) +{ + import std.typecons : Flag, Tuple, Yes; + + /** + Params: + r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives), + where the formatted input is read from + fmt = a $(MREF_ALTTEXT format string, std,format) + Range = the type of the input range `r` + Char = the character type used for `fmt` + + Returns: + A Tuple!Args with the elements filled. If the input range `r` ends early, + the missing arguments will be default initialized. + + Throws: + A $(REF_ALTTEXT FormatException, FormatException, std, format) + if reading did not succeed. + */ + Tuple!Args formattedRead(Range, Char)(auto ref Range r, const(Char)[] fmt, Flag!"exhaustive" exhaustive = Yes.exhaustive) + { + import core.lifetime : forward; + import std.exception : enforce; + import std.format : FormatException; + + Tuple!Args args; + const numArgsFilled = .formattedRead(forward!r, fmt, args.expand); + if (exhaustive) enforce!FormatException(numArgsFilled == Args.length); + return args; + } +} + +/// ditto +template formattedRead(alias fmt, Args...) +if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args)) +{ + import std.typecons : Flag, Tuple, Yes; + Tuple!Args formattedRead(Range)(auto ref Range r, Flag!"exhaustive" exhaustive = Yes.exhaustive) + { + import core.lifetime : forward; + import std.exception : enforce; + import std.format : FormatException; + + Tuple!Args args; + const numArgsFilled = .formattedRead!fmt(forward!r, args.expand); + if (exhaustive) enforce!FormatException(numArgsFilled == Args.length); + return args; + } +} + /** Reads a value from the given _input range and converts it according to a format specifier. From 71f05e6e85c801dc8138a6aa0b2d865b24bf53a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Louren=C3=A7o?= Date: Sat, 10 Dec 2022 14:59:09 +0000 Subject: [PATCH 3/3] test(formattedRead): add unnittests for the tuple return type overloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Lourenço --- std/format/read.d | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/std/format/read.d b/std/format/read.d index 093b381bb76..e64b791ae7b 100644 --- a/std/format/read.d +++ b/std/format/read.d @@ -741,6 +741,22 @@ if (Args.length && allSatisfy!(isType, Args)) } } +/// +@safe pure unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + import std.typecons : No, tuple; + + auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s"); + assert(complete == tuple("hello", 34.5, 124)); + + assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s")); + + auto missing = "hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s", No.exhaustive); + assert(missing == tuple("hello", 34.5, int.init)); +} + /// ditto template formattedRead(alias fmt, Args...) if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args)) @@ -759,6 +775,23 @@ if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isTy } } +/// The format string can be checked at compile-time: +@safe pure unittest +{ + import std.exception : assertThrown; + import std.format : FormatException; + import std.typecons : No, tuple; + + auto expected = tuple("hello", 124, 34.5); + auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double); + assert(result == expected); + + assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int)); + + auto missing = "hello!34.5:".formattedRead!("%s!%s:%s", string, double, int)(No.exhaustive); + assert(missing == tuple("hello", 34.5, int.init)); +} + /** Reads a value from the given _input range and converts it according to a format specifier.