User talk:Thecool1kevin/printf
Jump to navigation
Jump to search
Hello, everyone. This is where you get some free code of questionable quality to get you on your feet in OSDev! To use the following snippets, you need a memset(), a strlen() and a putc(), to put characters to the screen. Take what you like, as these are very simple implementations and some are of sub par quality. Free food for all (this is the closest you'll ever get to free code without credit).
Some downsides:
- Negative width is not supported, nor are doubles and floats.
- Kind of hard to maintain tbh.
The upsides:
- Free code to get you started.
- Portable and easy to use (for the most part)
The code provided here should by no means stay in your OS or even be part of your OS for very long. They are just meant to be starting points for formatted texts. Free code can sometimes bite y'know?
NOTE ON -lgcc
Everything below has to be linked with the -lgcc option! See "How to link with libgcc" in Libgcc. Basically:
i686-elf-gcc -T linker.ld -o myos.kernel -ffreestanding boot.o kernel.o -nostdlib -lgcc
strings.h
// Implement these yourself. The wiki has plenty of resources!
void* memset (void*, uint32_t, size_t);
size_t strlen(const char* str);
// Implementation provided below
int strtol(char a[]);
kprintf.h
// Implementations of these are going to be provided below
int kprintf(const char* format, ...);
int fprintf(uint8_t file, const char* format, va_list args);
int strtol(char[] a)
int strtol(char a[])
{
int n = 0;
for (int c = 0; c < strlen(a); c++)
n = n * 10 + a[c] - '0';
return n;
}
kprintf.c
/*
* Filename: kprintf.c
* Author: Kevin Dai
* Email: [email protected]
*
* Created on 31-Jul-2017 02:10:59 PM
*
* @ Last modified by: Kevin Dai
* @ Last modified time: 02-Aug-2017 12:52:38 PM
*/
// Have fun maintaining the code!
#include "kprintf.h"
// #include "a bunch of other stuff"
const char __base1[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
const char __base2[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
// There used to be wrapper methods above hardware implementation details that we do not need to worry about
void kputc(uint8_t file, const char c)
{
/* TODO: Implement a put character to screen */
}
int kprintstr(uint8_t file, const char* str)
{
for(uint32_t i = 0; i < strlen(str); i++)
kputc(file, str[i]);
return strlen(str);
}
// Our own secret code reusability thing that takes a bijillion arguments because I hate myself
// Look, this is bad code and I keep on sticking new arguments in but its so repeated that I thought why
// not stick it into some giant blob of disgustingness and forget about it?
int _convbase(uint8_t file, long long num, uint8_t base, bool small, bool isSigned, int width, char pad, char foo, bool isOct, char oct)
{
char ret[23]; memset(ret, 0, 23); // 22 for 64 bit octal, +1 for EOL
if(num == 0)
ret[0] = '0';
else
{
if(isSigned)
{
long long divres = num < 0 ? -num : num; // Absolute value
for(int i = 0; divres > 0; i++) // Do base conversion
{
if(!small)
ret[i] = __base1[divres % base];
else
ret[i] = __base2[divres % base];
divres /= base;
}
}
else
{
unsigned long long divres = (unsigned long long) num;
for(int i = 0; divres > 0; i++) // Do base conversion
{
if(!small)
ret[i] = __base1[divres % base];
else
ret[i] = __base2[divres % base];
divres /= base;
}
}
}
if(strlen(ret) > 1)
{
uint8_t i = 0;
uint8_t j = strlen(ret) - 1;
// Reverse the string
while (i < j)
{
char temp = ret[i];
ret[i] = ret[j];
ret[j] = temp;
i++; j--;
}
}
// Deal with the padding
if((signed) strlen(ret) + foo < width) // Deal with padding
{
for(uint32_t i = 0; i < (unsigned)(width - strlen(ret) - foo); i++)
kputc(file, pad);
if(isOct)
kputc(file, oct);
kprintstr(file, ret);
return width;
}
else
{
if(isOct)
kputc(file, oct);
return kprintstr(file, ret);
}
}
/* ==================================== THINGS THAT ARE EXPOSED TO THE REAL WORLD ==================================== */
/**
* Kernel print format to the normal output stream.
* @param format The formatted text, "%[flags][width][length]specifier" is supported
* @param VARARGS Arguments to go with the formatted text
* @return Length of text written. Will be negative if error occured.
*/
int kprintf(const char* restrict format, ...)
{
va_list args;
va_start(args, format);
int ret = fprintf(_NORM, format, args);
va_end(args);
return ret;
}
/**
* File print format for the kernel.
* @param file IO stream id: _NORM, _ERR, _INFO
* @param format Formatted text, "%[flags][width][length]specifier" is supported
* @param VARARGS Arguments to go with the formatted text
* @return Length of text written. Will be negative if error occured.
*/
int fprintf(uint8_t file, const char* restrict format, va_list args)
{
int written = 0;
while(*format != '\0')
{
if(written > INT_MAX) return -1; //TODO: Errnos
// Parse the format baby!
if (*format == '%')
{
// Initial values (the format properties)
bool flags[4] = { false, false, false, false };
int width = -1;
char length = 0;
char specifier = 0;
char tmp[11]; int _i = 0;
memset(tmp, 0, 11);
format++; // Let the parsing begin!
while (specifier == 0)
{
switch (*format) // Parse the formats now
{
// All of our flags. Here, we need to allow multiple flags to be accepted.
// The if statements ensure our formats are in the right order
case '0': // Handle the corner case with numbers
if (tmp[0] != 0 && width == -1 && length == 0 && specifier == 0 && _i < 10) { tmp[_i] = '0'; _i++; }
else if (!flags[0] && width == -1 && length == 0 && specifier == 0) flags[0] = true;
else return -1;
break;
case '+': if (!flags[1] && width == -1 && length == 0 && specifier == 0) flags[1] = true; else return -1; break;
case ' ': if (!flags[2] && width == -1 && length == 0 && specifier == 0) flags[2] = true; else return -1; break;
case '#': if (!flags[3] && width == -1 && length == 0 && specifier == 0) flags[3] = true; else return -1; break;
// Most of our widths
case '*':
if (width == -1 && length == 0 && specifier == 0) width = va_arg(args, int); else return -1;
break;
case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
if (width == -1 && length == 0 && specifier == 0 && _i < 10) { tmp[_i] = *format; _i++; }
else return -1;
break;
// All of our lengths
case 'h': case 'l':
if (length == 0 && specifier == 0) length = *format; else return -1;
break;
// All of our specifiers. Formats end when the specifier is specified :)
case 's': case 'x': case 'X': case 'u': case 'o': case 'c': case '%': case 'd': case 'i':
if (specifier == 0) specifier = *format; else return -1;
break;
default: return -1;
}
format++; // Move on in life
}
// Turn the string we collected into an actual number
if(width == -1 && tmp[0] == 0) // If no width was specified...
width = 0;
else if(width == -1 && tmp[0] != 0) // If a width was specified in the format
{
if(strtol(tmp) < INT_MAX)
width = strtol(tmp); // Convert it
else // Width too large
return -1;
}
// Now that all the information on the format has been collected, we need to print it on screen
if(specifier == 's')
{
const char* str = va_arg(args, const char*);
if(strlen(str) < (unsigned) width) // Deal with padding
{
for(uint32_t i = 0; i < (unsigned)(width - strlen(str)); i++)
kputc(file, ' ');
kprintstr(file, str);
written += width;
}
else
written += kprintstr(file, str);
}
else if(specifier == 'c')
{
const char c = (char) va_arg(args, const int);
if(1 < (unsigned) width) // Deal with padding
{
for(uint32_t i = 0; i < (unsigned)(width - 1); i++)
kputc(file, ' ');
kputc(file, c);
written += width;
}
else
{
kputc(file, c);
written++;
}
}
else if(specifier == '%')
{
kputc(file, '%');
written ++;
}
else if(specifier == 'x' || specifier == 'X' || specifier == 'u' || specifier == 'o')
{
unsigned long long val = 0;
// Collect the argument based on the length specifier
if(length == 'h')
val = (unsigned long long)((unsigned short int) va_arg(args, uint32_t)); // char promotes to int
else if(length == 'l')
val = va_arg(args, unsigned long long);
else
val = (unsigned long long) va_arg(args, uint32_t);
// Organize our information etc.
char pad = ' ';
if(flags[0]) pad = '0';
uint8_t base = 10;
if(specifier == 'x' || specifier == 'X') // Base 16 number (hex)
base = 16;
else if(specifier == 'u') // Base 10 (decimal)
base = 10;
else // Base 8 (octal)
base = 8;
// Deal with prefixes now
char foo = 0;
if(specifier == 'x' && flags[3]) { written += kprintstr(file, "0x"); foo += 2; }
else if(specifier == 'X' && flags[3]) { written += kprintstr(file, "0X"); foo += 2; }
else if(specifier == 'o' && flags[3]) foo++;
// Write it!
written += _convbase(file, val, base, specifier != 'X', false, width, pad, foo, (specifier == 'o' && flags[3]), '0');
}
else if(specifier == 'd' || specifier == 'i')
{
long long val = 0; // Fetch the values
if(length == 'h')
val = (long long)((short int) va_arg(args, long));
else if(length == 'l')
val = va_arg(args, long long);
else
val = (long long) va_arg(args, int);
if(val < 0 && flags[1]) val = -val; // Make positive
// Deal with the signage
char pad = ' ';
if(flags[0]) pad = '0';
char foo = 0;
if(val < 0) { foo++; written++; } // Negative sign
if(val < 0 && flags[0]) kprintstr(file, "-");
if(val >= 0 && flags[2]) { foo++; written += kprintstr(file, " "); } // Space for positive
// Write it!
written += _convbase(file, val, 10, false, true, width, pad, foo, (val < 0 && !flags[0]), '-');
}
}
// If we are not parsing a format
else
{
kputc(file, *format);
format++;
written++;
}
}
return written;
}
Unit testing (ish)
void __test_kprintf__()
{
// Test case 1:
kprintf("[1]: Hello, world!\n");
kprintf("[2]: %s %x %X %u %o %c %% %d %i \n", "Hello, world!", 0xDEADBEEF, 0xCAFEBABE, 10, 8, 'c', 'd', -10, 0);
kprintf("[3]: %0#8X %#010x %#4o %0*u\n", 0xDEAD, 0xBEEF, 8, 4, 10);
long long myLong = -9223372036854775807L;
kprintf("[4]: %ld 0x%lX 0x%hX %hi\n", myLong, 0xFEFFFFFFBFFFFFFFL, 0xDEADBEEF, myLong);
kprintf("[5]: %+ 6d %10s %10c\n", -1024, "string", 'c');
kprintf("[6]: % 6d %10i %#010X\n", 1024, -123456, 0xFEEDFACE);
kprintf("[7 (bug)]: %X %lX\n", 0xDECAFC0FFEEL, 0xDECAFC0FFEEL);
kprintf("[7 (fix)]: %X %lX\n", (int) 0xDECAFC0FFEEL, 0xDECAFC0FFEEL);
}