forked from xolatile/xhartae
Fixing folder names, I like more new style...
This commit is contained in:
parent
663fc649ff
commit
9c89d8ccb1
@ -1,457 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_0_SOURCE // These two, and "#endif" at the end of the file are header guards, we'll talk about them more when the time comes!
|
||||
#define CHAPTER_0_SOURCE
|
||||
|
||||
#include "chapter_0.h" // We're pasting macros, enumerations and function declarations from header file "chapter_0.h" into this file, at this location.
|
||||
|
||||
/*
|
||||
Function 'in' will perform read system call, literally reading 'size' bytes from standard input, which is terminal in most kernels unless it's redirected to some other file
|
||||
descriptor, and store it at 'data' memory address, if there's enough space in it. Since this is one of the core functions, if it fails, we want to abort the program and see what
|
||||
we did wrong... Maybe there wasn't enough space in 'data', maybe 'size' was negative and it overflowed because read system call internally uses 'size_t / unsigned long int', which
|
||||
is 64 bits wide, maybe we made some other mistake? Just abort, find the error and fix it.
|
||||
*/
|
||||
|
||||
void in (void * data, int size) {
|
||||
fatal_failure (data == NULL, "in: Failed to read from standard input, data is null pointer."); // This function is defined below, but we can call it here.
|
||||
fatal_failure (size == 0, "in: Failed to read from standard input, size is zero."); // That's because we declared it previously. Look at 'out' function.
|
||||
|
||||
(void) read (STDIN_FILENO, data, (unsigned long int) size); // I cast to void type return value of read and write, because I don't really care about it.
|
||||
}
|
||||
|
||||
/*
|
||||
Similar to 'in' function and read system call, write system call will store 'size' bytes from 'data' memory address into standard output, which is usually what you see in your
|
||||
terminal. Now, I won't talk much about teletypes, virtual terminals, file descriptor redirections and similar stuff, because I'm not very knowledgable about them. What matters is,
|
||||
you have a working operating system, terminal and compiler, and you can make things happen. Once you learn C better than me, and start writing your own multi-threaded kernel, core
|
||||
libraries, compilers and what not, you'll care about those things. I'll briefly talk about function structure for 'reallocate' function soon.
|
||||
*/
|
||||
|
||||
void out (void * data, int size) {
|
||||
fatal_failure (data == NULL, "out: Failed to write to standard output, data is null pointer."); // Notice how we can use function 'fatal_failure' before its' definition.
|
||||
fatal_failure (size == 0, "out: Failed to write to standard output, size is zero."); // That's because we declared it in 'chapter_0.h' header file.
|
||||
|
||||
(void) write (STDOUT_FILENO, data, (unsigned long int) size);
|
||||
}
|
||||
|
||||
/*
|
||||
Function 'echo' is just a simplification of function 'out', because we'll be using it a lot, notice that it must accept null terminated strings, which are sort of C-style thing. I
|
||||
really like them, because you don't need to always know size of the string in order to iterate it, but it requires some mental overhead in order to use them without creating hard
|
||||
to find bugs, which is why newer programming languages consider them unsafe. They're not unsafe, they need to be used like intended.
|
||||
*/
|
||||
|
||||
void echo (char * string) {
|
||||
out (string, string_length (string)); // This function fails when we pass a string that's not null terminated, and we don't care to check for errors...
|
||||
}
|
||||
|
||||
void fatal_failure (int condition, char * message) { // We use this function to abort the program if condition is met and to print the message.
|
||||
if (condition == TRUE) { // If the variable 'condition' is not equal to 0, we execute the code in curly braces.
|
||||
echo ("[\033[1;31mExiting\033[0m] "); // Simply printing the message using our 'echo' function, but we also use some colours, more on that later.
|
||||
echo (message); // Also, notice how "this or that" is implicity 'char *' type... Maybe it's too early to explain it at this point.
|
||||
echo ("\n"); // This will only print a new line, we'll see how to use it later.
|
||||
exit (EXIT_FAILURE); // This is the function (and '_exit' system call) that aborts the program with a return code.
|
||||
} // If condition isn't met, function will just return, and nothing is printed, execution continues.
|
||||
}
|
||||
|
||||
void limit (int * value, int minimum, int maximum) { // This is somewhat similar to limiting a variable to some values, inclusively, we'll use it later.
|
||||
if ( value == NULL) { return; } // We shouldn't dereference null pointer, but also don't want to abort the program for small mistake.
|
||||
if (* value <= minimum) { * value = minimum; } // You can also align similar consecutive statements like this, we'll see it more often in switch statement later on...
|
||||
if (* value >= maximum) { * value = maximum; } // If we pass a null pointer to this function, it won't do anything, just return.
|
||||
}
|
||||
|
||||
/*
|
||||
Memory management is a whole new topic that's too complex to cover it now in details, and it's the source of most security vunrabilities and hidden bugs. For now, just remember
|
||||
that every program can read, write or execute only parts of the memory that the kernel allows it. Program can request new memory or release old memory, so some other programs can
|
||||
use it. We'll learn to use program called Valgrind to find and fix memory related bugs in later chapters. We rely on functions 'calloc', 'realloc' and 'free' from <stdlib.h>
|
||||
header file, and we'll avoid 'malloc', and use 'realloc' carefully, because they leave new memory uninitialized.
|
||||
|
||||
We're internally using function 'calloc' to request new memory, function 'realloc' to enlarge existing memory and function 'free' to release old memory, data that won't be used
|
||||
later in the program. It's important to "free" all "malloc/calloc/realloc"-ed memory when program finishes successfully, and in some special cases, even when program fails and
|
||||
aborts. It's important for safety to do so, think of it like open and close braces, if you have some allocations, you should deallocate them later.
|
||||
|
||||
Some examples of using them directly (not wrapping them like I like to do) are:
|
||||
|
||||
@C
|
||||
char * data = NULL;
|
||||
|
||||
data = malloc (20 * sizeof (* data)); // Allocates 20 bytes of memory for 'data'.
|
||||
data = calloc (20, sizeof (* data)); // Allocates 20 bytes also, but initializes them to 0 value.
|
||||
data = realloc (data, 20 * sizeof (* data)); // When 'data' is null pointer, it will be same as 'malloc', else it will reallocate more memory (for correct usage).
|
||||
// Also, it's best to just use 'calloc', but it complicates some other tasks.
|
||||
free (data); // Deallocates memory, we'll talk about "double free" later.
|
||||
@
|
||||
*/
|
||||
|
||||
void * allocate (int size) {
|
||||
char * data = NULL;
|
||||
|
||||
data = calloc ((unsigned long int) size, sizeof (* data));
|
||||
|
||||
fatal_failure (data == NULL, "standard : allocate : Failed to allocate memory, internal function 'calloc' returned null pointer.");
|
||||
|
||||
return ((void *) data);
|
||||
}
|
||||
|
||||
/*
|
||||
Now, lets see that code formatting in action, with briefly describing function structure in C programming language. Our function is called "reallocate", its' inputs (arguments)
|
||||
are "data" with type 'void *' (pointer to any type of memory address) and "size" with type 'int' (integer), and its' output is also 'void *' (some memory address). All code
|
||||
between first '{' and last connected '}' is part of that function. We're using function 'realloc' inside, but we check for error (it return 'NULL' on error), then we print message
|
||||
and abort the program if there was an error, it there wasn't, we return new enlarged chunk of memory, changing the "data" variable.
|
||||
*/
|
||||
|
||||
void * reallocate (void * data, int size) {
|
||||
data = realloc (data, (unsigned long int) size);
|
||||
|
||||
fatal_failure (data == NULL, "standard : reallocate: Failed to reallocate memory, internal function 'realloc' returned null pointer.");
|
||||
|
||||
return (data);
|
||||
}
|
||||
|
||||
void * deallocate (void * data) {
|
||||
if (data != NULL) {
|
||||
free (data);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
This program is intended to be a book-like guide for this source code, which is also a book. We'll deal with strings a lot, and they're a good example of code formatting which is
|
||||
the main topic of chapter zero. In function 'string_length' we have for loop without a body, some people prefer to put '{}' or ';' in same or next line, to express the intention
|
||||
that the loop shouldn't have a body (code block {}). I just put ';' on the same line. Also, functions 'string_*' could depend on functions 'string_*_limit', but we won't do that
|
||||
now, and since we've already declared them in header file "chapter_0.h" we can define them and call them in whatever order we want. Nice.
|
||||
|
||||
@C
|
||||
// Simple example of how we could make 'string_*' function dependable on 'string_*_limit' function...
|
||||
|
||||
int string_compare (char * string_0, char * string_1) {
|
||||
return (string_compare_limit (string_0, string_1, string_length (string_0));
|
||||
}
|
||||
@
|
||||
*/
|
||||
|
||||
int string_length (char * string) {
|
||||
int length;
|
||||
|
||||
fatal_failure (string == NULL, "string_length: String is null pointer.");
|
||||
|
||||
for (length = 0; string [length] != CHARACTER_NULL; ++length); // Since in C, strings are null terminated, looping until we see null character is strings' length.
|
||||
|
||||
return (length);
|
||||
}
|
||||
|
||||
/*
|
||||
Now, I've implemented "unlimited" versions of string comparison, copying and concatenation different from "limited" versions. They correspond with standard library functions
|
||||
'strcmp', 'strcpy', 'strcat', 'strncmp', 'strncpy' and 'strncat' found in header file <string.h>. In "unlimited" versions, I rely on the fact that we want to apply the operation
|
||||
on entire strings, that those strings are null terminated and I used that in my advantage. For example, function 'string_compare' could be something like this:
|
||||
|
||||
@C
|
||||
int string_compare (char * string_0, char * string_1) {
|
||||
int offset;
|
||||
|
||||
fatal_failure (string_0 == NULL, "string_compare: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_compare: Source string is null pointer.");
|
||||
|
||||
for (offset = 0; (string_0 [offset] != CHARACTER_NULL) && (string_1 [offset] != CHARACTER_NULL); ++offset) {
|
||||
if (string_0 [offset] != string_1 [offset]) {
|
||||
return (FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
return (TRUE);
|
||||
}
|
||||
@
|
||||
|
||||
And I used this approach below to show that you can solve the problem using different solutions... You'll notice that "limited" versions have variable 'offset' of type integer. We
|
||||
use it to interate the strings, while in "unlimited" versions, we iterate on pointers to those strings, which are pushed to the stack. Both versions work, both versions give the
|
||||
same results, you can use any of them.
|
||||
*/
|
||||
|
||||
int string_compare (char * string_0, char * string_1) {
|
||||
fatal_failure (string_0 == NULL, "string_compare: Destination string is null pointer."); // This will be seen in next 5 functions too, we don't want NULL here.
|
||||
fatal_failure (string_1 == NULL, "string_compare: Source string is null pointer.");
|
||||
|
||||
for (; (* string_0 != CHARACTER_NULL) && (* string_1 != CHARACTER_NULL); ++string_0, ++string_1) { // We iterate until either string reaches the null character.
|
||||
if (* string_0 != * string_1) { // In case that characters at the same offset are different:
|
||||
return (FALSE); // > We return FALSE, 0, since strings aren't the same...
|
||||
}
|
||||
}
|
||||
if (* string_0 != * string_1) { // Now, we'll do one last termination check.
|
||||
return (FALSE);
|
||||
}
|
||||
|
||||
return (TRUE); // Otherwise, strings are same, we return TRUE, 1.
|
||||
}
|
||||
|
||||
char * string_copy (char * string_0, char * string_1) {
|
||||
char * result = string_0; // We need to save pointer to destination string before changing it.
|
||||
|
||||
fatal_failure (string_0 == NULL, "string_copy: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_copy: Source string is null pointer.");
|
||||
|
||||
for (; * string_1 != CHARACTER_NULL; ++string_0, ++string_1) { // This time and in next function, we iterate only source string.
|
||||
* string_0 = * string_1; // And we assign character at the same offset to destination string (aka copy it).
|
||||
}
|
||||
|
||||
* string_0 = CHARACTER_NULL; // Copying null termination, since the loop stopped on that condition.
|
||||
|
||||
return (result); // Lastly, we return the destination string, in order to be able to bind functions.
|
||||
}
|
||||
|
||||
char * string_concatenate (char * string_0, char * string_1) {
|
||||
char * result = string_0;
|
||||
|
||||
fatal_failure (string_0 == NULL, "string_concatenate: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_concatenate: Source string is null pointer.");
|
||||
|
||||
string_0 += string_length (string_0); // We'll first offset destination string to the end of it.
|
||||
// Because we want to start copying from the end, aka concatenate it.
|
||||
for (; * string_1 != CHARACTER_NULL; ++string_0, ++string_1) { // The rest of the function is same as string_copy, so:
|
||||
* string_0 = * string_1; // We could even use it here, but that defies the purpose of learning now.
|
||||
}
|
||||
|
||||
* string_0 = CHARACTER_NULL; // Again, assign null termination.
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
char * string_reverse (char * string) { // Example of implementing "unlimited" version by calling "limited" version.
|
||||
return (string_reverse_limit (string, string_length (string)));
|
||||
}
|
||||
|
||||
/*
|
||||
As for "limited" versions of previous 3 functions, they do the same thing, but are capped to some variable 'limit'. These functions have their own use-case, for example, if
|
||||
strings aren't null terminated, if you're not sure that they are null terminated, if we're dealing with binary (not textual) data (casted to char *), and many more cases. I won't
|
||||
write comments for 'string_copy_limit', 'string_concatenate_limit' and 'string_reverse_limit', try to read them and understand what kind of operation they'll perform with your
|
||||
current knowledge of C language.
|
||||
*/
|
||||
|
||||
int string_compare_limit (char * string_0, char * string_1, int limit) {
|
||||
int offset;
|
||||
|
||||
fatal_failure (string_0 == NULL, "string_compare_limit: Destination string is null pointer."); // This is the new trend, check for unimportant things.
|
||||
fatal_failure (string_1 == NULL, "string_compare_limit: Source string is null pointer."); // At least this isn't too verbose. I hope...
|
||||
|
||||
for (offset = 0; offset < limit; ++offset) { // Now, we'll iterate until 'limit' is reached, but it can overrun.
|
||||
if (string_0 [offset] != string_1 [offset]) { // All said here applies to next two functions as well...
|
||||
return (FALSE); // As soon as 2 characters mismatch, they're not same, we return FALSE.
|
||||
}
|
||||
}
|
||||
|
||||
return (TRUE); // Otherwise, we're reached the end, they're same, we return TRUE.
|
||||
}
|
||||
|
||||
char * string_copy_limit (char * string_0, char * string_1, int limit) {
|
||||
int offset;
|
||||
|
||||
fatal_failure (string_0 == NULL, "string_copy_limit: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_copy_limit: Source string is null pointer.");
|
||||
|
||||
if ((limit <= 0) || (string_1 == NULL)) {
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
for (offset = 0; offset < limit; ++offset) {
|
||||
string_0 [offset] = string_1 [offset];
|
||||
}
|
||||
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
char * string_concatenate_limit (char * string_0, char * string_1, int limit) {
|
||||
int offset, length_0, length_1;
|
||||
|
||||
fatal_failure (string_0 == NULL, "string_concatenate_limit: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_concatenate_limit: Source string is null pointer.");
|
||||
|
||||
if ((limit <= 0) || (string_1 == NULL)) {
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
length_0 = string_length (string_0);
|
||||
length_1 = string_length (string_1);
|
||||
|
||||
for (offset = 0; (offset < length_1) && (offset < limit); ++offset) {
|
||||
string_0 [length_0 + offset] = string_1 [offset];
|
||||
}
|
||||
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
char * string_reverse_limit (char * string, int limit) {
|
||||
int i;
|
||||
|
||||
fatal_failure (string == NULL, "string_reverse: String is null pointer.");
|
||||
|
||||
for (i = 0; i < limit / 2; ++i) {
|
||||
char temporary = string [i];
|
||||
string [i] = string [limit - 1 - i];
|
||||
string [limit - 1 - i] = temporary;
|
||||
}
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
/*
|
||||
We'll use this function in many other chapters (in other C source files!), but we'll be careful, since compiler can't catch all the mistakes we make. Lets listen a story!
|
||||
|
||||
Compiler likes you because you are his master. You tell him what to do (with passing flags as 'argv'), you give him the tool (your text files) and he'll do it. Sometimes he can
|
||||
complain, like "you gave me a shovel to chop some firewood" or "you gave me an axe to clean the leaves from the road", and he can't do that task. But if you give him a proper tool
|
||||
for proper task, he'll do it and won't complain at all. However, sometimes, you give him an imperfect tool (your C program full of subtle bugs) and he won't notice it. He'll do
|
||||
the job (translate bunch of ASCII characters into bunch of bytes), and say he's finished. Then you go out and see the results (run your executable), and it's all mess! You're like
|
||||
"What the hell, do as I say!", but he doesn't know what he did wrong... And in fact, it was your fault for giving him a imperfect tool all along. That's why we sometimes need more
|
||||
difficult to use servants. When you, as his master, tell him to do some job for you, he'll complain endlessly. You should use all your servants appropriately, each of them for
|
||||
different kind of task, and you'll learn to choose them wisely.
|
||||
|
||||
Now, funny story aside, some languages are considered safe because they won't compile your code if they find any kind of syntax mistake. Ada is very strict programming language,
|
||||
we'll talk about it in later chapters, as Ada compilers complain a lot about the source code, but they can't validate incorrect algorythm. Pust is also very strict language, but
|
||||
it sucks, and it's unreadable pile of characters. Remember, no matter how strict language you use, it'll never validate correctness of the algorythm, only syntax mistakes and
|
||||
few other checks like life-times, bounds, etc. I'll mention this a lot, think twice, write once.
|
||||
|
||||
Obedient servants:
|
||||
$ gcc -Wall
|
||||
$ clang -Wall
|
||||
|
||||
Complicated servants:
|
||||
$ gcc -ansi -Werror -Wall -Wextra -Wpedantic
|
||||
$ clang -ansi -Werror -Weverything
|
||||
|
||||
Terrible servants (same as above, but with also using):
|
||||
$ splint [custom flags]
|
||||
$ valgrind --show-leak-kinds=all --leak-check=full
|
||||
*/
|
||||
|
||||
char * string_realign (char * string, int amount, char character) { // Now, this is our "align string to right" function, lets explain it.
|
||||
int offset, length; // We're declaring two local (automatic) variables of type 'int'.
|
||||
|
||||
length = string_length (string); // We'll use variable 'length' later in the code, so we initialize it to length of the string.
|
||||
|
||||
for (offset = 0; offset != length; ++offset) { // We're essentially moving the string to the right, iterating through its' length to amount.
|
||||
string [amount - offset - 1] = string [length - offset - 1]; // Needless to say, string needs to have enough memory ((pre) allocated) for it to store it.
|
||||
}
|
||||
|
||||
for (offset = 0; offset != amount - length; ++offset) { // Now, we have some "garbage" data left from the actual string, so we iterate through left side and:
|
||||
string [offset] = character; // Assign to it argument 'character' that we provided in a function call. We can align with anything.
|
||||
}
|
||||
|
||||
string [amount] = CHARACTER_NULL; // I like to null terminate them explicitly, so I don't have to worry about tiny bugs later.
|
||||
|
||||
return (string); // Lastly, we return a pointer to our modified string, in order to, again, bind function calls.
|
||||
}
|
||||
|
||||
/*
|
||||
Again, please consider these 'terminal_*' functions black magic, as well as 'number_to_string' and 'format_to_string' as they are more complex to cover them at this point, we'll
|
||||
talk more about them later... For now, just take a look at how I format the code in them.
|
||||
*/
|
||||
|
||||
void terminal_clear (void) {
|
||||
echo ("\033[2J\033[H");
|
||||
}
|
||||
|
||||
void terminal_colour (int colour, int effect) {
|
||||
char format [8] = "\033[ ;3 m";
|
||||
|
||||
format [2] = (char) (effect % EFFECT_COUNT) + '0';
|
||||
format [5] = (char) (colour % COLOUR_COUNT) + '0';
|
||||
|
||||
echo (format);
|
||||
}
|
||||
|
||||
void terminal_cancel (void) {
|
||||
echo ("\033[0m");
|
||||
}
|
||||
|
||||
void terminal_show_cursor (int show) {
|
||||
if (show != 0) {
|
||||
echo ("\033[?25h");
|
||||
} else {
|
||||
echo ("\033[?25l");
|
||||
}
|
||||
}
|
||||
|
||||
char * number_to_string (int number) {
|
||||
int i, sign;
|
||||
|
||||
static char string [32];
|
||||
|
||||
for (i = 0; i != 32; ++i) {
|
||||
string [i] = CHARACTER_NULL;
|
||||
}
|
||||
|
||||
if (number == 0) {
|
||||
string [0] = '0';
|
||||
string [1] = CHARACTER_NULL;
|
||||
return (string);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
number *= -1;
|
||||
sign = 1;
|
||||
} else {
|
||||
sign = 0;
|
||||
}
|
||||
|
||||
for (i = (string [0] == '-'); number != 0; ++i) {
|
||||
string [i] = (char) (number % 10) + '0';
|
||||
number /= 10;
|
||||
}
|
||||
|
||||
if (sign != 0) {
|
||||
string [i] = '-';
|
||||
++i;
|
||||
}
|
||||
|
||||
string [i] = CHARACTER_NULL;
|
||||
|
||||
string_reverse (string);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
char * format_to_string (int number, int sign, int base, int amount, char character) {
|
||||
int i;
|
||||
|
||||
static char string [32];
|
||||
|
||||
for (i = 0; i != 32; ++i) {
|
||||
string [i] = CHARACTER_NULL;
|
||||
}
|
||||
|
||||
if (number == 0) {
|
||||
string [0] = '0';
|
||||
string [1] = CHARACTER_NULL;
|
||||
|
||||
string_realign (string, amount, character);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
number *= -1;
|
||||
}
|
||||
|
||||
for (i = (string [0] == '-'); number != 0; ++i) {
|
||||
string [i] = "0123456789ABCDEF" [number % base];
|
||||
number /= base;
|
||||
}
|
||||
|
||||
if (sign != 0) {
|
||||
string [i] = '-';
|
||||
++i;
|
||||
}
|
||||
|
||||
string [i] = CHARACTER_NULL;
|
||||
|
||||
string_reverse (string);
|
||||
|
||||
string_realign (string, amount, character);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,232 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_0_HEADER
|
||||
#define CHAPTER_0_HEADER
|
||||
|
||||
/*
|
||||
> Code formatting
|
||||
|
||||
It's very important that your code is aligned, readable and safe. So, we'll talk about it even before the first chapter, the so-called "zero chapter". I write assembly and Ada
|
||||
beside C, but still, 90% of my projects are written in C programming language! Why? Because C is like a girl that's a bit older than you, quiet but very smart and respectable, and
|
||||
you don't want to piss her off. Ada is more like nerdy girl who doesn't know what she wants, while assembly is ghost girl in your closet that you pretend not to see at night. So,
|
||||
this Ada girl was my first serious love, and she had a book "Ada Coding Standard", which is like a bible to her. It's a bit fanatical, but it has some good points which I'll
|
||||
discuss below without copy/paste, and apply to C.
|
||||
|
||||
Lets see some good examples of C code (in my opinion obviously), I'll provide few of them, and note that I use the first style in them. Needless to say, as this book is aimed to
|
||||
people who don't know C programming language, or are in phase of learning it, you should (and eventually will) develop your own style of writing it, but you shouldn't force it at
|
||||
the start, it should happen over time, naturally. Just be sure to always be consistent, and to avoid anything that'll generate compiler warnings.
|
||||
|
||||
General code formatting should follow these rules in C programming language:
|
||||
|
||||
- Always use tabs for indentation, and spaces for alignment. You'll hear people say the otherwise, don't trust them, it's pure cancer.
|
||||
- Separate most (not all!) operators with space before and after it. We'll provide exceptional cases for this rule below.
|
||||
- Align most (not all!) operators horizontally, and logical units vertically. Again, examples are the best showcase.
|
||||
- Always use curly braces and round braces, even when they're not required by the compiler. Most linters warn about this.
|
||||
- Avoid writing a lot of comments, or avoid them completely, except for license notice, this book is an exception! Also, don't write GPL notice like I do...
|
||||
- Think very carefully if you'll limit the line length, it depends on your workflow and attention span... For me, 180 characters per line.
|
||||
- If you want, use pagination, but I don't recommend it when working with C. I use it in Ada, because comments there start with "--".
|
||||
- Always use snake case (this_style), for enumerations and macros (THIS_STYLE), and never put underscore before or after the identifier.
|
||||
|
||||
For now, don't worry about what's an identifier, compiler, linter, those scary and unknown words will be explained later, when the time comes.
|
||||
|
||||
Spacing:
|
||||
|
||||
- Before and after:
|
||||
|
||||
A: B: C: D: E: F:
|
||||
+ += & &= && ==
|
||||
- -= | |= || !=
|
||||
* *= ^ ^= ! <=
|
||||
/ /= << <<= ? >=
|
||||
% %= >> >>= : <
|
||||
= ~ >
|
||||
|
||||
A) Standard operators, addition, subtraction, multiplication, division, modulus and assignment (except assignment obviously).
|
||||
B) Extension of standard operators, that'll use left-hand variable as first operand.
|
||||
C) Binary operators, bit-and, bit-or, bit-xor, bit-shift-left, bit-shift-right and bit-not.
|
||||
D) Again, same extension but for binary operators, that'll use left-hand variable as first operand (except bit-not as it's unary).
|
||||
E) Logical operators, and, or, not and operators '?' and ':' are used together, as they form ternary operator.
|
||||
F) Comparison operators, equals, not-equals, less-or-equal, greater-or-equal, less and greater.
|
||||
|
||||
Some more symbols are also operators, but my advice is not to treat them as such, just consider them some kind of special cases.
|
||||
|
||||
- Before:
|
||||
|
||||
( { [
|
||||
|
||||
- After:
|
||||
|
||||
) } ] , ;
|
||||
|
||||
- None (unary + and -):
|
||||
|
||||
. -> " ' ++ -- + -
|
||||
|
||||
Exceptions:
|
||||
|
||||
- When using more complicated expressions, apply rules above, or make an exception in them if you think you'll make it more readable by breaking the rules.
|
||||
- When merging consecutive opened or closed braces, for example, like when you're nesting function calls.
|
||||
- When declaring functions, align return types and open braces, and by taste, argument types for logical units.
|
||||
- When declaring variables, align types and asignment operators, and by taste, pointer or array operators.
|
||||
|
||||
Naming:
|
||||
|
||||
- Have you ever wondered, what the hell is stdio, unistd, stdint, termios, errno, strcat, strchr, strndupa, memcpy, atoi, fgetc, puts, lseek...? I did wonder.
|
||||
- Are identifiers such as string_concatenate, memory_copy, error_id, integer, boolean, constant, string_to_integer, file_offset too long to type...? I think not.
|
||||
|
||||
All that aside, C is an old programming language, and people who have made it knew a lot more about how computers work at their time than most people know nowdays. Hardware got
|
||||
faster and cheaper, but also more complex. We have enough memory today to edit our text files with word-wrapping enabled, highlight the syntax, and to store large files due to
|
||||
very long identifiers, 8-space-wide indentation instead of a single tab and overly complicated OOP code. If you can handle those names above, feel free to use them, but be
|
||||
consistent. I can't handle them, so I tolerate them with C keywords, because I'd actually prefer "constant / integer / character" over "const / int / char"... You'll notice that
|
||||
C has mixed them in keywords, and most standard library headers mix them too...
|
||||
|
||||
- Keywords: register double short signed break while / int char extern auto enum struct
|
||||
- Headers: string memory limits time complex threads / stdio ctype stdint unistd fcntl termios
|
||||
- Functions: read write open close exit clock / strncmp memcpy atoi fputc tcsetattr usleep
|
||||
|
||||
About that, I don't even know where to begin about talking how bad it is for readability... But lets ignore that for now and focus on code formatting. Text you see above this
|
||||
paragraph is enough to get you started, and text below is just expanding a little more on common sense when you're formatting your code. For example, how to align consequtive
|
||||
external or internal function or variable declarations, how to align if statement condition(s) or switch statement body, and more.
|
||||
|
||||
There's not much to say here, we'll talk about function declarations more in later chapters, as they are optional, but I like to use them. However, you should avoid to use too
|
||||
many function agruments, and always place them in consistent and logical order.
|
||||
|
||||
@C
|
||||
// Internal and external function declaration:
|
||||
|
||||
static int string_copy_limit (char * string_0, char * string_1, int limit);
|
||||
|
||||
extern int32_t string_copy (char * string_a,
|
||||
char * string_b,
|
||||
int32_t count);
|
||||
|
||||
extern size_t copy_string_n (
|
||||
string_t a,
|
||||
string_t b,
|
||||
size_t n
|
||||
);
|
||||
@
|
||||
|
||||
Internal ('static') and external ('extern') variable declarations are different from function declarations, because you need to initialize internal variables to some value, I like
|
||||
to provide either "zero" value for them, with the corresponding type (0, 0.0, '\0', NULL...), or an invalid value (-1, EOF...). Some C compilers and linters will warn you about
|
||||
redeclaring them, some will warn you that they're already initialized to zero, so when you some across those warnings, you'll know how to fix them. I hate this part of C...
|
||||
|
||||
@C
|
||||
// Internal and external variable declaration:
|
||||
|
||||
static int curses_active = 0;
|
||||
static char curses_signal = '\0';
|
||||
static char * curses_screen = NULL;
|
||||
|
||||
extern unsigned int positive;
|
||||
extern int negative;
|
||||
extern int * * coordinate_system;
|
||||
|
||||
static void (* encode_type_0 [TYPE_0_COUNT]) (void) = { NULL };
|
||||
static char * (* encode_type_1 [TYPE_1_COUNT]) (int l) = { NULL };
|
||||
static int (* encode_type_2 [TYPE_2_COUNT]) (int l, int r) = { NULL };
|
||||
|
||||
extern xcb_window_t blesses_window;
|
||||
extern xcb_gcontext_t blesses_context;
|
||||
extern xcb_pixmap_t blesses_pixmap;
|
||||
extern xcb_connection_t * blesses_connection;
|
||||
extern xcb_screen_t * blesses_screen;
|
||||
extern xcb_image_t * blesses_image;
|
||||
@
|
||||
|
||||
Only for 'main' function, you shouldn't add 'static' or 'extern' keywords before it. Every C file needs to have exactly one 'main' function, it's the programs' entry point. Also,
|
||||
some people like to define their own types, with keyword 'typedef', I'm not a fan of it, since in C types are also keywords unlike in Ada. Again, it'll become more clean in future
|
||||
chapters why I dislike user-defined types, unions and structures.
|
||||
|
||||
@C
|
||||
// Main function:
|
||||
|
||||
int main (int argc, char * * argv) {
|
||||
|
||||
int32_t main (int32_t argc,
|
||||
char * argv []) {
|
||||
|
||||
number_t main (
|
||||
number_t argc,
|
||||
string_t argv []) {
|
||||
@
|
||||
|
||||
Now, I'll write some basic functions that'll be used later in the program, so you can see that code formatting I've spent whole hour writing. Don't mind what they do, just keep an
|
||||
eye on how are they aligned and named. I'll reimplement some standard functions, and you can revisit them after reading few more chapters, it'll be more understandable then.
|
||||
*/
|
||||
|
||||
#include <stdlib.h> // We'll need this header file for malloc, calloc, realloc, free and exit.
|
||||
#include <fcntl.h> // This one for open and O_ flags.
|
||||
#include <unistd.h> // And this one for read, write, close and lseek.
|
||||
|
||||
enum { // This is completely unnecesary, but I don't care, it's a good showcase how boolean type can work, true is 1 and false is 0.
|
||||
FALSE,
|
||||
TRUE
|
||||
};
|
||||
|
||||
enum { // This is also one of my preferences, to use CHARACTER_NULL or CHARACTER_LINE_FEED instead of '\0' or '\n' in special (non-string) cases.
|
||||
CHARACTER_NULL, CHARACTER_START_HEADER, CHARACTER_START_TEXT, CHARACTER_END_TEXT,
|
||||
CHARACTER_END_TRANSMISSION, CHARACTER_ENQUIRY, CHARACTER_ACKNOWLEDGE, CHARACTER_BELL,
|
||||
CHARACTER_BACKSPACE, CHARACTER_TAB_HORIZONTAL, CHARACTER_LINE_FEED, CHARACTER_TAB_VERTICAL,
|
||||
CHARACTER_FORM_FEED, CHARACTER_CARRIAGE_RETURN, CHARACTER_SHIFT_OUT, CHARACTER_SHIFT_IN,
|
||||
CHARACTER_DATA_LINK_ESCAPE, CHARACTER_DEVICE_CONTROL_1, CHARACTER_DEVICE_CONTROL_2, CHARACTER_DEVICE_CONTROL_3,
|
||||
CHARACTER_DEVICE_CONTROL_4, CHARACTER_NOT_ACKNOWLEDGE, CHARACTER_SYNCHRONOUS_IDLE, CHARACTER_END_TRANSMISSION_BLOCK,
|
||||
CHARACTER_CANCEL, CHARACTER_END_MEDIUM, CHARACTER_SUBSTITUTE, CHARACTER_ESCAPE,
|
||||
CHARACTER_FILE_SEPARATOR, CHARACTER_GROUP_SEPARATOR, CHARACTER_RECORD_SEPARATOR, CHARACTER_UNIT_SEPARATOR,
|
||||
CHARACTER_COUNT // Not an actual ASCII table count (128), but for starting invisible characters.
|
||||
};
|
||||
|
||||
enum { // I like to align enumerations with 10, 20 or 40 characters per name, and optionally use NAME_ as prefix and NAME_COUNT as last element.
|
||||
EFFECT_NORMAL, EFFECT_BOLD, EFFECT_ITALIC, EFFECT_UNDERLINE, EFFECT_BLINK, EFFECT_REVERSE,
|
||||
EFFECT_COUNT
|
||||
};
|
||||
|
||||
enum { // Because of text auto-completition, it's always easy to find what I want, or to use NAME_COUNT in arrays. In some cases, order matters!
|
||||
COLOUR_GREY, COLOUR_RED, COLOUR_GREEN, COLOUR_YELLOW, COLOUR_BLUE, COLOUR_PINK, COLOUR_CYAN, COLOUR_WHITE,
|
||||
COLOUR_COUNT
|
||||
};
|
||||
|
||||
extern void in (void * data, int size); // We'll use these functions later as core standard input/output functions.
|
||||
extern void out (void * data, int size);
|
||||
|
||||
extern void echo (char * data); // Function 'echo' behaves similar to 'out', but it's easier to use because it doesn't require size.
|
||||
|
||||
extern void fatal_failure (int condition, char * message); // Some functions fail, and sometimes we want to abort everything, crash and leak memory, we just don't care.
|
||||
|
||||
extern void limit (int * value, int minimum, int maximum);
|
||||
|
||||
extern void * allocate ( int size); // Core memory management functions with some minimal error checking.
|
||||
extern void * reallocate (void * data, int size);
|
||||
extern void * deallocate (void * data );
|
||||
|
||||
extern int string_length (char * string); // We deal with strings a lot in this program, so string functions will be more important than character functions from chapter one.
|
||||
|
||||
extern int string_compare (char * string_0, char * string_1); // See how nicely they align, right?
|
||||
extern char * string_copy (char * string_0, char * string_1);
|
||||
extern char * string_concatenate (char * string_0, char * string_1);
|
||||
extern char * string_reverse (char * string); // Notice last function, we didn't align ');'...
|
||||
|
||||
extern int string_compare_limit (char * string_0, char * string_1, int limit); // These ones too, it's beautiful (in my opinion), tho some consider it useless.
|
||||
extern char * string_copy_limit (char * string_0, char * string_1, int limit);
|
||||
extern char * string_concatenate_limit (char * string_0, char * string_1, int limit);
|
||||
extern char * string_reverse_limit (char * string, int limit); // And we align the last argument in this case, use whatever you prefer.
|
||||
|
||||
extern char * string_realign (char * string, int amount, char character); // This is a simple function that realigns a string to right, we'll use it way later ahead.
|
||||
|
||||
// In chapter two, we'll explain ASCII escape sequences, for now, consider this to be some black magic.
|
||||
extern void terminal_clear (void); // Offset and clear terminal screen output.
|
||||
extern void terminal_colour (int colour, int effect); // Set terminal character attributes.
|
||||
extern void terminal_cancel (void); // Reset terminal character attributes.
|
||||
extern void terminal_show_cursor (int show); // Toggle rendering of terminal cursor.
|
||||
|
||||
// These will be useful in chapter three, where we'll learn about 'printf' function. It's too complex to cover it at this point.
|
||||
extern char * number_to_string (int number);
|
||||
extern char * format_to_string (int number, int sign, int base, int amount, char character);
|
||||
|
||||
#endif
|
@ -1,229 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_1_SOURCE
|
||||
#define CHAPTER_1_SOURCE
|
||||
|
||||
#include "chapter_1.h" // Get used to this... We're pasting macros, enumerations and function declarations again...
|
||||
|
||||
int character_is_uppercase (char character) { // Returns a boolean value, aka FALSE or TRUE, aka 0 or 1...
|
||||
return ((int) ((character >= 'A') && (character <= 'Z')));
|
||||
}
|
||||
|
||||
int character_is_lowercase (char character) {
|
||||
return ((int) ((character >= 'a') && (character <= 'z')));
|
||||
}
|
||||
|
||||
int character_is_digit (char character) {
|
||||
return ((int) ((character >= '0') && (character <= '9')));
|
||||
}
|
||||
|
||||
int character_is_blank (char character) { // Standard implementation also considers vertical tab and form feed as blank, we don't...
|
||||
return ((int) ((character == ' ') || (character == CHARACTER_TAB_HORIZONTAL) || (character == CHARACTER_CARRIAGE_RETURN) || (character == CHARACTER_LINE_FEED)));
|
||||
// If you like smaller line length limit, you can align it like this:
|
||||
// return ((character == ' ')
|
||||
// || (character == CHARACTER_TAB_HORIZONTAL)
|
||||
// || (character == CHARACTER_CARRIAGE_RETURN)
|
||||
// || (character == CHARACTER_LINE_FEED));
|
||||
// Or:
|
||||
// return ((character == ' ') ||
|
||||
// (character == CHARACTER_TAB_HORIZONTAL) ||
|
||||
// (character == CHARACTER_CARRIAGE_RETURN) ||
|
||||
// (character == CHARACTER_LINE_FEED));
|
||||
// Or even use literal characters:
|
||||
// return ((character == ' ') ||
|
||||
// (character == '\t') ||
|
||||
// (character == '\r') ||
|
||||
// (character == '\n'));
|
||||
}
|
||||
|
||||
int character_is_alpha (char character) { // Returns TRUE / 1 or FALSE / 0 depending on if the character is either uppercase or lowercase.
|
||||
return ((character_is_uppercase (character) != 0) || (character_is_lowercase (character) != 0));
|
||||
}
|
||||
|
||||
int character_is_symbol (char character) { // Returns TRUE / 1 if character is one of the characters in that string (array of characters), otherwise it returns FALSE / 0.
|
||||
return (character_compare_array (character, "~!@#$%^&*()+{}|:\"<>?`-=[]\\;',./"));
|
||||
}
|
||||
|
||||
int character_is_visible (char character) { // This is visible (printable) character range, and space is included in there.
|
||||
return ((int) ((character >= ' ') && (character <= '~')));
|
||||
}
|
||||
|
||||
int character_is_invisible (char character) { // If character is not visible, then guess what? It's invisible.
|
||||
return (character_is_visible (character) == FALSE);
|
||||
}
|
||||
|
||||
int character_is_escape (char character) { // We might use this function...
|
||||
return ((int) (character == CHARACTER_ESCAPE));
|
||||
}
|
||||
|
||||
int character_is_underscore (char character) { // I don't even know if I'll ever use this one, we'll see, I'm in the process of writing this "book"...
|
||||
return ((int) (character == '_'));
|
||||
}
|
||||
|
||||
int character_is_hexadecimal (char character) { // Same as function 'character_is_symbol', but for hexadecimal digits.
|
||||
return (character_compare_array (character, "0123456789ABCDEF"));
|
||||
}
|
||||
|
||||
/*
|
||||
Now, we can see how function 'character_compare_array' was implemented, but know that it could be even shorter, like you see below. However, I really think it's for the best to
|
||||
use curly and round braces, when even the compiler won't warn about them. You can easily see the scope of something if you have a text editor capable of highlighting matching
|
||||
braces, and almost all of them have that feature.
|
||||
|
||||
@C
|
||||
int character_compare_array (char character, char * character_array) {
|
||||
for (; * character_array != CHARACTER_NULL; ++character_array)
|
||||
if (character == * character_array)
|
||||
return (TRUE);
|
||||
|
||||
return (FALSE);
|
||||
}
|
||||
@
|
||||
*/
|
||||
|
||||
int character_compare_array (char character, char * character_array) { // I didn't use name "string", but "character_array", to explicitly show the intention of argument.
|
||||
int offset;
|
||||
|
||||
for (offset = 0; offset != string_length (character_array); ++offset) { // We iterate through string (character array!) and return TRUE / 1 if we found it.
|
||||
if (character == character_array [offset]) { // If we don't find it in that string, we return FALSE / 0 since it's not there.
|
||||
return (TRUE); // Note that we could do this without the variable 'offset', similar to string functions.
|
||||
}
|
||||
}
|
||||
|
||||
return (FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
You can see important information about some functions on manual pages in every Linux distro, with 'man ([optional] number) function_name', for example 'man 2 open', and I'll list
|
||||
few important ones down below with some copy & paste magic, but I'll keep it short.
|
||||
|
||||
@C
|
||||
#include <sys/types.h> // Core types.
|
||||
#include <sys/stat.h> // Core something, I don't even know...
|
||||
#include <fcntl.h> // Few system calls.
|
||||
|
||||
int open (const char *pathname, int flags);
|
||||
int open (const char *pathname, int flags, mode_t mode);
|
||||
int creat (const char *pathname, mode_t mode);
|
||||
int openat (int dirfd, const char *pathname, int flags);
|
||||
int openat (int dirfd, const char *pathname, int flags, mode_t mode);
|
||||
|
||||
// Flags (modes, one of the first three must always be present in mode mask):
|
||||
// - O_RDONLY: Open or create file as 'read only', prohibit writing to that file.
|
||||
// - O_WRONLY: Open or create file as 'write only', so you have permission to modify it.
|
||||
// - O_RDWR: Open or create file as 'read and write', so you can do whatever you want with it...
|
||||
// - O_APPEND: Before each write system call, the file offset is positioned at the end of the file, as if with lseek system call. Like, continue writing...
|
||||
// - O_CREAT: If path name doesn't exist, create a new file with the name you specified. The owner of the new file is set to the effective user ID of the process.
|
||||
// - O_TRUNC: If the file already exists and is a regular file and the access mode allows writing (is O_RDWR or O_WRONLY) it will be truncated to length 0.
|
||||
@
|
||||
|
||||
There's a lot more to read in manual pages, about various functions and libraries, they are old (and somewhat outdated, since not everyone use them nowdays, and some don't even
|
||||
update them) source of information, but can be good for standard library. Also keep in mind that most functions below return -1 on error.
|
||||
*/
|
||||
|
||||
int file_open (char * name, int mode) {
|
||||
int descriptor = -1; // Assume error value as default.
|
||||
|
||||
fatal_failure (name == NULL, "file_open: Failed to open file, name is null pointer."); // We must provide non-null address.
|
||||
fatal_failure ((descriptor = open (name, mode)) == -1, "file_open: Failed to open file, function open returned invalid descriptor."); // We abort of 'open' error...
|
||||
// We could write something like this too:
|
||||
// descriptor = open (name, mode);
|
||||
// fatal_failure (descriptor == -1, "file_open: Failed to open file, function open returned invalid descriptor.");
|
||||
// Or align it to break two longer function arguments:
|
||||
// fatal_failure ((descriptor = open (name, mode)) == -1,
|
||||
// "file_open: Failed to open file, function open returned invalid descriptor.");
|
||||
|
||||
return (descriptor); // Return opened file descriptor.
|
||||
}
|
||||
|
||||
int file_close (int file) {
|
||||
fatal_failure (file == -1, "file_close: Failed to close file, invalid file descriptor."); // If 'file' was already closed or corrupted, we abort.
|
||||
fatal_failure (close (file) == -1, "file_close: Failed to close file, function close returned invalid code."); // Keep in mind that this isn't always safe.
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
void file_read (int file, void * data, int size) {
|
||||
fatal_failure (file == -1, "file_read: Failed to read from file, invalid descriptor."); // We'll comment this out once, since it's all similar with 'file_write'.
|
||||
fatal_failure (data == NULL, "file_read: Failed to read from file, data is null pointer."); // This function is very similar to 'in', but it accepts a file descriptor.
|
||||
fatal_failure (size == 0, "file_read: Failed to read from file, size is zero."); // That means we handle the files, not standard input or output.
|
||||
|
||||
(void) read (file, data, (unsigned long int) size); // If there was no errors, we read, and don't check for errors at all...
|
||||
}
|
||||
|
||||
void file_write (int file, void * data, int size) {
|
||||
fatal_failure (file == -1, "file_write: Failed to write to file, invalid descriptor.");
|
||||
fatal_failure (data == NULL, "file_write: Failed to write to file, data is null pointer.");
|
||||
fatal_failure (size == 0, "file_write: Failed to write to file, size is zero.");
|
||||
|
||||
(void) write (file, data, (unsigned long int) size);
|
||||
}
|
||||
|
||||
int file_seek (int file, int whence) {
|
||||
fatal_failure (file == -1, "file_seek: Failed to seek in file, invalid descriptor."); // Make sure we have a valid file descriptor (it's also unsafe to assume it)...
|
||||
|
||||
return ((int) lseek (file, 0, whence)); // Keep in mind that C isn't safe language. It's safe only if you use your brain.
|
||||
}
|
||||
|
||||
int file_size (char * name) {
|
||||
int size = -1; // Lets just assume that everything is wrong, everything falls apart...
|
||||
int file = -1; // Everything is just -1 around us...
|
||||
|
||||
file = file_open (name, O_RDONLY); // We open a file to read it.
|
||||
size = (int) lseek (file, 0, SEEK_END); // We set the offset to the end of the file.
|
||||
|
||||
fatal_failure (size == -1, "file_size: Failed to get size of file, invalid file size."); // Again, error of 'lseek' would be -1, so we check for that...
|
||||
|
||||
file = file_close (file); // We close the file, meaning we didn't edit it.
|
||||
|
||||
return (size); // And we return file size in bytes.
|
||||
}
|
||||
|
||||
/*
|
||||
Lets pretend that each file type has only one extension and save our selves from headaches. Fuck C++ with it's 10 extensions like '.cpp', '.c++', '.cxx', '.cc', and variations for
|
||||
header files, with prefix 'h', it's cancer. Why the extension wasn't just '.c=c+1', huh?
|
||||
*/
|
||||
|
||||
int file_type (char * name) {
|
||||
// Keep in mind that I'm intentionally being inconsistent, so you can see several ways to properly align your code, readability is the key to safety!
|
||||
// Spacing between separate strings in array below is 10 characters, including comma and double quotes, and I "joined" curly braces too, it fits in 180 characters.
|
||||
// You could break it up on curly braces, or put each string in it's own line if you wanted.
|
||||
char * file_type_data [FILE_TYPE_COUNT] = { ".txt", ".s", ".fasm", ".gasm", ".nasm", ".yasm", ".c", ".h", ".adb", ".ads", ".cpp", ".hpp" };
|
||||
|
||||
int type;
|
||||
|
||||
for (; * name != '.'; ++name); // We offset the 'name' until we reach fullstop character.
|
||||
|
||||
for (type = 0; type != FILE_TYPE_COUNT; ++type) { // Then we check if it's one from this array by comparing them sequentially.
|
||||
if (string_compare (name, file_type_data [type]) != 0) { // If it is, we return the value of enumeration of file types.
|
||||
return (type);
|
||||
}
|
||||
}
|
||||
|
||||
return (-1); // If it's not in array, we return -1, so we don't access the wrong value in some other array.
|
||||
}
|
||||
|
||||
void * file_record (char * name) {
|
||||
int file = -1; // You can also initialize local variables in this way.
|
||||
int size = -1;
|
||||
char * data = NULL;
|
||||
|
||||
fatal_failure (name == NULL, "file_import: Failed to import file, name is null pointer.");
|
||||
|
||||
file = file_open (name, O_RDONLY); // Again, we open the file just in order to read it.
|
||||
size = file_size (name); // We do it again, but only to get it's size.
|
||||
data = allocate (size); // And we allocate new memory for data in that file.
|
||||
|
||||
file_read (file, data, size); // Rest if obvious. This could be implemented smarter. Try to notice why.
|
||||
|
||||
file = file_close (file);
|
||||
|
||||
return (data); // We return pointer to new memory, but remember, we have to free it later.
|
||||
}
|
||||
|
||||
#endif
|
@ -1,251 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_1_HEADER
|
||||
#define CHAPTER_1_HEADER
|
||||
|
||||
#include "chapter_0.h" // We need this header file for some core enumeration definitions and function declarations.
|
||||
|
||||
/*
|
||||
Now that you've read the chapter zero, we should get into some technical details about C programming language. This will be the most imporant chapter for people who know some
|
||||
other lower level programming language, or who intuitively understand "building blocks" of some system. Below is just a simple matrix of them, 8 x 4, so you can see that there are
|
||||
really 32 keywords (ANSI C standard, I dislike newer standards), and even more below we'll categorize them. Also, keep in mind that I'll briefly talk about other C standards such
|
||||
as K&R, C99, etc., and use parts of them in some places, so you don't get confused when you see them in other peoples' source code.
|
||||
|
||||
- Keywords:
|
||||
|
||||
static signed if typedef
|
||||
extern unsigned else enum
|
||||
const float for union
|
||||
auto double do struct
|
||||
register char while return
|
||||
volatile int switch goto
|
||||
sizeof short case break
|
||||
void long default continue
|
||||
|
||||
Keywords that you should never use, under any circumstances (except for writing your own C compiler or working in embedded) are:
|
||||
|
||||
- register: Hint to the compiler that some variable will be used a lot, so it can be stored in CPU register, modern compilers ignore this keyword.
|
||||
- volatile: Hint to the compiler that some variable may be changed by some external source. Such a cool keyword, but never use it.
|
||||
- auto: It specifies automatic storage duration for local variables inside a block. Simply, variable won't exist outside {} block it's defined in.
|
||||
- signed: We'll talk about types in C more, but this is always assumed, so use only 'unsigned', when you really need to.
|
||||
- union: You'll have very little excuse to use unions, because they often don't go well with type checking and introduce complexity for no reason.
|
||||
- goto: I'm kidding, feel free to use 'goto' statement, it's not harmful, some people just abused it to the point of it being bullied.
|
||||
|
||||
Keywords that you should really consider not to use in general, but only for some very specific cases (compiler warnings or using some libraries):
|
||||
|
||||
- const: Constant qualifier sometimes tells to the compiler that a value won't be changed. Use it only to silence the compiler warnings...
|
||||
- unsigned: Again, you shouldn't care if your variable is signed or unsigned, because C really likes integers, and hates naturals. We'll talk more about it.
|
||||
- float: Unless you're making a game engine with GPU acceleration or neural network, I wouldn't use this type at all due to it's limitations.
|
||||
- double: Well, same as 'float', but twice bigger and more precise floating point values. You'll see it being used very rarely.
|
||||
- short: Use it only to silence warnings, especially those from XCB and Xlib libraries, for whatever reason (read: X11) they use million different types.
|
||||
- long: Also use this only to silence warnings, because some standard library functions use this type, pure C-hating cancer...
|
||||
- typedef: I don't like it at all, but this one is my personal preference, because it just introduces unneeded mental overhead when programming.
|
||||
- do: It can only be used before '{', then you do the loop thing, then '}' and then 'while' statement. I prefer just to use 'for' everywhere.
|
||||
|
||||
Okay, now we're left with following actually useful and C-loving keywords, 18 of them, that I'll cover more:
|
||||
|
||||
- char: Type for storing ASCII characters, 8 bits wide, implicitly casted to 'int' in sometimes, arrays that use them are string literals.
|
||||
- int: Type for storing signed numerical values, 32 bits wide, most number literals are casted to this type, compiler will warn you for mixing types.
|
||||
- void: This is a black hole, nothing can escape it.
|
||||
- sizeof: Some people say this behaves like a function, but they are wrong, it's a statement. It literally does what is says, more on that later...
|
||||
- static: This qualifier has several meanings, that function or variable will only be used in that file or when inside a function, that variables persists.
|
||||
- extern: This qualifier is more simple, and implicit in older compilers, newer ones warn when it's not used. Something will be used outside that file.
|
||||
- if: If some condition(s) are met (equal to true or non-zero value), do the thing specified in the block.
|
||||
- else: With 'else' you can "bind" multiple 'if' statements, but know that you can completely avoid it, and get the same results.
|
||||
- for: Very complex kind of loop statement, that leads to segmentation faults and many other memory and safety related bugs. I love to use it.
|
||||
- while: Very simple king of loop statement, that leads to segmentation faults and many other memory and safety related bugs. I don't love to use it.
|
||||
- switch: This statement is the most powerful in the whole C language, it's not just if-else made pretty, we'll see examples later.
|
||||
- case: Used only inside 'switch' statement, it sets some number literal as an implicit label, so when the expression in 'switch' is equals it, it jumps there.
|
||||
- default: Used only because C didn't force curly brackets everywhere back when it was made, but every 'switch' should have just one 'default'.
|
||||
- enum: Sometimes treated as type (with 'typedef'), and always very good way to define a lot of constants, we'll talk about them more.
|
||||
- struct: I really advice against using structures, but most libraries use them, so you need to know how they work. In C, they are very weak part of the language.
|
||||
- return: Used in functions to return from them with or without a result (return value). It's best when function only uses it once.
|
||||
- break: When inside a loop, this statement will exit the loop. In newer standards it can take simple arguments, but we'll see why that's bad.
|
||||
- continue: Even more specific case, when inside a loop, this statement will skip to the end of the loop, and then continue again.
|
||||
|
||||
Now, of those 18 actually useful C keywords, I like to avoid 'struct', 'switch', 'case', 'default', 'while', and use (functional) 'static' and 'extern' only in order to silence
|
||||
compiler warnings, 'static' inside a function is more useful. That leaves us (me) with 12 C keywords that I love, out of complete 32 keywords in ANSI C standard. However, we'll
|
||||
see that sometimes is preferable to use switch statement somewhere, or while loop when for loop feels like overkill. In most real-world cases, you'll need to use some API or
|
||||
library that internally used structures everywhere, so you'll need to adapt to it, we'll see examples later...
|
||||
|
||||
So, real men need these keywords { char, int, void, sizeof, static, if, else, for, enum, return }, and use the rest of them in order to silence compiler warnings, use some
|
||||
standard library functions, clean the source code or access an API / library / header file. Lets see some more examples and keep in mind code formatting...
|
||||
|
||||
@C
|
||||
extern int this_is_kind (kind_type this);
|
||||
// extern - we declare this function as external one, because we'll create an object file (.o), and link it with other programs (or object files).
|
||||
// int - we set 'int', integer as return type of this function, and use 0 as false, and 1 as true, instead of 'bool' type from <stdbool.h>.
|
||||
// this_is_kind - we named our function like this, you'll see more of verbose naming when I write programs, because I think it's better...
|
||||
// kind_type - we choose the type of arguments in our function, since our function deals with 'kind', we choose proper 'kind_type'.
|
||||
// this - we named our argument about what's it supposed to be, use similar approach in return type, function, argument type and argument names.
|
||||
@
|
||||
|
||||
Before continuing, lets describe some imporant types in C very simply:
|
||||
|
||||
Word: Bytes: Bits: Kind: Minimum: Maximum:
|
||||
void / / Black hole / /
|
||||
void * 8 64 Memory address / /
|
||||
char 1 8 Integer -128 127
|
||||
short 2 16 Integer -32768 32767
|
||||
int 4 32 Integer -2147483648 2147483647
|
||||
long 8 64 Integer -9223372036854775808 9223372036854775807
|
||||
unsigned char 1 8 Natural 0 256
|
||||
unsigned short 2 16 Natural 0 65535
|
||||
unsigned int 4 32 Natural 0 4294967295
|
||||
unsigned long 8 64 Natural 0 18446744073709551615
|
||||
float 4 32 Real (IEEE754) / /
|
||||
double 8 64 Real (IEEE754) / /
|
||||
|
||||
Note that you shouldn't care for now about 'void' and 'void *', because they're special cases, nor about their minimum and maximum. Also, the less types you use, the more
|
||||
type-safe your code is. That's the reason why I use 'int', 'char *' and 'char', and sometimes 'void *' and 'int *'. You get less compiler and linter warnings about conversions,
|
||||
but you need to know exactly what you're doing and what your code will do in order to have no bugs. Again, think twice, write once.
|
||||
*/
|
||||
|
||||
enum { // We won't even cover all of those, this is just an example of how to do similar task without hardcoding file extensions.
|
||||
FILE_TYPE_TEXT, FILE_TYPE_COMMON_ASSEMBLY, FILE_TYPE_FLAT_ASSEMBLY, FILE_TYPE_GNU_ASSEMBLY,
|
||||
FILE_TYPE_NETWIDE_ASSEMBLY, FILE_TYPE_YET_ANOTHER_ASSEMBLY, FILE_TYPE_C_SOURCE, FILE_TYPE_C_HEADER,
|
||||
FILE_TYPE_ADA_BODY, FILE_TYPE_ADA_SPECIFICATION, FILE_TYPE_CPP_SOURCE, FILE_TYPE_CPP_HEADER,
|
||||
FILE_TYPE_COUNT
|
||||
};
|
||||
|
||||
/*
|
||||
Here are some "utility" functions that we'll maybe use, reimplementation of those from standard library header file <ctype.h>. I prefer names like this, and this is for learning
|
||||
purposes, so it's nice that you can see it here instead of searching through folders such as "/usr/include/". Lets talk about ASCII table now!
|
||||
|
||||
In functions 'character_is_uppercase', 'character_is_lowercase' and 'character_is_digit', we use characters that are in certain range on ASCII table, which we'll show just below.
|
||||
So, it's safe to use '>=' and '<=' operators, but in other cases, we want to compare them selectively, and for simplicity we use function 'character_compare_array'... Here's how
|
||||
ASCII table looks like, I don't like encodings like UTF-8 and others, so neither should you. We'll also write a subprogram that prints this to terminal or graphical window.
|
||||
|
||||
ASCII table:
|
||||
_______________________________________________________________________________________________________________________________________________________________
|
||||
|_0B______|_0O__|_0D__|_0X_|_SYM_|_Full_name____________________________________|_0B______|_0O__|_0D__|_0X_|_SYM_|_Full_name____________________________________|
|
||||
| | |
|
||||
| 0000000 | 000 | 0 | 00 | NUL | Null | 0000001 | 001 | 1 | 01 | SOH | Start of heading |
|
||||
| 0000010 | 002 | 2 | 02 | STX | Start of text | 0000011 | 003 | 3 | 03 | ETX | End of text |
|
||||
| 0000100 | 004 | 4 | 04 | EOT | End of transmission | 0000101 | 005 | 5 | 05 | ENQ | Enquiry |
|
||||
| 0000110 | 006 | 6 | 06 | ACK | Acknowledge | 0000111 | 007 | 7 | 07 | BEL | Bell |
|
||||
| 0001000 | 010 | 8 | 08 | BS | Backspace | 0001001 | 011 | 9 | 09 | HT | Horizontal tab |
|
||||
| 0001010 | 012 | 10 | 0A | LF | Line feed | 0001011 | 013 | 11 | 0B | VT | Vertical tab |
|
||||
| 0001100 | 014 | 12 | 0C | FF | Form feed | 0001101 | 015 | 13 | 0D | CR | Carriage return |
|
||||
| 0001110 | 016 | 14 | 0E | SO | Shift out | 0001111 | 017 | 15 | 0F | SI | Shift in |
|
||||
| 0010000 | 020 | 16 | 10 | DLE | Data link escape | 0010001 | 021 | 17 | 11 | DC1 | Device control 1 |
|
||||
| 0010010 | 022 | 18 | 12 | DC2 | Device control 2 | 0010011 | 023 | 19 | 13 | DC3 | Device control 3 |
|
||||
| 0010100 | 024 | 20 | 14 | DC4 | Device control 4 | 0010101 | 025 | 21 | 15 | NAK | Negative acknowledge |
|
||||
| 0010110 | 026 | 22 | 16 | SYN | Synchronous idle | 0010111 | 027 | 23 | 17 | ETB | End transmission block |
|
||||
| 0011000 | 030 | 24 | 18 | CAN | Cancel | 0011001 | 031 | 25 | 19 | EM | End of medium |
|
||||
| 0011010 | 032 | 26 | 1A | SUB | Substitute | 0011011 | 033 | 27 | 1B | ESC | Escape |
|
||||
| 0011100 | 034 | 28 | 1C | FS | File separator | 0011101 | 035 | 29 | 1D | GS | Group separator |
|
||||
| 0011110 | 036 | 30 | 1E | RS | Record separator | 0011111 | 037 | 31 | 1F | US | Unit separator |
|
||||
| 0100000 | 040 | 32 | 20 | | Space | 0100001 | 041 | 33 | 21 | ! | Exclamation mark |
|
||||
| 0100010 | 042 | 34 | 22 | " | Speech mark | 0100011 | 043 | 35 | 23 | # | Number sign |
|
||||
| 0100100 | 044 | 36 | 24 | $ | Dollar sign | 0100101 | 045 | 37 | 25 | % | Percent |
|
||||
| 0100110 | 046 | 38 | 26 | & | Ampersand | 0100111 | 047 | 39 | 27 | ' | Quote |
|
||||
| 0101000 | 050 | 40 | 28 | ( | Open parenthesis | 0101001 | 051 | 41 | 29 | ) | Close parenthesis |
|
||||
| 0101010 | 052 | 42 | 2A | * | Asterisk | 0101011 | 053 | 43 | 2B | + | Plus |
|
||||
| 0101100 | 054 | 44 | 2C | , | Comma | 0101101 | 055 | 45 | 2D | - | Minus |
|
||||
| 0101110 | 056 | 46 | 2E | . | Period | 0101111 | 057 | 47 | 2F | / | Slash |
|
||||
| 0110000 | 060 | 48 | 30 | 0 | Zero | 0110001 | 061 | 49 | 31 | 1 | One |
|
||||
| 0110010 | 062 | 50 | 32 | 2 | Two | 0110011 | 063 | 51 | 33 | 3 | Three |
|
||||
| 0110100 | 064 | 52 | 34 | 4 | Four | 0110101 | 065 | 53 | 35 | 5 | Five |
|
||||
| 0110110 | 066 | 54 | 36 | 6 | Six | 0110111 | 067 | 55 | 37 | 7 | Seven |
|
||||
| 0111000 | 070 | 56 | 38 | 8 | Eight | 0111001 | 071 | 57 | 39 | 9 | Nine |
|
||||
| 0111010 | 072 | 58 | 3A | : | Colon | 0111011 | 073 | 59 | 3B | ; | Semicolon |
|
||||
| 0111100 | 074 | 60 | 3C | < | Open angled bracket | 0111101 | 075 | 61 | 3D | = | Equal |
|
||||
| 0111110 | 076 | 62 | 3E | > | Close angled bracket | 0111111 | 077 | 63 | 3F | ? | Question mark |
|
||||
| 1000000 | 100 | 64 | 40 | @ | At sign | 1000001 | 101 | 65 | 41 | A | Uppercase A |
|
||||
| 1000010 | 102 | 66 | 42 | B | Uppercase B | 1000011 | 103 | 67 | 43 | C | Uppercase C |
|
||||
| 1000100 | 104 | 68 | 44 | D | Uppercase D | 1000101 | 105 | 69 | 45 | E | Uppercase E |
|
||||
| 1000110 | 106 | 70 | 46 | F | Uppercase F | 1000111 | 107 | 71 | 47 | G | Uppercase G |
|
||||
| 1001000 | 110 | 72 | 48 | H | Uppercase H | 1001001 | 111 | 73 | 49 | I | Uppercase I |
|
||||
| 1001010 | 112 | 74 | 4A | J | Uppercase J | 1001011 | 113 | 75 | 4B | K | Uppercase K |
|
||||
| 1001100 | 114 | 76 | 4C | L | Uppercase L | 1001101 | 115 | 77 | 4D | M | Uppercase M |
|
||||
| 1001110 | 116 | 78 | 4E | N | Uppercase N | 1001111 | 117 | 79 | 4F | O | Uppercase O |
|
||||
| 1010000 | 120 | 80 | 50 | P | Uppercase P | 1010001 | 121 | 81 | 51 | Q | Uppercase Q |
|
||||
| 1010010 | 122 | 82 | 52 | R | Uppercase R | 1010011 | 123 | 83 | 53 | S | Uppercase S |
|
||||
| 1010100 | 124 | 84 | 54 | T | Uppercase T | 1010101 | 125 | 85 | 55 | U | Uppercase U |
|
||||
| 1010110 | 126 | 86 | 56 | V | Uppercase V | 1010111 | 127 | 87 | 57 | W | Uppercase W |
|
||||
| 1011000 | 130 | 88 | 58 | X | Uppercase X | 1011001 | 131 | 89 | 59 | Y | Uppercase Y |
|
||||
| 1011010 | 132 | 90 | 5A | Z | Uppercase Z | 1011011 | 133 | 91 | 5B | [ | Opening bracket |
|
||||
| 1011100 | 134 | 92 | 5C | \ | Backslash | 1011101 | 135 | 93 | 5D | ] | Closing bracket |
|
||||
| 1011110 | 136 | 94 | 5E | ^ | Caret | 1011111 | 137 | 95 | 5F | _ | Underscore |
|
||||
| 1100000 | 140 | 96 | 60 | ` | Grave | 1100001 | 141 | 97 | 61 | a | Lowercase a |
|
||||
| 1100010 | 142 | 98 | 62 | b | Lowercase b | 1100011 | 143 | 99 | 63 | c | Lowercase c |
|
||||
| 1100100 | 144 | 100 | 64 | d | Lowercase d | 1100101 | 145 | 101 | 65 | e | Lowercase e |
|
||||
| 1100110 | 146 | 102 | 66 | f | Lowercase f | 1100111 | 147 | 103 | 67 | g | Lowercase g |
|
||||
| 1101000 | 150 | 104 | 68 | h | Lowercase h | 1101001 | 151 | 105 | 69 | i | Lowercase i |
|
||||
| 1101010 | 152 | 106 | 6A | j | Lowercase j | 1101011 | 153 | 107 | 6B | k | Lowercase k |
|
||||
| 1101100 | 154 | 108 | 6C | l | Lowercase l | 1101101 | 155 | 109 | 6D | m | Lowercase m |
|
||||
| 1101110 | 156 | 110 | 6E | n | Lowercase n | 1101111 | 157 | 111 | 6F | o | Lowercase o |
|
||||
| 1110000 | 160 | 112 | 70 | p | Lowercase p | 1110001 | 161 | 113 | 71 | q | Lowercase q |
|
||||
| 1110010 | 162 | 114 | 72 | r | Lowercase r | 1110011 | 163 | 115 | 73 | s | Lowercase s |
|
||||
| 1110100 | 164 | 116 | 74 | t | Lowercase t | 1110101 | 165 | 117 | 75 | u | Lowercase u |
|
||||
| 1110110 | 166 | 118 | 76 | v | Lowercase v | 1110111 | 167 | 119 | 77 | w | Lowercase w |
|
||||
| 1111000 | 170 | 120 | 78 | x | Lowercase x | 1111001 | 171 | 121 | 79 | y | Lowercase y |
|
||||
| 1111010 | 172 | 122 | 7A | z | Lowercase z | 1111011 | 173 | 123 | 7B | { | Opening brace |
|
||||
| 1111100 | 174 | 124 | 7C | | | Vertical bar | 1111101 | 175 | 125 | 7D | } | Closing brace |
|
||||
| 1111110 | 176 | 126 | 7E | ~ | Tilde | 1111111 | 177 | 127 | 7F | DEL | Delete |
|
||||
|_______________________________________________________________________________|_______________________________________________________________________________|
|
||||
|
||||
You can see that values of 'A' ... 'Z', 'a' ... 'z' and '0' ... '9' are sequential, but symbols and "system" characters are mixed up.
|
||||
|
||||
In C language, we have C source files with the extension '.c', and C header files with the extension '.h'. Both of those are just plain text files, and please use 7-bit ASCII
|
||||
encoding, since it's common sense, UTF is cancer, and 8-bit ASCII is for enlightened people like Terrence Andrew Davis. C language is completely separate (on some C compilers)
|
||||
from its' preprocessor, whose directives start with '#' character, continue on '\' character and break on '\n' (read: LINE FEED) character.
|
||||
|
||||
@C
|
||||
#include <path/to/file/file_name.h> // Copy the entire file from '/usr/include/' directory into this file, on the place where it was specified.
|
||||
#include "path/to/file/file_name.h" // Copy the entire file from current directory into this file, again on the place where it was specified.
|
||||
|
||||
#define SOMETHING // This will add additional information to the preprocessor about this file, it's mostly used for flags and header-guards.
|
||||
#undef SOMETHING // This will remove that additional information you've provided...
|
||||
#if SOMETHING // If SOMETHING (condition obviously) is true, then code until '#elif', '#else' or '#endif' will be included.
|
||||
#ifdef SOMETHING // If SOMETHING was previously '#define'-d, then code until '#elif', '#else' or '#endif' will be included.
|
||||
#ifndef SOMETHING // If SOMETHING wasn't (NOT!) previously '#define'-d, then code until '#elif', '#else' or '#endif' will be included.
|
||||
#elif // Essentially "else if" for preprocessor, it's very ugly, and nesting them looks bad and is a bad practice.
|
||||
#else // Essentially "else" for preprocessor, I don't think I ever used it in my entire life, but I saw other people use it.
|
||||
#endif // End if... Self-explanatory, and a sad thing that we need to ruin the beautiful C code with it.
|
||||
@
|
||||
|
||||
Okay, that's all you really need to know about C preprocessor, since we won't use it much. You can write a completely pure C project, using only C language, but you'll end up with
|
||||
copying and pasting a lot of code, especially external function and variable declarations. Because of that we need '#include' directive, and because of it, we need header guards,
|
||||
so it's all C-hating in the end. However, we need to cover some simple macros, so you can deal with other peoples' code bases. Remember, the less "building blocks" you have, if
|
||||
you learn them well, you can make anything, and you should be proud of "reinventing the wheel". If wheels weren't reinvented over and over again, then some expensive BMW would've
|
||||
wooden wheels attached to it.
|
||||
*/
|
||||
|
||||
extern int character_is_uppercase (char character); // Notice how we align those functions, I believe this improves the readability of any program, in any programming language.
|
||||
extern int character_is_lowercase (char character); // Some people would just use 'ischrupp' or something, but I hate reading code written like that...
|
||||
extern int character_is_digit (char character); // Important note is also that a programming language is not, and it should be like natural language, why?
|
||||
extern int character_is_blank (char character); // Because we need strict rules in programming language, same like in mathematical languages, now now, don't be scared.
|
||||
extern int character_is_alpha (char character);
|
||||
extern int character_is_symbol (char character);
|
||||
extern int character_is_visible (char character);
|
||||
extern int character_is_invisible (char character);
|
||||
extern int character_is_escape (char character);
|
||||
extern int character_is_underscore (char character);
|
||||
extern int character_is_hexadecimal (char character);
|
||||
|
||||
extern int character_compare_array (char character, char * character_array); // This function is singled out, because it's different from those above, and we use it internally.
|
||||
|
||||
/*
|
||||
And here are also utility functions that handle files, most of them are reimplemented using "system calls" from <fcntl.h> and <unistd.h>, but you also have access to <stdio.h>,
|
||||
which is probably the most used header file in C language. It handles the 'FILE *' type, not a file descriptors which are 'int', and has functions that are prefixed with character
|
||||
'f', for example, 'fopen / fclose / fread / fwrite / fseek' and many more.
|
||||
*/
|
||||
|
||||
extern int file_open (char * name, int mode); // We open a file descriptor 'name' with 'mode', obviously...
|
||||
extern int file_close (int file); // We. Every opened file descriptor should be closed when program finishes.
|
||||
extern void file_read (int file, void * data, int size); // We read from 'file' into 'data', by 'size' amount, similar to 'in'.
|
||||
extern void file_write (int file, void * data, int size); // We write from 'data' into 'file', by 'size' amount, similar to 'out'.
|
||||
extern int file_seek (int file, int whence); // We retrieve data about offsets in 'file'.
|
||||
extern int file_size (char * name); // We get the size of the file by its' 'name'.
|
||||
extern int file_type (char * name); // We get the type of the file by its' 'name' (by file name extension).
|
||||
extern void * file_record (char * name); // We store an entire file into some memory address.
|
||||
|
||||
#endif
|
@ -1,480 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_2_SOURCE
|
||||
#define CHAPTER_2_SOURCE
|
||||
|
||||
#include "chapter_2.h" // We're copying function and variable declarations from this file here. This shouldn't be copied twice, more on that later...
|
||||
|
||||
/*
|
||||
Function 'hello_world_0' uses (rougly said) a system call instead of a function, but I don't even know what will different compilers do to that line of code. What's a system call?
|
||||
Well, when you're writing assembly, you have some general purpose registers, on mine and your machine, there's 16 of them, and you move some data in them, then use 'syscall'
|
||||
instruction, which magically does something, like open or close a file descriptor, read or write to it, but more on that later! A lot more... Important thing to know is that when
|
||||
you use keyword 'sizeof' on a string, it'll count null terminator, so it'll have value 14, and type 'size_t'. It's provided in <unistd.h> header file.
|
||||
|
||||
@C
|
||||
write ( // mov rax 1 ; Literal of Linux write system call, defined internally.
|
||||
STDOUT_FILENO, // mov rdi 1 ; Literal of standard output file descriptor, defined as 'STDOUT_FILENO'.
|
||||
"Hello world!\n", // mov rsi X ; Address of our string.
|
||||
sizeof ("Hello world!\n") // mov rdx [Y] ; Literal of size of our string.
|
||||
); // syscall ; Ask kernel to do some work.
|
||||
@
|
||||
|
||||
We'll talk about assembly a lot in future chapters, but you should filter that out of your brain until you learn C well...
|
||||
*/
|
||||
|
||||
void hello_world_0 (void) {
|
||||
write (STDOUT_FILENO, "Hello world!\n", sizeof ("Hello world!\n"));
|
||||
}
|
||||
|
||||
/*
|
||||
In function 'hello_world_1' we're using function 'puts' this time, which is considered unsafe because it'll fail when input string isn't null terminated. Keep in mind that it also
|
||||
prints line feed (new line) on the end. Why I call it line feed? Some terminals and text editors distinguish carriage return ('\r' / 13 / CR) and line feed ('\n' / 10 / LF), and
|
||||
some of them use both, like old teleprinters used to do, where the name 'tty' on UNIX-based operating systems comes from.
|
||||
*/
|
||||
|
||||
void hello_world_1 (void) {
|
||||
puts ("Hello world!");
|
||||
}
|
||||
|
||||
/*
|
||||
Now, in function 'hello_world_2' and function 'hello_world_3', we're using 'printf' function, which is in my opinion important for debugging. It has many variations, so we'll have
|
||||
a separate chapter only about it. Know that it's a variadic function, which means that it can take more than one input argument, and later we'll learn to make our own variadic
|
||||
functions as well. It's also a bit unsafe, if you're not careful how you use it, but nothing drastic can happen, most compilers catch those kinds of errors.
|
||||
*/
|
||||
|
||||
void hello_world_2 (void) {
|
||||
printf ("Hello world!\n");
|
||||
}
|
||||
|
||||
/*
|
||||
It's also worth noting that we're using it's arguments in function 'hello_world_3', function 'printf' will print characters one by one from its' format, until it encounters '%',
|
||||
if the next character is '%', it prints one '%', if it's 's', then it'll print a string, which we provide in the next argument. When compiler optimizations are turned on, it will
|
||||
change all sorts of things here, since it's obvious that we're using simple string in this example.
|
||||
*/
|
||||
|
||||
void hello_world_3 (void) {
|
||||
printf ("%s %s!\n", "Hello", "world");
|
||||
}
|
||||
|
||||
/*
|
||||
Lastly, we have a global variable, some people like to call it that, and consider it evil. It's not evil if you know the pros and cons of using them, which causes intellectual
|
||||
overhead for certain sort of people. Pros, makes your code shorter and easier to refactor. Cons, it can potentially make your code less safe if you don't know what you're doing.
|
||||
You should use them, external variables as I like to call them, when you're working on your programs or in a small group, but they can lead to bad things if you're working with a
|
||||
lot of people, someone could change it in some bad place, and cause a bug that's difficult to track.
|
||||
|
||||
We could initialize it also in any part of our code later, in some function for example, as following:
|
||||
|
||||
@C
|
||||
// One by one:
|
||||
hello_world [0] = hello_world_0;
|
||||
hello_world [1] = hello_world_1;
|
||||
hello_world [2] = hello_world_2;
|
||||
hello_world [3] = hello_world_3;
|
||||
|
||||
// Or use it for some completely unrelated function if we wanted to, as long as function "kind" is same, they both take no input and no output:
|
||||
|
||||
static void unrelated_function (void) {
|
||||
puts ("I couldn't care less...");
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
hello_world [2] = unrelated_function;
|
||||
@
|
||||
|
||||
Just know that an array(s) of function pointers is my favorite kind of variable for bot AI in simple games, index can be derived from bot state.
|
||||
*/
|
||||
|
||||
void (* hello_world [4]) (void) = {
|
||||
hello_world_0,
|
||||
hello_world_1,
|
||||
hello_world_2,
|
||||
hello_world_3
|
||||
};
|
||||
|
||||
/*
|
||||
Now, we'll talk more about cursed functions! I broke code formatting rules for this, but it's worth it, time to learn. Function or variable with 'static' instead of 'extern'
|
||||
keyword before it is internal. That means it'll only be accessable in this file. That way, I can include file "chapter_2.h" in some other file, and know that I can't access or
|
||||
modify some internal variable within that other file. They can only be accessed or modified in this file! Lets show some formatting examples below:
|
||||
|
||||
@C
|
||||
static int my_size = 0;
|
||||
static char * my_name = "Name";
|
||||
static void * my_data = NULL;
|
||||
@
|
||||
|
||||
Unlike when declaring external variables (seen in previous header files), you need to initialize internal variables to some value, like you see above. You shouldn't initialize
|
||||
external variables, they have separate definition (initialization in this case). You can see them just below, they are initialized without 'extern' in front of them, and the same
|
||||
goes for functions declared in "chapter_2.h", but there's another trick to this... You can declare internal functions before defining them, so you have 2 options for them:
|
||||
|
||||
@C
|
||||
// Option A:
|
||||
static void * my_function (char * name, int size);
|
||||
|
||||
int my_function (char * name, int size) {
|
||||
int data = 0;
|
||||
|
||||
// Do some work involving function arguments and modify data.
|
||||
|
||||
return (data);
|
||||
}
|
||||
|
||||
// Option B:
|
||||
static int my_function (char * name, int size) {
|
||||
int data = 0;
|
||||
|
||||
// Do some work involving function arguments and modify data.
|
||||
|
||||
return (data);
|
||||
}
|
||||
@
|
||||
|
||||
Okay, external variables you see lower (without 'static') are all integers. Lets briefly see what types are the internal variables.
|
||||
|
||||
- curses_format / curses_format: Array of characters (also known as string!), and their size is already known at the compile time, it's inside square braces.
|
||||
- curses_screen: Pointer to character, we'll allocate memory for it later, so it'll be a dynamic array of characters, its' size can change.
|
||||
- curses_activator: Pointer to integer, we'll also allocate memory for it, so we can't use square braces for those. Remember that for later.
|
||||
- curses_action: Strictly speaking, pointer to another pointer to function of signature 'void SOMETHING (void);', but ignore it for now, don't get confused.
|
||||
*/
|
||||
|
||||
static char curses_format [CURSES_FORMAT + 1] = "\033[-;3-m-\033[0m"; // Internal variable holding data for rendering single character, using ASCII escape sequences.
|
||||
static char curses_cursor [CURSES_CURSOR + 1] = "\033[---;---H"; // Internal variable holding data for rendering cursor at some position.
|
||||
|
||||
static char * curses_screen = NULL; // We hold all terminal screen data here, you can think of it as an image, but better word is a framebuffer.
|
||||
|
||||
static int curses_action_count = 0; // Count of defined actions, these are part of event handling system.
|
||||
static int * curses_activator = NULL; // Array of action signals, when they are active, action is executed.
|
||||
|
||||
static void (* * curses_action) (void) = NULL; // Array of function pointers, we use it to set event actions, so that user can interact with the terminal.
|
||||
|
||||
static struct termios curses_old_terminal; // This is <termios.h> magic for making terminal enter and exit the raw mode.
|
||||
static struct termios curses_new_terminal;
|
||||
|
||||
// We've explained these external variables in header file already.
|
||||
int curses_character = 0;
|
||||
int curses_signal = SIGNAL_NONE;
|
||||
int curses_screen_width = 0;
|
||||
int curses_screen_height = 0;
|
||||
int curses_active = 1;
|
||||
|
||||
/*
|
||||
I need to quickly explain how I'm structuring this subprogram. You can think of functions and variables starting with 'curses_*' as tiny standalone library if it's easier. They
|
||||
deal with terminal input and output. User only needs to call function 'curses_configure' only once, and necessary amount of 'curses_bind' functions, before doing any rendering,
|
||||
then make an "infinite" loop that'll stop when external variable 'curses_active' is equal to zero. In that loop, user can call any amount 'curses_render_*' functions, and at the
|
||||
end call function 'curses_synchronize' again, only once. So the following program structure would look something like this:
|
||||
|
||||
@C
|
||||
// ...
|
||||
int main (int argc, char * * argv) {
|
||||
// ...
|
||||
curses_configure ();
|
||||
|
||||
while (curses_active != 0) { // Notice the negative condition, the loop will stop then the condition is 0, so when 'curses_active' is 0.
|
||||
// Calling functions 'curses_render_*' and doing other work...
|
||||
curses_synchronize ();
|
||||
}
|
||||
|
||||
// ...
|
||||
return (0);
|
||||
}
|
||||
@
|
||||
|
||||
We don't need to use 'curses_initialize' and the begining of the program, or 'curses_deinitialize' at the end. Also, there are no functions like 'BeginDrawing' or 'EndDrawing',
|
||||
with 'Draw*' functions strictly between those two, like you would see in Raylib. We won't cover SDL2 and Raylib in this book, since I think that anyone with minimal knowledge of
|
||||
C can efficiently write good programs using those libraries, please don't misunderstand me, those two are good libraries. We'll simply eliminate that "begin" or "end" step in
|
||||
our subprogram called 'curses'.
|
||||
*/
|
||||
|
||||
static void curses_initialize (void) { // This function will be called when 'curses_configure' is called, automatically.
|
||||
struct winsize screen_dimension;
|
||||
|
||||
fatal_failure (ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension) == -1, // If function 'ioctl' failed, we immediately aborting the entire program.
|
||||
"ioctl: Failed to get terminal dimensions."); // I split those error messages, you can find your own formatting style.
|
||||
|
||||
curses_screen_width = (int) screen_dimension.ws_col; // We get the dimensions of terminal window by calling that 'ioctl' function.
|
||||
curses_screen_height = (int) screen_dimension.ws_row;
|
||||
|
||||
fatal_failure (tcgetattr (STDIN_FILENO, & curses_old_terminal) == -1, // Now we need to obtain data for current non-raw terminal to restore it later.
|
||||
"tcgetattr: Failed to get default terminal attributes.");
|
||||
|
||||
curses_new_terminal = curses_old_terminal; // Here we set our raw terminal to be the same as the non-raw one.
|
||||
|
||||
curses_new_terminal.c_cc [VMIN] = (unsigned char) 0; // Now it's time to modify it to be raw, this essentially means no-echo.
|
||||
curses_new_terminal.c_cc [VTIME] = (unsigned char) 1;
|
||||
|
||||
curses_new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
curses_new_terminal.c_oflag &= (unsigned int) ~(OPOST);
|
||||
curses_new_terminal.c_cflag |= (unsigned int) (CS8);
|
||||
curses_new_terminal.c_lflag &= (unsigned int) ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
|
||||
fatal_failure (tcsetattr (STDIN_FILENO, TCSAFLUSH, & curses_new_terminal) == -1, // Finally, we're passing intormations to our terminal, and it becomes raw.
|
||||
"tcsetattr: Failed to set reverse terminal attributes.");
|
||||
}
|
||||
|
||||
static void curses_deinitialize (void) { // This function will be only called once, automatically, at the program exit.
|
||||
curses_screen = deallocate (curses_screen); // It's important to deallocate all previously allocated memory.
|
||||
curses_activator = deallocate (curses_activator);
|
||||
curses_action = deallocate (curses_action);
|
||||
|
||||
terminal_clear (); // This only make things look prettier, we don't have mess when we exit the program.
|
||||
|
||||
fatal_failure (tcsetattr (STDIN_FILENO, TCSAFLUSH, & curses_old_terminal) == -1, // Again, if this fails, we're doomed, we're aborting.
|
||||
"tcsetattr: Failed to set default terminal attributes.");
|
||||
}
|
||||
|
||||
/*
|
||||
Next two functions are also internal, and we'll use them in our other 'curses_*' functions. The goal of extracting that piece of code into separate functions is to eliminate code
|
||||
repetition. Copying and pasting the same piece of code everywhere has pros and cons. Pros are that they're very good indication what should be factored out. The best example that
|
||||
comes from top of my head is 'echo' function from chapter zero. It'd be boring to use 'out' everywhere, since it also requires the 'size' argument, and I want to just output
|
||||
string literals to terminal in most cases. But that doesn't mean that 'out' is useless, as you'll see, we'll use it in 'curses_synchronize' funcion. Cons are, if you really have
|
||||
repeating code all over your program, if there's a bug in one of them, there's a bug in all of them. Also, if you'll use some extracted function (also refered to as refactored in
|
||||
some cases) only once, it's not a bad thing. We've extracted some code into 'curses_initialize' function, and we call it only once, inside 'curses_configure' function, but the
|
||||
intention behind what it does is clear. However, I still believe that procedural code, that's executed line by line, from top to bottom, is best.
|
||||
|
||||
Now, we could also implement some error checking functions for 'curses_*' functions. Good idea, when something is wrong, like we want to render a character out of the screen, we
|
||||
just print an error message to terminal, right? Well, no. Remember, we're using terminal as a framebuffer (about which we'll talk about a lot more in ray tracing and rasterization
|
||||
chapters), so if we just print message there, it'll corrupt our framebuffer. So, we could just write them to some log file. How about binding them, whenever something is wrong,
|
||||
we don't just abort the program or stop rendering, but continue running it, while also writing error messages to that same file. Then, when we exit the program, it'll print all
|
||||
error messages (if any) normally. Here's how it would look like:
|
||||
|
||||
@C
|
||||
// Enumeration for log type.
|
||||
enum {
|
||||
LOG_FAILURE, LOG_WARNING, LOG_SUCCESS, LOG_COUNT
|
||||
};
|
||||
|
||||
static void log_in (int type, int condition, char * message);
|
||||
static void log_out (char * name);
|
||||
|
||||
// Usage example:
|
||||
log_in (LOG_FAILURE, condition != 0, "function_name: The kind of error that occured or a general notification.");
|
||||
log_out ("file_name.log");
|
||||
@
|
||||
|
||||
To be honest, I consider that a bad idea, because if we render something that's not supposed to be rendered (we pass wrong arguments to any of the 'curses_render_*' functions),
|
||||
it'll be obvious when we look at it. So, I wrote commented-out examples below of how we'd do it, but it won't be compiled into the final object file. If we pass a wrong value to
|
||||
those functions, they'll be fixed (modified), and give the wrong results, which will be obvious when we look at the screen. Nothing will crash or burn.
|
||||
*/
|
||||
|
||||
static char * curses_screen_offset (int x, int y) {
|
||||
// log_in (LOG_FAILURE, x <= -1, "curses_screen_offset: X position is below the lower bound.");
|
||||
// log_in (LOG_FAILURE, y <= -1, "curses_screen_offset: Y position is below the lower bound.");
|
||||
// log_in (LOG_FAILURE, x >= curses_screen_width, "curses_screen_offset: X position is above the upper bound.");
|
||||
// log_in (LOG_FAILURE, y >= curses_screen_height, "curses_screen_offset: Y position is above the upper bound.");
|
||||
|
||||
limit (& x, 0, curses_screen_width - 1); // We're limiting the values of X and Y coordinates to screen dimensions.
|
||||
limit (& y, 0, curses_screen_height - 1); // Function 'limit' uses inclusive values for minimum and maximum.
|
||||
|
||||
return (& curses_screen [CURSES_REVERT + CURSES_FORMAT * (y * curses_screen_width + x)]); // And returning the offset of the screen buffer.
|
||||
}
|
||||
|
||||
static char * curses_format_character (char character, int colour, int effect) {
|
||||
// log_in (LOG_WARNING, character_is_invisible (character), "curses_format_character: Can not format invisible characters.");
|
||||
// log_in (LOG_FAILURE, colour >= COLOUR_COUNT, "curses_format_character: Colour is invalid enumeration value.");
|
||||
// log_in (LOG_FAILURE, effect >= EFFECT_COUNT, "curses_format_character: Effect is invalid enumeration value.");
|
||||
|
||||
if (character_is_invisible (character) != 0) { // Since we don't want to render invisible ASCII characters, we'll just change 'character' to space.
|
||||
character = ' ';
|
||||
}
|
||||
|
||||
colour %= COLOUR_COUNT; // Instead of breaking and burning everything, we'll just "round" it down to one of the allowed values.
|
||||
effect %= EFFECT_COUNT;
|
||||
|
||||
switch (effect) { // Effects aren't enumerated nicely as colours, so here we'll use switch statement instead of nested if-else statements.
|
||||
case EFFECT_NORMAL: effect = 0; break; // We could break these into any combination of new lines, we'll just show one example below of how else you can do it.
|
||||
// case EFFECT_NORMAL:
|
||||
// effect = 0;
|
||||
// break;
|
||||
// case EFFECT_NORMAL: {
|
||||
// effect = 0;
|
||||
// } break;
|
||||
case EFFECT_BOLD: effect = 1; break;
|
||||
case EFFECT_ITALIC: effect = 3; break;
|
||||
case EFFECT_UNDERLINE: effect = 4; break;
|
||||
case EFFECT_BLINK: effect = 5; break;
|
||||
case EFFECT_REVERSE: effect = 7; break;
|
||||
default: effect = 0; break;
|
||||
}
|
||||
|
||||
curses_format [2] = (char) effect + '0'; // Now, here comes ASCII escape sequence magic, this will format the character to have colour and effect.
|
||||
curses_format [5] = (char) colour + '0';
|
||||
curses_format [7] = character;
|
||||
|
||||
return (curses_format); // And we return the value (pointer to character) of formatted internal variables.
|
||||
}
|
||||
|
||||
static void curses_idle (void) { return; } // If you have a lot of short functions that are intended to be in array of function pointers, you can align them like this.
|
||||
static void curses_exit (void) { curses_active = 0; } // And this is our main function for quitting main loop in curses.
|
||||
|
||||
/*
|
||||
External function definitions, those found in "chapter_2.h" header file.
|
||||
*/
|
||||
|
||||
void curses_configure (void) {
|
||||
atexit (curses_deinitialize); // Deinitialization is automatically on exit.
|
||||
|
||||
curses_initialize (); // Initializing curses, yaay.
|
||||
|
||||
curses_screen = allocate (CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR + 1); // We're requesting new memory for framebuffer.
|
||||
|
||||
curses_bind (SIGNAL_ESCAPE, curses_exit); // Binding universal exit key (signal).
|
||||
|
||||
terminal_clear ();
|
||||
|
||||
string_copy (& curses_screen [0], "\033[H"); // ASCII black magic to always clear screen.
|
||||
}
|
||||
|
||||
void curses_synchronize (void) {
|
||||
int signal;
|
||||
|
||||
curses_signal = curses_character = signal = 0; // Reassigning signals to 0.
|
||||
|
||||
out (curses_screen, CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR); // We output the entire framebuffer to terminal (present).
|
||||
|
||||
in (& signal, (int) sizeof (signal)); // We're now checking for user input to modify it.
|
||||
|
||||
curses_character = signal; // We need literal value of 'signal' for text input.
|
||||
|
||||
if (signal == '\033') { // And then we modify the actual 'curses_signal'.
|
||||
curses_signal |= SIGNAL_ESCAPE;
|
||||
} else if (character_is_digit ((char) signal) != 0) {
|
||||
curses_signal |= SIGNAL_0 + (int) (signal - '0');
|
||||
} else if (character_is_lowercase ((char) signal) != 0) {
|
||||
curses_signal |= SIGNAL_A + (int) (signal - 'a');
|
||||
} else if (character_is_uppercase ((char) signal) != 0) {
|
||||
curses_signal |= SIGNAL_A + (int) (signal - 'A');
|
||||
curses_signal |= SIGNAL_SHIFT;
|
||||
} else {
|
||||
curses_signal = SIGNAL_NONE;
|
||||
}
|
||||
|
||||
for (signal = 0; signal != curses_action_count; ++signal) { // Now, it's time to loop over bound actions.
|
||||
if (curses_signal == curses_activator [signal]) { // If we have a bound signal, then:
|
||||
curses_action [signal] (); // We execute corresponding action (function).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
It's still to early to talk about signal binding and unbinding, so I'll skip over them as they are more complex.
|
||||
*/
|
||||
|
||||
void curses_bind (int signal, void (* action) (void)) {
|
||||
++curses_action_count;
|
||||
|
||||
curses_activator = reallocate (curses_activator, curses_action_count * (int) sizeof (* curses_activator));
|
||||
curses_action = reallocate (curses_action, curses_action_count * (int) sizeof (* curses_action));
|
||||
|
||||
curses_activator [curses_action_count - 1] = signal;
|
||||
curses_action [curses_action_count - 1] = action;
|
||||
}
|
||||
|
||||
void curses_unbind (int signal) {
|
||||
(void) signal;
|
||||
|
||||
curses_activator [curses_action_count - 1] = SIGNAL_NONE;
|
||||
curses_action [curses_action_count - 1] = curses_idle;
|
||||
|
||||
--curses_action_count;
|
||||
}
|
||||
|
||||
/*
|
||||
Finally, we're getting to rendering, something that we can actually see on our screen. There's ASCII escape sequences involved into this, so lets explain them briefly. We
|
||||
mentioned in "chapter_2.h" header file about how some of them work. Now, we're extending them, with minimal amount of error checking in order to keep things more clear. Worth
|
||||
noting is that not all terminals support all of ASCII escape sequences, but most of them support at least a subset of them. There are many terminals such as ones provided by
|
||||
desktop environment (xfce4-terminal, etc.), and independent ones (st, sakura, xterm, etc.), use whatever, it'll (in 99% of cases) work as intended.
|
||||
|
||||
If you want to know what rendering truly is, it's just copying certain value to some offset. Nothing more...
|
||||
*/
|
||||
|
||||
void curses_render_cursor (int x, int y) {
|
||||
// We're expecting that our terminal dimensions (width and height in unit of 'characters') is always less 1000. If it's not, I really don't care.
|
||||
// Also, there's the application of "limited" string functions, since we don't want to copy until null character ('\0') is reached.
|
||||
// Lastly, if you remember that 'curses_cursor' is string "\033[---;---H", we're just writing two numbers to "---" parts, from 0 to 999 inclusively.
|
||||
// We're adding one because ASCII uses 1 ... 1000, and we use 0 ... 999, so it doesn't causes hidden bugs, and realign them to be prefixed with '0' characters.
|
||||
// If we have set of values (x = 31, y = 37), then the copied string would look like "\033[031;037H". It's not complex as it may sound.
|
||||
// And remember our ASCII table, thing scary thing (\033) is just octal value for number 27, which is CHARACTER_ESCAPE, hence the escape sequences start with it.
|
||||
x %= 1000;
|
||||
y %= 1000;
|
||||
|
||||
string_copy_limit (curses_cursor + 2, string_realign (number_to_string (y + 1), 3, '0'), 3);
|
||||
string_copy_limit (curses_cursor + 6, string_realign (number_to_string (x + 1), 3, '0'), 3);
|
||||
|
||||
string_copy_limit (& curses_screen [CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height], curses_cursor, CURSES_CURSOR); // Actual rendering.
|
||||
}
|
||||
|
||||
void curses_render_character (char character, int colour, int effect, int x, int y) {
|
||||
if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) { // If any of these are true, we don't render.
|
||||
return;
|
||||
}
|
||||
// Again, lets show some code formatting examples:
|
||||
// if ((x < 0)
|
||||
// || (y < 0)
|
||||
// || (x > curses_screen_width - 1)
|
||||
// || (y > curses_screen_height - 1)) {
|
||||
// return;
|
||||
// }
|
||||
// Or, if you don't want to subtract one from values there:
|
||||
// if ((x < 0) ||
|
||||
// (y < 0) ||
|
||||
// (x >= curses_screen_width) ||
|
||||
// (y >= curses_screen_height)) {
|
||||
// return;
|
||||
// }
|
||||
// Or if you really hate adding 2 more lines of code and curly braces:
|
||||
// if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) return;
|
||||
|
||||
string_copy_limit (curses_screen_offset (x, y), curses_format_character (character, colour, effect), CURSES_FORMAT); // Again, actual rendering, copying a value to offset.
|
||||
}
|
||||
|
||||
void curses_render_background (char character, int colour, int effect) {
|
||||
int x, y; // We declare local variables like this, I usually don't initialize them right away.
|
||||
|
||||
for (y = 0; y != curses_screen_height; ++y) { // Iterating through rows (by height) of our framebuffer ('curses_screen').
|
||||
for (x = 0; x != curses_screen_width; ++x) { // Iterating through columns (by width) of our framebuffer.
|
||||
curses_render_character (character, colour, effect, x, y); // Now, we can use function 'curses_render_character' to simplify our life...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
We've mentioned before, in chapter zero, that you can implement 'string_*' functions by 'string_*_limit', and here's an example of that. Functions below are quite self
|
||||
explanatory, so you can do a "homework" of reading them and trying to understand what they'll do. Since I use very verbose naming style, I hope that won't be a problem...
|
||||
*/
|
||||
|
||||
int curses_render_string (char * string, int colour, int effect, int x, int y) { // Keep in mind, we could reimplement this to be very similar to "limited" version.
|
||||
return (curses_render_string_limit (string, string_length (string), colour, effect, x, y));
|
||||
}
|
||||
|
||||
int curses_render_number (int number, int colour, int effect, int x, int y) { // This is not particularly smart solution, but it's simple to understand.
|
||||
return (curses_render_string (number_to_string (number), colour, effect, x, y));
|
||||
}
|
||||
|
||||
int curses_render_string_limit (char * string, int limit, int colour, int effect, int x, int y) { // This uses short "fix" for blank characters, but we'll align them better later.
|
||||
int offset;
|
||||
|
||||
for (offset = 0; offset != limit; ++offset) {
|
||||
if (string [offset] == '\n') {
|
||||
x = 0;
|
||||
y += 1;
|
||||
} else if (string [offset] == '\t') {
|
||||
x += 8;
|
||||
} else {
|
||||
curses_render_character (string [offset], colour, effect, x, y);
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (limit);
|
||||
}
|
||||
|
||||
int curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y) { // Finally, this will align the number to the right and limit it as well.
|
||||
return (curses_render_string_limit (number_to_string (number), limit, colour, effect, x, y));
|
||||
}
|
||||
|
||||
#endif
|
@ -1,177 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_2_HEADER
|
||||
#define CHAPTER_2_HEADER
|
||||
|
||||
#include <stdio.h> // We need this header file for functions 'puts' and 'printf'.
|
||||
#include <unistd.h> // And in this header file we have write system call.
|
||||
|
||||
/*
|
||||
Lets talk about function declaration. In C, sometimes you need to declare functions that you'll use before they're called. I prefer to always declare functions except when I'm
|
||||
quickly prototyping something out. Function declarations are property of old programming languages, you don't have to use them if you're calling every function after it's defined
|
||||
(but not declared, there's a difference), but if that function is only used in one file, you should use 'static' keyword like in the example below. Keyword 'extern' tells the
|
||||
compiler that your function will be used in other files, or if you're compiling your files separately, it won't add the function address (in some cases). In any case, function
|
||||
declarations should be in C header file, and function definitions should be in C source file.
|
||||
|
||||
@C
|
||||
// Function declaration: // # // Output: // Input:
|
||||
extern void function_0 (void); // 0 // undefined (none) // undefined (none)
|
||||
extern float function_1 (double a); // 1 // ieee754_32b // ieee754_64b
|
||||
extern int function_2 (int a, int b); // 2 // integer_32b // integer_32b, integer_32b
|
||||
static unsigned int function_3 (char * a, int b, char c); // 3 // natural_32b // integer_8b_pointer, integer_32b, integer_8b
|
||||
static char * function_4 (char * a); // 4 // integer_8b_pointer // integer_8b_pointer
|
||||
static void * function_5 (struct s * a, void * b); // 5 // undefined_pointer // [structure]_pointer, undefined_pointer
|
||||
|
||||
extern unsigned long int * PLEASE_NO (const signed short int, void *, const char const * *, long double []);
|
||||
@
|
||||
|
||||
So, lets go through those 6 normal functions, and then lets see what's wrong with 'PLEASE_NO' function.
|
||||
|
||||
0) This is the simplest form of function, which can be used to make the code more clear (sometimes) by breaking down long functions into smaller ones, and calling them in certain
|
||||
order. However, don't abuse it for code refactoring, because most of the time, procedural code is the easiest to read, write and debug.
|
||||
1) In C (and in non-embedded environment, also known as your normal post-2012 laptop or computer), keyword 'float' is (almost) real number encoded in IEEE754, 32 bits or 4 bytes
|
||||
long, while keyword 'double' is same as float, but enlarged to 64 bits or 8 bytes. They are pain to use in some cases because of limited precision.
|
||||
2) Functions can have multiple arguments (input variables), but only one return value (output "variable"). In C, you can name the arguments, but not the return value, also keep in
|
||||
mind that you should choose descriptive names (in most cases) for function arguments, not name them a, b, c, d like in these examples.
|
||||
3) If you really want the compiler to verify that some return value is of type 'unsigned int' aka 'uint32_t', you can do so, I prefer 'int' since all number literals in C are
|
||||
integers by default ('int'). We'll use terms like 'int / integer/ int32_t' interchangably, so keep that in mind in future chapters.
|
||||
4) Also, type 'char *' can be pointer to (address of) of some 'char' typed variable or array of type 'char'. We'll talk more about arrays in future chapters, but know that we
|
||||
could use 'char a []' in functions 3 and 4 as well, it's same. You could use them both in order to know which is array and which is pointer.
|
||||
5) Lastly, this function returns pointer to any type, hence compiler won't do type-checking, it's necesary to this sometimes. It also accepts pointer to (previously defined)
|
||||
structure 's', and if you used 'typedef' with it, you could just say 's * a', we'll talk more about those.
|
||||
|
||||
Now, lets talk very briefly about what's wrong with 'PLEASE_NO':
|
||||
|
||||
- Don't use long types 'unsigned long int', if you really type with those characteristics, use 'size_t : stdlib', 'uint64_t : stdint' or 'unsigned long' in worst case.
|
||||
- Don't use 'const' keyword at all, that's my honest advice, it won't catch that many potential bugs in C, it'll just cause useless compiler warnings. Live unsafe, think more.
|
||||
- Don't use 'signed', since it's default for any integer type, as well as you shouldn't use keywords 'register', 'volatile', 'auto', but more on that later...
|
||||
- You can use '[]' in function declarations, but it doesn't mean much since array is passed as pointer to first element in C (array decay), so '*' is enough.
|
||||
- Keep in mind that newer (non-ANSI) C standards allow some of the things that I'm not using here, but I don't personally like newer C standards, I'll mention that a lot.
|
||||
- Last one is tricky, you should name function agruments in function declarations, but some linters will warn you not to do it, since some compiler don't check them.
|
||||
|
||||
Very soon, you'll be able to write your own small C programs, so prepare for it.
|
||||
|
||||
This is probably the first program new programmers write in language they're learning. It simply prints text to standard output. As C is very old programming language, you have a
|
||||
lot of ways to do it, so we'll simply put all of them into array of function pointers, and call them sequentially in loop in our main function.
|
||||
*/
|
||||
|
||||
extern void hello_world_0 (void); // All 4 functions take no agruments, and return nothing. They just execute the code that's defined in them.
|
||||
extern void hello_world_1 (void);
|
||||
extern void hello_world_2 (void);
|
||||
extern void hello_world_3 (void);
|
||||
|
||||
extern void (* hello_world [4]) (void); // External (global) variable with name 'hello_world' of type 'array of 4 function pointers with no input/output'.
|
||||
|
||||
/*
|
||||
Alright, we'll never use those 4 functions, but now you know how to declare any function, it's time for more serious examples. Below you'll see some scary looking cursed functions
|
||||
that interact with the terminal in which we're executing this program. We'll change the terminal from default (sane) mode to raw (insane) mode. That means the characters we press
|
||||
on our keyboard won't be printed in terminal, like usual. That'll allow us to make cursed C program that'll scare away all GUI fanatics. In the ancient times, when people used
|
||||
teletypewriters, terminal was obscure magic. Nowdays, parents scare their children into obedience by showing them terminals. Like, if you don't behave, I'll edit X11 scripts and
|
||||
you won't be able to see icons, you won't pass the Login text on your screen, you'll never open your web browser again (lynx rules).
|
||||
*/
|
||||
|
||||
#include <termios.h> // Terminal input, output and configuration.
|
||||
#include <sys/ioctl.h> // Rest is some Linux related cancer...
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "chapter_0.h" // We use functions declared in these two header files, so in order to make them visible, we included those files here.
|
||||
#include "chapter_1.h"
|
||||
|
||||
#define SIGNAL_ARROW_UP (0X415B1B) // Just hardcoding some arrow key ASCII escape sequences, we'll explain them in later chapters.
|
||||
#define SIGNAL_ARROW_DOWN (0X425B1B)
|
||||
#define SIGNAL_ARROW_RIGHT (0X435B1B)
|
||||
#define SIGNAL_ARROW_LEFT (0X445B1B)
|
||||
|
||||
#define SIGNAL_CONTROL (0X1000000) // We're also defining some signal masks, they'll be used later to mark which key combination is pressed.
|
||||
#define SIGNAL_SHIFT (0X2000000)
|
||||
#define SIGNAL_ALTERNATE (0X4000000)
|
||||
#define SIGNAL_SYSTEM (0X8000000)
|
||||
|
||||
#define CURSES_FORMAT ((int) sizeof ("\033[-;3-m-\033[0m") - 1) // We'll use these three macros through-out cursed functions.
|
||||
#define CURSES_REVERT ((int) sizeof ("\033[H") - 1)
|
||||
#define CURSES_CURSOR ((int) sizeof ("\033[---;---H") - 1)
|
||||
|
||||
enum { // This enumeration will be used for signal processing.
|
||||
SIGNAL_NONE,
|
||||
SIGNAL_ANY,
|
||||
SIGNAL_A, SIGNAL_B, SIGNAL_C, SIGNAL_D, SIGNAL_E, SIGNAL_F, SIGNAL_G, SIGNAL_H,
|
||||
SIGNAL_I, SIGNAL_J, SIGNAL_K, SIGNAL_L, SIGNAL_M, SIGNAL_N, SIGNAL_O, SIGNAL_P,
|
||||
SIGNAL_Q, SIGNAL_R, SIGNAL_S, SIGNAL_T, SIGNAL_U, SIGNAL_V, SIGNAL_W, SIGNAL_X,
|
||||
SIGNAL_Y, SIGNAL_Z, SIGNAL_0, SIGNAL_1, SIGNAL_2, SIGNAL_3, SIGNAL_4, SIGNAL_5,
|
||||
SIGNAL_6, SIGNAL_7, SIGNAL_8, SIGNAL_9, SIGNAL_ESCAPE, SIGNAL_TABULATOR, SIGNAL_RETURN, SIGNAL_NEW_LINE,
|
||||
SIGNAL_SLASH, SIGNAL_BACKSLASH, SIGNAL_QUOTE, SIGNAL_BACKQUOTE, SIGNAL_SPACE, SIGNAL_BACKSPACE, SIGNAL_DOT, SIGNAL_COMMA,
|
||||
SIGNAL_QUOTATION, SIGNAL_CAPS_LOCK, SIGNAL_L_BRACKET, SIGNAL_R_BRACKET, SIGNAL_MINUS, SIGNAL_EQUAL,
|
||||
SIGNAL_COUNT
|
||||
};
|
||||
|
||||
/*
|
||||
These below are external variables, they can be accessed and modified in any file where they're (re)declared. We're compiling C source files (.c) separately, so the compiler will
|
||||
output object files (.o), and then we can link them together (most compilers are also linkers, this is nothing out of ordinary) into final executable. We can, for example, use
|
||||
functions declared in "chapter_2.h" and defined in "chapter_2.c" in completely separate file "chapter_3.c", if we have included it with '#include "chapter_2.h". Keep in mind that
|
||||
header files can be included recursively, that's why we use those header guards, '#ifndef SOMETHING', '#define SOMETHING' and '#endif' at the end. That way, they'll be included
|
||||
only once, so compiler won't be confused and spit out errors.
|
||||
|
||||
Now, lets talk about ASCII escape sequences. Most terminals support some special combination of characters, that aren't printed like you'd normally expect. We call them escape
|
||||
sequences because they all start with character literal '\033', '\x1b' or '\e', which is same as decimal 27, and in our character enumeration 'CHARACTER_ESCAPE'. They are somewhat
|
||||
standardized way of interacting with your terminal. I'll use '\033' here because it does well with C strings and it's the most widely supported. Also, we'll use formatting of
|
||||
'printf' function from <stdio.h>, which we'll need to cover later in more details.
|
||||
|
||||
"\033[H": Set cursor position to upper left corner (position 1, 1).
|
||||
"\033[%i;%iH": Set cursor position at Y and X coordinates, ranging from 1 to N (I simply limit N as 1000, and they can be prefixed with '0's).
|
||||
"\033[6n": Get current cursor position, this will output response string defined just below.
|
||||
"\033[%i;%iR": Response is similar to 'set-cursor-position' string, except it's 'R' instead of 'H', so you'll need to parse it.
|
||||
"\033[2K": Clear an entire line.
|
||||
"\033[2J": Clear an entire screen.
|
||||
"\033[c": Reset terminal to initial state, we won't use this string, but 'tcsetattr' function.
|
||||
"\033[%dA": Move cursor N characters up, if you don't provide N, it's assumed to be equal to 1.
|
||||
"\033[%dB": Move cursor N characters down, if you don't provide N, it's assumed to be equal to 1.
|
||||
"\033[%dC": Move cursor N characters right, if you don't provide N, it's assumed to be equal to 1.
|
||||
"\033[%dD": Move cursor N characters left, if you don't provide N, it's assumed to be equal to 1. Multiple cursors edited this file...
|
||||
"\033[%d;3%dm": Set character attributes (effect and colour) to types described below, first is effect, second is colour.
|
||||
"\033[0m": Reset character attributes to default state.
|
||||
|
||||
Effects: Colours:
|
||||
- Normal: 0 - Grey: 0
|
||||
- Bold: 1 - Red: 1
|
||||
- Italic: 3 - Green: 2
|
||||
- Underline: 4 - Yellow: 3
|
||||
- Blink: 5 - Blue: 4
|
||||
- Reverse: 7 - Pink: 5
|
||||
- Cyan: 6
|
||||
- White: 7
|
||||
|
||||
Instead of hardcoding values yourself and remembering those, you can just use enumerations from "chapter_0.h", like, COLOUR_BLUE and EFFECT_BOLD.
|
||||
*/
|
||||
|
||||
extern int curses_character; // Literal character (integer) that user has pressed, some of them are more than single byte, like arrow keys defined above.
|
||||
extern int curses_signal; // Converted value of 'curses_character' into values of macros and enumeration values named as 'SIGNAL_*'.
|
||||
extern int curses_screen_width; // Width of terminal window in which we're rendering.
|
||||
extern int curses_screen_height; // Height of terminal window in which we're rendering.
|
||||
extern int curses_active; // As long as there's no termination signal, this variable will be different from zero. We use it to exit the program.
|
||||
|
||||
extern void curses_configure (void); // I like using the approach of "just do it" instead of "begin" and "end", due to that we'll see more internal functions and variables.
|
||||
extern void curses_synchronize (void); // This function needs to be explained in more details, that'll be done in file "chapter_2.c".
|
||||
|
||||
extern void curses_bind (int signal, void (* action) (void)); // Binding specific function pointer to some key, so each time key is pressed, function is executed.
|
||||
extern void curses_unbind (int signal); // Unbinding any function that was bound to value of 'signal'.
|
||||
|
||||
extern void curses_render_cursor (int x, int y); // Render terminal cursor at position X and Y.
|
||||
|
||||
extern void curses_render_character (char character, int colour, int effect, int x, int y); // Render single character at position X and Y.
|
||||
extern void curses_render_background (char character, int colour, int effect); // Render entire buffer with the same character.
|
||||
|
||||
// Remember that in chapter zero, I've separated 'string_*' and 'string_*_limit' functions. Now, there's always more ways to logically organize your code, for example, as below:
|
||||
extern int curses_render_string (char * string, int colour, int effect, int x, int y);
|
||||
extern int curses_render_number (int number, int colour, int effect, int x, int y);
|
||||
extern int curses_render_string_limit (char * string, int limit, int colour, int effect, int x, int y);
|
||||
extern int curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y);
|
||||
// I really hope that you already know what these functions do just by looking at the names of the arguments...
|
||||
|
||||
#endif
|
@ -1,193 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_3_SOURCE
|
||||
#define CHAPTER_3_SOURCE
|
||||
|
||||
#include "chapter_3.h"
|
||||
|
||||
/*
|
||||
This is probably a good time to show you how switch statement works, and two (in my opinion best) ways to align them, but I advise against mixing two alignments you'll see in
|
||||
'print_colour' and 'print_format' functions. Also, it's a good practice to always use 'default' keyword at the end of you switch statements. You don't always need to use 'break'
|
||||
at the end of 'case', but we intend to "end" the switch statement there, so it's fine. Cases can be "fall-through", so if you don't put 'break', it'll execute the code in the next
|
||||
one, or you could have several of cases before any code. I don't like to use them like that, but here are few examples:
|
||||
|
||||
@C
|
||||
// If argument 'character' is any of the uppercase letters, it returns TRUE, if lowercase, it returns FALSE, otherwise exits the program.
|
||||
|
||||
static bool hexadecimal_letter_is_uppercase (char character)
|
||||
switch (character) {
|
||||
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return (TRUE); // We don't need to break here, because these two return from function.
|
||||
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return (FALSE); // If there was code after the switch statement, we would use 'break' keyword.
|
||||
default: exit (EXIT_FAILURE); // We don't need to break here yet again, because we exit a program!
|
||||
}
|
||||
}
|
||||
@
|
||||
|
||||
You can use only integers in cases, and remember, 'char' is implicitly promoted to 'int', aka integer. And important thing to keep in mind is that you can write every switch
|
||||
statement as if-else statement, but it would be longer, more error-prone, some lines of code would repeat, and many more inconveniences would come, especially because in C, cases
|
||||
can fall-through, like in the example below. Sad truth is, people are dumb, and many compilers and linters will output a warning message complaining that you used fall-through
|
||||
case(s) in your switch statement. Lets explain what 'echo_one_by_one' function would do.
|
||||
|
||||
@C
|
||||
// Print out some stuff, it'll look like this for these inputs (and it'll do nothing for input that isn't between 0 and 3 (inclusive)):
|
||||
// echo_one_by_one (0) => "Zero One Two Three"
|
||||
// echo_one_by_one (1) => "One Two Three"
|
||||
// echo_one_by_one (2) => "Two Three"
|
||||
// echo_one_by_one (3) => "Three"
|
||||
|
||||
static void echo_one_by_one (int number) {
|
||||
switch (number) {
|
||||
case 0: echo ("Zero ");
|
||||
case 1: echo ("One ");
|
||||
case 2: echo ("Two ");
|
||||
case 3: echo ("Three ");
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@
|
||||
|
||||
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, 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". 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 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) ("/", 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 list, int file, char * string) {
|
||||
switch (format_id) { // We use character '%' this time, same as 'printf' function does, lets see...
|
||||
case '%': {
|
||||
(* printing) ("%", 1, file, string);
|
||||
} break;
|
||||
case 'i': {
|
||||
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.
|
||||
char * format;
|
||||
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 * 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) ("?", 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 '%': { ... } 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).
|
||||
}
|
||||
|
||||
/*
|
||||
Before we take a look at the actual implementation of our simple 'print' function, here's how you'd use it.
|
||||
|
||||
@C
|
||||
print ("My integer is %i.\n", 404); // Prints "My integer is 404." with new line.
|
||||
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.
|
||||
*/
|
||||
|
||||
static void print_select (char * format, va_list list, int file, char * string) {
|
||||
int offset, length;
|
||||
|
||||
length = string_length (format);
|
||||
|
||||
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], 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], list, file, string); // We're calling function that will format our agruments, so we pass variable 'list'.
|
||||
} else {
|
||||
(* printing) (& format [offset], 1, file, string); // Not a special character? Okay, we'll just print them one by one.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
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
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
|
||||
|
||||
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
|
||||
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_3_HEADER
|
||||
#define CHAPTER_3_HEADER
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_1.h"
|
||||
|
||||
/*
|
||||
Now, time has come to talk about (sadly) the most important standard library function in C programming language. Function 'printf' is like some semi-drunk old man, stumbling on
|
||||
the streets, talking to himself, talking to other people who try to ignore him, but if you stand and talk to him, he'll sometimes start talking about random things, and sometimes
|
||||
he'll stay on-topic. So, what does function 'printf' do? Essentially, it'll print formatted string to standard output, formatted in this case means that in the main string (first
|
||||
argument to 'printf' function) there are special characters that'll accept variadic arguments, format them (convert them to string in special manner) and output that final string.
|
||||
|
||||
It's located in <stdio.h> header file, and it's commonly used through-out most C programs. You can learn more about it from manual pages with command 'man 3 printf', and below you
|
||||
can see some variations of that function. I just realigned them and added 'extern', which C assumes as default (but some compilers would generate a warning for that). We'll learn
|
||||
to implement variadic functions as well in this chapter, but I consider that a bad practice, even tho they can be extremely useful. Functions we learned to declare and define so
|
||||
far have constant and limited amount of arguments, while variadic functions can have any amount of arguments greater than one, as you can see below.
|
||||
|
||||
@C
|
||||
extern int printf ( const char * format, ...);
|
||||
extern int sprintf (char * str, const char * format, ...);
|
||||
extern int snprintf (char * str, size_t size, const char * format, ...);
|
||||
extern int fprintf (FILE * stream, const char * format, ...);
|
||||
extern int dprintf (int fd, const char * format, ...);
|
||||
|
||||
extern int vprintf ( const char * format, va_list ap);
|
||||
extern int vsprintf (char * str, const char * format, va_list ap);
|
||||
extern int vsnprintf (char * str, size_t size, const char * format, va_list ap);
|
||||
extern int vfprintf (FILE * stream, const char * format, va_list ap);
|
||||
extern int vdprintf (int fd, const char * format, va_list ap);
|
||||
@
|
||||
|
||||
Why do I dislike them? Because they're implemented with the help of macros from <stdarg.h> header file, in not particularly good way, and I dislike using 'va_*' because I simply
|
||||
don't like the naming style. Also, you can't properly verify that you won't cause tiny bugs in your program, so before writing 'printf' function, you need to think, which is
|
||||
something I don't like to do mostly. For example, if you pass a 'float' type variable to 'printf' function, with "%f" in its' format, you need to convert it to 'double', because
|
||||
that 'va_arg' macro accepts only fully promoted types, which we'll explain below. All that aside, they can be very useful in specific cases!
|
||||
|
||||
You'd use it something like these few examples below, and it's a good practise not to mix 'printf' with 'write' functions (if you're using both of them), because they're
|
||||
synchronized differently. Because of how they're buffered, mixing them can (in some cases) end up with mixed output.
|
||||
|
||||
@C
|
||||
// And ignore the alignment here, it's just an usage example. You can put all of this into 'main' function in some temporary file, compile it and run it.
|
||||
|
||||
int a = 1; printf ("Integer: %d\n", a);
|
||||
char b = 'A'; printf ("Character: %c\n", b);
|
||||
char * c = "Heyo"; printf ("%s world!\n", c);
|
||||
float d = 1.0F; printf ("%f\n", (double) d);
|
||||
double e = 1.0; printf ("%f\n", e);
|
||||
uint32_t f = 0X0011AAU; printf ("0X%X\n", f);
|
||||
size_t g = sizeof (a); printf ("%ul\n", g);
|
||||
int * h = & a; printf ("\t%p", (void *) h);
|
||||
printf ("\nCyaa world!\n" );
|
||||
|
||||
// You can have more than one arguments, like this:
|
||||
|
||||
printf ("%s world!\n Our integer is %d.\n Our character is '%c'.\nCyaa world!\n", c, a, b);
|
||||
|
||||
// Never do this:
|
||||
|
||||
char * message = "Heyo world!";
|
||||
|
||||
printf (message);
|
||||
@
|
||||
|
||||
Now, don't get scared, C is old language, and I'm aware that this looks like someone made a joke 50 years ago and nobody understood it, they took it serious. In the end, variadic
|
||||
argument list type 'va_list' is black magic, as well as other 'va_*' stuff. Since 'va_arg' takes only fully promoted types, we're left with types 'int', 'double' and 'char *'
|
||||
pretty much. Also, keep in mind that you don't need more arguments in 'printf' function, only that "const char * fmt" needs to be there always. It may take some time to get used
|
||||
to this function, but you can use it for very quick and easy debugging. If you're not sure what value your variables hold at some point, you can just print it there.
|
||||
|
||||
I'll show you how to implement variadic argument functions, and we'll use these in few places, but I'm still not a big fan of them. Of course, we won't implement everything that
|
||||
'printf' function from standard library has, or any more of its' alternatives, just these three below. However, we'll add few small additions, like colouring text with ASCII
|
||||
escape sequences and doing some basic formatting. Once you learn C better, you should take a look at manual pages for functions that you find interesting. Or even better, instead
|
||||
of reading stuff, try to implement them yourself, it's the best approach to learning anything.
|
||||
*/
|
||||
|
||||
extern void print ( char * format, ...); // Notice the "...", which means it can accept any amount of arguments after that 'format'.
|
||||
extern void file_print (int file, char * format, ...); // Same as above, but it won't print to terminal, it'll write it to a file descriptor instead.
|
||||
extern void string_print (char * string, char * format, ...); // Same as above again, but it'll copy it to some string.
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user