184 lines
16 KiB
C
184 lines
16 KiB
C
/*
|
|
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' in 'hello_world' functions below.
|
|
#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. You can see how I declare functions in '.h' files, and define them in '.c' files,
|
|
unless I don't want to make some function external in some '.c' file, I use 'static' there.
|
|
|
|
@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. Also, the most important chemical element for organisms is, you guessed it, C (carbon). It's
|
|
the same in programming too, C is the most important language that a good programmer should know. Other languages are just extra. Now:
|
|
|
|
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. You can see how I use those chapters to create new programs, and all that is in 'program/' folder. You can also just
|
|
open some 'compile.sh' file or 'makefile' and see how I compile and link.
|
|
|
|
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.
|
|
|
|
extern void curses_render_rectangle_fill (char character, int colour, int effect, int x, int y, int width, int height); // Guess what these functions do...?
|
|
extern void curses_render_rectangle_line (char character, int colour, int effect, int x, int y, int width, int height);
|
|
|
|
// 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
|