#include #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 = 0, MOD_HALF = 1, MOD_HALF_HALF = 3, MOD_LONG = 3, MOD_LONG_LONG = 4, MOD_INVALID = 5, }; enum format_conversion { // decimal numbers FMT_CHAR = 0, FMT_INT = 4, FMT_UINT = 5, // otehr numbers FMT_OCT, FMT_HEX, FMT_HEX_UPPER, // text FMT_STR, FMT_PTR, // misc FMT_INVALID }; enum format_length { LEN_UCHAR, LEN_CHAR, LEN_USHORT, LEN_SHORT, LEN_UINT, LEN_INT, LEN_ULONG, LEN_LONG, LEN_ULONGLONG, LEN_LONGLONG, LEN_SIZET, LEN_INVALID }; struct spacing { unsigned left; unsigned length; unsigned right; bool zero; }; enum charcase { UPPERCASE, LOWERCASE }; enum printtype { NONE, OCTAL, HEX }; static enum printtype conversion_to_printtype(enum format_conversion conversion) { switch (conversion) { case FMT_OCT: return OCTAL; case FMT_HEX: case FMT_HEX_UPPER: case FMT_PTR: return HEX; default: return NONE; } } static enum charcase conversion_to_charcase(enum format_conversion conversion) { switch (conversion) { case FMT_HEX_UPPER: return UPPERCASE; default: return LOWERCASE; } } static enum format_flag read_flags(const char *format, const char **end) { enum format_flag flags = FLG_NONE; for (; *format != '\0'; format++) { switch (*format) { case '#': flags |= FLG_ALTERNATE; break; case '0': flags |= FLG_ZERO; break; case '-': flags |= FLG_LEFT_ALIGN; break; case '+': flags |= FLG_ADD_SIGN; break; default: *end = format; return flags; } } *end = format; return flags; } 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; format++; 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; default: return FMT_INVALID; } } static char ctoa_print( char c, // the single digit to convert to ascii enum charcase cc // if this digit should be uppercase or lowercase ) { char ascii_base = cc == UPPERCASE ? 'A' : 'a'; if (c >= 0 && c <= 9) return c + '0'; c -= 10; return c + ascii_base; } static char *ulltoa_print( unsigned long long num, // the number to convert to ascii unsigned radix, // the divisor for the number (base 10, octal, hex) char *buf, // the buffer to write to enum charcase cc, // if any a-z ascii should be uppercase or lowercase bool addsign // add a sign even if its positive ) { // we are printing the string backwards, add null byte *buf = '\0'; buf--; // if the number is zero, the while loop will never run // so we have to add the zero digit ourselves if (num == 0) { *buf = '0'; buf--; } // for each digit, convert to ascii and add to buffer while (num != 0) { char i = num % radix; char c = ctoa_print(i, cc); *buf = c; buf--; num /= radix; } // since this is the unsigned function, add a plus sign if // requested if (addsign) { *buf = '+'; buf--; } buf++; // move forward one to be at the start of the string return buf; } static char *lltoa_print( signed long long num, // the number to convert to ascii unsigned radix, // the divistor for the number (base 10, octal, hex) char *buf, // the buffer to write to enum charcase cc, // if any a-z ascii should be uppercase or lowercase bool addsign // add a sign even if its positive ) { bool isneg = num < 0; if (isneg) num = -num; buf = ulltoa_print(num, radix, buf, cc, !isneg && addsign); if (isneg && num < 0) { buf--; *buf = '-'; } return buf; } static void print_string_buffer( const char *buf, // buffer containing the text we want to print struct spacing spacing // the spacing on the left right and middle ) { for (unsigned i = 0; i < spacing.left; i++) { kputc(' '); } for (unsigned i = 0; i < spacing.length; i++) { kputc(*buf++); } for (unsigned i = 0; i < spacing.right; i++) { kputc(' '); } } static void print_number_buffer( const char *buf, // buffer containing the text we want to print struct spacing spacing, // the spacing on the left right and middle enum printtype type, enum charcase cc, enum format_flag flags ) { // put the 0x at the start of the string if (spacing.left && spacing.zero && type == HEX && (flags & FLG_ALTERNATE)) { if (cc == UPPERCASE) { kputs("0X"); } else { kputs("0x"); } } if ((!spacing.left || !spacing.zero) && type == OCTAL && (flags & FLG_ALTERNATE)) { kputc('0'); } for (unsigned i = 0; i < spacing.left; i++) { spacing.zero ? kputc('0') : kputc(' '); } if ((!spacing.left || !spacing.zero) && type == HEX && (flags & FLG_ALTERNATE)) { if (cc == UPPERCASE) { kputs("0X"); } else { kputs("0x"); } } for (unsigned i = 0; i < spacing.length; i++) { kputc(*buf++); } for (unsigned i = 0; i < spacing.right; i++) { kputc(' '); } } static unsigned get_string_len( const char *str, struct format_precision precision ) { if (!precision.defined) return strlen(str); unsigned max = precision.value; for (unsigned i = 0; i < max; i++) { if (str[i] == '\0') { // we broke early // precision is greater then strlen return i; } } return max; } static struct spacing get_spacing( struct format_width width, struct format_precision precision, enum format_flag flags, unsigned length ) { struct spacing spacing; unsigned min = 0, max = 0; if (width.defined) { min = width.value; } if (precision.defined) { max = precision.value; if (length > max) { length = max; } if (min > max) { min = max; } } unsigned gap = 0; if (min > length) { gap = min - length; } spacing.length = length; if (flags & FLG_LEFT_ALIGN) { spacing.left = 0; spacing.right = gap; } else { spacing.left = gap; spacing.right = 0; } spacing.zero = (flags & FLG_ZERO); return spacing; } static struct spacing get_string_spacing( const char *str, struct format_width width, struct format_precision precision, enum format_flag flags ) { unsigned length = get_string_len(str, precision); return get_spacing(width, precision, flags, length); } static void print_string( const char *str, enum format_flag flags, struct format_width width, struct format_precision precision ) { struct spacing spacing = get_string_spacing(str, width, precision, flags); print_string_buffer(str, spacing); } static enum format_length apply_modifier( enum format_conversion conversion, enum format_modifier modifier ) { switch (conversion) { case FMT_CHAR: if (modifier == MOD_NONE) return LEN_CHAR; else return LEN_INVALID; case FMT_PTR: if (modifier == MOD_NONE) return LEN_SIZET; else return LEN_INVALID; case FMT_INT: if (modifier == MOD_LONG) return LEN_LONG; else if (modifier == MOD_LONG_LONG) return LEN_LONGLONG; else if (modifier == MOD_HALF) return LEN_SHORT; else if (modifier == MOD_HALF_HALF) return LEN_CHAR; else return LEN_INT; case FMT_UINT: case FMT_OCT: case FMT_HEX: case FMT_HEX_UPPER: if (modifier == MOD_LONG) return LEN_ULONG; else if (modifier == MOD_LONG_LONG) return LEN_ULONGLONG; else if (modifier == MOD_HALF) return LEN_USHORT; else if (modifier == MOD_HALF_HALF) return LEN_UCHAR; else return LEN_UINT; default: return LEN_INVALID; } } static unsigned printtype_to_radix( enum printtype type ) { switch (type) { case OCTAL: return 8; case HEX: return 16; case NONE: default: return 10; } } static void print_signed_number( signed long long num, enum format_flag flags, struct format_width width, struct format_precision precision, enum printtype type, enum charcase cc ) { unsigned radix = printtype_to_radix(type); char buf[256]; char *ptr = lltoa_print(num, radix, buf + 255, cc, flags & FLG_ADD_SIGN); unsigned length = strlen(ptr); struct spacing spacing = get_spacing(width, precision, flags, length); print_number_buffer(ptr, spacing, type, cc, flags); } static void print_unsigned_number( unsigned long long num, enum format_flag flags, struct format_width width, struct format_precision precision, enum printtype type, enum charcase cc ) { unsigned radix = printtype_to_radix(type); char buf[256]; char *ptr = ulltoa_print(num, radix, buf + 255, cc, flags & FLG_ADD_SIGN); unsigned length = strlen(ptr); struct spacing spacing = get_spacing(width, precision, flags, length); print_number_buffer(ptr, spacing, type, cc, flags); } static bool is_conversion_number(enum format_conversion conversion) { switch (conversion) { case FMT_INT: case FMT_UINT: case FMT_OCT: case FMT_HEX: case FMT_HEX_UPPER: case FMT_PTR: return true; case FMT_CHAR: case FMT_STR: case FMT_INVALID: default: return false; } } void kvprintf(const char *format, va_list args) { for (; *format != '\0'; format++) { char c = *format; if (c == '%') { enum format_flag flags; struct format_width width; struct format_precision precision; enum format_modifier modifier; enum format_conversion conversion; const char *ptr = format + 1; if (*(format + 1) == '%') { kputc('%'); format++; continue; } flags = read_flags(ptr, &ptr); width = read_width(ptr, &ptr); precision = read_precision(ptr, &ptr); modifier = read_modifier(ptr, &ptr); if (modifier == MOD_INVALID) { goto error; } conversion = read_conversion(ptr, &ptr); if (width.varys) { int len = va_arg(args, int); width.value = len; } if (precision.varys) { int len = va_arg(args, int); precision.value = len; } if (conversion == FMT_INVALID) { goto error; } if (conversion == FMT_INT) { enum format_length length = apply_modifier(conversion, modifier); enum printtype type = NONE; enum charcase cc = LOWERCASE; signed long long num = 0; switch (length) { case LEN_CHAR: case LEN_SHORT: case LEN_INT: num = va_arg(args, signed int); break; case LEN_LONG: num = va_arg(args, signed long); break; case LEN_LONGLONG: num = va_arg(args, signed long long); break; default: goto error; } print_signed_number(num, flags, width, precision, type, cc); } else if (is_conversion_number(conversion)) { enum format_length length = apply_modifier(conversion, modifier); enum printtype type = conversion_to_printtype(conversion); enum charcase cc = conversion_to_charcase(conversion); unsigned long long num = 0; switch (length) { case LEN_UCHAR: case LEN_USHORT: case LEN_UINT: num = va_arg(args, unsigned int); break; case LEN_ULONG: num = va_arg(args, unsigned long); break; case LEN_ULONGLONG: num = va_arg(args, unsigned long long); break; case LEN_SIZET: num = va_arg(args, size_t); break; default: goto error; } print_unsigned_number(num, flags, width, precision, type, cc); } else if (conversion == FMT_STR) { char *str = va_arg(args, char *); print_string(str, flags, width, precision); } else if (conversion == FMT_CHAR) { char temp[2]; temp[0] = va_arg(args, int); temp[1] = '\0'; print_string(temp, flags, width, precision); } format = ptr - 1; continue; error: kputc('%'); format++; continue; } else { kputc(c); } } } void kprintf(const char *format, ...) { va_list args; va_start(args, format); kvprintf(format, args); va_end(args); }