2023-11-13 21:03:29 -05:00
/*
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
2023-11-26 11:58:59 -05:00
# include <stdio.h> // We need this header file for functions 'puts' and 'printf' in 'hello_world' functions below.
2023-11-13 21:03:29 -05:00
# 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
2023-11-26 11:58:59 -05:00
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 .
2023-11-13 21:03:29 -05:00
@ 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 .
2023-11-26 11:58:59 -05:00
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 :
2023-11-13 21:03:29 -05:00
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
2023-11-26 11:58:59 -05:00
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 .
2023-11-13 21:03:29 -05:00
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.
2023-11-28 15:30:55 -05:00
extern void curses_render_rectangle ( char character , int colour , int effect , int x , int y , int width , int height ) ; // Guess what this function does...?
2023-11-13 21:03:29 -05:00
// 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