forked from Bananymous/banan-os
				
			LibC: add probably functional *printf
I wrote a general printf function that takes an putc function pointer. We can use this to implement all the printf family functions. I haven't done thorough testing with this, but it seems to be functional for the most part
This commit is contained in:
		
							parent
							
								
									1cf7ef3de6
								
							
						
					
					
						commit
						d5ce4c9d2c
					
				| 
						 | 
				
			
			@ -5,6 +5,7 @@ project(libc CXX)
 | 
			
		|||
set(LIBC_SOURCES
 | 
			
		||||
	ctype.cpp
 | 
			
		||||
	fcntl.cpp
 | 
			
		||||
	printf_impl.cpp
 | 
			
		||||
	stdio.cpp
 | 
			
		||||
	stdlib.cpp
 | 
			
		||||
	string.cpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <sys/cdefs.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
__BEGIN_DECLS
 | 
			
		||||
 | 
			
		||||
int printf_impl(const char* format, va_list arguments, int (*putc_fun)(int, void*), void* data);
 | 
			
		||||
 | 
			
		||||
__END_DECLS
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,341 @@
 | 
			
		|||
#include <ctype.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#define BAN_PRINTF_PUTC(c)						\
 | 
			
		||||
	do											\
 | 
			
		||||
	{											\
 | 
			
		||||
		if (putc_fun((c), data) == EOF) 		\
 | 
			
		||||
			return -1;							\
 | 
			
		||||
		written++;								\
 | 
			
		||||
	} while (false)
 | 
			
		||||
 | 
			
		||||
struct format_options_t
 | 
			
		||||
{
 | 
			
		||||
	bool alternate_form { false };
 | 
			
		||||
	bool zero_padded { false };
 | 
			
		||||
	bool left_justified { false };
 | 
			
		||||
	bool show_plus_sign_as_space { false };
 | 
			
		||||
	bool show_plus_sign { false };
 | 
			
		||||
	int width { -1 };
 | 
			
		||||
	int percision { -1 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
static void integer_to_string(char* buffer, T value, int base, bool upper, const format_options_t options)
 | 
			
		||||
{
 | 
			
		||||
	int width = 1;
 | 
			
		||||
	bool zero_padded = options.zero_padded;
 | 
			
		||||
	if (options.percision != -1)
 | 
			
		||||
	{
 | 
			
		||||
		width = options.percision;
 | 
			
		||||
		zero_padded = false;
 | 
			
		||||
	}
 | 
			
		||||
	else if (options.width != -1)
 | 
			
		||||
		width = options.width;
 | 
			
		||||
	
 | 
			
		||||
	auto digit_char = [](int digit, bool upper)
 | 
			
		||||
	{
 | 
			
		||||
		if (digit < 10)
 | 
			
		||||
			return '0' + digit;
 | 
			
		||||
		return (upper ? 'A' : 'a') + (digit - 10);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	bool sign = false;
 | 
			
		||||
	int offset = 0;
 | 
			
		||||
	if (value < 0)
 | 
			
		||||
	{
 | 
			
		||||
		sign = true;
 | 
			
		||||
		buffer[offset++] = digit_char(-(value % base), upper);
 | 
			
		||||
		value = -(value / base);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	while (value)
 | 
			
		||||
	{
 | 
			
		||||
		buffer[offset++] = digit_char(value % base, upper);
 | 
			
		||||
		value /= base;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int prefix_length = 0;
 | 
			
		||||
 | 
			
		||||
	if (sign || options.show_plus_sign || options.show_plus_sign_as_space)
 | 
			
		||||
		prefix_length++;
 | 
			
		||||
	if (base == 8 && options.alternate_form)
 | 
			
		||||
		prefix_length++;
 | 
			
		||||
	if (base == 16 && options.alternate_form)
 | 
			
		||||
		prefix_length += 2;
 | 
			
		||||
 | 
			
		||||
	while (offset < width - prefix_length)
 | 
			
		||||
		buffer[offset++] = (zero_padded ? '0' : ' ');
 | 
			
		||||
 | 
			
		||||
	if (sign)
 | 
			
		||||
		buffer[offset++] = '-';
 | 
			
		||||
	else if (options.show_plus_sign)
 | 
			
		||||
		buffer[offset++] = '+';
 | 
			
		||||
	else if (options.show_plus_sign_as_space)
 | 
			
		||||
		buffer[offset++] = ' ';
 | 
			
		||||
 | 
			
		||||
	if (base == 8 && options.alternate_form)
 | 
			
		||||
		buffer[offset++] = '0';
 | 
			
		||||
	if (base == 16 && options.alternate_form)
 | 
			
		||||
	{
 | 
			
		||||
		buffer[offset++] = 'x';
 | 
			
		||||
		buffer[offset++] = '0';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i < offset / 2; i++)
 | 
			
		||||
	{
 | 
			
		||||
		char temp = buffer[i];
 | 
			
		||||
		buffer[i] = buffer[offset - i - 1];
 | 
			
		||||
		buffer[offset - i - 1] = temp;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buffer[offset++] = '\0';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
static void floating_point_to_exponent_string(char* buffer, T value, bool upper, const format_options_t options)
 | 
			
		||||
{
 | 
			
		||||
	int percision = 6;
 | 
			
		||||
	if (options.percision != -1)
 | 
			
		||||
		percision = options.percision;
 | 
			
		||||
	
 | 
			
		||||
	strcpy(buffer, "??e??");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
static void floating_point_to_string(char* buffer, T value, bool upper, const format_options_t options)
 | 
			
		||||
{
 | 
			
		||||
	int percision = 6;
 | 
			
		||||
	if (options.percision != -1)
 | 
			
		||||
		percision = options.percision;
 | 
			
		||||
 | 
			
		||||
	strcpy(buffer, "??.??");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" int printf_impl(const char* format, va_list arguments, int (*putc_fun)(int, void*), void* data)
 | 
			
		||||
{
 | 
			
		||||
	int written = 0;
 | 
			
		||||
	while (*format)
 | 
			
		||||
	{
 | 
			
		||||
		if (*format == '%')
 | 
			
		||||
		{
 | 
			
		||||
			format_options_t options;
 | 
			
		||||
 | 
			
		||||
			// PARSE FLAGS
 | 
			
		||||
			bool flags_done = false;
 | 
			
		||||
			while (!flags_done)
 | 
			
		||||
			{
 | 
			
		||||
				format++;
 | 
			
		||||
				switch (*format)
 | 
			
		||||
				{
 | 
			
		||||
					case '#': options.alternate_form = true; break;
 | 
			
		||||
					case '0': options.zero_padded = true; break;
 | 
			
		||||
					case '-': options.left_justified = true; break;
 | 
			
		||||
					case ' ': options.show_plus_sign_as_space = true; break;
 | 
			
		||||
					case '+': options.show_plus_sign = true; break;
 | 
			
		||||
				default:
 | 
			
		||||
					flags_done = true;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (options.zero_padded && options.left_justified)
 | 
			
		||||
				options.zero_padded = false;
 | 
			
		||||
 | 
			
		||||
			// PARSE FIELD WIDTH
 | 
			
		||||
			if (*format == '*')
 | 
			
		||||
			{
 | 
			
		||||
				options.width = va_arg(arguments, int);
 | 
			
		||||
				format++;
 | 
			
		||||
			}
 | 
			
		||||
			else if (isdigit(*format) && *format != '0')
 | 
			
		||||
			{
 | 
			
		||||
				int width = 0;
 | 
			
		||||
				while (isdigit(*format))
 | 
			
		||||
				{
 | 
			
		||||
					width *= 10;
 | 
			
		||||
					width += *format - '0';
 | 
			
		||||
					format++;
 | 
			
		||||
				}
 | 
			
		||||
				options.width = width;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// PARSE PERCISION
 | 
			
		||||
			if (*format == '.')
 | 
			
		||||
			{
 | 
			
		||||
				format++;
 | 
			
		||||
				int percision = 0;
 | 
			
		||||
				if (isdigit(*format))
 | 
			
		||||
				{
 | 
			
		||||
					while (isdigit(*format))
 | 
			
		||||
					{
 | 
			
		||||
						percision *= 10;
 | 
			
		||||
						percision += *format - '0';
 | 
			
		||||
						format++;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				else if (*format == '*')
 | 
			
		||||
				{
 | 
			
		||||
					percision = va_arg(arguments, int);
 | 
			
		||||
				}
 | 
			
		||||
				options.percision = percision;
 | 
			
		||||
			}
 | 
			
		||||
		
 | 
			
		||||
			// TODO: Lenght modifier
 | 
			
		||||
 | 
			
		||||
			static char conversion[100];
 | 
			
		||||
 | 
			
		||||
			const char* string = nullptr;
 | 
			
		||||
			switch (*format)
 | 
			
		||||
			{
 | 
			
		||||
			case 'd':
 | 
			
		||||
			case 'i':
 | 
			
		||||
			{
 | 
			
		||||
				int value = va_arg(arguments, int);
 | 
			
		||||
				integer_to_string<int>(conversion, value, 10, false, options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'o':
 | 
			
		||||
			{
 | 
			
		||||
				unsigned int value = va_arg(arguments, unsigned int);
 | 
			
		||||
				integer_to_string<unsigned int>(conversion, value, 8, false, options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'u':
 | 
			
		||||
			{
 | 
			
		||||
				unsigned int value = va_arg(arguments, unsigned int);
 | 
			
		||||
				integer_to_string<unsigned int>(conversion, value, 10, false, options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'x':
 | 
			
		||||
			case 'X':
 | 
			
		||||
			{
 | 
			
		||||
				unsigned int value = va_arg(arguments, unsigned int);
 | 
			
		||||
				integer_to_string<unsigned int>(conversion, value, 16, *format == 'X', options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'e':
 | 
			
		||||
			case 'E':
 | 
			
		||||
			{
 | 
			
		||||
				double value = va_arg(arguments, double);
 | 
			
		||||
				floating_point_to_exponent_string<double>(conversion, value, *format == 'E', options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'f':
 | 
			
		||||
			case 'F':
 | 
			
		||||
			{
 | 
			
		||||
				double value = va_arg(arguments, double);
 | 
			
		||||
				floating_point_to_string<double>(conversion, value, *format == 'F', options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'g':
 | 
			
		||||
			case 'G':
 | 
			
		||||
				// TODO
 | 
			
		||||
				break;
 | 
			
		||||
			case 'a':
 | 
			
		||||
			case 'A':
 | 
			
		||||
				// TODO
 | 
			
		||||
				break;
 | 
			
		||||
			case 'c':
 | 
			
		||||
			{
 | 
			
		||||
				conversion[0] = va_arg(arguments, int);
 | 
			
		||||
				conversion[1] = '\0';
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 's':
 | 
			
		||||
			{
 | 
			
		||||
				string = va_arg(arguments, const char*);
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'C':
 | 
			
		||||
				// TODO (depricated)
 | 
			
		||||
				break;
 | 
			
		||||
			case 'S':
 | 
			
		||||
				// TODO (depricated)
 | 
			
		||||
				break;
 | 
			
		||||
			case 'p':
 | 
			
		||||
			{
 | 
			
		||||
				void* ptr = va_arg(arguments, void*);
 | 
			
		||||
				options.alternate_form = true;
 | 
			
		||||
				integer_to_string<uintptr_t>(conversion, (uintptr_t)ptr, 16, false, options);
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'n':
 | 
			
		||||
			{
 | 
			
		||||
				int* target = va_arg(arguments, int*);
 | 
			
		||||
				*target = written;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case 'm':
 | 
			
		||||
			{
 | 
			
		||||
				// NOTE: this is a glibc extension
 | 
			
		||||
				if (options.alternate_form)
 | 
			
		||||
					string = strerror(errno); // TODO: string = strerrorname_np(errno);
 | 
			
		||||
				else
 | 
			
		||||
					string = strerror(errno);
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			case '%':
 | 
			
		||||
			{
 | 
			
		||||
				conversion[0] = '%';
 | 
			
		||||
				conversion[1] = '\0';
 | 
			
		||||
				string = conversion;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			default:
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (string)
 | 
			
		||||
			{
 | 
			
		||||
				int len = strlen(string);
 | 
			
		||||
 | 
			
		||||
				if (options.width == -1)
 | 
			
		||||
					options.width = 0;
 | 
			
		||||
 | 
			
		||||
				if (!options.left_justified)
 | 
			
		||||
					for (int i = len; i < options.width; i++)
 | 
			
		||||
						BAN_PRINTF_PUTC(' ');
 | 
			
		||||
 | 
			
		||||
				while (*string)
 | 
			
		||||
				{
 | 
			
		||||
					BAN_PRINTF_PUTC(*string);
 | 
			
		||||
					string++;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (options.left_justified)
 | 
			
		||||
					for (int i = len; i < options.width; i++)
 | 
			
		||||
						BAN_PRINTF_PUTC(' ');
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			BAN_PRINTF_PUTC(*format);
 | 
			
		||||
			format++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return written;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										100
									
								
								libc/stdio.cpp
								
								
								
								
							
							
						
						
									
										100
									
								
								libc/stdio.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
#include <errno.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <printf_impl.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -426,11 +427,23 @@ void setbuf(FILE*, char*);
 | 
			
		|||
// TODO
 | 
			
		||||
int setvbuf(FILE*, char*, int, size_t);
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
int snprintf(char*, size_t, const char*, ...);
 | 
			
		||||
int snprintf(char* buffer, size_t max_size, const char* format, ...)
 | 
			
		||||
{
 | 
			
		||||
	va_list arguments;
 | 
			
		||||
	va_start(arguments, format);
 | 
			
		||||
	int ret = vsnprintf(buffer, max_size, format, arguments);
 | 
			
		||||
	va_end(arguments);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
int sprintf(char*, const char*, ...);
 | 
			
		||||
int sprintf(char* buffer, const char* format, ...)
 | 
			
		||||
{
 | 
			
		||||
	va_list arguments;
 | 
			
		||||
	va_start(arguments, format);
 | 
			
		||||
	int ret = vsprintf(buffer, format, arguments);
 | 
			
		||||
	va_end(arguments);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
int sscanf(const char*, const char*, ...);
 | 
			
		||||
| 
						 | 
				
			
			@ -449,43 +462,7 @@ int ungetc(int, FILE*);
 | 
			
		|||
 | 
			
		||||
int vfprintf(FILE* file, const char* format, va_list arguments)
 | 
			
		||||
{
 | 
			
		||||
	int written = 0;
 | 
			
		||||
	while (*format)
 | 
			
		||||
	{
 | 
			
		||||
		if (*format == '%')
 | 
			
		||||
		{
 | 
			
		||||
			format++;
 | 
			
		||||
			switch (*format)
 | 
			
		||||
			{
 | 
			
		||||
			case '%':
 | 
			
		||||
				if (fputc('%', file) == EOF)
 | 
			
		||||
					return -1;
 | 
			
		||||
				written++;
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			case 's':
 | 
			
		||||
			{
 | 
			
		||||
				const char* string = va_arg(arguments, const char*);
 | 
			
		||||
				if (fputs(string, file) == EOF)
 | 
			
		||||
					return -1;
 | 
			
		||||
				written += strlen(string);
 | 
			
		||||
				format++;
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			default:
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			if (fputc(*format, file) == EOF)
 | 
			
		||||
				return -1;
 | 
			
		||||
			written++;
 | 
			
		||||
			format++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	return written;
 | 
			
		||||
	return printf_impl(format, arguments, [](int c, void* file) { return fputc(c, (FILE*)file); }, file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
| 
						 | 
				
			
			@ -500,10 +477,47 @@ int vprintf(const char* format, va_list arguments)
 | 
			
		|||
int vscanf(const char*, va_list);
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
int vsnprintf(char*, size_t, const char*, va_list);
 | 
			
		||||
int vsnprintf(char* buffer, size_t max_size, const char* format, va_list arguments)
 | 
			
		||||
{
 | 
			
		||||
	if (buffer == nullptr)
 | 
			
		||||
		return printf_impl(format, arguments, [](int, void*) { return 0; }, nullptr);
 | 
			
		||||
 | 
			
		||||
	struct print_info
 | 
			
		||||
	{
 | 
			
		||||
		char* buffer;
 | 
			
		||||
		size_t remaining;
 | 
			
		||||
	};
 | 
			
		||||
	print_info info { buffer, max_size };
 | 
			
		||||
 | 
			
		||||
	return printf_impl(format, arguments,
 | 
			
		||||
		[](int c, void* _info)
 | 
			
		||||
		{
 | 
			
		||||
			print_info* info = (print_info*)_info;
 | 
			
		||||
			if (info->remaining)
 | 
			
		||||
			{
 | 
			
		||||
				*info->buffer = (info->remaining == 1 ? '\0' : c);
 | 
			
		||||
				info->buffer++;
 | 
			
		||||
				info->remaining--;
 | 
			
		||||
			}
 | 
			
		||||
			return 0;
 | 
			
		||||
		}, &info
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
int vsprintf(char*, const char*, va_list);
 | 
			
		||||
int vsprintf(char* buffer, const char* format, va_list arguments)
 | 
			
		||||
{
 | 
			
		||||
	if (buffer == nullptr)
 | 
			
		||||
		return printf_impl(format, arguments, [](int, void*) { return 0; }, nullptr);
 | 
			
		||||
	
 | 
			
		||||
	return printf_impl(format, arguments,
 | 
			
		||||
		[](int c, void* _buffer)
 | 
			
		||||
		{
 | 
			
		||||
			*(*(char**)_buffer)++ = c;
 | 
			
		||||
			return 0;
 | 
			
		||||
		}, &buffer
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
int vsscanf(const char*, const char*, va_list);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue