anon 5 months ago
parent
commit
b61756a718
18 changed files with 1126 additions and 136 deletions
  1. +37
    -0
      chapter/chapter_0.c
  2. +7
    -4
      chapter/chapter_0.h
  3. +5
    -5
      chapter/chapter_1.c
  4. +0
    -0
      chapter/chapter_1.h
  5. +37
    -21
      chapter/chapter_2.c
  6. +0
    -0
      chapter/chapter_2.h
  7. +193
    -0
      chapter/chapter_3.c
  8. +93
    -0
      chapter/chapter_3.h
  9. +330
    -0
      chapter/chapter_4.c
  10. +38
    -0
      chapter/chapter_4.h
  11. +209
    -0
      chapter/chapter_5.c
  12. +83
    -0
      chapter/chapter_5.h
  13. +0
    -67
      chapters/chapter_3.c
  14. +0
    -20
      chapters/chapter_3.h
  15. +26
    -6
      compile.sh
  16. +44
    -0
      program/example.c
  17. +7
    -0
      program/hello_world.c
  18. +17
    -13
      xhartae.c

chapters/chapter_0.c → chapter/chapter_0.c View File

@@ -181,6 +181,7 @@ int string_compare (char * string_0, char * string_1) {
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);
}
@@ -345,6 +346,42 @@ char * string_realign (char * string, int amount, char character) { //
}

/*
Ignore what next two functions do, it's about memory management that we'll cover in later chapters.
*/

int memory_compare (void * memory, void * source, int length) {
int offset;

char * cast_0 = (char *) memory;
char * cast_1 = (char *) source;

fatal_failure (memory == NULL, "memory_compare: Memory is null pointer.");
fatal_failure (source == NULL, "memory_compare: Source is null pointer.");

for (offset = 0; offset != length; ++offset) {
if (cast_0 [offset] != cast_1 [offset]) {
return (FALSE);
}
}

return (TRUE);
}

void memory_copy (void * memory, void * source, int length) {
int offset;

char * cast_0 = (char *) memory;
char * cast_1 = (char *) source;

fatal_failure (memory == NULL, "memory_copy: Memory is null pointer.");
fatal_failure (source == NULL, "memory_copy: Source is null pointer.");

for (offset = 0; offset != length; ++offset) {
cast_0 [offset] = cast_1 [offset];
}
}

/*
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.
*/

chapters/chapter_0.h → chapter/chapter_0.h View File

@@ -9,6 +9,10 @@ It is distributed in the hope that it will be useful or harmful, it really depen
#ifndef CHAPTER_0_HEADER
#define CHAPTER_0_HEADER

#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.

/*
> Code formatting

@@ -161,10 +165,6 @@ Now, I'll write some basic functions that'll be used later in the program, so yo
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
@@ -219,6 +219,9 @@ extern char * string_reverse_limit (char * string, int li

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.

extern int memory_compare (void * memory, void * source, int length); // We'll cover these functions later, they are more complex.
extern void memory_copy (void * memory, void * source, int length);

// 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.

chapters/chapter_1.c → chapter/chapter_1.c View File

@@ -174,8 +174,8 @@ 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 = lseek (file, 0, SEEK_END); // We set the offset to the end of the file.
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...

@@ -213,13 +213,13 @@ void * file_record (char * name) {
int size = -1;
char * data = NULL;

fatal_failure (name == NULL, "file_import: Failed to import file, name is null pointer.");
fatal_failure (name == NULL, "file_import: Failed to import file, name is null pointer."); // We should abort if the 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.
size = file_size (name) + 1; // We do it again, but only to get it's size and then increment it.
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_read (file, data, size - 1); // Rest if obvious. This could be implemented smarter. Try to notice why.

file = file_close (file);


chapters/chapter_1.h → chapter/chapter_1.h View File


chapters/chapter_2.c → chapter/chapter_2.c View File

@@ -160,7 +160,7 @@ int curses_character = 0;
int curses_signal = SIGNAL_NONE;
int curses_screen_width = 0;
int curses_screen_height = 0;
int curses_active = 1;
int curses_active = FALSE;

/*
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
@@ -190,8 +190,12 @@ C can efficiently write good programs using those libraries, please don't misund
our subprogram called 'curses'.
*/

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.

static void curses_initialize (void) { // This function will be called when 'curses_configure' is called, automatically.
struct winsize screen_dimension;
struct winsize screen_dimension; // We need this ugly structure for our 'ioctl' function to get the dimensions.
int screen_memory; // And you can use local variables to shorten some lines of code if you want.

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.
@@ -204,7 +208,7 @@ static void curses_initialize (void) {

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 [VMIN] = (unsigned char) 0; // Now it's time to modify it to be raw.
curses_new_terminal.c_cc [VTIME] = (unsigned char) 1;

curses_new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
@@ -214,6 +218,16 @@ static void curses_initialize (void) {

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.");

screen_memory = CURSES_FORMAT * curses_screen_width * curses_screen_height; // This is square area of our terminal, and multiplied by 12, size of FORMAT.

curses_screen = allocate (CURSES_REVERT + screen_memory + CURSES_CURSOR + 1); // We're requesting new memory for framebuffer.

curses_bind (SIGNAL_ESCAPE, curses_exit); // Binding universal exit key (signal).

string_copy (& curses_screen [0], "\033[H"); // ASCII black magic to always clear screen.

terminal_clear ();
}

static void curses_deinitialize (void) { // This function will be only called once, automatically, at the program exit.
@@ -221,6 +235,13 @@ static void curses_deinitialize (void) {
curses_activator = deallocate (curses_activator);
curses_action = deallocate (curses_action);

curses_action_count = 0;
curses_character = 0;
curses_signal = SIGNAL_NONE;
curses_screen_width = 0;
curses_screen_height = 0;
curses_active = FALSE;

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.
@@ -288,10 +309,10 @@ static char * curses_format_character (char character, int colour, int effect) {
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;
// effect = 0;
// break;
// case EFFECT_NORMAL: {
// effect = 0;
// effect = 0;
// } break;
case EFFECT_BOLD: effect = 1; break;
case EFFECT_ITALIC: effect = 3; break;
@@ -308,25 +329,16 @@ static char * curses_format_character (char character, int colour, int effect) {
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_active = TRUE; // This is important part, and something I like to do in my standalone libraries.

curses_bind (SIGNAL_ESCAPE, curses_exit); // Binding universal exit key (signal).
atexit (curses_deinitialize); // Deinitialization is automatically executed on exit point of the program, since we called 'atexit' function.

terminal_clear ();

string_copy (& curses_screen [0], "\033[H"); // ASCII black magic to always clear screen.
curses_initialize (); // Initializing curses, finally, yaay.
}

void curses_synchronize (void) {
@@ -342,11 +354,11 @@ void curses_synchronize (void) {

if (signal == '\033') { // And then we modify the actual 'curses_signal'.
curses_signal |= SIGNAL_ESCAPE;
} else if (character_is_digit (signal) != 0) {
} else if (character_is_digit ((char) signal) != 0) {
curses_signal |= SIGNAL_0 + (int) (signal - '0');
} else if (character_is_lowercase (signal) != 0) {
} else if (character_is_lowercase ((char) signal) != 0) {
curses_signal |= SIGNAL_A + (int) (signal - 'a');
} else if (character_is_uppercase (signal) != 0) {
} else if (character_is_uppercase ((char) signal) != 0) {
curses_signal |= SIGNAL_A + (int) (signal - 'A');
curses_signal |= SIGNAL_SHIFT;
} else {
@@ -358,6 +370,10 @@ void curses_synchronize (void) {
curses_action [signal] (); // We execute corresponding action (function).
}
}

if (curses_active == FALSE) { // Lastly, if we exited curses, we want to deinitialize.
curses_deinitialize (); // It's no problem if we do it more than once...
}
}

/*

chapters/chapter_2.h → chapter/chapter_2.h View File


+ 193
- 0
chapter/chapter_3.c View File

@@ -0,0 +1,193 @@
/*
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

+ 93
- 0
chapter/chapter_3.h View File

@@ -0,0 +1,93 @@
/*
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);

// Instead:
char * message = "Heyo world!";
printf ("%s", message);
// Or:
printf ("Heyo world!");
@

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

+ 330
- 0
chapter/chapter_4.c View File

@@ -0,0 +1,330 @@
/*
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_4_SOURCE
#define CHAPTER_4_SOURCE

#include "chapter_4.h"

/*
Of course, we could just write something like 'preview_unhighlighted_text_file' function (name is obviously a joke), but this would stylize (apply colour and effect character
attributes) to our entire text file. When we're writing programs, syntax highlighting makes a lot of difference to readability, the same way the code formatting does, and initial
program design structure. If you use a lot of external variables everywhere, the entire programs starts to be messy or difficult to maintain, write and debug (or both). However,
if you use a lot of variables, and you pass each of them separately into functions, or if you have one huge monolithic structure (this time literal 'struct'), you aren't doing
much better, except the compiler will have easier time to optimize your code, even tho that kind of code becomes pain to write. So, having few external functions, that do one
thing well, and having few external variables, that won't be edited outside of that file is the best in my opinion.

Your function calls won't be long, if you don't want to make those external ("global") variables visible to some other file, just move them to C source file instead of C header
file, and redeclare them as 'static', making them internal variables. Keep in mind, you have to use brain more in that case, and think about what you're modifying, where and why.
Well, if you want to write programs, and not to think about them, just close this and read Jin Ping Mei instead or something. There's no cheatsheet for making good programs, you
choose your constraints, your program design structure, and you start working on it. Sometimes you'll have to choose between performance, maintainability, simplicity or low memory
usage, and even if you are smart and manage to get three of them to work out in your project, fourth won't. I can't teach you how to choose, maybe you want to learn embedded or
game development, and they each have their own advantages and disadvantages.

@C
void preview_unhighlighted_text_file (char * text_file, int x, int y) {
char * text_data;

text_data = file_record (text_file);

for (curses_active = 1; curses_active != 0; ) {
curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL);
curses_render_string (text_data, COLOUR_WHITE, EFFECT_NORMAL, x, x);

curses_synchronize ();
}

text_data = deallocate (text_data);
}
@

So, lets write very basic C programming language syntax highlighting, explain how can we easily do it in little more than 150 lines of (scarily verbose and nicely aligned) code
and why we don't need regular expressions for it. You can use these 'syntax_*' functions to tokenize some source code, highlight the syntax of it or something else that I didn't
even think about if you're creative. Of course, we can use it to highlight syntax of some other programming language, not only C, and we'll use it later to highlight assembly,
Ada, C++, and maybe few more programming languages.

Note that regular expressions are way more powerful way of achieving the same results, and doing even more things like replacing some parts of the strings. This is simple solution
for simple problem. We'll define some internal variables below, functions 'syntax_delete' (that'll be called automatically when we exit the program), 'syntax_define' to make rules
about our character and string matching and 'syntax_select' to process our text file (which is just an array of character, also known as, low and behold, a string). Last function,
'syntax_select', will return index of the syntax rule that matches to our offset in string and store size of the match in 'length' variable, we'll look into it.
*/

static int syntax_count = 0; // Number of previously defined syntax rules.
static int syntax_active = FALSE; // Syntax "library" or subprogram was initialized if this value is TRUE.
static int * syntax_enrange = NULL; // Syntax rule can start with any character from 'syntax_begin' if this value is TRUE.
static int * syntax_derange = NULL; // Syntax rule can start with any character from 'syntax_end' if this value is TRUE.
static char * * syntax_begin = NULL; // Strings containing valid character (sub)sequence for begining the scan.
static char * * syntax_end = NULL; // Strings containing valid character (sub)sequence for ending the scan.
static char * syntax_escape = NULL; // Escape sequence for the rule, useful for line-breaks in C macros and line-based languages.
static int * syntax_colour = NULL; // Colour for our token, these two could be completely independent, but I like to keep them here.
static int * syntax_effect = NULL; // Effect for our token.

/*
Lets go in more details about how this function works. Standard library function 'atexit' will take as an argument function pointer of form 'extern void name (void)' that will,
imagine my shock, be executed at the exit point of our little program. We can make mistakes using it, if we don't think while we write our programs, the error will be obvious,
memory will be leaked or double-freed, Valgrind will detect it, we'd fix it. Also keep in mind, you can't have too much functions executed at the end, you can check for the value
of 'ATEXIT_MAX', which is at least 32 by some standards (POSIX).

So the goal is, we think a bit more when we structure our program, and we don't worry about if we forgot to deinitialize something. We'll reuse this in chapter five, but use
contra-approach, where we want to explicitly deinitialize syntax. If you take a look in the next function, 'syntax_define', you'll see that we'll use 'atexit' function only once,
when 'syntax_active' is FALSE, we'll change it to true, so 'atexit' won't be executed every time we call 'syntax_define', which is good. Lastly, in the 'syntax_delete' function,
we're just deallocating (freeing) the memory, so we don't leak it and generate Valgrind warnings.
*/

static void syntax_delete (void) {
int offset;

if (syntax_active == FALSE) { // If 'syntax' subprogram wasn't active, we don't want to deallocate memory, we just return.
return;
}

// We could reverse-loop through this without a local variable 'offset' using this approach, but I consider this bad for readability.
// --syntax_count;
// do {
// syntax_begin [syntax_count] = deallocate (syntax_begin [syntax_count]);
// syntax_end [syntax_count] = deallocate (syntax_end [syntax_count]);
// } while (--syntax_count != -1);

for (offset = 0; offset < syntax_count; ++offset) {
syntax_begin [offset] = deallocate (syntax_begin [offset]); // Since these two are arrays of strings, we need to deallocate, otherwise we'll leak memory.
syntax_end [offset] = deallocate (syntax_end [offset]); // We're basically freeing memory one by one string, you'll see below how we allocate it.
}

syntax_enrange = deallocate (syntax_enrange); // And now we're deallocating the rest of arrays, so no memory is leaked.
syntax_derange = deallocate (syntax_derange);
syntax_begin = deallocate (syntax_begin);
syntax_end = deallocate (syntax_end);
syntax_escape = deallocate (syntax_escape);
syntax_colour = deallocate (syntax_colour);
syntax_effect = deallocate (syntax_effect);

syntax_active = FALSE; // Lastly, I like to do this, but you don't have to. We'll use it in chapter five tho.
syntax_count = 0;
}

/*
In 'syntax_define' function we're reallocating (enlarging) memory, effectively adding a new element into our arrays, and assigning or copying data to them. These syntax rules will
be used with 'syntax_select' function to make our syntax highlighting. Lets explain what those function arguments do:

@C
static int syntax_define (int enrange, // Strict matching of string 'begin' in buffer range if FALSE, any character matching if TRUE.
int derange, // Strict matching of string 'end' in buffer range if FALSE, and again, any character matching if TRUE.
char * begin, // String of array of characters to begin matching.
char * end, // String of array of characters to end matching, I don't know why I explain these...
char escape, // Escape character, useful for C preprocessor.
int colour, // Colour.
int effect); // Effect, I hate explaining the code when the identifiers are descriptive.
@
*/

static int syntax_define (int enrange, int derange, char * begin, char * end, char escape, int colour, int effect) {
if (syntax_active == FALSE) { // If our syntax data isn't active, we'll execute this once.
syntax_active = TRUE; // Now we set it to active state, and:

atexit (syntax_delete); // Mark this function to be executed at program exit point.
}

fatal_failure (begin == NULL, "syntax_define: Begin string is null pointer."); // I don't like checking for errors, but here, voila.
fatal_failure (end == NULL, "syntax_define: End string is null pointer.");

++syntax_count;

syntax_enrange = reallocate (syntax_enrange, syntax_count * (int) sizeof (* syntax_enrange)); // Now, we have block of memory reallocation for syntax data:
syntax_derange = reallocate (syntax_derange, syntax_count * (int) sizeof (* syntax_derange));
syntax_begin = reallocate (syntax_begin, syntax_count * (int) sizeof (* syntax_begin));
syntax_end = reallocate (syntax_end, syntax_count * (int) sizeof (* syntax_end));
syntax_escape = reallocate (syntax_escape, syntax_count * (int) sizeof (* syntax_escape));
syntax_colour = reallocate (syntax_colour, syntax_count * (int) sizeof (* syntax_colour));
syntax_effect = reallocate (syntax_effect, syntax_count * (int) sizeof (* syntax_effect));

syntax_enrange [syntax_count - 1] = enrange; // In order to "make space" for our actual data.
syntax_derange [syntax_count - 1] = derange;
syntax_escape [syntax_count - 1] = escape;
syntax_colour [syntax_count - 1] = colour;
syntax_effect [syntax_count - 1] = effect;

syntax_begin [syntax_count - 1] = allocate ((string_length (begin) + 1) * (int) sizeof (* * syntax_begin)); // We need to allocate enough memory for our strings now.
syntax_end [syntax_count - 1] = allocate ((string_length (end) + 1) * (int) sizeof (* * syntax_end)); // Notice, we won't REallocate, just allocate!

string_copy (syntax_begin [syntax_count - 1], begin); // Finally, we're copying our strings into syntax data.
string_copy (syntax_end [syntax_count - 1], end);

return (syntax_count - 1); // We return the index, but we won't use it in this chapter.
}

/*
This is more complex, but if you use your eyes to look, your brain to comprehend and your heart to love, I'm sure that you'll understand it.
*/

static int syntax_select (char * string, int * length) {
int offset, select;

fatal_failure (syntax_active == FALSE, "syntax_select: Syntax is not active."); // Don't select without rules, abort!
fatal_failure (string == NULL, "syntax_select: String is null.");
fatal_failure (length == NULL, "syntax_select: Length is null.");

// In this first part of the function, we need to check if our syntax rule has been detected at the string offset we've provided. We're looping defined syntax rules and
// choosing whether to compare any of the characters, or full string, depending on 'syntax_enrange' value which is essentially boolean, true or false, which I express with
// 'int' type for "type-safety simplicity". Keep in mind that we're not returning or modifying the string we provided, so it won't be null-terminated, instead I think
// it's best to modify only variable 'length', hence we check with 'string_compare_limit' function.
for (select = offset = 0; select != syntax_count; ++select) { // We're looping defined syntax rules:
if (syntax_enrange [select] == FALSE) { // Choosing the comparisson:
if (syntax_derange [select] == FALSE) {
if (string_compare_limit (string, syntax_begin [select], string_length (syntax_begin [select])) == TRUE) { // Limiting our string comparisson.
break; // If strings are same, we exit the loop.
}
} else {
if ((string_compare_limit (string, syntax_begin [select], string_length (syntax_begin [select])) == TRUE)
&& (character_compare_array (string [offset + string_length (syntax_begin [select])], syntax_end [select]) == TRUE)) {
break;
}
}
} else { // Else, we compare any character.
if (character_compare_array (string [offset], syntax_begin [select]) == TRUE) { // With our obviously named function...
break; // We found it, exit the loop!
} // If we didn't, just continue.
}
} // And now we have our 'select' value.

// If there was no syntax rule detected, we need to return from a function, and increment the offset by setting variable 'length' to 1. If we don't increment it, at the
// first unrecognized character, our second nested-loop inside function 'preview_c_file' would use uninitialized or zero value, depending on how we structured our code
// before that. We also return 'syntax_count' as the syntax rule index, which is invalid, and would produce Valgrind warning if we didn't handle it. In my unimportant
// opinion, this if statement is the ugliest part of the function.
if (select >= syntax_count) {
* length = 1;
return (syntax_count);
}

// In this second part, we have our 'select' value, index of the syntax rule, and we want to know the 'length' value, by trying to match with 'syntax_end' string. We have
// to again, separate two cases for matching any character or full string, except that we use it to determine its' match-length. Important difference is also that there's
// special case where we have escape character matching, and where 'syntax_end' string is empty (but not NULL), so in that case we match only one character. We could have
// nested loop there, and second loop would need goto statement to exit it, so we only use one loop.
for (offset = 1; string [offset - 1] != '\0'; ++offset) { // Now, offset must be 1, and we loop...
if (string [offset] == syntax_escape [select]) { // Here's our escape exception.
++offset;
continue;
}

if (syntax_derange [select] == FALSE) { // Choosing what to compare, yet again...
if (string_compare_limit (& string [offset], syntax_end [select], string_length (syntax_end [select])) == TRUE) { // Again, we're comparing full string.
* length = offset + string_length (syntax_end [select]); // We found it, yaay!
break;
}
} else {
if (syntax_end [select] [0] == CHARACTER_NULL) { // And here's our empty string exception.
* length = offset;
break;
}
if (character_compare_array (string [offset], syntax_end [select]) == TRUE) {
* length = offset;
break;
}
} // These two loops look similar, but no!
} // And now we have our 'length' value.

return (select); // Lastly, return syntax rule index.
}

/*
Imagine my shock, we can now print coloured text, without regular expressions. Nothing much, we can print it without using 'curses_*' functions, but if we want to preview large,
well more than 24 line of code, we'd want to scroll it or modify it if we're making a text editor, hence, using curses is good. Lets see how our "mini-main" subprogram-like
function does its' work, and how we use 'syntax_*' functions in them.
*/

void preview_c_file (char * text_file, int width, int height, int x, int y) {
// Before we begin (Ada pun intended, remove this in final version), I won't (re)align 'separators' and 'keywords', because they fuck-up my comments, which I never write
// in my "official" programs. I write comments only here, to explain stuff in more details. Have fun... Oh, and type of variable 'keywords' an array of string pointers of
// automatic length, which we get with "sizeof (keywords) / sizeof (keywords [0])" part, for those keywords, it would be 32UL, and we cast it to integer.
char * separators = ".,:;<=>+*-/%!&~^?|()[]{}'\" \t\r\n";

char * keywords [] = {
"register", "volatile", "auto", "const", "static", "extern", "if", "else",
"do", "while", "for", "continue", "switch", "case", "default", "break",
"enum", "union", "struct", "typedef", "goto", "void", "return", "sizeof",
"char", "short", "int", "long", "signed", "unsigned", "float", "double"
};

char * text_data;

int word, reset_x, reset_y;

(void) width;
(void) height;

syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD); // Below, we're simply using our 'syntax_define' function.
syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
syntax_define (FALSE, FALSE, "#", "\n", '\\', COLOUR_YELLOW, EFFECT_ITALIC);
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);

for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
}

syntax_define (TRUE, FALSE, "()[]{}", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
syntax_define (TRUE, FALSE, ".,:;<=>+*-/%!&~^?|", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);

syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);

text_data = file_record (text_file); // And, imagine, importing our file data into a buffer!

reset_x = x;
reset_y = y;

for (curses_active = 1; curses_active != 0; ) { // We enter our main subprogram loop.
int offset, select, length;

curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL); // We need to clear the screen buffer before rendering.

x = reset_x;
y = reset_y;
select = syntax_count;
length = 0;

for (offset = 0; offset < string_length (text_data); offset += length) { // And it's time to start rendering our C file.
int suboffset, colour, effect;

select = syntax_select (& text_data [offset], & length); // Here we're evaluating variables 'select' and 'length'.

// We can do the same thing in 2 lines of code, but it's less readable in my opinion, I prefer longer verbose way below...
// colour = (select >= syntax_count) ? COLOUR_WHITE : syntax_colour [select];
// effect = (select >= syntax_count) ? EFFECT_NORMAL : syntax_effect [select];
// Or, if you find this more intuitive:
// colour = (select < syntax_count) ? syntax_colour [select] : COLOUR_WHITE;
// effect = (select < syntax_count) ? syntax_effect [select] : EFFECT_NORMAL;
if (select >= syntax_count) { // Here, we're handling error value of 'syntax_select'.
colour = COLOUR_WHITE;
effect = EFFECT_NORMAL;
} else {
colour = syntax_colour [select];
effect = syntax_effect [select];
}

for (suboffset = 0; suboffset < length; ++suboffset) { // Sadly, we need to render them one by one character.
if (text_data [offset + suboffset] == CHARACTER_LINE_FEED) { // Rendering of blank characters isn't counted, so:
x = reset_x; // If there's a new line, we need to reset 'x' value.
y += 1; // And increment 'y' value.
} else if (text_data [offset + suboffset] == CHARACTER_TAB_HORIZONTAL) { // If there's a tab, we offset 'x' value by normal count.
x += 8; // Normal indentation is 8-characters wide.
} else {
curses_render_character (text_data [offset + suboffset], colour, effect, x, y); // Finally, we can render it character by character.
x += 1;
}
}
}

curses_synchronize (); // Lastly, we synchronize our terminal.
}

text_data = deallocate (text_data); // And deallocate the memory when we exit the subprogram.
}

#endif

+ 38
- 0
chapter/chapter_4.h View File

@@ -0,0 +1,38 @@
/*
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_4_HEADER
#define CHAPTER_4_HEADER

#include "chapter_0.h"
#include "chapter_1.h"
#include "chapter_2.h"
#include "chapter_3.h"

/*
I believe that this chapter should be a breakpoint for you to write a simple C program. So far, we've learned in:

- chapter 0: To format our code properly in order to increase readability and we've implemented some core functions for memory management, strings and input / output.
- chapter 1: To declare and define functions, and we've covered character and file descriptor related functions, as well as ASCII table and discussed C keywords.
- chapter 2: To use external variables, function pointers and minor part of 'libncurses' reimplementation that doesn't care about portability.
- chapter 3: To use standard library 'printf' function, and to implement variadic argument functions, while also covering switch statement in more depth.

From this moment onwards, some chapters will have few functions called 'program_*', which we can use to forge even larger programs. They'll each have their own dependencies, for
example, some of them will require functions from some or all previous chapter source and header files, but I'll make sure not to use in them functions that'll be in future
chapters. Instead of that, we'll (re)implement newer stuff with different approach if necessary. That way, you can be sure that if you're reading chapter four, for example, it'll
only use functions and variables defined in chapters zero to three. Lets begin.

I'll write this huge 'preview_c_file' function in procedural style, so to say, and in the next chapter, we'll reimplement it in more modular way, using other functions. Learning
anything, including the C programming language, is like a journey. Maybe you think it won't last long, and it ends up being quite long journey, or maybe you think it'll be very
long, that you'll walk miles and miles, and it ends up being short (you rage-quit). The final destination you're going towards always depends on where you left-off, where you're
coming from. For example, if you wrote Ada, you'll like chapter four, if you wrote C++, you'll like chapter five.
*/

extern void preview_c_file (char * text_file, int width, int height, int x, int y);

#endif

+ 209
- 0
chapter/chapter_5.c View File

@@ -0,0 +1,209 @@
/*
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_5_SOURCE
#define CHAPTER_5_SOURCE

#include "chapter_5.h"

/*
So, what are actually getters and setters, and why you should never use them? Lets explain.

@C
static number_t game_get_screen_width (game_t * game) { return (game->screen_width); }
static number_t game_get_screen_height (game_t * game) { return (game->screen_height); }
static number_t game_set_screen_width (game_t * game, number_t width) { return (game->screen_width = width); }
static number_t game_set_screen_height (game_t * game, number_t height) { return (game->screen_height = height); }
@
*/

#define MEMORY_LIMIT (1024 * 1024)

static string_t memory_store [MEMORY_LIMIT];

static memory_t memorize (number_t size) {
static number_t memory_count = 0;

fatal_failure (memory_count + size >= MEMORY_LIMIT, "memorize: You have reached the 1 MiB memory limit.");

memory_count += size;

return ((memory_t) ((string_t) memory_store + memory_count - size));
}

static void move_player (void) { ++player.x; }

static void game_configure (void) {
curses_configure ();

curses_bind (SIGNAL_W, move_player);
curses_bind (SIGNAL_S, move_player);
curses_bind (SIGNAL_A, move_player);
curses_bind (SIGNAL_D, move_player);

game.active = curses_active;
game.screen_width = curses_screen_width;
game.screen_height = curses_screen_height;

player.x = 0;
player.y = 0;
}

static void game_synchronize (void) {
return;
}

game_t game;
player_t player;

skill_t * game_skill (string_t name, bundle_t * points, ...) {
skill_t * skill;
va_list list;
number_t action;

va_start (list, points);

skill = memorize ((int) sizeof (* skill));

string_copy ((skill->name = memorize (string_length (name) + 1)), name);
memory_copy ((skill->points = memorize ((int) sizeof (* skill->points))), points, (int) sizeof (* points));

for (;;) {
action = (number_t) va_arg (list, int);

if (action > 0) {
skill->positive_count += 1;
skill->positive = memorize ((int) sizeof (action));
skill->positive [skill->positive_count - 1] = (action_t) action;
} else break;
}

va_end (list);

return (skill);
}

attribute_t * game_attribute (string_t name, bundle_t * points, ...) {
attribute_t * attribute;
va_list list;
number_t action;

va_start (list, points);

attribute = memorize ((int) sizeof (* attribute));


string_copy ((attribute->name = memorize (string_length (name) + 1)), name);
memory_copy ((attribute->points = memorize ((int) sizeof (* attribute->points))), points, (int) sizeof (* points));

for (;;) {
action = (number_t) va_arg (list, int);

if (action > 0) {
attribute->positive_count += 1;
attribute->positive = memorize ((int) sizeof (action));
attribute->positive [attribute->positive_count - 1] = (action_t) action;
} else if (action < 0) {
attribute->negative_count += 1;
attribute->negative = memorize ((int) sizeof (action));
attribute->negative [attribute->negative_count - 1] = (action_t) action;
} else {
break;
}
}

va_end (list);

return (attribute);
}

void game_render_skill (skill_t * skill, number_t x, number_t y) {
curses_render_string (skill->name, COLOUR_CYAN, EFFECT_NORMAL, x, y);

curses_render_string ("[ ", COLOUR_GREY, EFFECT_BOLD, x + 18, y);
curses_render_string (format_to_string (skill->points->minimum, 0, 10, 4, ' '), COLOUR_RED, EFFECT_NORMAL, x = 22, y);
curses_render_string (format_to_string (skill->points->current, 0, 10, 4, ' '), COLOUR_WHITE, EFFECT_NORMAL, x = 26, y);
curses_render_string (format_to_string (skill->points->maximum, 0, 10, 4, ' '), COLOUR_GREEN, EFFECT_NORMAL, x = 30, y);
curses_render_string (" ]", COLOUR_GREY, EFFECT_BOLD, x = 34, y);
}

void game_render_attribute (attribute_t * attribute, number_t x, number_t y) {
curses_render_string (attribute->name, COLOUR_CYAN, EFFECT_NORMAL, x, y);

curses_render_string ("[ ", COLOUR_GREY, EFFECT_BOLD, x + 18, y);
curses_render_string (format_to_string (attribute->points->minimum, 0, 10, 4, ' '), COLOUR_RED, EFFECT_NORMAL, x = 22, y);
curses_render_string (format_to_string (attribute->points->current, 0, 10, 4, ' '), COLOUR_WHITE, EFFECT_NORMAL, x = 26, y);
curses_render_string (format_to_string (attribute->points->maximum, 0, 10, 4, ' '), COLOUR_GREEN, EFFECT_NORMAL, x = 30, y);
curses_render_string (" ]", COLOUR_GREY, EFFECT_BOLD, x = 34, y);
}

void play_game (void) {
bundle_t skill_points = { 10, 120, 0, 0 };
bundle_t attribute_points = { 1, 12, 0, 0 };

skill_t * blades, * axes, * bows, * spears, * puppet_magic, * nature_magic, * rune_magic, * charm_magic;
attribute_t * strength, * edurance, * wisdom, * agility;

strength = game_attribute ("Strength", & attribute_points, GAME_ACTION_SWING_BLADE, GAME_ACTION_SWING_AXE, -GAME_ACTION_CAMP, 0);
edurance = game_attribute ("Edurance", & attribute_points, GAME_ACTION_WALK, GAME_ACTION_CAMP, -GAME_ACTION_REST, 0);
wisdom = game_attribute ("Wisdom", & attribute_points, GAME_ACTION_CITE_RUNE, GAME_ACTION_CAST_CHARM, -GAME_ACTION_WALK, 0);
agility = game_attribute ("Agility", & attribute_points, GAME_ACTION_SHOOT_ARROW, GAME_ACTION_THROW_SPEAR, -GAME_ACTION_WAIT, 0);

blades = game_skill ("Blades", & skill_points, GAME_ACTION_SWING_BLADE, 0);
axes = game_skill ("Axes", & skill_points, GAME_ACTION_SWING_AXE, 0);
bows = game_skill ("Bows", & skill_points, GAME_ACTION_SHOOT_ARROW, 0);
spears = game_skill ("Spears", & skill_points, GAME_ACTION_THROW_SPEAR, 0);
puppet_magic = game_skill ("Puppet Magic", & skill_points, GAME_ACTION_SUMMON_PUPPET, 0);
nature_magic = game_skill ("Nature Magic", & skill_points, GAME_ACTION_CALL_NATURE, 0);
rune_magic = game_skill ("Rune Magic", & skill_points, GAME_ACTION_CITE_RUNE, 0);
charm_magic = game_skill ("Charm Magic", & skill_points, GAME_ACTION_CAST_CHARM, 0);

game_configure ();

while (curses_active) {
curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL);

curses_render_string ("Attributes:", COLOUR_WHITE, EFFECT_BOLD, 0, 0);

game_render_attribute (strength, 2, 1);
game_render_attribute (edurance, 2, 2);
game_render_attribute (wisdom, 2, 3);
game_render_attribute (agility, 2, 4);

curses_render_string ("Skills:", COLOUR_WHITE, EFFECT_BOLD, 0, 5);

game_render_skill (blades, 2, 6);
game_render_skill (axes, 2, 7);
game_render_skill (bows, 2, 8);
game_render_skill (spears, 2, 9);
game_render_skill (puppet_magic, 2, 10);
game_render_skill (nature_magic, 2, 11);
game_render_skill (rune_magic, 2, 12);
game_render_skill (charm_magic, 2, 13);

curses_render_character ('@', COLOUR_CYAN, EFFECT_BOLD, player.x, player.y);

curses_synchronize ();

game_synchronize ();
}

//~char*a,*b,*c;
//~a=memorize(12); string_copy(a,"heyo world\n"); terminal_colour(1,1); echo(a); terminal_cancel();
//~b=memorize( 3); string_copy(b,"!\n"); terminal_colour(2,1); echo(b); terminal_cancel();
//~c=memorize(12); string_copy(c,"cyaa world\n"); terminal_colour(3,1); echo(c); terminal_cancel();
//~out(memory_store,512);
//~terminal_colour(1,1); echo(number_to_string((int)a)); terminal_cancel();
//~terminal_colour(2,1); echo(number_to_string((int)b)); terminal_cancel();
//~terminal_colour(3,1); echo(number_to_string((int)c)); terminal_cancel(); echo("\n");
//~terminal_colour(1,1); echo(a); terminal_cancel();
//~terminal_colour(2,1); echo(b); terminal_cancel();
//~terminal_colour(3,1); echo(c); terminal_cancel();
}

#endif

+ 83
- 0
chapter/chapter_5.h View File

@@ -0,0 +1,83 @@
/*
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_5_HEADER
#define CHAPTER_5_HEADER

#include "chapter_0.h"
#include "chapter_1.h"
#include "chapter_2.h"
#include "chapter_3.h"

/*
Okay, we're finally on chapter five, and now we'll write something fun, not serious and boring. Before the Great Flood, when our ancestors were riding dinosaurs, building
pyramids, killing African men and mating with Asian women, people didn't have dedicated nor integrated graphical processing units, called GPUs. They only had their terminals,
built inside some of their spaceships. And what did they do with them? They played terminal rogue-like games, similar to those that archeologists discovered in ancient Egypt,
Syria, Sumeria, Greece and Atlantis. They were reconstructed around 50 years ago by some guy that made the game Rogue. So, all those myths, sagas and legends about Anubis,
Gilgamesh, Achilles, Inana, Gaea, they were just playable characters or main characters in those games, with different statistics, skills, attributes and back-story. Now, lets
make a simple terminal rogue-like game using what we wrote in previous chapters.

First of all, lets talk briefly about keyword 'typedef' and why I hate to use it.
*/

typedef int number_t;
typedef char * string_t;
typedef void * memory_t;

typedef enum action_t {
GAME_ACTION_NONE,
GAME_ACTION_WAIT, GAME_ACTION_WALK, GAME_ACTION_REST, GAME_ACTION_CAMP,
GAME_ACTION_SWING_BLADE, GAME_ACTION_SWING_AXE, GAME_ACTION_SHOOT_ARROW, GAME_ACTION_THROW_SPEAR,
GAME_ACTION_SUMMON_PUPPET, GAME_ACTION_CALL_NATURE, GAME_ACTION_CITE_RUNE, GAME_ACTION_CAST_CHARM,
GAME_ACTION_COUNT
} action_t;

typedef struct game_t {
number_t active, screen_width, screen_height;
} game_t;

typedef struct bundle_t {
number_t minimum, maximum, current, booster;
} bundle_t;

typedef struct skill_t {
string_t name;
number_t positive_count;
number_t learning_rate;
bundle_t * points;
action_t * positive;
} skill_t;

typedef struct attribute_t {
string_t name;
number_t positive_count, negative_count;
bundle_t * points;
action_t * positive, * negative;
} attribute_t;

typedef struct player_t {
string_t name;
number_t x, y;
bundle_t health, armour, mana, stamina;
attribute_t strength, edurance, intelligence, agility;
skill_t blades, axes, bows, spears;
skill_t puppet_magic, nature_magic, rune_magic, charm_magic;
} player_t;

extern game_t game;
extern player_t player;

extern skill_t * game_skill (string_t name, bundle_t * points, ...);
extern attribute_t * game_attribute (string_t name, bundle_t * points, ...);

extern void game_render_skill (skill_t * skill, number_t x, number_t y);
extern void game_render_attribute (attribute_t * attribute, number_t x, number_t y);

extern void play_game (void);

#endif

+ 0
- 67
chapters/chapter_3.c View File

@@ -1,67 +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"

void print (char * format, ...) {
va_list argument_array;

int offset, length;

int integer;
char * string;
double ieee754;

length = string_length (format);

va_start (argument_array, format);

for (offset = 0; offset != length; ++offset) {
if (format [offset] == '%') {
++offset;
if (format [offset] == '%') {
out ("%", 1);
} else if (format [offset] == 'i') {
integer = va_arg (argument_array, int);
echo (number_to_string (integer));
} else if (format [offset] == 'F') {
ieee754 = va_arg (argument_array, double);
echo (number_to_string ((int) ieee754));
} else if (format [offset] == 's') {
string = va_arg (argument_array, char *);
echo (string);
} else {
out ("?", 1);
}
} else if (format [offset] == '/') {
++offset;
if (format [offset] == '/') {
out ("/", 1);
}
} else {
out (& format [offset], 1);
}
}

va_end (argument_array);
}

void file_print (char * format, ...) {
(void) format;
return;
}

void string_print (char * format, ...) {
(void) format;
return;
}

#endif

+ 0
- 20
chapters/chapter_3.h View File

@@ -1,20 +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"

extern void print (char * format, ...);
extern void file_print (char * format, ...);
extern void string_print (char * format, ...);

#endif

+ 26
- 6
compile.sh View File

@@ -2,13 +2,33 @@

set -xe

gcc -g -Wall -Wextra -Wpedantic -Werror -O0 -c -o chapters/chapter_0.o chapters/chapter_0.c
gcc -g -Wall -Wextra -Wpedantic -Werror -O0 -c -o chapters/chapter_1.o chapters/chapter_1.c
gcc -g -Wall -Wextra -Wpedantic -Werror -O0 -c -o chapters/chapter_2.o chapters/chapter_2.c
gcc -g -Wall -Wextra -Wpedantic -Werror -O0 -c -o chapters/chapter_3.o chapters/chapter_3.c
clang -DSTATIC_SOURCE -g -Weverything -O0 -o xhartae xhartae.c

gcc -g -Wall -Wextra -Wpedantic -Werror -O0 -c -o xhartae.o xhartae.c
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_0.o chapter/chapter_0.c
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_1.o chapter/chapter_1.c
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_2.o chapter/chapter_2.c
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_3.o chapter/chapter_3.c
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_4.o chapter/chapter_4.c
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_5.o chapter/chapter_5.c

gcc -o xhartae xhartae.o chapters/chapter_0.o chapters/chapter_1.o chapters/chapter_2.o chapters/chapter_3.o
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o xhartae.o xhartae.c

gcc -o xhartae xhartae.o chapter/chapter_0.o chapter/chapter_1.o chapter/chapter_2.o chapter/chapter_3.o chapter/chapter_4.o chapter/chapter_5.o

#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_0.h
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_0.c
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_1.h
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_1.c
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_2.h
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_2.c
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_3.h
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_3.c
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_4.h
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_4.c
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_5.h
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_5.c
#~splint -weak -warnposix -retvalother -syntax -type xhartae.c

#~valgrind --show-leak-kinds=all --leak-check=full ./xhartae

exit

+ 44
- 0
program/example.c View File

@@ -0,0 +1,44 @@
#include <meme.h>
#include "meme.h"

// ASDF

/* ASDF */

enum {
REGISTER_0_64, REGISTER_1_64, REGISTER_2_64, REGISTER_3_64, REGISTER_4_64, REGISTER_5_64, REGISTER_6_64, REGISTER_7_64,
REGISTER_8_64, REGISTER_9_64, REGISTER_A_64, REGISTER_B_64, REGISTER_C_64, REGISTER_D_64, REGISTER_E_64, REGISTER_F_64,
REGISTER_0_32, REGISTER_1_32, REGISTER_2_32, REGISTER_3_32, REGISTER_4_32, REGISTER_5_32, REGISTER_6_32, REGISTER_7_32,
REGISTER_8_32, REGISTER_9_32, REGISTER_A_32, REGISTER_B_32, REGISTER_C_32, REGISTER_D_32, REGISTER_E_32, REGISTER_F_32,
REGISTER_0_16, REGISTER_1_16, REGISTER_2_16, REGISTER_3_16, REGISTER_4_16, REGISTER_5_16, REGISTER_6_16, REGISTER_7_16,
REGISTER_8_16, REGISTER_9_16, REGISTER_A_16, REGISTER_B_16, REGISTER_C_16, REGISTER_D_16, REGISTER_E_16, REGISTER_F_16,
REGISTER_0_8, REGISTER_1_8, REGISTER_2_8, REGISTER_3_8, REGISTER_4_8, REGISTER_5_8, REGISTER_6_8, REGISTER_7_8,
REGISTER_8_8, REGISTER_9_8, REGISTER_A_8, REGISTER_B_8, REGISTER_C_8, REGISTER_D_8, REGISTER_E_8, REGISTER_F_8,
REGISTER_0_8X, REGISTER_1_8X, REGISTER_2_8X, REGISTER_3_8X,
REGISTER_COUNT
};

static void (* encode [FORMAT_COUNT] [INSTRUCTION_COUNT]) (int instruction, int left, int right);

static int data [16] = { 0 };

static void data_push (int byte) { data [0]++; data [data [0]] = byte; }
static void data_pop (void) { data [data [0]] = 0X00; data [0]--; }
static int data_look (int look) { return (data [look]); }
static int data_size (void) { return (data [0]); }
static void data_free (void) { data [0] = 0; }

static void data_echo (void) {
char byte [4] = " ";
int i;

char character;

_meme = MEME = meme;

for (i = 0; i != data [0]; ++i) {
byte [0] = "0123456789ABCDEF" [data [i + 1] / '\020'];
byte [1] = "0123456789ABCDEF" [data [i + 1] % 'A'];
echo (byte);
}
}

+ 7
- 0
program/hello_world.c View File

@@ -0,0 +1,7 @@
#include <stdio.h>

int main (void) {
puts ("Hello world!");

return (0);
}

+ 17
- 13
xhartae.c View File

@@ -6,10 +6,21 @@ And when you do redistribute it or modify it, it will use either version 3 of th
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.
*/

#include "chapters/chapter_0.h"
#include "chapters/chapter_1.h"
#include "chapters/chapter_2.h"
#include "chapters/chapter_3.h"
#ifdef STATIC_SOURCE
#include "chapter/chapter_0.c"
#include "chapter/chapter_1.c"
#include "chapter/chapter_2.c"
#include "chapter/chapter_3.c"
#include "chapter/chapter_4.c"
#include "chapter/chapter_5.c"
#else
#include "chapter/chapter_0.h"
#include "chapter/chapter_1.h"
#include "chapter/chapter_2.h"
#include "chapter/chapter_3.h"
#include "chapter/chapter_4.h"
#include "chapter/chapter_5.h"
#endif

/*
About this "book":
@@ -121,16 +132,9 @@ int main (int argc, char * * argv) {

curses_configure ();

while (curses_active != 0) {
curses_render_background ('.', COLOUR_GREY, EFFECT_BOLD);
curses_render_character ('@', COLOUR_RED, EFFECT_BOLD, 1, 1);
curses_render_string ("Heyo world!", COLOUR_GREEN, EFFECT_ITALIC, 2, 2);
curses_render_number_limit (-420, 6, COLOUR_YELLOW, EFFECT_UNDERLINE, 0, 3);
preview_c_file ("program/example.c", curses_screen_width, curses_screen_height, 0, 0);

curses_synchronize ();
}

print ("%F\n%s\n%i\n", 69.1, "Heyo", 420);
play_game ();

return (EXIT_SUCCESS);
}

Loading…
Cancel
Save