|
|
@@ -54,77 +54,82 @@ static void echo_one_by_one (int number) { |
|
|
|
|
|
|
|
You can find situations in which fall-through cases are good, for example, they can be very useful when encoding some CPU instructions into machine code, but guess what? The |
|
|
|
compiler will think you've made some kind of mistake, like that you forgot to break from those cases, and it'll warn you about it. I like to clean all compiler warnings (and some |
|
|
|
linter warnings, if they're not totally brain-dead), so I just don't use them. I know, sounds stupid, but there's usually some other way to do it, and to get the same solution. |
|
|
|
linter warnings, if they're not totally brain-dead), so I just don't use them. I know, sounds stupid, but there's usually some other way to do it, to get the same solution. |
|
|
|
|
|
|
|
Since we have several methods for printing text, they use standard output (terminal), file descriptor and a string respectively, we could implement them in separate functions, use |
|
|
|
function pointers or simply copy+paste bunch of code into lot of functions, and form a "function family". I'm using the simplest solution here, we have one general function, and |
|
|
|
explicitly declaring where I want my data to be printed. Simple as that. Lets call it 'printing_crossroad' for fun. |
|
|
|
function pointers or simply copy+paste bunch of code into lot of functions, and form a "function family". Lets do something very simple and straight forward. We'll end up with |
|
|
|
repeated code, but sometimes the simplicity can benefit us more than some smart solutions that are harder to understand. I'll explain as we progress... |
|
|
|
*/ |
|
|
|
|
|
|
|
static void printing_crossroad (char * text, int size, int file, char * string) { // We pass a lot of arguments here, it makes things simpler below. |
|
|
|
if (file > 0) { // We don't want to print to standard input, which is file descriptor of value 0. |
|
|
|
file_write (file, text, size); // And we simply "output" our text of certain size to wanted location. |
|
|
|
} else if (string != NULL) { |
|
|
|
string_copy_limit (string, text, size); |
|
|
|
} else { |
|
|
|
out (text, size); |
|
|
|
} |
|
|
|
} |
|
|
|
static void to_output (char * data, int size, int file, char * string) { (void) file; (void) string; out ( data, size); } |
|
|
|
static void to_file (char * data, int size, int file, char * string) { (void) string; file_write (file, data, size); } |
|
|
|
static void to_string (char * data, int size, int file, char * string) { (void) file; string_concatenate_limit (string, data, size); } |
|
|
|
|
|
|
|
/* |
|
|
|
Lets break down what's going on here, since it might be confusing for beginners. We've defined 3 internal functions, that'll only be used in this file and no other. They look |
|
|
|
similar, and they ignore some of their arguments by casting them to 'void', that's how you silence the compiler warnings about unused function agruments. But why are we passing |
|
|
|
those arguments if we won't use them? Because we can safely use one function pointer to any of those 3 functions. |
|
|
|
|
|
|
|
Internal variable 'printing' is a function pointer to one of those 3 functions, and the default value for it is the memory address of function 'to_output'. So, if we just use it, |
|
|
|
by default it'll print to standard output. If we call functions below, they'll change the value of 'printing' to coresponding function memory address. I chose to use concatenation |
|
|
|
for 'print_string', but if you wanted, you could use something else, or just reinitialize it to null string, and think about memory before using it. Unlike 'printf' function from |
|
|
|
<stdio.h> header file, my print functions don't allocate memory or make buffers. |
|
|
|
*/ |
|
|
|
|
|
|
|
static void (* printing) (char * data, int size, int file, char * string) = to_output; |
|
|
|
|
|
|
|
static void print_colour (char colour_id, int file, char * string) { |
|
|
|
switch (colour_id) { // We use "special" character '/' to use terminal colours. |
|
|
|
case '/': printing_crossroad ("/", 1, file, string); break; // If we have literally typed "//" in our 'format' string, it'll just output "/". |
|
|
|
case '0': printing_crossroad ("\033[1;30m", 7, file, string); break; // Notice that we couldn't use function 'terminal_colour', it only uses standard output. |
|
|
|
case '1': printing_crossroad ("\033[1;31m", 7, file, string); break; // Since we want to support file descriptors and strings, we use 'printing_crossroad'. |
|
|
|
case '2': printing_crossroad ("\033[1;32m", 7, file, string); break; |
|
|
|
case '3': printing_crossroad ("\033[1;33m", 7, file, string); break; |
|
|
|
case '4': printing_crossroad ("\033[1;34m", 7, file, string); break; |
|
|
|
case '5': printing_crossroad ("\033[1;35m", 7, file, string); break; |
|
|
|
case '6': printing_crossroad ("\033[1;36m", 7, file, string); break; |
|
|
|
case '7': printing_crossroad ("\033[1;37m", 7, file, string); break; |
|
|
|
case '-': printing_crossroad ("\033[0m", 4, file, string); break; |
|
|
|
default: printing_crossroad ("?", 1, file, string); break; // Now, if we provided some other character after "/", I like to make an intentional mismatch. |
|
|
|
} // It's not such a big mistake to abort the program, since it's obvious that results are bad. |
|
|
|
switch (colour_id) { // We use "special" character '/' to use terminal colours. |
|
|
|
case '/': (* printing) ("/", 1, file, string); break; // If we have literally typed "//" in our 'format' string, it'll just output "/". |
|
|
|
case '0': (* printing) ("\033[1;30m", 7, file, string); break; // Notice that we couldn't use function 'terminal_colour', it only uses standard output. |
|
|
|
case '1': (* printing) ("\033[1;31m", 7, file, string); break; // Since we want to support file descriptors and strings, we use 'printing'. |
|
|
|
case '2': (* printing) ("\033[1;32m", 7, file, string); break; // Also, we're dereferencing 'printing' pointer, which essentially calls one of the 3 functions. |
|
|
|
case '3': (* printing) ("\033[1;33m", 7, file, string); break; |
|
|
|
case '4': (* printing) ("\033[1;34m", 7, file, string); break; |
|
|
|
case '5': (* printing) ("\033[1;35m", 7, file, string); break; |
|
|
|
case '6': (* printing) ("\033[1;36m", 7, file, string); break; |
|
|
|
case '7': (* printing) ("\033[1;37m", 7, file, string); break; |
|
|
|
case '-': (* printing) ("\033[0m", 4, file, string); break; |
|
|
|
default: (* printing) ("?", 1, file, string); break; // Now, if we provided some other character after "/", I like to make an intentional mismatch. |
|
|
|
} // It's not such a big mistake to abort the program, since it's obvious that results are bad. |
|
|
|
} |
|
|
|
|
|
|
|
static void print_format (char format_id, va_list argument_list, int file, char * string) { |
|
|
|
switch (format_id) { // We use character '%' this time, same as 'printf' function does, lets see... |
|
|
|
static void print_format (char format_id, va_list list, int file, char * string) { |
|
|
|
switch (format_id) { // We use character '%' this time, same as 'printf' function does, lets see... |
|
|
|
case '%': { |
|
|
|
printing_crossroad ("%", 1, file, string); |
|
|
|
(* printing) ("%", 1, file, string); |
|
|
|
} break; |
|
|
|
case 'i': { |
|
|
|
int integer; // Leave these local variables uninitialized (don't assign a value to them). |
|
|
|
char * format; |
|
|
|
int length; |
|
|
|
integer = va_arg (argument_list, int); // Macro 'va_arg' will move argument from the list into our local variable, with the provided type. |
|
|
|
length = string_length (format = number_to_string (integer)); |
|
|
|
printing_crossroad (format, length, file, string); // You might get the feeling that this isn't type safe, and you'd be totally right. |
|
|
|
int integer; // Leave these local variables uninitialized (don't assign a value to them). |
|
|
|
char * format; // We'll use format here and below in order to shorten the length of lines. |
|
|
|
integer = va_arg (list, int); // Macro 'va_arg' will pop an argument from the list, with the provided type. |
|
|
|
format = number_to_string (integer); |
|
|
|
(* printing) (format, string_length (format), file, string); // You might get the feeling that this isn't type safe, and you're totally right. |
|
|
|
} break; |
|
|
|
case 'f': { |
|
|
|
double ieee754; // Because we used curly braces, we can declare local variables in these blocks of code. |
|
|
|
ieee754 = va_arg (argument_list, double); // I intentionally call this IEEE754 because I hate to use 'float' and 'double'. |
|
|
|
double ieee754; // Because we used curly braces, we can declare local variables in these blocks of code. |
|
|
|
char * format; |
|
|
|
// And we're printing to terminal our (rounded) number. |
|
|
|
format = number_to_string ((int) ieee754); |
|
|
|
printing_crossroad (format, string_length (format), file, string); |
|
|
|
ieee754 = va_arg (list, double); // I intentionally call this IEEE754 because I hate to use 'float' and 'double'. |
|
|
|
format = number_to_string ((int) ieee754); // And we're printing to terminal our (rounded) number. |
|
|
|
(* printing) (format, string_length (format), file, string); |
|
|
|
} break; |
|
|
|
case 's': { |
|
|
|
char * literal; // Really simple stuff, but needs some time getting used to it, we're writing in an old language. |
|
|
|
literal = va_arg (argument_list, char *); // In my opinion, this should be the part of the C language itself, not some macro black magic. |
|
|
|
// You can write a bunch of variadic functions yourself if you want, to easy your workflow in some cases. |
|
|
|
printing_crossroad (literal, string_length (literal), file, string); |
|
|
|
char * format; // Really simple stuff, but needs some time getting used to it. |
|
|
|
format = va_arg (list, char *); // In my opinion, this should be the part of the C language itself, but oh well... |
|
|
|
(* printing) (format, string_length (format), file, string); // This could be written even shorter, but it'd require more mental-overhead. |
|
|
|
} break; |
|
|
|
default: { |
|
|
|
printing_crossroad ("?", 1, file, string); // Again, I think it's best to print a bad thing, see it and fix it, then to abort the program in this case... |
|
|
|
(* printing) ("?", 1, file, string); // Lets not abort the program in this case... |
|
|
|
} break; |
|
|
|
} |
|
|
|
|
|
|
|
// This entire switch statement can be written more shortly (in terms of lines of code) like this: |
|
|
|
// switch (format_id) { |
|
|
|
// case '%': { out ("%", 1); } break; |
|
|
|
// case 'i': { int integer; integer = va_arg (argument_list, int); echo (number_to_string (integer)); } break; |
|
|
|
// case 'f': { double ieee754; ieee754 = va_arg (argument_list, double); echo (number_to_string ((int) ieee754)); } break; |
|
|
|
// case 's': { char * string; string = va_arg (argument_list, char *); echo (string); } break; |
|
|
|
// default: { out ("?", 1); } break; |
|
|
|
// case '%': { ... } break; |
|
|
|
// case 'i': { ... } break; |
|
|
|
// case 'f': { ... } break; |
|
|
|
// case 's': { ... } break; |
|
|
|
// default: { ... } break; |
|
|
|
// } |
|
|
|
// Choose your own preference with switch statement (and any other one), and stay consistent with how you format it. |
|
|
|
// Some people prefer more shorter lines of code, others prefer less longer lines of code (like I do). |
|
|
@@ -138,42 +143,51 @@ print ("My integer is %i.\n", 404); // Prints "My integer is 404." with new |
|
|
|
print ("Heyo %s %s", "world", "#6!\n"); // Prints "Heyo world #6!" with new line. |
|
|
|
print ("/1Cyaa world!/-\n"); // Prints red "Cyaa world!" with new line. |
|
|
|
@ |
|
|
|
*/ |
|
|
|
|
|
|
|
void print (char * format, ...) { |
|
|
|
va_list argument_list; // This is mandatory local variable if we'll use variadic arguments in this function. |
|
|
|
Now, since we're dealing with unsafe macros, no type-checking, and in general code that I don't like to write, we have that one general printing function, which splits into 3 |
|
|
|
functions, for standard output, file descriptor and string. Our general function is 'print_select', and it'll handle colouring and formating special characters for us, calling |
|
|
|
other functions we defined previously, 'print_colour' and 'print_format'. Since those 2 functions are used only once, we could just place (inline) them where they're called, but |
|
|
|
that way we'd end up with very indented code, which I'm not a fan of. I don't follow any indentation rules, I just don't indent a lot. |
|
|
|
*/ |
|
|
|
|
|
|
|
static void print_select (char * format, va_list list, int file, char * string) { |
|
|
|
int offset, length; |
|
|
|
|
|
|
|
length = string_length (format); |
|
|
|
|
|
|
|
va_start (argument_list, format); // Every variadic function needs to start with this (before using 'va_arg'). |
|
|
|
|
|
|
|
for (offset = 0; offset != length; ++offset) { // We start iterating through our 'format' string, and looking for special characters below. |
|
|
|
if (format [offset] == '/') { // Colouring special character is '/', and colours are specified from '0'...'7', and '-' to cancel them. |
|
|
|
for (offset = 0; offset != length; ++offset) { // We start iterating through our 'format' string, and looking for special characters below. |
|
|
|
if (format [offset] == '/') { // Colouring special character is '/', and colours are specified from '0'...'7', and '-' to cancel them. |
|
|
|
++offset; |
|
|
|
print_colour (format [offset], 0, NULL); // We're calling function that will colour our printed text, this one is simple. |
|
|
|
} else if (format [offset] == '%') { // And formatting special character is '%', it'll use variadic arguments! |
|
|
|
print_colour (format [offset], file, string); // We're calling function that will colour our printed text, this one is simple. |
|
|
|
} else if (format [offset] == '%') { // And formatting special character is '%', it'll use variadic arguments! |
|
|
|
++offset; |
|
|
|
print_format (format [offset], argument_list, 0, NULL); // We're calling function that will format our agruments, so we pass variable 'argument_list'. |
|
|
|
print_format (format [offset], list, file, string); // We're calling function that will format our agruments, so we pass variable 'list'. |
|
|
|
} else { |
|
|
|
out (& format [offset], 1); // Not a special character? Okay, we'll just print them one by one. |
|
|
|
(* printing) (& format [offset], 1, file, string); // Not a special character? Okay, we'll just print them one by one. |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
va_end (argument_list); // And every variadic function needs to end with this. Puns intended. |
|
|
|
} |
|
|
|
|
|
|
|
void file_print (int file, char * format, ...) { |
|
|
|
(void) file; |
|
|
|
(void) format; |
|
|
|
return; |
|
|
|
} |
|
|
|
/* |
|
|
|
Now, lets break down what those last 3 very similar functions do. Everything I say about 'print', is same for the other two, but with exception to their output. |
|
|
|
|
|
|
|
@C |
|
|
|
void print (char * format, ...) { |
|
|
|
va_list list; // Every variadic function needs to have this list, and sometimes you need to pass it to other functions. |
|
|
|
|
|
|
|
void string_print (char * string, char * format, ...) { |
|
|
|
(void) string; |
|
|
|
(void) format; |
|
|
|
return; |
|
|
|
printing = to_output; // We're selecting different output method, notice the difference between 3 functions below, it's minimal, and this is part of it. |
|
|
|
|
|
|
|
va_start (list, format); // Every variadic function needs to start with this macro (or function depending on the implementation in <stdarg.h>). |
|
|
|
|
|
|
|
print_select (format, list, 0, NULL); // And we just call our general super smart function to handle everything for us. |
|
|
|
|
|
|
|
va_end (list); // Every variadic function needs to end with this macro... Pun intended. |
|
|
|
} |
|
|
|
@ |
|
|
|
*/ |
|
|
|
|
|
|
|
void print ( char * format, ...) { va_list list; printing = to_output; va_start (list, format); print_select (format, list, 0, NULL); va_end (list); } |
|
|
|
void file_print (int file, char * format, ...) { va_list list; printing = to_file; va_start (list, format); print_select (format, list, file, NULL); va_end (list); } |
|
|
|
void string_print (char * string, char * format, ...) { va_list list; printing = to_string; va_start (list, format); print_select (format, list, 0, string); va_end (list); } |
|
|
|
|
|
|
|
#endif |