Check format specifier at compile time

This commit is contained in:
Eugen Wissner 2017-11-25 22:29:45 +01:00
parent 72d5760589
commit f334e6a1a0

View File

@ -225,7 +225,7 @@ private void raise2Power10(double* ohi,
* of the decimal point in decimalPos. +/-Inf and NaN are specified by special * of the decimal point in decimalPos. +/-Inf and NaN are specified by special
* values returned in the decimalPos parameter. * values returned in the decimalPos parameter.
* fracDigits is absolute normally, but if you want from first significant * fracDigits is absolute normally, but if you want from first significant
* digits (got %g and %e), or in 0x80000000 * digits (got %g), or in 0x80000000
*/ */
private int real2String(ref const(char)* start, private int real2String(ref const(char)* start,
ref uint len, ref uint len,
@ -425,24 +425,17 @@ private int real2String(ref const(char)* start,
return ng; return ng;
} }
private void leadSign(uint fl, char* sign) private void leadSign(bool negative, ref char[8] sign)
pure nothrow @nogc pure nothrow @nogc
{ {
sign[0] = 0; sign[0] = 0;
if (fl & Modifier.negative) if (negative)
{ {
sign[0] = 1; sign[0] = 1;
sign[1] = '-'; sign[1] = '-';
} }
} }
private enum Modifier : uint
{
intMax = 32,
negative = 128,
halfWidth = 512,
}
// Copies d to bits w/ strict aliasing (this compiles to nothing on /Ox). // Copies d to bits w/ strict aliasing (this compiles to nothing on /Ox).
private void copyFp(T, U)(ref T dest, ref U src) private void copyFp(T, U)(ref T dest, ref U src)
{ {
@ -471,134 +464,19 @@ private void ddmulthi(ref double oh,
ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo;
} }
/* private char[] vsprintf(string fmt, Args...)(return char[] buf, va_list va)
* Get float info.
*
* Returns: Sign bit.
*/
private int real2Parts(long* bits, out int exponent, const double value)
pure nothrow @nogc
{
long b;
// Load value and round at the fracDigits.
double d = value;
copyFp(b, d);
*bits = b & (((cast(ulong) 1) << 52) - 1);
// 1023 is the exponent bias, calculated as 2^(k - 1) - 1, where k is the
// number of bits used to represent the exponent, 11 bit for double.
exponent = cast(int) (((b >> 52) & 0x7ff) - 1023);
return cast(int) (b >> 63);
}
private char[] vsprintf(string fmt)(return char[] buf, va_list va)
pure nothrow @nogc pure nothrow @nogc
{ {
char* bf = buf.ptr; char* bf = buf.ptr;
string f = fmt;
int tlen; int tlen;
FmtLoop: while (true)
{
// Fast copy everything up to the next % (or end of string).
while ((cast(size_t) f.ptr) & 3)
{
schk:
if (f.length == 0)
{
break FmtLoop;
}
if (f[0] == '%')
{
goto scandd;
}
*bf++ = f[0];
f.popFront();
}
while (true)
{
// Check if the next 4 bytes contain %(0x25) or end of string.
// Using the 'hasless' trick:
// https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord
uint v = *cast(uint*) f;
uint c = (~v) & 0x80808080;
if ((((v ^ 0x25252525) - 0x01010101) & c) || f.length <= 3)
{
goto schk;
}
*cast(uint*) bf = v;
bf += 4;
f = f[4 .. $];
}
scandd:
f.popFront();
// Ok, we have a percent, read the modifiers first. // Ok, we have a percent, read the modifiers first.
int fw = 0;
int precision = -1; int precision = -1;
int tz = 0; int tz = 0;
uint fl = 0; bool negative;
// Get the field width.
if (f[0] == '*')
{
fw = va_arg!uint(va);
f.popFront();
}
else
{
while ((f[0] >= '0') && (f[0] <= '9'))
{
fw = fw * 10 + f[0] - '0';
f.popFront();
}
}
// Get the precision.
if (f[0] == '.')
{
f.popFront();
if (f[0] == '*')
{
precision = va_arg!uint(va);
f.popFront();
}
else
{
precision = 0;
while ((f[0] >= '0') && (f[0] <= '9'))
{
precision = precision * 10 + f[0] - '0';
f.popFront();
}
}
}
// Handle integer size overrides.
switch (f[0])
{
// are we halfwidth?
case 'h':
fl |= Modifier.halfWidth;
f.popFront();
break;
// are we 64-bit?
case 'l':
fl |= Modifier.intMax;
f.popFront();
break;
default:
break;
}
// Handle each replacement. // Handle each replacement.
enum NUMSZ = 512; // Big enough for e308 (with commas) or e-307. char[512] num; // Big enough for e308 (with commas) or e-307.
char[NUMSZ] num;
char[8] lead; char[8] lead;
char[8] tail; char[8] tail;
char *s; char *s;
@ -610,9 +488,8 @@ pure nothrow @nogc
int decimalPos; int decimalPos;
const(char)* sn; const(char)* sn;
switch (f[0]) static if (fmt[0] == 's') // String
{ {
case 's':
// Get the string. // Get the string.
s = va_arg!(char[])(va).ptr; s = va_arg!(char[])(va).ptr;
if (s is null) if (s is null)
@ -635,15 +512,6 @@ pure nothrow @nogc
++sn; ++sn;
} }
n = 0xffffffff; n = 0xffffffff;
if (precision >= 0)
{
n = cast(uint) (sn - s);
if (n >= cast(uint) precision)
{
goto ld;
}
n = (cast(uint) (precision - n)) >> 2;
}
while (n) while (n)
{ {
uint v = *cast(uint*) sn; uint v = *cast(uint*) sn;
@ -655,33 +523,29 @@ pure nothrow @nogc
--n; --n;
} }
goto lchk; goto lchk;
ld:
ld:
l = cast(uint) (sn - s); l = cast(uint) (sn - s);
// Clamp to precision.
if (l > cast(uint) precision)
{
l = precision;
}
lead[0] = 0; lead[0] = 0;
tail[0] = 0; tail[0] = 0;
precision = 0; precision = 0;
decimalPos = 0; decimalPos = 0;
// Copy the string in. // Copy the string in.
goto scopy; }
else static if (fmt[0] == 'c') // Char
case 'c': // Char. {
// Get the character. // Get the character.
s = num.ptr + NUMSZ - 1; s = num.ptr + num.length - 1;
*s = cast(char) va_arg!int(va); *s = cast(char) va_arg!int(va);
l = 1; l = 1;
lead[0] = 0; lead[0] = 0;
tail[0] = 0; tail[0] = 0;
precision = 0; precision = 0;
decimalPos = 0; decimalPos = 0;
goto scopy; }
else static if (fmt[0] == 'g') // Float
case 'g': // Float. {
fv = va_arg!double(va); fv = va_arg!double(va);
if (precision == -1) if (precision == -1)
{ {
@ -699,7 +563,7 @@ pure nothrow @nogc
fv, fv,
(precision - 1) | 0x80000000)) (precision - 1) | 0x80000000))
{ {
fl |= Modifier.negative; negative = true;
} }
// Clamp the precision and delete extra zeros after clamp. // Clamp the precision and delete extra zeros after clamp.
@ -747,25 +611,9 @@ pure nothrow @nogc
} }
goto dofloatfromg; goto dofloatfromg;
case 'e': // Float.
fv = va_arg!double(va);
if (precision == -1)
{
precision = 6; // Default is 6.
}
// read the double into a string
if (real2String(sn,
l,
num.ptr,
decimalPos,
fv,
precision | 0x80000000))
{
fl |= Modifier.negative;
}
doexpfromg: doexpfromg:
tail[0] = 0; tail[0] = 0;
leadSign(fl, lead.ptr); leadSign(negative, lead);
if (decimalPos == special) if (decimalPos == special)
{ {
s = cast(char*) sn; s = cast(char*) sn;
@ -821,21 +669,9 @@ pure nothrow @nogc
} }
goto flt_lead; goto flt_lead;
case 'f': // Float.
fv = va_arg!double(va);
doafloat:
if (precision == -1)
{
precision = 6; // Default is 6.
}
// Read the double into a string.
if (real2String(sn, l, num.ptr, decimalPos, fv, precision))
{
fl |= Modifier.negative;
}
dofloatfromg: dofloatfromg:
tail[0] = 0; tail[0] = 0;
leadSign(fl, lead.ptr); leadSign(negative, lead);
if (decimalPos == special) if (decimalPos == special)
{ {
s = cast(char*) sn; s = cast(char*) sn;
@ -971,21 +807,16 @@ pure nothrow @nogc
// Get the length that we copied. // Get the length that we copied.
l = cast(uint) (s - (num.ptr + 64)); l = cast(uint) (s - (num.ptr + 64));
s = num.ptr + 64; s = num.ptr + 64;
goto scopy;
case 'p': // Pointer
static if (size_t.sizeof == 8)
{
fl |= Modifier.intMax;
} }
else static if (fmt[0] == 'p') // Pointer
{
l = (4 << 4) | (4 << 8); l = (4 << 4) | (4 << 8);
lead[0] = 2; lead[0] = 2;
lead[1] = '0'; lead[1] = '0';
lead[2] = 'x'; lead[2] = 'x';
radixnum:
// Get the number. // Get the number.
if (fl & Modifier.intMax) static if (size_t.sizeof == 8)
{ {
n64 = va_arg!ulong(va); n64 = va_arg!ulong(va);
} }
@ -994,60 +825,53 @@ pure nothrow @nogc
n64 = va_arg!uint(va); n64 = va_arg!uint(va);
} }
s = num.ptr + NUMSZ; s = num.ptr + num.length;
decimalPos = 0; decimalPos = 0;
// Clear tail, and clear leading if value is zero. // Clear tail, and clear leading if value is zero.
tail[0] = 0; tail[0] = 0;
if (n64 == 0) if (n64 == 0)
{ {
lead[0] = 0; lead[0] = 0;
if (precision == 0)
{
l = 0;
goto scopy;
}
} }
// Convert to string. // Convert to string.
for (;;) for (;;)
{ {
*--s = hex[cast(size_t) (n64 & ((1 << (l >> 8)) - 1))]; *--s = hex[cast(size_t) (n64 & ((1 << (l >> 8)) - 1))];
n64 >>= (l >> 8); n64 >>= l >> 8;
if (!((n64) || (cast(int) ((num.ptr + NUMSZ) - s) < precision))) if (n64 == 0)
{ {
break; break;
} }
} }
// Get the length that we copied. // Get the length that we copied.
l = cast(uint)((num.ptr + NUMSZ) - s); l = cast(uint)((num.ptr + num.length) - s);
// Copy it. }
goto scopy; else static if (fmt[0] == 'u' || fmt[0] == 'i' || fmt[0] == 'l') // Integer
{
case 'u': // Unsigned.
case 'i': // Signed.
// Get the integer and abs it. // Get the integer and abs it.
if (fl & Modifier.intMax) if (fmt[0] == 'l')
{ {
long i64 = va_arg!long(va); long i64 = va_arg!long(va);
n64 = cast(ulong) i64; n64 = cast(ulong) i64;
if ((f[0] != 'u') && (i64 < 0)) if ((fmt[0] != 'u') && (i64 < 0))
{ {
n64 = cast(ulong) -i64; n64 = cast(ulong) -i64;
fl |= Modifier.negative; negative = true;
} }
} }
else else
{ {
int i = va_arg!int(va); int i = va_arg!int(va);
n64 = cast(uint) i; n64 = cast(uint) i;
if ((f[0] != 'u') && (i < 0)) if ((fmt[0] != 'u') && (i < 0))
{ {
n64 = cast(uint) -i; n64 = cast(uint) -i;
fl |= Modifier.negative; negative = true;
} }
} }
// Convert to string. // Convert to string.
s = num.ptr + NUMSZ; s = num.ptr + num.length;
l = 0; l = 0;
for (;;) for (;;)
@ -1078,7 +902,7 @@ pure nothrow @nogc
} }
if (n64 == 0) if (n64 == 0)
{ {
if ((s[0] == '0') && (s != (num.ptr + NUMSZ))) if ((s[0] == '0') && (s != (num.ptr + num.length)))
{ {
++s; ++s;
} }
@ -1091,10 +915,10 @@ pure nothrow @nogc
} }
tail[0] = 0; tail[0] = 0;
leadSign(fl, lead.ptr); leadSign(negative, lead);
// Get the length that we copied. // Get the length that we copied.
l = cast(uint) ((num.ptr + NUMSZ) - s); l = cast(uint) ((num.ptr + num.length) - s);
if (l == 0) if (l == 0)
{ {
*--s = '0'; *--s = '0';
@ -1104,53 +928,26 @@ pure nothrow @nogc
{ {
precision = 0; precision = 0;
} }
}
else
{
static assert(false);
}
scopy: scopy:
// Get fw=leading/trailing space, precision=leading zeros. // Get fw=leading/trailing space, precision=leading zeros.
if (precision < cast(int) l) if (precision < cast(int) l)
{ {
precision = l; precision = l;
} }
n = precision + lead[0] + tail[0] + tz; n = precision + lead[0] + tail[0] + tz;
if (fw < cast(int) n)
{
fw = n;
}
fw -= n;
precision -= l; precision -= l;
// Copy the spaces and/or zeros. // Copy the spaces and/or zeros.
if (fw + precision) if (precision)
{ {
int i; int i;
// copy leading spaces (or when doing %8.4d stuff)
while (fw > 0)
{
i = fw;
fw -= i;
while (i)
{
if (((cast(size_t) bf) & 3) == 0)
{
break;
}
*bf++ = ' ';
--i;
}
while (i >= 4)
{
*cast(uint*) bf = 0x20202020;
bf += 4;
i -= 4;
}
while (i)
{
*bf++ = ' ';
--i;
}
}
// copy leader // copy leader
sn = lead.ptr + 1; sn = lead.ptr + 1;
while (lead[0]) while (lead[0])
@ -1258,32 +1055,17 @@ pure nothrow @nogc
--i; --i;
} }
} }
break;
default: // Unknown, just copy code.
s = num.ptr + NUMSZ - 1;
*s = f[0];
l = 1;
fw = precision = fl = 0;
lead[0] = 0;
tail[0] = 0;
precision = 0;
decimalPos = 0;
goto scopy;
}
f.popFront();
}
*bf = 0; *bf = 0;
return buf[0 .. tlen + cast(int) (bf - buf.ptr)]; return buf[0 .. tlen + cast(int) (bf - buf.ptr)];
} }
char[] format(string fmt)(return char[] buf, ...) char[] format(string fmt, Args...)(return char[] buf, ...)
nothrow nothrow
{ {
va_list va; va_list va;
va_start(va, buf); va_start(va, buf);
auto result = vsprintf!fmt(buf, va); auto result = vsprintf!(fmt, Args)(buf, va);
va_end(va); va_end(va);
return result; return result;
} }
@ -1292,75 +1074,59 @@ nothrow unittest
{ {
char[318] buffer; char[318] buffer;
// Format without arguments.
assert(format!""(buffer) == "");
assert(format!"asdfqweryxcvz"(buffer) == "asdfqweryxcvz");
// Modifiers. // Modifiers.
assert(format!"%g"(buffer, 8.5) == "8.5"); assert(format!("g", double)(buffer, 8.5) == "8.5");
assert(format!"%5g"(buffer, 8.6) == " 8.6"); assert(format!("g", double)(buffer, 8.6) == "8.6");
assert(format!"%i"(buffer, 1000) == "1000"); assert(format!("i", int)(buffer, 1000) == "1000");
assert(format!"%*i"(buffer, 5, 1) == " 1"); assert(format!("i", int)(buffer, 1) == "1");
assert(format!"%.1f"(buffer, 10.25) == "10.3"); assert(format!("g", double)(buffer, 10.25) == "10.25");
assert(format!"%.*f"(buffer, 1, 10.25) == "10.3"); assert(format!("i", int)(buffer, 1) == "1");
assert(format!"%i"(buffer, 1) == "1"); assert(format!("g", double)(buffer, 0.01) == "0.01");
assert(format!"%7.3g"(buffer, 0.01) == " 0.01");
// Integer size. // Integer size.
assert(format!"%hi"(buffer, 10) == "10"); assert(format!("i", short)(buffer, 10) == "10");
assert(format!"%li"(buffer, 10) == "10"); assert(format!("l", long)(buffer, 10L) == "10");
assert(format!"%li"(buffer, 10L) == "10");
// String printing. // String printing.
assert(format!"%s"(buffer, "Some weired string") == "Some weired string"); assert(format!("s", string)(buffer, "Some weired string") == "Some weired string");
assert(format!"%s"(buffer, cast(string) null) == "null"); assert(format!("s", string)(buffer, cast(string) null) == "null");
assert(format!"%.4s"(buffer, "Some weired string") == "Some"); assert(format!("c", char)(buffer, 'c') == "c");
assert(format!"%c"(buffer, 'c') == "c");
// Integer conversions. // Integer conversions.
assert(format!"%i"(buffer, 8) == "8"); assert(format!("i", int)(buffer, 8) == "8");
assert(format!"%i"(buffer, 8) == "8"); assert(format!("i", int)(buffer, 8) == "8");
assert(format!"%i"(buffer, -8) == "-8"); assert(format!("i", int)(buffer, -8) == "-8");
assert(format!"%li"(buffer, -8L) == "-8"); assert(format!("l", long)(buffer, -8L) == "-8");
assert(format!"%u"(buffer, 8) == "8"); assert(format!("u", uint)(buffer, 8) == "8");
assert(format!"%i"(buffer, 100000001) == "100000001"); assert(format!("i", int)(buffer, 100000001) == "100000001");
assert(format!"%.12i"(buffer, 99999999L) == "000099999999"); assert(format!("i", int)(buffer, 99999999L) == "99999999");
assert(format!"%i"(buffer, 100000001) == "100000001");
// Floating point conversions. // Floating point conversions.
assert(format!"%g"(buffer, 0.1234) == "0.1234"); assert(format!("g", double)(buffer, 0.1234) == "0.1234");
assert(format!"%g"(buffer, 0.3) == "0.3"); assert(format!("g", double)(buffer, 0.3) == "0.3");
assert(format!"%g"(buffer, 0.333333333333) == "0.333333"); assert(format!("g", double)(buffer, 0.333333333333) == "0.333333");
assert(format!"%g"(buffer, 38234.1234) == "38234.1"); assert(format!("g", double)(buffer, 38234.1234) == "38234.1");
assert(format!"%g"(buffer, -0.3) == "-0.3"); assert(format!("g", double)(buffer, -0.3) == "-0.3");
assert(format!"%g"(buffer, 0.000000000000000006) == "6e-18"); assert(format!("g", double)(buffer, 0.000000000000000006) == "6e-18");
assert(format!"%g"(buffer, 0.0) == "0"); assert(format!("g", double)(buffer, 0.0) == "0");
assert(format!"%f"(buffer, 0.0) == "0.000000"); assert(format!("g", double)(buffer, double.init) == "NaN");
assert(format!"%f"(buffer, double.init) == "NaN"); assert(format!("g", double)(buffer, -double.init) == "-NaN");
assert(format!"%f"(buffer, double.infinity) == "Inf"); assert(format!("g", double)(buffer, double.infinity) == "Inf");
assert(format!"%.0g"(buffer, 0.0) == "0"); assert(format!("g", double)(buffer, -double.infinity) == "-Inf");
assert(format!"%f"(buffer, 0.000000000000000000000000003) == "0.000000"); assert(format!("g", double)(buffer, 0.000000000000000000000000003) == "3e-27");
assert(format!"%g"(buffer, 0.23432e304) == "2.3432e+303"); assert(format!("g", double)(buffer, 0.23432e304) == "2.3432e+303");
assert(format!"%f"(buffer, -0.23432e8) == "-23432000.000000"); assert(format!("g", double)(buffer, -0.23432e8) == "-2.3432e+07");
assert(format!"%e"(buffer, double.init) == "NaN"); assert(format!("g", double)(buffer, 1e-307) == "1e-307");
assert(format!"%f"(buffer, 1e-307) == "0.000000"); assert(format!("g", double)(buffer, 1e+8) == "1e+08");
assert(format!"%f"(buffer, 1e+8) == "100000000.000000"); assert(format!("g", double)(buffer, 111234.1) == "111234");
assert(format!"%05g"(buffer, 111234.1) == "111234"); assert(format!("g", double)(buffer, 0.999) == "0.999");
assert(format!"%.2g"(buffer, double.init) == "Na"); assert(format!("g", double)(buffer, 0x1p-16382L)); // "6.95336e-310"
assert(format!"%.1e"(buffer, 0.999) == "1.0e+00"); assert(format!("g", double)(buffer, 1e+3) == "1000");
assert(format!"%.0f"(buffer, 0.999) == "1"); assert(format!("g", double)(buffer, 38234.1234) == "38234.1");
assert(format!"%.9f"(buffer, 1e-307) == "0.000000000");
assert(format!"%g"(buffer, 0x1p-16382L)); // "6.95336e-310"
assert(format!"%f"(buffer, 1e+3) == "1000.000000");
assert(format!"%g"(buffer, 38234.1234) == "38234.1");
// Pointer conversions. // Pointer convesions.
assert(format!"%p"(buffer, cast(void*) 1) == "0x1"); assert(format!("p", void*)(buffer, cast(void*) 1) == "0x1");
assert(format!"%p"(buffer, cast(void*) 20) == "0x14"); assert(format!("p", void*)(buffer, cast(void*) 20) == "0x14");
// Unknown specifier.
assert(format!"%k"(buffer) == "k");
assert(format!"%%k"(buffer) == "%k");
} }
private struct FormatSpec private struct FormatSpec