#include #include #include 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); }