diff options
Diffstat (limited to 'src/print.c')
-rw-r--r-- | src/print.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/src/print.c b/src/print.c new file mode 100644 index 0000000..f158985 --- /dev/null +++ b/src/print.c @@ -0,0 +1,564 @@ +#include <lib.h> +#include <serial.h> +#include <stdarg.h> + +void kputc(char c) { + serial_out(c); +} + +void kputs(const char *s) { + serial_out_str(s); +} + +enum format_flag { + FLG_NONE = 0x00, + FLG_ALTERNATE = 0x01, + FLG_ZERO = 0x02, + FLG_LEFT_ALIGN = 0x04, + FLG_ADD_SIGN = 0x08 +}; + +struct format_width { + bool defined; + bool varys; + int value; +}; + +struct format_precision { + bool defined; + bool varys; + int value; +}; + +enum format_modifier { + MOD_NONE, + MOD_INVALID, + MOD_HALF_HALF, + MOD_HALF, + MOD_LONG_LONG, + MOD_LONG, +}; + +enum format_conversion { + FMT_INT, + FMT_UINT, + FMT_OCT, + FMT_HEX, + FMT_HEX_UPPER, + FMT_CHAR, + FMT_STR, + FMT_PTR, + FMT_PTR_UPPER, + FMT_PERCENT, + FMT_INVALID +}; + +static enum format_flag read_flag(const char *format, const char **end) { + + enum format_flag flag = FLG_NONE; + + for (; *format != '\0'; format++) { + switch (*format) { + case '#': + flag |= FLG_ALTERNATE; + break; + case '0': + flag |= FLG_ZERO; + break; + case '-': + flag |= FLG_LEFT_ALIGN; + break; + case '+': + flag |= FLG_ADD_SIGN; + break; + default: + *end = format; + return flag; + } + } + + *end = format; + return flag; +} + +static struct format_width read_width(const char *format, const char **end) { + + struct format_width width; + width.defined = false; + width.varys = false; + width.value = 0; + + int value = 0; + + for (; *format != '\0'; format++) { + char c = *format; + + if (c == '*' && width.defined == false) { + width.defined = true; + width.varys = true; + break; + } + + if (!isdigit(c)) + break; + + int i = c - '0'; + value *= 10; + value += i; + + width.value = value; + width.defined = true; + width.varys = false; + } + + *end = format; + return width; + +} + +static struct format_precision read_precision(const char *format, const char **end) { + + struct format_precision precision; + precision.varys = false; + precision.defined = false; + precision.value = 0; + + if (*format != '.') { + *end = format; + return precision; + } + + format++; + + int value = 0; + + for (; *format != '\0'; format++) { + char c = *format; + + if (c == '*' && precision.defined == false) { + precision.defined = true; + precision.varys = true; + break; + } + + if (!isdigit(c)) + break; + + int i = c - '0'; + value *= 10; + value += i; + + precision.value = value; + precision.defined = true; + precision.varys = false; + } + + *end = format; + return precision; +} + +static enum format_modifier read_modifier(const char *format, const char **end) { + + enum format_modifier mod = MOD_NONE; + + for (; *format != '\0'; format++) { + *end = format; + switch (*format) { + case 'l': + if (mod == MOD_NONE) + mod = MOD_LONG; + else if (mod == MOD_LONG) + return MOD_LONG_LONG; + else + return MOD_INVALID; + break; + case 'L': + if (mod == MOD_NONE) + return MOD_LONG_LONG; + else + return MOD_INVALID; + break; + case 'h': + if (mod == MOD_NONE) + mod = MOD_HALF; + else if (mod == MOD_HALF) + return MOD_HALF_HALF; + else + return MOD_INVALID; + break; + case 'H': + if (mod == MOD_NONE) + return MOD_HALF_HALF; + else + return MOD_INVALID; + break; + default: + return mod; + } + } + + return MOD_INVALID; +} + +static enum format_conversion read_conversion(const char *format, const char **end) { + *end = format + 1; + switch (*format) { + case 'd': + case 'i': + return FMT_INT; + case 'o': + return FMT_OCT; + case 'u': + return FMT_UINT; + case 'x': + return FMT_HEX; + case 'X': + return FMT_HEX_UPPER; + case 'c': + return FMT_CHAR; + case 's': + return FMT_STR; + case 'p': + return FMT_PTR; + case 'P': + return FMT_PTR_UPPER; + case '%': + return FMT_PERCENT; + default: + return FMT_INVALID; + } +} + +static void print_string( + const char *str, + enum format_flag flag, + struct format_width width, + struct format_precision precision +) { + + size_t max_len = 0; + size_t min_len = 0; + size_t len = 0; + + if (width.defined) + min_len = width.value; + + if (precision.defined) { + max_len = precision.value; + len = max_len; + if (max_len < min_len) + min_len = max_len; + } else { + len = strlen(str); + } + + if (!(flag & FLG_LEFT_ALIGN) && len < min_len) { + for (size_t i = 0; i < (min_len - len); i++) { + kputc(' '); + } + } + + for (size_t i = 0; i < len; i++) { + kputc(str[i]); + } + + if ((flag & FLG_LEFT_ALIGN) && len < min_len) { + for (size_t i = 0; i < (min_len - len); i++) { + kputc(' '); + } + } +} + +static char get_letter( + char c, + char base +) { + if (c >= 0 && c <= 9) + return c + '0'; + c -= 10; + return c + base; +} + +static char *get_decimal( + long long num, + char *buf, + char sign, + int radix, + char base +) { + + *buf = '\0'; + buf--; + + if (num == 0) { + *buf = '0'; + buf--; + } + + while (num != 0) { + char i = num % radix; + char c = get_letter(i, base); + *buf = c; + buf--; + num /= radix; + } + + if (sign) { + *buf = sign; + buf--; + } + + buf++; + + return buf; +} + +static void print_unum( + unsigned long long num, + enum format_flag flag, + struct format_width width, + struct format_precision precision, + bool isneg, + int radix, + char base +) { + + size_t max_len = 0; + size_t min_len = 0; + size_t len = 0; + + char sign = 0; + if (isneg) + sign = '-'; + else if (flag & FLG_ADD_SIGN) + sign = '+'; + + char buf[1024]; + char *str = get_decimal( + num, + buf, + sign, + radix, + base + ); + + bool space_pre = (flag & FLG_LEFT_ALIGN) || !(flag & FLG_ZERO); + + if (space_pre && radix == 16 && flag & FLG_ALTERNATE) { + char x = base + ('x' - 'a'); + serial_out('0'); + serial_out(x); + } + + if (width.defined) + min_len = width.value; + + if (precision.defined) { + max_len = precision.value; + len = max_len; + if (max_len < min_len) + min_len = max_len; + } else { + len = strlen(str); + } + + bool zero_padded = false; + + if (!(flag & FLG_LEFT_ALIGN) && len < min_len) { + for (size_t i = 0; i < (min_len - len); i++) { + (flag & FLG_ZERO) ? kputc('0') : kputc(' '); + } + if (flag & FLG_ZERO) + zero_padded = true; + } + + kputs(str); + + if (!zero_padded && (flag & FLG_ALTERNATE) && radix == 8) + kputc('0'); + + if ((flag & FLG_LEFT_ALIGN) && len < min_len) { + for (size_t i = 0; i < (min_len - len); i++) { + (flag & FLG_ZERO) ? kputc('0') : kputc(' '); + } + } +} + +static void print_num( + long long num, + enum format_flag flag, + struct format_width width, + struct format_precision precision, + int radix, + char base +) { + bool isneg = false; + + if (num < 0) { + num = ~num; + isneg = true; + } + + print_unum( + num, + flag, + width, + precision, + isneg, + radix, + base + ); +} + +void kvprintf(const char *format, va_list args) { + for (; *format != '\0'; format++) { + char c = *format; + if (c == '%') { + enum format_flag flag; + struct format_width width; + struct format_precision precision; + enum format_modifier modifier; + enum format_conversion conversion; + + const char *ptr = format + 1; + + flag = read_flag(ptr, &ptr); + width = read_width(ptr, &ptr); + precision = read_precision(ptr, &ptr); + modifier = read_modifier(ptr, &ptr); + + if (modifier == MOD_INVALID) { + kputc('%'); + continue; + } + + conversion = read_conversion(ptr, &ptr); + + if (conversion == FMT_INVALID) { + kputc('%'); + continue; + } + + union { + unsigned long long u; + long long l; + char c; + const char *str; + void *ptr; + } data; + + int radix = 0; + char base = 0; + + switch (conversion) { + case FMT_INT: + if (modifier == MOD_NONE) + data.l = va_arg(args, int); + else if (modifier == MOD_HALF) + data.l = (short) va_arg(args, int); + else if (modifier == MOD_HALF_HALF) + data.l = (char) va_arg(args, int); + else if (modifier == MOD_LONG) + data.l = va_arg(args, long); + else if (modifier == MOD_LONG_LONG) + data.l = va_arg(args, long long); + radix = 10; + goto printnum; + case FMT_UINT: + case FMT_OCT: + case FMT_HEX_UPPER: + case FMT_HEX: + if (modifier == MOD_NONE) + data.u = va_arg(args, unsigned int); + else if (modifier == MOD_HALF) + data.u = (unsigned short) va_arg(args, unsigned int); + else if (modifier == MOD_HALF_HALF) + data.u = (unsigned char) va_arg(args, unsigned int); + else if (modifier == MOD_LONG) + data.u = va_arg(args, unsigned long); + else if (modifier == MOD_LONG_LONG) + data.u = va_arg(args, unsigned long long); + + if (conversion == FMT_UINT) { + radix = 10; + } else if (conversion == FMT_OCT) { + radix = 8; + } else if (conversion == FMT_HEX) { + radix = 16; + base = 'a'; + } else if (conversion == FMT_HEX_UPPER) { + radix = 16; + base = 'A'; + } + goto printunum; + case FMT_PTR: + case FMT_PTR_UPPER: + flag |= FLG_ZERO; + data.u = va_arg(args, size_t); + radix = 16; + if (conversion == FMT_PTR) + base = 'a'; + else + base = 'A'; + goto printunum; + printnum: + print_num( + data.l, + flag, + width, + precision, + radix, + base + ); + break; + printunum: + print_unum( + data.u, + flag, + width, + precision, + false, + radix, + base + ); + break; + case FMT_CHAR: { + char buf[2]; + buf[0] = (char) va_arg(args, int); + buf[1] = '\0'; + print_string( + buf, + flag, + width, + precision + ); + break; + } + case FMT_STR: + data.str = va_arg(args, const char*); + print_string( + data.str, + flag, + width, + precision + ); + break; + case FMT_PERCENT: + kputc('%'); + break; + case FMT_INVALID: + break; + } + format = ptr - 1; + } else { + kputc(c); + } + } +} + +void kprintf(const char *format, ...) { + va_list args; + va_start(args, format); + kvprintf(format, args); + va_end(args); +} |