forked from xolatile/xhartae
Finished printing...
This commit is contained in:
parent
17df8ad502
commit
663fc649ff
@ -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.
|
||||
@
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
void print (char * format, ...) {
|
||||
va_list argument_list; // This is mandatory local variable if we'll use variadic arguments in this function.
|
||||
|
||||
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.
|
||||
|
||||
void string_print (char * string, char * format, ...) {
|
||||
(void) string;
|
||||
(void) format;
|
||||
return;
|
||||
@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.
|
||||
|
||||
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
|
||||
|
@ -23,4 +23,6 @@ splint -weak -warnposix -retvalother -syntax -type chapters/chapter_3.h
|
||||
splint -weak -warnposix -retvalother -syntax -type chapters/chapter_3.c
|
||||
splint -weak -warnposix -retvalother -syntax -type xhartae.c
|
||||
|
||||
valgrind --show-leak-kinds=all --leak-check=full ./xhartae
|
||||
|
||||
exit
|
||||
|
Loading…
Reference in New Issue
Block a user