/*
* fmt.d
* A lightweight string formatter
*
* This offers a subset of sprintf(), for cases where the full-size
* features (and code size) of the standard library formatter are
* not appropriate.
*/
module fmt;
/*
* isdig()
* Is @c a digit?
*/
bool
isdig(char c)
{
return (c >= '0') && (c <= '9');
}
/*
* ntoa()
* Return string for number
*/
string
ntoa(T)(T val)
{
string res = "";
// Negative number
bool neg = false;
if (val < 0) {
val = -val;
neg = true;
}
// Assemble digits
do {
char dig = '0' + (val % 10);
res = dig ~ res;
val /= 10;
} while (val != 0);
// Add negative sign?
if (neg) {
res = '-' ~ res;
}
return res;
}
/*
* format_num()
* Format %d value
*/
private:
string format_num(T)(int count, bool zpad, T val)
{
// Basic number string
string res = ntoa(val);
// Pad on right if negative count
bool padr = false;
if (count < 0) {
count = -count;
// It's not rational to pad zeroes here
if (!zpad) {
padr = true;
}
}
// Padding
while (res.length < count) {
if (padr) {
res = res ~ ' ';
} else {
res = (zpad ? '0' : ' ') ~ res;
}
}
return res;
}
/*
* format_s()
* Format %s value
*/
string format_s(int count, in string val)
{
string res = val.dup();
bool padl = false;
if (count < 0) {
count = -count;
padl = true;
}
while (res.length < count) {
if (padl) {
res = ' ' ~ res;
} else {
res ~= ' ';
}
}
return res;
}
// Different types stored in an ArgPos
enum ap_type { AP_INT, AP_UINT, AP_LONG, AP_ULONG, AP_STR, AP_CHAR };
// The value in one single argument position
struct ArgPos {
ap_type ap_typ;
union {
int ap_int;
uint ap_uint;
long ap_long;
ulong ap_ulong;
string ap_str;
char ap_char;
}
this(in int val) {
ap_typ = ap_type.AP_INT;
ap_int = val;
}
this(in uint val) {
ap_typ = ap_type.AP_UINT;
ap_int = val;
}
this(in long val) {
ap_typ = ap_type.AP_LONG;
ap_long = val;
}
this(in ulong val) {
ap_typ = ap_type.AP_ULONG;
ap_long = val;
}
this(in string val) {
ap_typ = ap_type.AP_STR;
ap_str = val;
}
this(in char val) {
ap_typ = ap_type.AP_CHAR;
ap_char = val;
}
int as_int() {
assert(ap_typ == ap_type.AP_INT);
return(ap_int);
}
uint as_uint() {
if (ap_typ == ap_type.AP_UINT) {
return(ap_uint);
}
assert(ap_typ == ap_type.AP_INT);
assert(ap_int >= 0);
return(ap_int);
}
long as_long() {
assert(ap_typ == ap_type.AP_LONG);
return(ap_long);
}
ulong as_ulong() {
if (ap_typ == ap_type.AP_ULONG) {
return(ap_ulong);
}
assert(ap_typ == ap_type.AP_LONG);
assert(ap_long >= 0);
return(ap_long);
}
string as_str() {
if (ap_typ == ap_type.AP_STR) {
return(ap_str);
}
assert(ap_typ == ap_type.AP_CHAR);
return "" ~ ap_char;
}
char as_char() {
assert(ap_typ == ap_type.AP_CHAR);
return(ap_char);
}
}
/*
* fmt()
* Convert args and printf-style string to display string
*
* (This is my exploration of a smaller implementation than the
* system one, which clocks in at ~200k of code.)
*
* Note that \n and friends are handled in string construction,
* so we don't need to worry about that here.
*/
public:
string
fmt(T...)(in string fmts, T inargs)
{
auto res = "";
bool infmt, incount, zpad, isneg, modl;
int count;
size_t narg = 0;
ArgPos[] args;
// Pre-process our varargs
foreach (arg; inargs) {
args ~= ArgPos(arg);
}
infmt = incount = modl = false;
foreach (c; fmts) {
// Assembling count for formatting
if (incount) {
if (isdig(c)) {
count = (count * 10) + (c - '0');
continue;
}
// Done with count
incount = false;
assert(infmt);
}
// In format? Emit content for this position
if (infmt) {
if (isdig(c)) {
count = c - '0';
if (c == '0') {
zpad = true;
}
isneg = false;
incount = true;
continue;
}
if (c == '-') {
count = 0;
isneg = incount = true;
continue;
}
// Long modifier?
if (c == 'l') {
modl = true;
continue;
}
// Ready to format this argument
if (narg == args.length) {
throw new Exception("Too few arguments to fmt()");
}
auto arg = args[narg++];
string s;
// Apply negation now
if (isneg) {
count = -count;
}
// Dispatch format
switch (c) {
case 'd':
if (modl) {
s = format_num(count, zpad, arg.as_long());
} else {
s = format_num(count, zpad, arg.as_int());
}
break;
case 'u':
if (modl) {
s = format_num(count, zpad, arg.as_ulong());
} else {
s = format_num(count, zpad, arg.as_uint());
}
break;
case 's':
s = format_s(count, arg.as_str());
break;
case 'c':
s = arg.as_str();
break;
default:
throw new Exception("Unsupported format char: " ~ c);
break;
}
// Another format position processed
res ~= s;
infmt = false;
continue;
}
// Start formatting
if (c == '%') {
infmt = true;
modl = isneg = zpad = false;
count = 0;
continue;
}
// Non-formatting, use literally
res ~= c;
}
return res;
}