polka/src/ConsolePrintFormat.cc

372 lines
9.1 KiB
C++

/* ConsolePrintFormat.cc
* vim: set tw=80:
* Eryn Wells <eryn@erynwells.me>
*/
/**
* Implementation of Console::printFormat, a printf style method.
*/
#include <stdarg.h>
#include "Console.hh"
#include "kstd/ASCII.hh"
#include "kstd/CString.hh"
#include "kstd/Types.hh"
namespace {
void itoa(int value, char* buffer, int base);
struct Spec {
enum class Size {
Normal,
DoubleShort,
Short,
Long,
DoubleLong,
};
enum class Type {
Int,
Unsigned,
Hex,
Char,
String
};
bool zeroPadded;
bool capitalized;
int width;
Size size;
Type type;
union Value {
i8 hhd;
i16 hd;
i32 d;
i64 ld;
i64 lld;
u8 hhu;
u16 hu;
u32 u;
u64 lu;
u64 llu;
u8 hhx;
u16 hx;
u32 x;
u64 lx;
u64 llx;
char c;
char* s;
} value;
void clear();
int print(kernel::Console& console);
};
void
Spec::clear()
{
zeroPadded = false;
capitalized = false;
width = 0;
size = Size::Normal;
type = Type::Int;
value.llx = 0;
}
int
Spec::print(kernel::Console& console)
{
int nchars = 0;
int length = 0;
char buf[32];
char pad = ' ';
char *str;
/*
* TypeChar is a special case because it will always be a single character
* in length and there is no \0 to terminate it.
*/
if (type == Type::Char) {
if (width == 0) {
width = 1;
}
for (int i = 1; i < width; i++) {
console.printChar(' ');
}
console.printChar(value.c);
return width;
}
if (type == Type::Int || type == Type::Hex) {
if (zeroPadded) {
pad = '0';
}
if (type == Type::Int) {
switch (size) {
case Size::Normal:
itoa(value.d, buf, 10);
break;
case Size::DoubleShort:
itoa(value.hhd, buf, 10);
break;
case Size::Short:
itoa(value.hd, buf, 10);
break;
case Size::Long:
itoa(value.ld, buf, 10);
break;
case Size::DoubleLong:
itoa(value.lld, buf, 10);
break;
}
} else {
switch (size) {
case Size::Normal:
kstd::CString::fromUnsignedInteger(value.x, buf, 16, capitalized);
break;
case Size::DoubleShort:
kstd::CString::fromUnsignedInteger(value.hhx, buf, 16, capitalized);
break;
case Size::Short:
kstd::CString::fromUnsignedInteger(value.hx, buf, 16, capitalized);
break;
case Size::Long:
kstd::CString::fromUnsignedLongInteger(value.lx, buf, 16, capitalized);
break;
case Size::DoubleLong:
kstd::CString::fromUnsignedLongInteger(value.llx, buf, 16, capitalized);
break;
}
}
length = kstd::CString::length(buf, 32);
if (width < length) {
width = length;
}
str = buf;
} else if (type == Type::String) {
length = kstd::CString::length(value.s);
if (width < length) {
width = length;
}
str = value.s;
} else {
// Don't know how to print this.
return 0;
}
for (int i = width; i > 0; i--) {
if (i <= length) {
console.printChar(str[length - i]);
}
else if (i <= width) {
console.printChar(pad);
}
}
return nchars;
}
void
itoa(int value,
char* buffer,
int base)
{
const bool neg = base == 10 && value < 0;
if (neg) {
value *= -1;
}
unsigned int v = (unsigned int)value;
char* p = buffer;
int place;
do {
place = v % base;
v /= base;
*p++ = place < 10 ? place + '0' : (place - 10) + 'a';
} while (v != 0);
if (neg) {
*p++ = '-';
}
*p-- = '\0';
// Reverse the string.
char* t = buffer;
char c;
while (p > t) {
c = *p;
*p-- = *t;
*t++ = c;
}
}
inline bool
is_size(char c)
{
return c == 'h' || c == 'l';
}
inline bool
is_specifier(char c)
{
return c == 'd' || c == 'x' || c == 'X' || c == 'c' || c == 's';
}
} /* anonymous namespace */
namespace kernel {
int
Console::printFormat(const char* fmt,
...)
{
enum {
Default = 0,
Percent,
Width,
Size
} state = Default;
va_list vl;
va_start(vl, fmt);
int nchars = 0;
Spec spec;
for (const char* p = fmt; *p != 0; p++) {
switch (state) {
case Default:
if (*p == '%') {
state = Percent;
spec.clear();
} else {
printChar(*p);
nchars++;
}
break;
case Percent:
if (*p == '%') {
state = Default;
printChar(*p);
nchars++;
} else if (kstd::Char::isDigit(*p)) {
if (*p == '0' && !spec.zeroPadded) {
spec.zeroPadded = true;
} else {
state = Width;
spec.width = *p - '0';
}
} else if (is_size(*p)) {
goto state_size;
} else if (is_specifier(*p)) {
goto state_specifier;
}
break;
case Width:
if (kstd::Char::isDigit(*p)) {
spec.width = 10 * spec.width + (*p - '0');
} else if (is_size(*p)) {
state = Size;
goto state_size;
} else if (is_specifier(*p)) {
goto state_specifier;
}
break;
case Size:
if (is_size(*p)) {
goto state_size;
} else if (is_specifier(*p)) {
goto state_specifier;
}
break;
default:
printChar(*p);
nchars++;
break;
}
continue;
state_size:
if (*p == 'h') {
spec.size = (spec.size == Spec::Size::Short)
? Spec::Size::DoubleShort
: Spec::Size::Short;
} else if (*p == 'l') {
spec.size = (spec.size == Spec::Size::Long)
? Spec::Size::DoubleLong
: Spec::Size::Long;
}
continue;
state_specifier:
state = Default;
switch (*p) {
case 'd':
case 'i':
switch (spec.size) {
case Spec::Size::Normal:
spec.value.d = va_arg(vl, int);
break;
case Spec::Size::DoubleShort:
spec.value.hhd = (signed char)va_arg(vl, int);
break;
case Spec::Size::Short:
spec.value.hd = (short int)va_arg(vl, int);
break;
case Spec::Size::Long:
spec.value.ld = va_arg(vl, long int);
break;
case Spec::Size::DoubleLong:
spec.value.lld = va_arg(vl, long long int);
break;
}
spec.type = Spec::Type::Int;
break;
case 'x':
case 'X':
switch (spec.size) {
case Spec::Size::Normal:
spec.value.x = va_arg(vl, unsigned int);
break;
case Spec::Size::DoubleShort:
spec.value.hhx = (unsigned char)va_arg(vl, unsigned int);
break;
case Spec::Size::Short:
spec.value.hx = (unsigned short int)va_arg(vl, unsigned int);
break;
case Spec::Size::Long:
spec.value.lx = va_arg(vl, unsigned long int);
break;
case Spec::Size::DoubleLong:
spec.value.llx = va_arg(vl, unsigned long long int);
break;
}
spec.type = Spec::Type::Hex;
break;
case 'c':
spec.value.c = va_arg(vl, int);
spec.type = Spec::Type::Char;
break;
case 's':
spec.value.s = va_arg(vl, char*);
spec.type = Spec::Type::String;
break;
}
nchars += spec.print(*this);
continue;
}
va_end(vl);
return nchars;
}
} /* namespace kernel */