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
							
								
									ff2e2937a5
								
							
						
					
					
						commit
						49fe3d0d4f
					
				|  | @ -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