User talk:Thecool1kevin/printf

From OSDev Wiki
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);
}