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
* values returned in the decimalPos parameter.
* 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,
ref uint len,
@ -425,24 +425,17 @@ private int real2String(ref const(char)* start,
return ng;
}
private void leadSign(uint fl, char* sign)
private void leadSign(bool negative, ref char[8] sign)
pure nothrow @nogc
{
sign[0] = 0;
if (fl & Modifier.negative)
if (negative)
{
sign[0] = 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).
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;
}
/*
* 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)
private char[] vsprintf(string fmt, Args...)(return char[] buf, va_list va)
pure nothrow @nogc
{
char* bf = buf.ptr;
string f = fmt;
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.
int fw = 0;
int precision = -1;
int tz = 0;
uint fl = 0;
// 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;
}
bool negative;
// Handle each replacement.
enum NUMSZ = 512; // Big enough for e308 (with commas) or e-307.
char[NUMSZ] num;
char[512] num; // Big enough for e308 (with commas) or e-307.
char[8] lead;
char[8] tail;
char *s;
@ -610,9 +488,8 @@ pure nothrow @nogc
int decimalPos;
const(char)* sn;
switch (f[0])
static if (fmt[0] == 's') // String
{
case 's':
// Get the string.
s = va_arg!(char[])(va).ptr;
if (s is null)
@ -635,15 +512,6 @@ pure nothrow @nogc
++sn;
}
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)
{
uint v = *cast(uint*) sn;
@ -655,33 +523,29 @@ pure nothrow @nogc
--n;
}
goto lchk;
ld:
ld:
l = cast(uint) (sn - s);
// Clamp to precision.
if (l > cast(uint) precision)
{
l = precision;
}
lead[0] = 0;
tail[0] = 0;
precision = 0;
decimalPos = 0;
// Copy the string in.
goto scopy;
case 'c': // Char.
}
else static if (fmt[0] == 'c') // Char
{
// Get the character.
s = num.ptr + NUMSZ - 1;
s = num.ptr + num.length - 1;
*s = cast(char) va_arg!int(va);
l = 1;
lead[0] = 0;
tail[0] = 0;
precision = 0;
decimalPos = 0;
goto scopy;
case 'g': // Float.
}
else static if (fmt[0] == 'g') // Float
{
fv = va_arg!double(va);
if (precision == -1)
{
@ -699,7 +563,7 @@ pure nothrow @nogc
fv,
(precision - 1) | 0x80000000))
{
fl |= Modifier.negative;
negative = true;
}
// Clamp the precision and delete extra zeros after clamp.
@ -747,25 +611,9 @@ pure nothrow @nogc
}
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:
tail[0] = 0;
leadSign(fl, lead.ptr);
leadSign(negative, lead);
if (decimalPos == special)
{
s = cast(char*) sn;
@ -821,21 +669,9 @@ pure nothrow @nogc
}
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:
tail[0] = 0;
leadSign(fl, lead.ptr);
leadSign(negative, lead);
if (decimalPos == special)
{
s = cast(char*) sn;
@ -971,21 +807,16 @@ pure nothrow @nogc
// Get the length that we copied.
l = cast(uint) (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);
lead[0] = 2;
lead[1] = '0';
lead[2] = 'x';
radixnum:
// Get the number.
if (fl & Modifier.intMax)
static if (size_t.sizeof == 8)
{
n64 = va_arg!ulong(va);
}
@ -994,60 +825,53 @@ pure nothrow @nogc
n64 = va_arg!uint(va);
}
s = num.ptr + NUMSZ;
s = num.ptr + num.length;
decimalPos = 0;
// Clear tail, and clear leading if value is zero.
tail[0] = 0;
if (n64 == 0)
{
lead[0] = 0;
if (precision == 0)
{
l = 0;
goto scopy;
}
}
// Convert to string.
for (;;)
{
*--s = hex[cast(size_t) (n64 & ((1 << (l >> 8)) - 1))];
n64 >>= (l >> 8);
if (!((n64) || (cast(int) ((num.ptr + NUMSZ) - s) < precision)))
n64 >>= l >> 8;
if (n64 == 0)
{
break;
}
}
// Get the length that we copied.
l = cast(uint)((num.ptr + NUMSZ) - s);
// Copy it.
goto scopy;
case 'u': // Unsigned.
case 'i': // Signed.
l = cast(uint)((num.ptr + num.length) - s);
}
else static if (fmt[0] == 'u' || fmt[0] == 'i' || fmt[0] == 'l') // Integer
{
// Get the integer and abs it.
if (fl & Modifier.intMax)
if (fmt[0] == 'l')
{
long i64 = va_arg!long(va);
n64 = cast(ulong) i64;
if ((f[0] != 'u') && (i64 < 0))
if ((fmt[0] != 'u') && (i64 < 0))
{
n64 = cast(ulong) -i64;
fl |= Modifier.negative;
negative = true;
}
}
else
{
int i = va_arg!int(va);
n64 = cast(uint) i;
if ((f[0] != 'u') && (i < 0))
if ((fmt[0] != 'u') && (i < 0))
{
n64 = cast(uint) -i;
fl |= Modifier.negative;
negative = true;
}
}
// Convert to string.
s = num.ptr + NUMSZ;
s = num.ptr + num.length;
l = 0;
for (;;)
@ -1078,7 +902,7 @@ pure nothrow @nogc
}
if (n64 == 0)
{
if ((s[0] == '0') && (s != (num.ptr + NUMSZ)))
if ((s[0] == '0') && (s != (num.ptr + num.length)))
{
++s;
}
@ -1091,10 +915,10 @@ pure nothrow @nogc
}
tail[0] = 0;
leadSign(fl, lead.ptr);
leadSign(negative, lead);
// Get the length that we copied.
l = cast(uint) ((num.ptr + NUMSZ) - s);
l = cast(uint) ((num.ptr + num.length) - s);
if (l == 0)
{
*--s = '0';
@ -1104,6 +928,11 @@ pure nothrow @nogc
{
precision = 0;
}
}
else
{
static assert(false);
}
scopy:
// Get fw=leading/trailing space, precision=leading zeros.
@ -1112,45 +941,13 @@ pure nothrow @nogc
precision = l;
}
n = precision + lead[0] + tail[0] + tz;
if (fw < cast(int) n)
{
fw = n;
}
fw -= n;
precision -= l;
// Copy the spaces and/or zeros.
if (fw + precision)
if (precision)
{
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
sn = lead.ptr + 1;
while (lead[0])
@ -1258,32 +1055,17 @@ pure nothrow @nogc
--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;
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
{
va_list va;
va_start(va, buf);
auto result = vsprintf!fmt(buf, va);
auto result = vsprintf!(fmt, Args)(buf, va);
va_end(va);
return result;
}
@ -1292,75 +1074,59 @@ nothrow unittest
{
char[318] buffer;
// Format without arguments.
assert(format!""(buffer) == "");
assert(format!"asdfqweryxcvz"(buffer) == "asdfqweryxcvz");
// Modifiers.
assert(format!"%g"(buffer, 8.5) == "8.5");
assert(format!"%5g"(buffer, 8.6) == " 8.6");
assert(format!"%i"(buffer, 1000) == "1000");
assert(format!"%*i"(buffer, 5, 1) == " 1");
assert(format!"%.1f"(buffer, 10.25) == "10.3");
assert(format!"%.*f"(buffer, 1, 10.25) == "10.3");
assert(format!"%i"(buffer, 1) == "1");
assert(format!"%7.3g"(buffer, 0.01) == " 0.01");
assert(format!("g", double)(buffer, 8.5) == "8.5");
assert(format!("g", double)(buffer, 8.6) == "8.6");
assert(format!("i", int)(buffer, 1000) == "1000");
assert(format!("i", int)(buffer, 1) == "1");
assert(format!("g", double)(buffer, 10.25) == "10.25");
assert(format!("i", int)(buffer, 1) == "1");
assert(format!("g", double)(buffer, 0.01) == "0.01");
// Integer size.
assert(format!"%hi"(buffer, 10) == "10");
assert(format!"%li"(buffer, 10) == "10");
assert(format!"%li"(buffer, 10L) == "10");
assert(format!("i", short)(buffer, 10) == "10");
assert(format!("l", long)(buffer, 10L) == "10");
// String printing.
assert(format!"%s"(buffer, "Some weired string") == "Some weired string");
assert(format!"%s"(buffer, cast(string) null) == "null");
assert(format!"%.4s"(buffer, "Some weired string") == "Some");
assert(format!"%c"(buffer, 'c') == "c");
assert(format!("s", string)(buffer, "Some weired string") == "Some weired string");
assert(format!("s", string)(buffer, cast(string) null) == "null");
assert(format!("c", char)(buffer, 'c') == "c");
// Integer conversions.
assert(format!"%i"(buffer, 8) == "8");
assert(format!"%i"(buffer, 8) == "8");
assert(format!"%i"(buffer, -8) == "-8");
assert(format!"%li"(buffer, -8L) == "-8");
assert(format!"%u"(buffer, 8) == "8");
assert(format!"%i"(buffer, 100000001) == "100000001");
assert(format!"%.12i"(buffer, 99999999L) == "000099999999");
assert(format!"%i"(buffer, 100000001) == "100000001");
assert(format!("i", int)(buffer, 8) == "8");
assert(format!("i", int)(buffer, 8) == "8");
assert(format!("i", int)(buffer, -8) == "-8");
assert(format!("l", long)(buffer, -8L) == "-8");
assert(format!("u", uint)(buffer, 8) == "8");
assert(format!("i", int)(buffer, 100000001) == "100000001");
assert(format!("i", int)(buffer, 99999999L) == "99999999");
// Floating point conversions.
assert(format!"%g"(buffer, 0.1234) == "0.1234");
assert(format!"%g"(buffer, 0.3) == "0.3");
assert(format!"%g"(buffer, 0.333333333333) == "0.333333");
assert(format!"%g"(buffer, 38234.1234) == "38234.1");
assert(format!"%g"(buffer, -0.3) == "-0.3");
assert(format!"%g"(buffer, 0.000000000000000006) == "6e-18");
assert(format!"%g"(buffer, 0.0) == "0");
assert(format!"%f"(buffer, 0.0) == "0.000000");
assert(format!"%f"(buffer, double.init) == "NaN");
assert(format!"%f"(buffer, double.infinity) == "Inf");
assert(format!"%.0g"(buffer, 0.0) == "0");
assert(format!"%f"(buffer, 0.000000000000000000000000003) == "0.000000");
assert(format!"%g"(buffer, 0.23432e304) == "2.3432e+303");
assert(format!"%f"(buffer, -0.23432e8) == "-23432000.000000");
assert(format!"%e"(buffer, double.init) == "NaN");
assert(format!"%f"(buffer, 1e-307) == "0.000000");
assert(format!"%f"(buffer, 1e+8) == "100000000.000000");
assert(format!"%05g"(buffer, 111234.1) == "111234");
assert(format!"%.2g"(buffer, double.init) == "Na");
assert(format!"%.1e"(buffer, 0.999) == "1.0e+00");
assert(format!"%.0f"(buffer, 0.999) == "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");
assert(format!("g", double)(buffer, 0.1234) == "0.1234");
assert(format!("g", double)(buffer, 0.3) == "0.3");
assert(format!("g", double)(buffer, 0.333333333333) == "0.333333");
assert(format!("g", double)(buffer, 38234.1234) == "38234.1");
assert(format!("g", double)(buffer, -0.3) == "-0.3");
assert(format!("g", double)(buffer, 0.000000000000000006) == "6e-18");
assert(format!("g", double)(buffer, 0.0) == "0");
assert(format!("g", double)(buffer, double.init) == "NaN");
assert(format!("g", double)(buffer, -double.init) == "-NaN");
assert(format!("g", double)(buffer, double.infinity) == "Inf");
assert(format!("g", double)(buffer, -double.infinity) == "-Inf");
assert(format!("g", double)(buffer, 0.000000000000000000000000003) == "3e-27");
assert(format!("g", double)(buffer, 0.23432e304) == "2.3432e+303");
assert(format!("g", double)(buffer, -0.23432e8) == "-2.3432e+07");
assert(format!("g", double)(buffer, 1e-307) == "1e-307");
assert(format!("g", double)(buffer, 1e+8) == "1e+08");
assert(format!("g", double)(buffer, 111234.1) == "111234");
assert(format!("g", double)(buffer, 0.999) == "0.999");
assert(format!("g", double)(buffer, 0x1p-16382L)); // "6.95336e-310"
assert(format!("g", double)(buffer, 1e+3) == "1000");
assert(format!("g", double)(buffer, 38234.1234) == "38234.1");
// Pointer conversions.
assert(format!"%p"(buffer, cast(void*) 1) == "0x1");
assert(format!"%p"(buffer, cast(void*) 20) == "0x14");
// Unknown specifier.
assert(format!"%k"(buffer) == "k");
assert(format!"%%k"(buffer) == "%k");
// Pointer convesions.
assert(format!("p", void*)(buffer, cast(void*) 1) == "0x1");
assert(format!("p", void*)(buffer, cast(void*) 20) == "0x14");
}
private struct FormatSpec