blob: a3f3bc4f14105669afb38c4de3a840912e697f0d [file] [log] [blame]
/**
* Check the arguments to `printf` and `scanf` against the `format` string.
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/chkformat.d, _chkformat.d)
* Documentation: https://dlang.org/phobos/dmd_chkformat.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/chkformat.d
*/
module dmd.chkformat;
//import core.stdc.stdio : printf, scanf;
import core.stdc.ctype : isdigit;
import dmd.astenums;
import dmd.cond;
import dmd.errors;
import dmd.expression;
import dmd.globals;
import dmd.identifier;
import dmd.mtype;
import dmd.target;
/******************************************
* Check that arguments to a printf format string are compatible
* with that string. Issue errors for incompatibilities.
*
* Follows the C99 specification for printf.
*
* Takes a generous, rather than strict, view of compatiblity.
* For example, an unsigned value can be formatted with a signed specifier.
*
* Diagnosed incompatibilities are:
*
* 1. incompatible sizes which will cause argument misalignment
* 2. deferencing arguments that are not pointers
* 3. insufficient number of arguments
* 4. struct arguments
* 5. array and slice arguments
* 6. non-pointer arguments to `s` specifier
* 7. non-standard formats
* 8. undefined behavior per C99
*
* Per the C Standard, extra arguments are ignored.
*
* No attempt is made to fix the arguments or the format string.
*
* Params:
* loc = location for error messages
* format = format string
* args = arguments to match with format string
* isVa_list = if a "v" function (format check only)
*
* Returns:
* `true` if errors occurred
* References:
* C99 7.19.6.1
* https://www.cplusplus.com/reference/cstdio/printf/
*/
bool checkPrintfFormat(ref const Loc loc, scope const char[] format, scope Expression[] args, bool isVa_list)
{
//printf("checkPrintFormat('%.*s')\n", cast(int)format.length, format.ptr);
size_t n, gnu_m_count; // index in args / number of Format.GNU_m
for (size_t i = 0; i < format.length;)
{
if (format[i] != '%')
{
++i;
continue;
}
bool widthStar;
bool precisionStar;
size_t j = i;
const fmt = parsePrintfFormatSpecifier(format, j, widthStar, precisionStar);
const slice = format[i .. j];
i = j;
if (fmt == Format.percent)
continue; // "%%", no arguments
if (isVa_list)
{
// format check only
if (fmt == Format.error)
deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr);
continue;
}
if (fmt == Format.GNU_m)
++gnu_m_count;
Expression getNextArg(ref bool skip)
{
if (n == args.length)
{
if (args.length < (n + 1) - gnu_m_count)
deprecation(loc, "more format specifiers than %d arguments", cast(int)n);
else
skip = true;
return null;
}
return args[n++];
}
void errorMsg(const char* prefix, Expression arg, const char* texpect, Type tactual)
{
deprecation(arg.loc, "%sargument `%s` for format specification `\"%.*s\"` must be `%s`, not `%s`",
prefix ? prefix : "", arg.toChars(), cast(int)slice.length, slice.ptr, texpect, tactual.toChars());
}
if (widthStar)
{
bool skip;
auto e = getNextArg(skip);
if (skip)
continue;
if (!e)
return true;
auto t = e.type.toBasetype();
if (t.ty != Tint32 && t.ty != Tuns32)
errorMsg("width ", e, "int", t);
}
if (precisionStar)
{
bool skip;
auto e = getNextArg(skip);
if (skip)
continue;
if (!e)
return true;
auto t = e.type.toBasetype();
if (t.ty != Tint32 && t.ty != Tuns32)
errorMsg("precision ", e, "int", t);
}
bool skip;
auto e = getNextArg(skip);
if (skip)
continue;
if (!e)
return true;
auto t = e.type.toBasetype();
auto tnext = t.nextOf();
const c_longsize = target.c.longsize;
const ptrsize = target.ptrsize;
// Types which are promoted to int are allowed.
// Spec: C99 6.5.2.2.7
final switch (fmt)
{
case Format.u: // unsigned int
case Format.d: // int
if (t.ty != Tint32 && t.ty != Tuns32)
errorMsg(null, e, fmt == Format.u ? "uint" : "int", t);
break;
case Format.hhu: // unsigned char
case Format.hhd: // signed char
if (t.ty != Tint32 && t.ty != Tuns32 && t.ty != Tint8 && t.ty != Tuns8)
errorMsg(null, e, fmt == Format.hhu ? "ubyte" : "byte", t);
break;
case Format.hu: // unsigned short int
case Format.hd: // short int
if (t.ty != Tint32 && t.ty != Tuns32 && t.ty != Tint16 && t.ty != Tuns16)
errorMsg(null, e, fmt == Format.hu ? "ushort" : "short", t);
break;
case Format.lu: // unsigned long int
case Format.ld: // long int
if (!(t.isintegral() && t.size() == c_longsize))
{
if (fmt == Format.lu)
errorMsg(null, e, (c_longsize == 4 ? "uint" : "ulong"), t);
else
errorMsg(null, e, (c_longsize == 4 ? "int" : "long"), t);
}
break;
case Format.llu: // unsigned long long int
case Format.lld: // long long int
if (t.ty != Tint64 && t.ty != Tuns64)
errorMsg(null, e, fmt == Format.llu ? "ulong" : "long", t);
break;
case Format.ju: // uintmax_t
case Format.jd: // intmax_t
if (t.ty != Tint64 && t.ty != Tuns64)
{
if (fmt == Format.ju)
errorMsg(null, e, "core.stdc.stdint.uintmax_t", t);
else
errorMsg(null, e, "core.stdc.stdint.intmax_t", t);
}
break;
case Format.zd: // size_t
if (!(t.isintegral() && t.size() == ptrsize))
errorMsg(null, e, "size_t", t);
break;
case Format.td: // ptrdiff_t
if (!(t.isintegral() && t.size() == ptrsize))
errorMsg(null, e, "ptrdiff_t", t);
break;
case Format.GNU_a: // Format.GNU_a is only for scanf
case Format.lg:
case Format.g: // double
if (t.ty != Tfloat64 && t.ty != Timaginary64)
errorMsg(null, e, "double", t);
break;
case Format.Lg: // long double
if (t.ty != Tfloat80 && t.ty != Timaginary80)
errorMsg(null, e, "real", t);
break;
case Format.p: // pointer
if (t.ty != Tpointer && t.ty != Tnull && t.ty != Tclass && t.ty != Tdelegate && t.ty != Taarray)
errorMsg(null, e, "void*", t);
break;
case Format.n: // pointer to int
if (!(t.ty == Tpointer && tnext.ty == Tint32))
errorMsg(null, e, "int*", t);
break;
case Format.ln: // pointer to long int
if (!(t.ty == Tpointer && tnext.isintegral() && tnext.size() == c_longsize))
errorMsg(null, e, (c_longsize == 4 ? "int*" : "long*"), t);
break;
case Format.lln: // pointer to long long int
if (!(t.ty == Tpointer && tnext.ty == Tint64))
errorMsg(null, e, "long*", t);
break;
case Format.hn: // pointer to short
if (!(t.ty == Tpointer && tnext.ty == Tint16))
errorMsg(null, e, "short*", t);
break;
case Format.hhn: // pointer to signed char
if (!(t.ty == Tpointer && tnext.ty == Tint16))
errorMsg(null, e, "byte*", t);
break;
case Format.jn: // pointer to intmax_t
if (!(t.ty == Tpointer && tnext.ty == Tint64))
errorMsg(null, e, "core.stdc.stdint.intmax_t*", t);
break;
case Format.zn: // pointer to size_t
if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == ptrsize))
errorMsg(null, e, "size_t*", t);
break;
case Format.tn: // pointer to ptrdiff_t
if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == ptrsize))
errorMsg(null, e, "ptrdiff_t*", t);
break;
case Format.c: // char
if (t.ty != Tint32 && t.ty != Tuns32)
errorMsg(null, e, "char", t);
break;
case Format.lc: // wint_t
if (t.ty != Tint32 && t.ty != Tuns32)
errorMsg(null, e, "wchar_t", t);
break;
case Format.s: // pointer to char string
if (!(t.ty == Tpointer && (tnext.ty == Tchar || tnext.ty == Tint8 || tnext.ty == Tuns8)))
errorMsg(null, e, "char*", t);
break;
case Format.ls: // pointer to wchar_t string
if (!(t.ty == Tpointer && tnext.ty.isSomeChar && tnext.size() == target.c.wchar_tsize))
errorMsg(null, e, "wchar_t*", t);
break;
case Format.error:
deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr);
break;
case Format.GNU_m:
break; // not assert(0) because it may go through it if there are extra arguments
case Format.percent:
assert(0);
}
}
return false;
}
/******************************************
* Check that arguments to a scanf format string are compatible
* with that string. Issue errors for incompatibilities.
*
* Follows the C99 specification for scanf.
*
* Takes a generous, rather than strict, view of compatiblity.
* For example, an unsigned value can be formatted with a signed specifier.
*
* Diagnosed incompatibilities are:
*
* 1. incompatible sizes which will cause argument misalignment
* 2. deferencing arguments that are not pointers
* 3. insufficient number of arguments
* 4. struct arguments
* 5. array and slice arguments
* 6. non-standard formats
* 7. undefined behavior per C99
*
* Per the C Standard, extra arguments are ignored.
*
* No attempt is made to fix the arguments or the format string.
*
* Params:
* loc = location for error messages
* format = format string
* args = arguments to match with format string
* isVa_list = if a "v" function (format check only)
*
* Returns:
* `true` if errors occurred
* References:
* C99 7.19.6.2
* https://www.cplusplus.com/reference/cstdio/scanf/
*/
bool checkScanfFormat(ref const Loc loc, scope const char[] format, scope Expression[] args, bool isVa_list)
{
size_t n = 0;
for (size_t i = 0; i < format.length;)
{
if (format[i] != '%')
{
++i;
continue;
}
bool asterisk;
size_t j = i;
const fmt = parseScanfFormatSpecifier(format, j, asterisk);
const slice = format[i .. j];
i = j;
if (fmt == Format.percent || asterisk)
continue; // "%%", "%*": no arguments
if (isVa_list)
{
// format check only
if (fmt == Format.error)
deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr);
continue;
}
Expression getNextArg()
{
if (n == args.length)
{
if (!asterisk)
deprecation(loc, "more format specifiers than %d arguments", cast(int)n);
return null;
}
return args[n++];
}
void errorMsg(const char* prefix, Expression arg, const char* texpect, Type tactual)
{
deprecation(arg.loc, "%sargument `%s` for format specification `\"%.*s\"` must be `%s`, not `%s`",
prefix ? prefix : "", arg.toChars(), cast(int)slice.length, slice.ptr, texpect, tactual.toChars());
}
auto e = getNextArg();
if (!e)
return true;
auto t = e.type.toBasetype();
auto tnext = t.nextOf();
const c_longsize = target.c.longsize;
const ptrsize = target.ptrsize;
final switch (fmt)
{
case Format.n:
case Format.d: // pointer to int
if (!(t.ty == Tpointer && tnext.ty == Tint32))
errorMsg(null, e, "int*", t);
break;
case Format.hhn:
case Format.hhd: // pointer to signed char
if (!(t.ty == Tpointer && tnext.ty == Tint16))
errorMsg(null, e, "byte*", t);
break;
case Format.hn:
case Format.hd: // pointer to short
if (!(t.ty == Tpointer && tnext.ty == Tint16))
errorMsg(null, e, "short*", t);
break;
case Format.ln:
case Format.ld: // pointer to long int
if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == c_longsize))
errorMsg(null, e, (c_longsize == 4 ? "int*" : "long*"), t);
break;
case Format.lln:
case Format.lld: // pointer to long long int
if (!(t.ty == Tpointer && tnext.ty == Tint64))
errorMsg(null, e, "long*", t);
break;
case Format.jn:
case Format.jd: // pointer to intmax_t
if (!(t.ty == Tpointer && tnext.ty == Tint64))
errorMsg(null, e, "core.stdc.stdint.intmax_t*", t);
break;
case Format.zn:
case Format.zd: // pointer to size_t
if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == ptrsize))
errorMsg(null, e, "size_t*", t);
break;
case Format.tn:
case Format.td: // pointer to ptrdiff_t
if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == ptrsize))
errorMsg(null, e, "ptrdiff_t*", t);
break;
case Format.u: // pointer to unsigned int
if (!(t.ty == Tpointer && tnext.ty == Tuns32))
errorMsg(null, e, "uint*", t);
break;
case Format.hhu: // pointer to unsigned char
if (!(t.ty == Tpointer && tnext.ty == Tuns8))
errorMsg(null, e, "ubyte*", t);
break;
case Format.hu: // pointer to unsigned short int
if (!(t.ty == Tpointer && tnext.ty == Tuns16))
errorMsg(null, e, "ushort*", t);
break;
case Format.lu: // pointer to unsigned long int
if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == c_longsize))
errorMsg(null, e, (c_longsize == 4 ? "uint*" : "ulong*"), t);
break;
case Format.llu: // pointer to unsigned long long int
if (!(t.ty == Tpointer && tnext.ty == Tuns64))
errorMsg(null, e, "ulong*", t);
break;
case Format.ju: // pointer to uintmax_t
if (!(t.ty == Tpointer && tnext.ty == Tuns64))
errorMsg(null, e, "core.stdc.stdint.uintmax_t*", t);
break;
case Format.g: // pointer to float
if (!(t.ty == Tpointer && tnext.ty == Tfloat32))
errorMsg(null, e, "float*", t);
break;
case Format.lg: // pointer to double
if (!(t.ty == Tpointer && tnext.ty == Tfloat64))
errorMsg(null, e, "double*", t);
break;
case Format.Lg: // pointer to long double
if (!(t.ty == Tpointer && tnext.ty == Tfloat80))
errorMsg(null, e, "real*", t);
break;
case Format.GNU_a:
case Format.GNU_m:
case Format.c:
case Format.s: // pointer to char string
if (!(t.ty == Tpointer && (tnext.ty == Tchar || tnext.ty == Tint8 || tnext.ty == Tuns8)))
errorMsg(null, e, "char*", t);
break;
case Format.lc:
case Format.ls: // pointer to wchar_t string
if (!(t.ty == Tpointer && tnext.ty.isSomeChar && tnext.size() == target.c.wchar_tsize))
errorMsg(null, e, "wchar_t*", t);
break;
case Format.p: // double pointer
if (!(t.ty == Tpointer && tnext.ty == Tpointer))
errorMsg(null, e, "void**", t);
break;
case Format.error:
deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr);
break;
case Format.percent:
assert(0);
}
}
return false;
}
private:
/**************************************
* Parse the *format specifier* which is of the form:
*
* `%[*][width][length]specifier`
*
* Params:
* format = format string
* idx = index of `%` of start of format specifier,
* which gets updated to index past the end of it,
* even if `Format.error` is returned
* asterisk = set if there is a `*` sub-specifier
* Returns:
* Format
*/
Format parseScanfFormatSpecifier(scope const char[] format, ref size_t idx,
out bool asterisk) nothrow pure @safe
{
auto i = idx;
assert(format[i] == '%');
const length = format.length;
Format error()
{
idx = i;
return Format.error;
}
++i;
if (i == length)
return error();
if (format[i] == '%')
{
idx = i + 1;
return Format.percent;
}
// * sub-specifier
if (format[i] == '*')
{
++i;
if (i == length)
return error();
asterisk = true;
}
// fieldWidth
while (isdigit(format[i]))
{
i++;
if (i == length)
return error();
}
/* Read the scanset
* A scanset can be anything, so we just check that it is paired
*/
if (format[i] == '[')
{
while (i < length)
{
if (format[i] == ']')
break;
++i;
}
// no `]` found
if (i == length)
return error();
++i;
// no specifier after `]`
// it could be mixed with the one above, but then idx won't have the right index
if (i == length)
return error();
}
/* Read the specifier
*/
char genSpec;
Format specifier = parseGenericFormatSpecifier(format, i, genSpec);
if (specifier == Format.error)
return error();
idx = i;
return specifier; // success
}
/**************************************
* Parse the *format specifier* which is of the form:
*
* `%[flags][field width][.precision][length modifier]specifier`
*
* Params:
* format = format string
* idx = index of `%` of start of format specifier,
* which gets updated to index past the end of it,
* even if `Format.error` is returned
* widthStar = set if * for width
* precisionStar = set if * for precision
* Returns:
* Format
*/
Format parsePrintfFormatSpecifier(scope const char[] format, ref size_t idx,
out bool widthStar, out bool precisionStar) nothrow pure @safe
{
auto i = idx;
assert(format[i] == '%');
const length = format.length;
bool hash;
bool zero;
bool flags;
bool width;
bool precision;
Format error()
{
idx = i;
return Format.error;
}
++i;
if (i == length)
return error();
if (format[i] == '%')
{
idx = i + 1;
return Format.percent;
}
/* Read the `flags`
*/
while (1)
{
const c = format[i];
if (c == '-' ||
c == '+' ||
c == ' ')
{
flags = true;
}
else if (c == '#')
{
hash = true;
}
else if (c == '0')
{
zero = true;
}
else
break;
++i;
if (i == length)
return error();
}
/* Read the `field width`
*/
{
const c = format[i];
if (c == '*')
{
width = true;
widthStar = true;
++i;
if (i == length)
return error();
}
else if ('1' <= c && c <= '9')
{
width = true;
++i;
if (i == length)
return error();
while ('0' <= format[i] && format[i] <= '9')
{
++i;
if (i == length)
return error();
}
}
}
/* Read the `precision`
*/
if (format[i] == '.')
{
precision = true;
++i;
if (i == length)
return error();
const c = format[i];
if (c == '*')
{
precisionStar = true;
++i;
if (i == length)
return error();
}
else if ('0' <= c && c <= '9')
{
++i;
if (i == length)
return error();
while ('0' <= format[i] && format[i] <= '9')
{
++i;
if (i == length)
return error();
}
}
}
/* Read the specifier
*/
char genSpec;
Format specifier = parseGenericFormatSpecifier(format, i, genSpec);
if (specifier == Format.error)
return error();
switch (genSpec)
{
case 'c':
case 's':
if (hash || zero)
return error();
break;
case 'd':
case 'i':
if (hash)
return error();
break;
case 'n':
if (hash || zero || precision || width || flags)
return error();
break;
default:
break;
}
idx = i;
return specifier; // success
}
/* Different kinds of formatting specifications, variations we don't
care about are merged. (Like we don't care about the difference between
f, e, g, a, etc.)
For `scanf`, every format is a pointer.
*/
enum Format
{
d, // int
hhd, // signed char
hd, // short int
ld, // long int
lld, // long long int
jd, // intmax_t
zd, // size_t
td, // ptrdiff_t
u, // unsigned int
hhu, // unsigned char
hu, // unsigned short int
lu, // unsigned long int
llu, // unsigned long long int
ju, // uintmax_t
g, // float (scanf) / double (printf)
lg, // double (scanf)
Lg, // long double (both)
s, // char string (both)
ls, // wchar_t string (both)
c, // char (printf)
lc, // wint_t (printf)
p, // pointer
n, // pointer to int
hhn, // pointer to signed char
hn, // pointer to short
ln, // pointer to long int
lln, // pointer to long long int
jn, // pointer to intmax_t
zn, // pointer to size_t
tn, // pointer to ptrdiff_t
GNU_a, // GNU ext. : address to a string with no maximum size (scanf)
GNU_m, // GNU ext. : string corresponding to the error code in errno (printf) / length modifier (scanf)
percent, // %% (i.e. no argument)
error, // invalid format specification
}
/**************************************
* Parse the *length specifier* and the *specifier* of the following form:
* `[length]specifier`
*
* Params:
* format = format string
* idx = index of of start of format specifier,
* which gets updated to index past the end of it,
* even if `Format.error` is returned
* genSpecifier = Generic specifier. For instance, it will be set to `d` if the
* format is `hdd`.
* Returns:
* Format
*/
Format parseGenericFormatSpecifier(scope const char[] format,
ref size_t idx, out char genSpecifier, bool useGNUExts =
findCondition(global.versionids, Identifier.idPool("CRuntime_Glibc"))) nothrow pure @trusted
{
const length = format.length;
/* Read the `length modifier`
*/
const lm = format[idx];
bool lm1; // if jztL
bool lm2; // if `hh` or `ll`
if (lm == 'j' ||
lm == 'z' ||
lm == 't' ||
lm == 'L')
{
++idx;
if (idx == length)
return Format.error;
lm1 = true;
}
else if (lm == 'h' || lm == 'l')
{
++idx;
if (idx == length)
return Format.error;
lm2 = lm == format[idx];
if (lm2)
{
++idx;
if (idx == length)
return Format.error;
}
}
/* Read the `specifier`
*/
Format specifier;
const sc = format[idx];
genSpecifier = sc;
switch (sc)
{
case 'd':
case 'i':
if (lm == 'L')
specifier = Format.error;
else
specifier = lm == 'h' && lm2 ? Format.hhd :
lm == 'h' ? Format.hd :
lm == 'l' && lm2 ? Format.lld :
lm == 'l' ? Format.ld :
lm == 'j' ? Format.jd :
lm == 'z' ? Format.zd :
lm == 't' ? Format.td :
Format.d;
break;
case 'u':
case 'o':
case 'x':
case 'X':
if (lm == 'L')
specifier = Format.error;
else
specifier = lm == 'h' && lm2 ? Format.hhu :
lm == 'h' ? Format.hu :
lm == 'l' && lm2 ? Format.llu :
lm == 'l' ? Format.lu :
lm == 'j' ? Format.ju :
lm == 'z' ? Format.zd :
lm == 't' ? Format.td :
Format.u;
break;
case 'a':
if (useGNUExts)
{
// https://www.gnu.org/software/libc/manual/html_node/Dynamic-String-Input.html
specifier = Format.GNU_a;
break;
}
goto case;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'A':
if (lm == 'L')
specifier = Format.Lg;
else if (lm1 || lm2 || lm == 'h')
specifier = Format.error;
else
specifier = lm == 'l' ? Format.lg : Format.g;
break;
case 'c':
if (lm1 || lm2 || lm == 'h')
specifier = Format.error;
else
specifier = lm == 'l' ? Format.lc : Format.c;
break;
case 's':
if (lm1 || lm2 || lm == 'h')
specifier = Format.error;
else
specifier = lm == 'l' ? Format.ls : Format.s;
break;
case 'p':
if (lm1 || lm2 || lm == 'h' || lm == 'l')
specifier = Format.error;
else
specifier = Format.p;
break;
case 'n':
if (lm == 'L')
specifier = Format.error;
else
specifier = lm == 'l' && lm2 ? Format.lln :
lm == 'l' ? Format.ln :
lm == 'h' && lm2 ? Format.hhn :
lm == 'h' ? Format.hn :
lm == 'j' ? Format.jn :
lm == 'z' ? Format.zn :
lm == 't' ? Format.tn :
Format.n;
break;
case 'm':
if (useGNUExts)
{
// https://www.gnu.org/software/libc/manual/html_node/Other-Output-Conversions.html
specifier = Format.GNU_m;
break;
}
goto default;
default:
specifier = Format.error;
break;
}
++idx;
return specifier; // success
}
unittest
{
/* parseGenericFormatSpecifier
*/
char genSpecifier;
size_t idx;
assert(parseGenericFormatSpecifier("hhd", idx, genSpecifier) == Format.hhd);
assert(genSpecifier == 'd');
idx = 0;
assert(parseGenericFormatSpecifier("hn", idx, genSpecifier) == Format.hn);
assert(genSpecifier == 'n');
idx = 0;
assert(parseGenericFormatSpecifier("ji", idx, genSpecifier) == Format.jd);
assert(genSpecifier == 'i');
idx = 0;
assert(parseGenericFormatSpecifier("lu", idx, genSpecifier) == Format.lu);
assert(genSpecifier == 'u');
idx = 0;
assert(parseGenericFormatSpecifier("k", idx, genSpecifier) == Format.error);
/* parsePrintfFormatSpecifier
*/
bool widthStar;
bool precisionStar;
// one for each Format
idx = 0;
assert(parsePrintfFormatSpecifier("%d", idx, widthStar, precisionStar) == Format.d);
assert(idx == 2);
assert(!widthStar && !precisionStar);
idx = 0;
assert(parsePrintfFormatSpecifier("%ld", idx, widthStar, precisionStar) == Format.ld);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%lld", idx, widthStar, precisionStar) == Format.lld);
assert(idx == 4);
idx = 0;
assert(parsePrintfFormatSpecifier("%jd", idx, widthStar, precisionStar) == Format.jd);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%zd", idx, widthStar, precisionStar) == Format.zd);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%td", idx, widthStar, precisionStar) == Format.td);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%g", idx, widthStar, precisionStar) == Format.g);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%Lg", idx, widthStar, precisionStar) == Format.Lg);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%p", idx, widthStar, precisionStar) == Format.p);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%n", idx, widthStar, precisionStar) == Format.n);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%ln", idx, widthStar, precisionStar) == Format.ln);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%lln", idx, widthStar, precisionStar) == Format.lln);
assert(idx == 4);
idx = 0;
assert(parsePrintfFormatSpecifier("%hn", idx, widthStar, precisionStar) == Format.hn);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%hhn", idx, widthStar, precisionStar) == Format.hhn);
assert(idx == 4);
idx = 0;
assert(parsePrintfFormatSpecifier("%jn", idx, widthStar, precisionStar) == Format.jn);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%zn", idx, widthStar, precisionStar) == Format.zn);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%tn", idx, widthStar, precisionStar) == Format.tn);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%c", idx, widthStar, precisionStar) == Format.c);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%lc", idx, widthStar, precisionStar) == Format.lc);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%s", idx, widthStar, precisionStar) == Format.s);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%ls", idx, widthStar, precisionStar) == Format.ls);
assert(idx == 3);
idx = 0;
assert(parsePrintfFormatSpecifier("%%", idx, widthStar, precisionStar) == Format.percent);
assert(idx == 2);
// Synonyms
idx = 0;
assert(parsePrintfFormatSpecifier("%i", idx, widthStar, precisionStar) == Format.d);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%u", idx, widthStar, precisionStar) == Format.u);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%o", idx, widthStar, precisionStar) == Format.u);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%x", idx, widthStar, precisionStar) == Format.u);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%X", idx, widthStar, precisionStar) == Format.u);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%f", idx, widthStar, precisionStar) == Format.g);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%F", idx, widthStar, precisionStar) == Format.g);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%G", idx, widthStar, precisionStar) == Format.g);
assert(idx == 2);
idx = 0;
Format g = parsePrintfFormatSpecifier("%a", idx, widthStar, precisionStar);
assert(g == Format.g || g == Format.GNU_a);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%A", idx, widthStar, precisionStar) == Format.g);
assert(idx == 2);
idx = 0;
assert(parsePrintfFormatSpecifier("%lg", idx, widthStar, precisionStar) == Format.lg);
assert(idx == 3);
// width, precision
idx = 0;
assert(parsePrintfFormatSpecifier("%*d", idx, widthStar, precisionStar) == Format.d);
assert(idx == 3);
assert(widthStar && !precisionStar);
idx = 0;
assert(parsePrintfFormatSpecifier("%.*d", idx, widthStar, precisionStar) == Format.d);
assert(idx == 4);
assert(!widthStar && precisionStar);
idx = 0;
assert(parsePrintfFormatSpecifier("%*.*d", idx, widthStar, precisionStar) == Format.d);
assert(idx == 5);
assert(widthStar && precisionStar);
// Too short formats
{
foreach (s; ["%", "%-", "%+", "% ", "%#", "%0", "%*", "%1", "%19", "%.", "%.*", "%.1", "%.12",
"%j", "%z", "%t", "%l", "%h", "%ll", "%hh"])
{
idx = 0;
assert(parsePrintfFormatSpecifier(s, idx, widthStar, precisionStar) == Format.error);
assert(idx == s.length);
}
}
// Undefined format combinations
{
foreach (s; ["%#d", "%llg", "%jg", "%zg", "%tg", "%hg", "%hhg",
"%#c", "%0c", "%jc", "%zc", "%tc", "%Lc", "%hc", "%hhc", "%llc",
"%#s", "%0s", "%js", "%zs", "%ts", "%Ls", "%hs", "%hhs", "%lls",
"%jp", "%zp", "%tp", "%Lp", "%hp", "%lp", "%hhp", "%llp",
"%-n", "%+n", "% n", "%#n", "%0n", "%*n", "%1n", "%19n", "%.n", "%.*n", "%.1n", "%.12n", "%Ln", "%K"])
{
idx = 0;
assert(parsePrintfFormatSpecifier(s, idx, widthStar, precisionStar) == Format.error);
assert(idx == s.length);
}
}
/* parseScanfFormatSpecifier
*/
bool asterisk;
// one for each Format
idx = 0;
assert(parseScanfFormatSpecifier("%d", idx, asterisk) == Format.d);
assert(idx == 2);
assert(!asterisk);
idx = 0;
assert(parseScanfFormatSpecifier("%hhd", idx, asterisk) == Format.hhd);
assert(idx == 4);
idx = 0;
assert(parseScanfFormatSpecifier("%hd", idx, asterisk) == Format.hd);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%ld", idx, asterisk) == Format.ld);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%lld", idx, asterisk) == Format.lld);
assert(idx == 4);
idx = 0;
assert(parseScanfFormatSpecifier("%jd", idx, asterisk) == Format.jd);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%zd", idx, asterisk) == Format.zd);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%td", idx, asterisk,) == Format.td);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%u", idx, asterisk) == Format.u);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%hhu", idx, asterisk,) == Format.hhu);
assert(idx == 4);
idx = 0;
assert(parseScanfFormatSpecifier("%hu", idx, asterisk) == Format.hu);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%lu", idx, asterisk) == Format.lu);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%llu", idx, asterisk) == Format.llu);
assert(idx == 4);
idx = 0;
assert(parseScanfFormatSpecifier("%ju", idx, asterisk) == Format.ju);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%g", idx, asterisk) == Format.g);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%lg", idx, asterisk) == Format.lg);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%Lg", idx, asterisk) == Format.Lg);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%p", idx, asterisk) == Format.p);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%s", idx, asterisk) == Format.s);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%ls", idx, asterisk,) == Format.ls);
assert(idx == 3);
idx = 0;
assert(parseScanfFormatSpecifier("%%", idx, asterisk) == Format.percent);
assert(idx == 2);
// Synonyms
idx = 0;
assert(parseScanfFormatSpecifier("%i", idx, asterisk) == Format.d);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%n", idx, asterisk) == Format.n);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%o", idx, asterisk) == Format.u);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%x", idx, asterisk) == Format.u);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%f", idx, asterisk) == Format.g);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%e", idx, asterisk) == Format.g);
assert(idx == 2);
idx = 0;
g = parseScanfFormatSpecifier("%a", idx, asterisk);
assert(g == Format.g || g == Format.GNU_a);
assert(idx == 2);
idx = 0;
assert(parseScanfFormatSpecifier("%c", idx, asterisk) == Format.c);
assert(idx == 2);
// asterisk
idx = 0;
assert(parseScanfFormatSpecifier("%*d", idx, asterisk) == Format.d);
assert(idx == 3);
assert(asterisk);
idx = 0;
assert(parseScanfFormatSpecifier("%9ld", idx, asterisk) == Format.ld);
assert(idx == 4);
assert(!asterisk);
idx = 0;
assert(parseScanfFormatSpecifier("%*25984hhd", idx, asterisk) == Format.hhd);
assert(idx == 10);
assert(asterisk);
// scansets
idx = 0;
assert(parseScanfFormatSpecifier("%[a-zA-Z]s", idx, asterisk) == Format.s);
assert(idx == 10);
assert(!asterisk);
idx = 0;
assert(parseScanfFormatSpecifier("%*25[a-z]hhd", idx, asterisk) == Format.hhd);
assert(idx == 12);
assert(asterisk);
// Too short formats
foreach (s; ["%", "% ", "%#", "%0", "%*", "%1", "%19",
"%j", "%z", "%t", "%l", "%h", "%ll", "%hh", "%K"])
{
idx = 0;
assert(parseScanfFormatSpecifier(s, idx, asterisk) == Format.error);
assert(idx == s.length);
}
// Undefined format combinations
foreach (s; ["%Ld", "%llg", "%jg", "%zg", "%tg", "%hg", "%hhg",
"%jc", "%zc", "%tc", "%Lc", "%hc", "%hhc", "%llc",
"%jp", "%zp", "%tp", "%Lp", "%hp", "%lp", "%hhp", "%llp",
"%-", "%+", "%#", "%0", "%.", "%Ln"])
{
idx = 0;
assert(parseScanfFormatSpecifier(s, idx, asterisk) == Format.error);
assert(idx == s.length);
}
// Invalid scansets
foreach (s; ["%[]", "%[s", "%[0-9lld", "%[", "%[a-z]"])
{
idx = 0;
assert(parseScanfFormatSpecifier(s, idx, asterisk) == Format.error);
assert(idx == s.length);
}
}