| 1 |
/* Compile-time conversions between strings and numeric values. |
|---|
| 2 |
* |
|---|
| 3 |
* In most cases, it's not possible to overload template value parameters, |
|---|
| 4 |
* so most of these functions have unique names. |
|---|
| 5 |
* Compile with -version=testmeta to run unit tests. |
|---|
| 6 |
* Author: Don Clugston. License: Public domain. |
|---|
| 7 |
*/ |
|---|
| 8 |
/* |
|---|
| 9 |
Normally, a function which processes the first part of a string |
|---|
| 10 |
would return the part which was not consumed (the 'tail'). |
|---|
| 11 |
This doesn't work very well for D templates, so for such functions, |
|---|
| 12 |
we supply a seperate metafunction with the suffix 'Consumed' which returns |
|---|
| 13 |
the number of characters consumed. (This is the same as the index of the first |
|---|
| 14 |
character in the string which was not consumed). |
|---|
| 15 |
This results in some code duplication, but seems to results in a much cleaner design. |
|---|
| 16 |
*/ |
|---|
| 17 |
|
|---|
| 18 |
module meta.conv; |
|---|
| 19 |
private import meta.math; |
|---|
| 20 |
private import meta.string; |
|---|
| 21 |
private import meta.ctype; |
|---|
| 22 |
|
|---|
| 23 |
template decimaldigit(int n) { const char [] decimaldigit = "0123456789"[n..n+1]; } |
|---|
| 24 |
template hexdigit(int n) { const char [] hexdigit = "0123456789ABCDEF"[n..n+1]; } |
|---|
| 25 |
|
|---|
| 26 |
/* ***************************************************** |
|---|
| 27 |
* char [] itoa!(long n); |
|---|
| 28 |
*/ |
|---|
| 29 |
template itoa(long n) |
|---|
| 30 |
{ |
|---|
| 31 |
static if (n<0) |
|---|
| 32 |
const char [] itoa = "-" ~ itoa!(-n); |
|---|
| 33 |
else static if (n<10L) |
|---|
| 34 |
const char [] itoa = decimaldigit!(n); |
|---|
| 35 |
else |
|---|
| 36 |
const char [] itoa = itoa!(n/10L) ~ decimaldigit!(n%10L); |
|---|
| 37 |
} |
|---|
| 38 |
|
|---|
| 39 |
template toHexString(ulong n) |
|---|
| 40 |
{ |
|---|
| 41 |
static if (n<16L) |
|---|
| 42 |
const char [] toHexString = hexdigit!(n); |
|---|
| 43 |
else |
|---|
| 44 |
const char [] toHexString = toHexString!(n >> 4) ~ hexdigit!(n&0xF); |
|---|
| 45 |
} |
|---|
| 46 |
|
|---|
| 47 |
/* ***************************************************** |
|---|
| 48 |
* long atoi!(char [] s); |
|---|
| 49 |
*/ |
|---|
| 50 |
template atoi(char [] s, long result=0, int indx=0) |
|---|
| 51 |
{ |
|---|
| 52 |
static if (s.length==indx) |
|---|
| 53 |
const long atoi = result; |
|---|
| 54 |
else static if (indx==0 && s[indx]=='-') |
|---|
| 55 |
const long atoi = - atoi!(s, 0, 1); |
|---|
| 56 |
else static if (!isdigit!( (s[indx]) ) ) |
|---|
| 57 |
const long atoi = result; |
|---|
| 58 |
else |
|---|
| 59 |
const long atoi = atoi!(s, result * 10 + s[indx]-'0', indx+1); |
|---|
| 60 |
} |
|---|
| 61 |
|
|---|
| 62 |
/* ***************************************************** |
|---|
| 63 |
* the number of characters in s[] which would be 'consumed' by atoi!(). |
|---|
| 64 |
*/ |
|---|
| 65 |
template atoiConsumed(char [] s, int indx=0) |
|---|
| 66 |
{ |
|---|
| 67 |
static if(s.length==indx) |
|---|
| 68 |
const int atoiConsumed = indx; |
|---|
| 69 |
else static if ((indx==0 && s[indx]=='-') || isdigit!( (s[indx]) ) ) |
|---|
| 70 |
const int atoiConsumed = atoiConsumed!(s,indx + 1); |
|---|
| 71 |
else // invalid character |
|---|
| 72 |
const int atoiConsumed = indx; |
|---|
| 73 |
} |
|---|
| 74 |
|
|---|
| 75 |
private enum EFloatParseState { START=0, GOTSIGN, WAITDOT, WAITEXP=3, GOTEXPCHAR, GOTEXPSIGN, EXPDIG }; |
|---|
| 76 |
|
|---|
| 77 |
|
|---|
| 78 |
// Like atoi!(), except that internal underscores are allowable |
|---|
| 79 |
template parseInt(char [] s, long result=0, EFloatParseState state =0 /* start*/) |
|---|
| 80 |
{ |
|---|
| 81 |
static if (s.length==0) |
|---|
| 82 |
const long parseInt = result; |
|---|
| 83 |
else static if (state == EFloatParseState.START && s[0]=='-') |
|---|
| 84 |
const long parseInt = - parseInt!(s[1..$], 0, GOTSIGN); |
|---|
| 85 |
else static if (state == EFloatParseState.WAITDOT && s[0]=='_') |
|---|
| 86 |
const long parseInt = parseInt!(s[1..$], result, state); |
|---|
| 87 |
else static if (isdigit!( (s[0]) ) ) |
|---|
| 88 |
const long parseInt = parseInt!(s[1..$], result*10 + s[0]-'0', WAITDOT); |
|---|
| 89 |
} |
|---|
| 90 |
|
|---|
| 91 |
// Returns the value of the exponent. |
|---|
| 92 |
// must be of the form "" or "e-543" or "E+2_453" etc |
|---|
| 93 |
template parseExponent(char [] s, int result = 0, EFloatParseState state = EFloatParseState.WAITEXP) |
|---|
| 94 |
{ |
|---|
| 95 |
static if (s.length == 0) { |
|---|
| 96 |
// an empty string is acceptable |
|---|
| 97 |
static if (state == EFloatParseState.WAITEXP || state == EFloatParseState.EXPDIG) |
|---|
| 98 |
const int parseExponent = result; |
|---|
| 99 |
else { |
|---|
| 100 |
pragma(msg, "Error: No exponent found in floating-point literal."); |
|---|
| 101 |
static assert(0); |
|---|
| 102 |
} |
|---|
| 103 |
} else static if (state == EFloatParseState.WAITEXP && (s[0]=='e' || s[0]=='E') ) |
|---|
| 104 |
const int parseExponent = parseExponent!(s[1..$], 0, EFloatParseState.GOTEXPCHAR); |
|---|
| 105 |
else static if (state == EFloatParseState.GOTEXPCHAR && s[0]=='-') |
|---|
| 106 |
const int parseExponent = -parseExponent!(s[1..$], 0, EFloatParseState.GOTEXPSIGN); |
|---|
| 107 |
else static if (state == EFloatParseState.GOTEXPCHAR && s[0]=='+') |
|---|
| 108 |
const int parseExponent = parseExponent!(s[1..$], 0, EFloatParseState.GOTEXPSIGN); |
|---|
| 109 |
else static if (state == EFloatParseState.EXPDIG && s[0]=='_') |
|---|
| 110 |
// embedded underscores are allowed after the first digit |
|---|
| 111 |
const int parseExponent = parseExponent!(s[1..$], result, state); |
|---|
| 112 |
else static if (isdigit!( (s[0]) ) ) |
|---|
| 113 |
const int parseExponent = parseExponent!(s[1..$], result*10 + (s[0]-'0'), EFloatParseState.EXPDIG); |
|---|
| 114 |
else { |
|---|
| 115 |
pragma(msg, "Error: Invalid characters found in floating-point literal."); |
|---|
| 116 |
static assert(0); |
|---|
| 117 |
} |
|---|
| 118 |
} |
|---|
| 119 |
|
|---|
| 120 |
// parse a %f-style floating-point number, returning the result as a real. |
|---|
| 121 |
// A minus sign is allowed as the first character. |
|---|
| 122 |
// Embedded underscores are allowed any time after the first digit. |
|---|
| 123 |
template parseMantissa(char [] s, EFloatParseState state = EFloatParseState.START, real result=0.0L) |
|---|
| 124 |
{ |
|---|
| 125 |
static if (s.length==0) |
|---|
| 126 |
const real parseMantissa = result; |
|---|
| 127 |
else static if (state==EFloatParseState.START && s[0]=='-') |
|---|
| 128 |
const real parseMantissa = - parseMantissa!(s[1..$], EFloatParseState.GOTSIGN, 0.0L); |
|---|
| 129 |
else static if (state==EFloatParseState.WAITDOT && s[0]=='.') |
|---|
| 130 |
const real parseMantissa = result + parseMantissa!(s[1..$], EFloatParseState.WAITEXP, 0.0L); |
|---|
| 131 |
else static if (state!=EFloatParseState.START && state!=EFloatParseState.GOTSIGN && s[0]=='_') |
|---|
| 132 |
// allow embedded underscores, but not before the first digit |
|---|
| 133 |
const real parseMantissa = parseMantissa!(s[1..$], state, result); |
|---|
| 134 |
else static if (isdigit!( (s[0]) )) { |
|---|
| 135 |
static if (state == EFloatParseState.WAITEXP) |
|---|
| 136 |
// fractional part |
|---|
| 137 |
const real parseMantissa = result + (s[0]-'0')/10.0L + parseMantissa!(s[1..$], state, 0.0L) /10.0L; |
|---|
| 138 |
else |
|---|
| 139 |
const real parseMantissa = parseMantissa!(s[1..$], EFloatParseState.WAITDOT, result*10.0L + (s[0]-'0') ); |
|---|
| 140 |
} else static if (state == EFloatParseState.WAITDOT || state == EFloatParseState.WAITEXP) |
|---|
| 141 |
const real parseMantissa = result; |
|---|
| 142 |
else { |
|---|
| 143 |
pragma(msg, "Error: No digits found in floating-point literal."); |
|---|
| 144 |
static assert(0); |
|---|
| 145 |
} |
|---|
| 146 |
} |
|---|
| 147 |
|
|---|
| 148 |
template parseMantissaConsumed(char [] s, EFloatParseState state = EFloatParseState.START) |
|---|
| 149 |
{ |
|---|
| 150 |
static if (s.length == 0) |
|---|
| 151 |
const int parseMantissaConsumed = 0; |
|---|
| 152 |
else static if (state==EFloatParseState.START && s[0]=='-') |
|---|
| 153 |
const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], EFloatParseState.GOTSIGN); |
|---|
| 154 |
else static if (state==EFloatParseState.WAITDOT && s[0]=='.') |
|---|
| 155 |
const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], EFloatParseState.WAITEXP); |
|---|
| 156 |
else static if (state!=EFloatParseState.START && state!=EFloatParseState.GOTSIGN && s[0]=='_') |
|---|
| 157 |
// allow embedded underscores, but not before the first digit |
|---|
| 158 |
const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], state); |
|---|
| 159 |
else static if (isdigit!( (s[0]) )) |
|---|
| 160 |
const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], |
|---|
| 161 |
(state == EFloatParseState.WAITEXP) ? state : EFloatParseState.WAITDOT ); |
|---|
| 162 |
else // found first offending character. |
|---|
| 163 |
//static assert(state != EFloatParseState.START && state!= EFloatParseState.GOTSIGN); |
|---|
| 164 |
const int parseMantissaConsumed = 0; |
|---|
| 165 |
} |
|---|
| 166 |
|
|---|
| 167 |
|
|---|
| 168 |
// accepts %f or %e format. |
|---|
| 169 |
template atof(char [] s) |
|---|
| 170 |
{ |
|---|
| 171 |
const real atof = parseMantissa!(s) * meta.math.pow!(10.0L, |
|---|
| 172 |
parseExponent!(s[parseMantissaConsumed!(s)..$])); |
|---|
| 173 |
} |
|---|
| 174 |
|
|---|
| 175 |
// returns number of decimal places in s. |
|---|
| 176 |
// s must be a valid floating-point literal |
|---|
| 177 |
template decimalplaces(char [] s, bool gotDot = false) |
|---|
| 178 |
{ |
|---|
| 179 |
static if (s.length==0) |
|---|
| 180 |
const int decimalplaces = 0; |
|---|
| 181 |
else static if ( !gotDot && s[0]=='.') |
|---|
| 182 |
const int decimalplaces = decimalplaces!(s[1..$], true); |
|---|
| 183 |
else static if (s[0]=='_' || !gotDot) { |
|---|
| 184 |
const int decimalplaces = decimalplaces!(s[1..$], gotDot); |
|---|
| 185 |
} else { |
|---|
| 186 |
static if (isdigit!( (s[0]) ) ) |
|---|
| 187 |
const int decimalplaces = 1 + decimalplaces!(s[1..$], true); |
|---|
| 188 |
else // we've finished |
|---|
| 189 |
const int decimalplaces = 0; |
|---|
| 190 |
} |
|---|
| 191 |
} |
|---|
| 192 |
|
|---|
| 193 |
|
|---|
| 194 |
//------------------------------------------------ |
|---|
| 195 |
// Given a number x, where 0<= x <1, |
|---|
| 196 |
// returns the first 'maxdigs' digits after the decimal point. |
|---|
| 197 |
template afterdec(real x, int maxdigs=real.dig) |
|---|
| 198 |
{ |
|---|
| 199 |
static if (maxdigs==0 || x==0) const char [] afterdec = ""; |
|---|
| 200 |
else const char [] afterdec = decimaldigit!(cast(int)(x*10)) ~ afterdec!(x*10-cast(int)(x*10), maxdigs-1); |
|---|
| 201 |
} |
|---|
| 202 |
|
|---|
| 203 |
/* ***************************************************** |
|---|
| 204 |
* char [] fcvt!(real x) |
|---|
| 205 |
* Convert a real number x to %f format |
|---|
| 206 |
*/ |
|---|
| 207 |
template fcvt(real x) |
|---|
| 208 |
{ |
|---|
| 209 |
static if (x<0) { |
|---|
| 210 |
const real fcvt = "-" ~ .fcvt!(-x); |
|---|
| 211 |
} else static if (x==cast(long)x) { |
|---|
| 212 |
const char [] fcvt = itoa!(cast(long)x); |
|---|
| 213 |
} else { |
|---|
| 214 |
const char [] fcvt = itoa!(cast(long)x) ~ "." ~ chomp!(afterdec!(x - cast(long)x), '0'); |
|---|
| 215 |
} |
|---|
| 216 |
} |
|---|
| 217 |
|
|---|
| 218 |
template itoaWithSign(long x) |
|---|
| 219 |
{ |
|---|
| 220 |
static if (x>=0) const char [] itoaWithSign = "+" ~ itoa!(x); |
|---|
| 221 |
else const char [] itoaWithSign = itoa!(x); |
|---|
| 222 |
} |
|---|
| 223 |
|
|---|
| 224 |
/* ***************************************************** |
|---|
| 225 |
* char [] pcvt!(real x) |
|---|
| 226 |
* Convert a real number x to %a format, eg 0x1.ABCDp+30 |
|---|
| 227 |
*/ |
|---|
| 228 |
template pcvt(real x) |
|---|
| 229 |
{ |
|---|
| 230 |
static if (isnan!(x)) const char [] pcvt = "nan"; |
|---|
| 231 |
else static if (x<0) const char [] pcvt = "-" ~ .pcvt!(-x); |
|---|
| 232 |
else static if (x==real.infinity) const char [] pcvt = "inf"; |
|---|
| 233 |
else const char [] pcvt = "0x1." ~ toHexString!(cast(ulong)(0x1000000 *(binaryMantissa!(x)-1.0)) ) ~ "p" ~ itoaWithSign!(binaryExponent!(x)); |
|---|
| 234 |
} |
|---|
| 235 |
|
|---|
| 236 |
version(testmeta) { |
|---|
| 237 |
|
|---|
| 238 |
|
|---|
| 239 |
static assert(parseMantissa!("3.34") == 3.34); |
|---|
| 240 |
static assert(parseMantissa!("-548_29.317_1abc") == -548_29.317_1); |
|---|
| 241 |
static assert( isPositiveZero!(parseMantissa!("0.0"))); |
|---|
| 242 |
static assert( isNegativeZero!(parseMantissa!("-0.0"))); |
|---|
| 243 |
static assert(parseMantissaConsumed!("-31_4.3252e34") == 10); |
|---|
| 244 |
static assert(parseMantissaConsumed!("_23e112") == 0); |
|---|
| 245 |
|
|---|
| 246 |
static assert( pcvt!(0x1.12345p954L) == "0x1.123450p+954" ); |
|---|
| 247 |
static assert( fcvt!(12.345) == "12.345" ); |
|---|
| 248 |
|
|---|
| 249 |
static assert( atoi!("3580abc")==3580); |
|---|
| 250 |
static assert( atoi!("-0326")==-326); |
|---|
| 251 |
static assert( atoiConsumed!("325827wip")==6); |
|---|
| 252 |
static assert( atoiConsumed!("abc")==0); |
|---|
| 253 |
} |
|---|