From 967613809980472eae5c28f41e9e295866c146ce Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 15 May 2022 15:16:01 -0400 Subject: [PATCH] ANSI code parsing is only half functional --- .vscode/settings.json | 3 +- bin/shade.bin | Bin 22504 -> 32320 bytes bin/shade.iso | Bin 26603520 -> 26613760 bytes include/printf.h | 117 +++ .../drivers/vga_text_mode.h} | 24 + include/shade/util.h | 3 + obj/kernel/kernel.o | Bin 3496 -> 3824 bytes obj/kernel/print.o | Bin 3936 -> 0 bytes src/boot/entry64.asm | 2 +- src/kernel/kernel.c | 35 +- src/kernel/platform/drivers/vga_text_mode.c | 328 +++++++ src/kernel/platform/interrupts/isr.c | 16 +- src/kernel/print.c | 110 --- src/libc/printf.c | 923 ++++++++++++++++++ 14 files changed, 1415 insertions(+), 146 deletions(-) create mode 100644 include/printf.h rename include/shade/{print.h => platform/drivers/vga_text_mode.h} (64%) delete mode 100644 obj/kernel/print.o create mode 100644 src/kernel/platform/drivers/vga_text_mode.c delete mode 100644 src/kernel/print.c create mode 100644 src/libc/printf.c diff --git a/.vscode/settings.json b/.vscode/settings.json index 288d5f0..0300e4e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "files.associations": { - "types.h": "c" + "types.h": "c", + "vga_text_mode.h": "c" } } diff --git a/bin/shade.bin b/bin/shade.bin index 117886589ed94d1ebcb39bd1fa79482fb6fd145f..7881844fe7905d1fdddf98ccdb743fe1c65b6ecf 100755 GIT binary patch literal 32320 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 literal 22504 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 delta 69856 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 AF#rGn diff --git a/include/printf.h b/include/printf.h new file mode 100644 index 0000000..e58f666 --- /dev/null +++ b/include/printf.h @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +void _putchar(char character); + + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +#define printf printf_ +int printf_(const char* format, ...); + + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char* buffer, const char* format, ...); + + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +int snprintf_(char* buffer, size_t count, const char* format, ...); +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); + + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char* format, va_list va); + + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ \ No newline at end of file diff --git a/include/shade/print.h b/include/shade/platform/drivers/vga_text_mode.h similarity index 64% rename from include/shade/print.h rename to include/shade/platform/drivers/vga_text_mode.h index edd8a9e..deb00fb 100644 --- a/include/shade/print.h +++ b/include/shade/platform/drivers/vga_text_mode.h @@ -3,6 +3,8 @@ #define REG_SCREEN_CTRL 0x3d4 #define REG_SCREEN_DATA 0x3d5 +#include + enum { PRINT_COLOR_BLACK = 0, PRINT_COLOR_BLUE = 1, @@ -22,6 +24,11 @@ enum { PRINT_COLOR_WHITE = 15, }; +typedef struct ansi_code_struct { + int flag; + int color; +} ansi_color_code_t; + const static int NUM_COLS = 80; const static int NUM_ROWS = 25; @@ -30,15 +37,32 @@ struct Char { char color; }; +static bool is_parsing_ansi_string = false; +static bool ansi_string_found_bracket = false; + +static bool ansi_string_found_semi = false; +static bool ansi_string_found_m = false; + +static bool ansi_string_found_number = false; + +static int _flag = 0; +static int _color = 0; + +static int working_num = 0; + void clear_row(int row); void clear_all(); void print_newline(); void print_char(char character); +void _print_char(char character); void print_set_color(char foreground, char background); void print_str(char* str); void set_cursor_pos(int col, int row); void kernel_msg_ok(char* msg); + +void handle_ansi_code(ansi_color_code_t code); + #endif \ No newline at end of file diff --git a/include/shade/util.h b/include/shade/util.h index 7ffca01..9985e39 100644 --- a/include/shade/util.h +++ b/include/shade/util.h @@ -1,6 +1,9 @@ #ifndef UTIL_H #define UTIL_H +#define IS_DIGIT(x) (x == '0' || x == '1' || x == '2' || x == '3' || x == '4' || x == '5' || x == '6' || x == '7' || x == '8' || x == '9') +#define IS_HEX(x) (IS_DIGIT(x) || x == 'a' || x == 'b' || x == 'c' || x == 'd' || x == 'e' || x == 'f') + void memcpy(char *source, char *dest, int nbytes); void memset(char *dest, char val, int len); diff --git a/obj/kernel/kernel.o b/obj/kernel/kernel.o index aec1acb5e90e4b9b11fded44fd38cba856b1b10f..e299fec3a62438c72bfa83ec59500aeffc82551f 100644 GIT binary patch literal 3824 zcmb_fPiP%Q7@wEawzg>-tF_gNOe>TY%r<%PVk`RMBR0V{m8PLmk?m%8^0H?CF*7g8 zD~O8VpIf}B;88^Iq6g7Jp{WS!MZuFd@t`0mir_&(t>3ph-|KYxUJl|1v-5qw@Bhr~ zynWA2o_uVeR%1jp_At9H5z5#H+pqJDl5MaZY>3TGz52sC%9rr*BR;O+V|@@FrPx!u zYeVhM4YmJ^@7mL!V(C*a{yaSO`Q#t$`RzA#07cjh-I%s)?Rw8nlN*ay4nyR1PT`#> zW#lghoDe_#1Li&G3Bc}xaZV&*o(isW&Sn>bNU#Ij6waq-`I3;CNcgglD$X;`M!mXM z-^R|gvn_uX}tVNqDJJTnZ)RvWPr#V`mp*#S59YB-mH%avVXjgMk`} z)j=L)5zlfiRts=eJeNF9izEh%l|afM(;eHb8~E&^;8-9};!tEt@G!{uJYabn0tT8N zRULKop=**wM%pO~3D!pG{~m^WIk{3dvE z7`MOx9UEY^tB4WRj7;z99Y#^Rc!t$hhim(XwrpJk zD&0ef1HF$A(j&hCVct#bw`;vj27BHhj189E+~|SJRmk+u+>b%ow~&xRL1v z!9Br#8x+VC^fBPOyH%UL1?)2zWD0DTz5=cn{w?4br#u_LmuCRx@B7&Q(g**$5567h z?Ui>=ADmT}l^3#^xl?}g^vT(hIdl5lEQ^(21aOCCEkDRq>?+`yW+x5KV#5rN)cPx9mo)0bD zj`OvJ?*X3H|2qr6)53qXa68WL2B&qtVB|k7`@O(Z{6TYHV86Fn_*R2coL7whZp+^8 zYp-K})z}~CV}Ho8-)H2}KK74T_&%%7X$!~i9O`Sv;8g#Lk!LM?dmYX@_W0X~6j}Cm zzr55k_}#6B5n02*>E2|X>ZCfDhgr*NUL;P6B=EFY)y#_m9WZZRDdx$%65z89Ke(8e z4KgtE zr7ab6!Tg}&zMwDNOSq>$F{bN()fhJLkXytp9@#M%XiDD$ zh#`^xCr(1Dzp`TNL155ilmIioU<$%_)g}r@^P`}|rE>GXY5d)|ZhpKsF_!E9h4Ft9 zbT+yEbD-?CfBVe;JIasyhrZ-b``0Ts=HnO?s4#_LINS=?|5m`H7Z*hhSfo delta 1162 zcmZ{jL1+^}6ozMZlXhpbyCx;NS?e}Qb1)RqC`v%A=|O~u2tD+mSaQ&y#Wt;NAl_0G z1Vtgs6A=XIMG(CR@gNAM$9fR#-9rz8f(Si}80YQIz>t~;?=kP2|IM4(o!RwYjdzOL zO*5Fv2A90xTRK>eXMYfGJq+RKW0*QJ?FBm-xJT2;|8VU+33jh02NvkTEqYY@`4{rB zG;GpCl)zW&!*4o4N!Qa#6xMYYp6N{*fF0}3k|cH{B*&eS7?HV&kXw;E@|@7|B(xHb zV>0364-qC6*p9oj8pg{Qy&FO)6@gGLg_>3v?}zb87++w7&WH~uM-NZd)|#tiWp$y^ ztgY3XwQI|Z%QA1&TZ;>gIxLM|sK&*jxNV)gq2xH^2O~K5ryrSzkQ?#@cgZJ!8tz;H zr??GPorv(BsHE8{=(Xkn`#cAItu83Rm}aMR3{Sl*ns}UOit!;mSHr z|Bf>n}_>h@QW4NwnEgMj|q+pFT*BxG zeOcKO73Y-7WYc3;3s1*wOBCY=XE5&^F~oa#1A&!=w@wDyu{eIX>j?eHtgXId{(8J$v1aCZ6QZ zz4tpm_x#PB`M}!$YRE8zB160^Dm#S=VK!9OQMrtY^`b#^#=PHRg9|&o{<+w!byp0m z6EoJL86JBCh4R+#vsMXL^V`NdU;9VX*pxRj`J)0S!n%wmQ0Ye#!-1A~c-Wk= zmZ3@RwZCQx0G5sb;EM^&{Uq9x1&9q>4`UQrzzQ+ey=oTMT2d^TEwTV^pbRz_wy@aO z+WMloZC_jGc5~P6$gapvAyxux!od6hLxhAeg_t6P_Uhf=L=5BX0b!hOGB!7?UNZvB zrvb--^e#U3G&X${YF)o7lmdx>_Y&ejd*p-oeUdG8-;`V-e&zho&t?5S;$N!bUo7)4 z5*eg5{E`6LjRDSg|U(OBWnoQcEU%o4}oNEV15Ax z$}2fpQ^58Teg$SESxcC)2T}e49e4?uK!;%jMiUI3&H{k) z28AYTghIV;cRCwMinh+z~cKl$~(nu5(Ic>}))lb2E+`?{zZ7 zNTgDMfq2?JmFiC0v`d~yxU|e<%PX|tq#Rc`x$IGK(st8!D&CVh9(PWPr^sOpXElt{ zazJk}d=C5$f!X14-C*+g%~0|`)xnE(@cVV}=jpvf zKk*Bz?7zbB^$c%gIF~=fa4z4?a4w%?IG6vh4sL=ENEil%BKJ}Gtl;Y2dt1ZRy*HrY z>fZZQ!_~bvrQzz{yRG5s9)_zSJ$4#+X(ww(j;C{xUf1ch-Rv2VK~4Kayvt4W*yPdwn@mv05U`gy zMQM|#`Y0ZStF{8aZ`e!GG7>C0u&IH*ApNzlMqRvXkeY)@Y=s$d?HF;B0R23DzXNp9 zrmp`j0qQ&uk%Yc-+65c5$$Jd>|5d+*^x+q-itxu3>9JrcKb8M#-U}Q|m41Ga^mV&> zJcmHHmjBHZXtO$qJka$!0k2g*9}5OFC7(jz7Il@rs$Z?MQDrAynn0s;K7Iob()ID1 gq1O%|ag!P{j~hl>IPlL^TYnr61UeQ><)^OyHzwFD>;M1& diff --git a/src/boot/entry64.asm b/src/boot/entry64.asm index 2d10b51..f35259b 100644 --- a/src/boot/entry64.asm +++ b/src/boot/entry64.asm @@ -13,5 +13,5 @@ entry_x64: mov gs, ax call kmain ; Jump into C kernel entry - + hlt \ No newline at end of file diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index eaa1d47..0a7720b 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -1,31 +1,22 @@ #include -#include +#include #include #include +#include void kernel_welcome() { - print_str("Welcome to "); + printf("Welcome to "); print_set_color(PRINT_COLOR_CYAN, PRINT_COLOR_BLACK); - print_str("Shade"); + printf("Shade"); print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); - print_str("!\n"); - - print_str("shadeOS kernel version "); - + printf("!\nshadeOS kernel version "); print_set_color(PRINT_COLOR_YELLOW, PRINT_COLOR_BLACK); - print_str("0.2.2"); - + printf("0.2.2\n"); print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); - print_newline(); - - print_str("Running on "); - + printf("Running on "); print_set_color(PRINT_COLOR_YELLOW, PRINT_COLOR_BLACK); - print_str("shade-development"); - + printf("shade-development\n"); print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); - - print_newline(); } // kMain @@ -41,14 +32,16 @@ void kmain() { kernel_msg_ok("Initialized display successfully\n"); kernel_welcome(); - print_str("Copyright (c) e3team 2022. All rights reserved.\n"); - print_str("This program is provided \"as-is\" and no express or implied warranty is provided.\n"); - print_str("The full license can be found at /sys/LICENCE on this system or ./LICENCE in the source tree.\n"); + printf("Copyright (c) e3team 2022. All rights reserved.\n"); + printf("This program is provided \"as-is\" and no express or implied warranty is provided.\n"); + printf("The full license can be found at /sys/LICENCE on this system or ./LICENCE in the source tree.\n"); pic_remap(0x20, 0x28); idt_assemble(); - kernel_msg_ok("Enabled interrupts"); + kernel_msg_ok("Enabled interrupts\n"); __asm__ __volatile__("int $2"); + printf("ANSI code test: double \x1b[3;31m \x1b[31m \x1b[12m \x1b[0m\n"); + for (;;) {} } \ No newline at end of file diff --git a/src/kernel/platform/drivers/vga_text_mode.c b/src/kernel/platform/drivers/vga_text_mode.c new file mode 100644 index 0000000..8d77f12 --- /dev/null +++ b/src/kernel/platform/drivers/vga_text_mode.c @@ -0,0 +1,328 @@ +#include +#include +#include + +int row = 0; +int col = 0; +char color = PRINT_COLOR_WHITE | PRINT_COLOR_BLACK << 4; + +void clear_row(int row) { + struct Char* buffer = (struct Char*) 0xb8000; + + struct Char empty = (struct Char) { + character: ' ', + color: color, + }; + + for (int x = 0; x < NUM_COLS; x++) { + buffer[x + NUM_COLS * row] = empty; + } + set_cursor_pos(col, row); +} + +void clear_all() { + for (int i = 0; i < NUM_ROWS; i++) { + clear_row(i); + } + set_cursor_pos(col, row); +} + +void print_newline() { + struct Char* buffer = (struct Char*) 0xb8000; + + col = 0; + + if (row < NUM_ROWS - 1) { + row++; + return; + } + + for (int row = 1; row < NUM_ROWS; row++) { + for (int col = 0; col < NUM_COLS; col++) { + struct Char character = buffer[col + NUM_COLS * row]; + buffer[col + NUM_COLS * (row - 1)] = character; + } + } + + clear_row(NUM_COLS - 1); + set_cursor_pos(col, row); +} + +bool is_hexchar(char character) { + switch (character) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return true; + break; + default: + return false; + break; + } +} + +bool is_digit(char ch) { + return (ch >= '0') && (ch <= '9'); +} + +unsigned int _atoi(const char** str) { + unsigned int i = 0U; + while (is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + + +void print_char(char character) { + // ANSI parsing + // 1. is the current char \x1b? + if (character == '\x1b') { + // are we already printing an ansi string? + if (is_parsing_ansi_string) { + // string is invalid now + is_parsing_ansi_string = false; + return; + } else { + // no! set flag and continue + is_parsing_ansi_string = true; + return; + } + } + + // 2. are we working on an ansi string right now? + if (is_parsing_ansi_string) { + if (!ansi_string_found_bracket && character != '[') { + // string is invalid, stop + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + _print_char(character); + return; + } else { + // found bracket + ansi_string_found_bracket = true; + return; + } + if (ansi_string_found_bracket && !(ansi_string_found_semi || ansi_string_found_m)) { + // looking for flag OR color, idk yet + if (ansi_string_found_number) { + if (character == 'm') { + if (ansi_string_found_m) { + // invalid + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + _print_char(character); + return; + } + ansi_string_found_m = true; + // string is done + _color = working_num; + ansi_color_code_t code; + code.flag = _flag; + code.color = color; + handle_ansi_code(code); + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + return; + } else if (character == ';') { + if (ansi_string_found_semi) { + // invalid + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + _print_char(character); + return; + } + _flag = working_num; + working_num = 0; + ansi_string_found_number = false; + ansi_string_found_semi = true; + return; + } + } + if (!is_digit(character)) { + // not a digit, print out and discard existing string + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _print_char(character); + return; + } else { + // is digit, add to working + working_num *= 10; + working_num += _atoi(character); + ansi_string_found_number = true; + } + } + if (ansi_string_found_bracket && ansi_string_found_semi && !ansi_string_found_m) { + if (ansi_string_found_number) { + if (character == 'm') { + if (ansi_string_found_m) { + // invalid + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + _print_char(character); + return; + } + ansi_string_found_m = true; + // string is done + _color = working_num; + ansi_color_code_t code; + code.flag = _flag; + code.color = _color; + handle_ansi_code(code); + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + return; + } + if (!_is_digit(character)) { + // not a digit, print out and discard existing string + is_parsing_ansi_string = false; + ansi_string_found_bracket = false; + working_num = 0; + ansi_string_found_m = false; + ansi_string_found_semi = false; + ansi_string_found_number = false; + _flag = 0; + _color = 0; + _print_char(character); + return; + } else { + // is digit, add to working + working_num *= 10; + working_num += _atoi(character); + ansi_string_found_number = true; + } + } + } + } + + _print_char(character); + return; +} + +void _print_char(char character) { + struct Char* buffer = (struct Char*) 0xb8000; + + if (character == '\n') { + print_newline(); + return; + } + + if (col > NUM_COLS) { + print_newline(); + } + + struct Char cr = (struct Char) { + character: character, + color: color, + }; + buffer[col + NUM_COLS * row] = cr; + + col++; + set_cursor_pos(col, row); +} + +void print_str(char* str) { + for (int i = 0; 1; i++) { + char character = (char) str[i]; + + if (character == '\0') { + return; + } + + print_char(character); + } +} + +void print_set_color(char foreground, char background) { + color = foreground + (background << 4); +} + +void set_cursor_pos(int col, int row) { + int offset = col + NUM_COLS * row; + outb(REG_SCREEN_CTRL, 14); + outb(REG_SCREEN_DATA, (unsigned char)(offset >> 8)); + outb(REG_SCREEN_CTRL, 15); + outb(REG_SCREEN_DATA, (unsigned char)(offset & 0xff)); +} + +void kernel_msg_ok(char* msg) { + char ok_p1[] = "[ "; + char ok_p2[] = "OK "; + char ok_p3[] = "] "; + + print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); + print_str(ok_p1); + + print_set_color(PRINT_COLOR_GREEN, PRINT_COLOR_BLACK); + print_str(ok_p2); + + print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); + print_str(ok_p3); + print_str(msg); +} + + +void handle_ansi_code(ansi_color_code_t code) { + char s[50]; + itoa(code.flag, s); + print_str("ansi code: ["); + print_str(s); + print_str(";"); + itoa(code.color, s); + print_str(s); + print_str("m found\n"); +} \ No newline at end of file diff --git a/src/kernel/platform/interrupts/isr.c b/src/kernel/platform/interrupts/isr.c index c6517ce..8476771 100644 --- a/src/kernel/platform/interrupts/isr.c +++ b/src/kernel/platform/interrupts/isr.c @@ -1,21 +1,11 @@ -#include +#include #include void exception_handler(isr_xframe_t frame) { - char s[256]; - itoa(err_count, s); - print_str(" "); - print_str(s); - print_str(": cpu: check_exception "); - itoa(frame.base_frame.vector, s); - print_str(s); - print_str(" err_code => "); - itoa(frame.base_frame.error_code, s); - print_str(s); - print_str("\n"); + printf(" %i: cpu: check_exception 0x%x err_code => %i\n", err_count, frame.base_frame.vector, frame.base_frame.error_code); err_count++; if (err_count > ERR_MAX) { - print_str("cpu: ierr hit err_max, halt"); + printf("cpu: ierr hit err_max, halt\n"); __asm__ __volatile__("cli; hlt"); } } \ No newline at end of file diff --git a/src/kernel/print.c b/src/kernel/print.c deleted file mode 100644 index fb4213a..0000000 --- a/src/kernel/print.c +++ /dev/null @@ -1,110 +0,0 @@ -#include -#include - -int row = 0; -int col = 0; -char color = PRINT_COLOR_WHITE | PRINT_COLOR_BLACK << 4; - -void clear_row(int row) { - struct Char* buffer = (struct Char*) 0xb8000; - - struct Char empty = (struct Char) { - character: ' ', - color: color, - }; - - for (int x = 0; x < NUM_COLS; x++) { - buffer[x + NUM_COLS * row] = empty; - } - set_cursor_pos(col, row); -} - -void clear_all() { - for (int i = 0; i < NUM_ROWS; i++) { - clear_row(i); - } - set_cursor_pos(col, row); -} - -void print_newline() { - struct Char* buffer = (struct Char*) 0xb8000; - - col = 0; - - if (row < NUM_ROWS - 1) { - row++; - return; - } - - for (int row = 1; row < NUM_ROWS; row++) { - for (int col = 0; col < NUM_COLS; col++) { - struct Char character = buffer[col + NUM_COLS * row]; - buffer[col + NUM_COLS * (row - 1)] = character; - } - } - - clear_row(NUM_COLS - 1); - set_cursor_pos(col, row); -} - -void print_char(char character) { - struct Char* buffer = (struct Char*) 0xb8000; - - if (character == '\n') { - print_newline(); - return; - } - - if (col > NUM_COLS) { - print_newline(); - } - - struct Char cr = (struct Char) { - character: character, - color: color, - }; - buffer[col + NUM_COLS * row] = cr; - - col++; - set_cursor_pos(col, row); -} - -void print_str(char* str) { - for (int i = 0; 1; i++) { - char character = (char) str[i]; - - if (character == '\0') { - return; - } - - print_char(character); - } -} - -void print_set_color(char foreground, char background) { - color = foreground + (background << 4); -} - -void set_cursor_pos(int col, int row) { - int offset = col + NUM_COLS * row; - outb(REG_SCREEN_CTRL, 14); - outb(REG_SCREEN_DATA, (unsigned char)(offset >> 8)); - outb(REG_SCREEN_CTRL, 15); - outb(REG_SCREEN_DATA, (unsigned char)(offset & 0xff)); -} - -void kernel_msg_ok(char* msg) { - char ok_p1[] = "[ "; - char ok_p2[] = "OK "; - char ok_p3[] = "] "; - - print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); - print_str(ok_p1); - - print_set_color(PRINT_COLOR_GREEN, PRINT_COLOR_BLACK); - print_str(ok_p2); - - print_set_color(PRINT_COLOR_WHITE, PRINT_COLOR_BLACK); - print_str(ok_p3); - print_str(msg); -} \ No newline at end of file diff --git a/src/libc/printf.c b/src/libc/printf.c new file mode 100644 index 0000000..916c9d9 --- /dev/null +++ b/src/libc/printf.c @@ -0,0 +1,923 @@ + + +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include // c0repwn3r: implmenent _putchar with shade's VGA text driver +#include // c0repwn3r + +#include "printf.h" + +// c0repwn3r: implement _putchar with shade's VGA text driver, and ANSI support +void _putchar(char character) { + print_char(character); +} +// end: implement _putchar with shade's VGA text driver, and ANSI support + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)character; (void)buffer; (void)idx; (void)maxlen; +} + + +// internal _putchar wrapper +static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)buffer; (void)idx; (void)maxlen; + if (character) { + _putchar(character); + } +} + + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)idx; (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); + } +} + + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) +{ + const char* s; + for (s = str; *s && maxsize--; ++s); + return (unsigned int)(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) +{ + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) +{ + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } + else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } + else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } + else if (diff < 0.5) { + } + else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } + else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } + else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } + else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) +{ + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) + { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } + else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; + case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; + case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; + case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; + case '#': flags |= FLAGS_HASH; format++; n = 1U; break; + default : n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } + else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } + else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } + else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l' : + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h' : + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't' : + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z' : + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default : + break; + } + + // evaluate specifier + switch (*format) { + case 'd' : + case 'i' : + case 'u' : + case 'x' : + case 'X' : + case 'o' : + case 'b' : { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } + else if (*format == 'o') { + base = 8U; + } + else if (*format == 'b') { + base = 2U; + } + else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } + else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } + else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f' : + case 'F' : + if (*format == 'F') flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c' : { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's' : { + const char* p = va_arg(va, char*); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p' : { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); + } + else { +#endif + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%' : + out('%', buffer, idx++, maxlen); + format++; + break; + + default : + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char* format, ...) +{ + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int sprintf_(char* buffer, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int snprintf_(char* buffer, size_t count, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + + +int vprintf_(const char* format, va_list va) +{ + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + + +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) +{ + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + + +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) +{ + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = { out, arg }; + const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} \ No newline at end of file