xhartae/chapter/chapter_1.c

319 lines
15 KiB
C
Raw Normal View History

2023-11-13 21:03:29 -05:00
#ifndef CHAPTER_1_SOURCE
#define CHAPTER_1_SOURCE
#include <stdlib.h> // We'll need this header file for malloc, calloc, realloc, free, atexit, rand and exit (some will be used in future chapters).
#include <fcntl.h> // This one for open and 'O_' flags.
#include <unistd.h> // And this one for read, write, close and lseek.
2023-11-13 21:03:29 -05:00
#include "chapter_1.h" // Get used to this... We're pasting macros, enumerations and function declarations again...
int character_is_uppercase (char character) { // Returns a boolean value, aka FALSE or TRUE, aka 0 or 1...
return ((int) ((character >= 'A') && (character <= 'Z')));
}
int character_is_lowercase (char character) {
return ((int) ((character >= 'a') && (character <= 'z')));
}
int character_is_digit (char character) {
return ((int) ((character >= '0') && (character <= '9')));
}
int character_is_blank (char character) { // Standard implementation also considers vertical tab and form feed as blank, we don't...
return ((int) ((character == ' ') || (character == CHARACTER_TAB_HORIZONTAL) || (character == CHARACTER_CARRIAGE_RETURN) || (character == CHARACTER_LINE_FEED)));
// If you like smaller line length limit, you can align it like this:
// return ((character == ' ')
// || (character == CHARACTER_TAB_HORIZONTAL)
// || (character == CHARACTER_CARRIAGE_RETURN)
// || (character == CHARACTER_LINE_FEED));
// Or:
// return ((character == ' ') ||
// (character == CHARACTER_TAB_HORIZONTAL) ||
// (character == CHARACTER_CARRIAGE_RETURN) ||
// (character == CHARACTER_LINE_FEED));
// Or even use literal characters:
// return ((character == ' ') ||
// (character == '\t') ||
// (character == '\r') ||
// (character == '\n'));
}
int character_is_alpha (char character) { // Returns TRUE / 1 or FALSE / 0 depending on if the character is either uppercase or lowercase.
return ((character_is_uppercase (character) != 0) || (character_is_lowercase (character) != 0));
}
int character_is_symbol (char character) { // Returns TRUE / 1 if character is one of the characters in that string (array of characters), otherwise it returns FALSE / 0.
return (character_compare_array (character, "~!@#$%^&*()+{}|:\"<>?`-=[]\\;',./"));
}
int character_is_visible (char character) { // This is visible (printable) character range, and space is included in there.
return ((int) ((character >= ' ') && (character <= '~')));
}
int character_is_invisible (char character) { // If character is not visible, then guess what? It's invisible.
return (character_is_visible (character) == FALSE);
}
int character_is_escape (char character) { // We might use this function...
return ((int) (character == CHARACTER_ESCAPE));
}
int character_is_underscore (char character) { // I don't even know if I'll ever use this one, we'll see, I'm in the process of writing this "book"...
return ((int) (character == '_'));
}
int character_is_hexadecimal (char character) { // Same as function 'character_is_symbol', but for hexadecimal digits.
return (character_compare_array (character, "0123456789ABCDEF"));
}
/*
Now, we can see how function 'character_compare_array' was implemented, but know that it could be even shorter, like you see below. However, I really think it's for the best to
use curly and round braces, when even the compiler won't warn about them. You can easily see the scope of something if you have a text editor capable of highlighting matching
braces, and almost all of them have that feature. Also, don't be afraid of longer lines of code. You can even enable word-wrapping in your ed text editor running on Commodore 64
with 128 GiB of RAM. Just focus more on what you're doing, don't limit yourself to mere 80 characters per line.
2023-11-13 21:03:29 -05:00
@C
int character_compare_array (char character, char * character_array) {
for (; * character_array != CHARACTER_NULL; ++character_array)
if (character == * character_array)
return (TRUE);
return (FALSE);
}
@
*/
int character_compare_array (char character, char * character_array) { // I didn't use name "string", but "character_array", to explicitly show the intention of argument.
int offset;
for (offset = 0; offset != string_length (character_array); ++offset) { // We iterate through string (character array!) and return TRUE / 1 if we found it.
if (character == character_array [offset]) { // If we don't find it in that string, we return FALSE / 0 since it's not there.
return (TRUE); // Note that we could do this without the variable 'offset', similar to string functions.
}
}
return (FALSE);
}
/*
You can see important information about some functions on manual pages in every Linux distro, with 'man ([optional] number) function_name', for example 'man 2 open', and I'll list
few important ones down below with some copy & paste magic, but I'll keep it short.
@C
#include <sys/types.h> // Core types.
#include <sys/stat.h> // Core something, I don't even know...
#include <fcntl.h> // Few system calls.
int open (const char * pathname, int flags );
int open (const char * pathname, int flags, mode_t mode);
int creat (const char * pathname, mode_t mode);
int openat (int dirfd, const char * pathname, int flags );
int openat (int dirfd, const char * pathname, int flags, mode_t mode);
2023-11-13 21:03:29 -05:00
// Some commonly used file flags (modes, one of the first three must always be present in mode mask):
2023-11-13 21:03:29 -05:00
// - O_RDONLY: Open or create file as 'read only', prohibit writing to that file.
// - O_WRONLY: Open or create file as 'write only', so you have permission to modify it.
// - O_RDWR: Open or create file as 'read and write', so you can do whatever you want with it...
// - O_APPEND: Before each write system call, the file offset is positioned at the end of the file, as if with lseek system call. Like, continue writing...
// - O_CREAT: If path name doesn't exist, create a new file with the name you specified. The owner of the new file is set to the effective user ID of the process.
// - O_TRUNC: If the file already exists and is a regular file and the access mode allows writing (is O_RDWR or O_WRONLY) it will be truncated to length 0.
@
There's a lot more to read in manual pages, about various functions and libraries, they are old (and somewhat outdated, since not everyone use them nowdays, and some don't even
update them) source of information, but can be good for standard library. Also keep in mind that most functions below return -1 on error.
*/
int file_open (char * name, int mode) {
int descriptor = -1; // Assume error value as default.
fatal_failure (name == NULL, "file_open: Failed to open file, name is null pointer."); // We must provide non-null address.
fatal_failure ((descriptor = open (name, mode)) == -1, "file_open: Failed to open file, function open returned invalid descriptor."); // We abort of 'open' error...
// We could write something like this too:
// descriptor = open (name, mode);
// fatal_failure (descriptor == -1, "file_open: Failed to open file, function open returned invalid descriptor.");
// Or align it to break two longer function arguments:
// fatal_failure ((descriptor = open (name, mode)) == -1,
// "file_open: Failed to open file, function open returned invalid descriptor.");
// As you can see, if you want to inline variable assignment, you must use braces, like I did in uncommented example.
// And don't confuse '==' equality comparison and '=' assignment operators!
2023-11-13 21:03:29 -05:00
return (descriptor); // Return opened file descriptor.
}
int file_close (int file) {
fatal_failure (file == -1, "file_close: Failed to close file, invalid file descriptor."); // If 'file' was already closed or corrupted, we abort.
fatal_failure (close (file) == -1, "file_close: Failed to close file, function close returned invalid code."); // Keep in mind that this isn't always safe.
return (-1);
}
void file_read (int file, void * data, int size) {
fatal_failure (file == -1, "file_read: Failed to read from file, invalid descriptor."); // We'll comment this out once, since it's all similar with 'file_write'.
fatal_failure (data == NULL, "file_read: Failed to read from file, data is null pointer."); // This function is very similar to 'in', but it accepts a file descriptor.
fatal_failure (size == 0, "file_read: Failed to read from file, size is zero."); // That means we handle the files, not standard input or output.
(void) read (file, data, (unsigned long int) size); // If there was no errors, we read, and don't check for errors at all...
}
void file_write (int file, void * data, int size) {
fatal_failure (file == -1, "file_write: Failed to write to file, invalid descriptor.");
fatal_failure (data == NULL, "file_write: Failed to write to file, data is null pointer.");
fatal_failure (size == 0, "file_write: Failed to write to file, size is zero.");
(void) write (file, data, (unsigned long int) size);
}
int file_seek (int file, int whence) {
fatal_failure (file == -1, "file_seek: Failed to seek in file, invalid descriptor."); // Make sure we have a valid file descriptor (it's also unsafe to assume it)...
return ((int) lseek (file, 0, whence)); // Keep in mind that C isn't safe language. It's safe only if you use your brain.
}
int file_size (char * name) {
int size = -1; // Lets just assume that everything is wrong, everything falls apart...
int file = -1; // Everything is just -1 around us...
2023-11-26 11:58:59 -05:00
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.
2023-11-13 21:03:29 -05:00
fatal_failure (size == -1, "file_size: Failed to get size of file, invalid file size."); // Again, error of 'lseek' would be -1, so we check for that...
file = file_close (file); // We close the file, meaning we didn't edit it.
return (size); // And we return file size in bytes.
}
/*
Lets pretend that each file type has only one extension and save our selves from headaches. Fuck C++ with it's 10 extensions like '.cpp', '.c++', '.cxx', '.cc', and variations for
header files, with prefix 'h', it's cancer. Why the extension wasn't just '.c=c+1', huh?
*/
int file_type (char * name) {
// Keep in mind that I'm intentionally being inconsistent, so you can see several ways to properly align your code, readability is the key to safety!
// Spacing between separate strings in array below is 10 characters, including comma and double quotes, and I "joined" curly braces too, it fits in 180 characters.
// You could break it up on curly braces, or put each string in it's own line if you wanted.
char * file_type_data [FILE_TYPE_COUNT] = { ".txt", ".s", ".fasm", ".gasm", ".nasm", ".yasm", ".c", ".h", ".adb", ".ads", ".cpp", ".hpp" };
int type;
for (; * name != '.'; ++name); // We offset the 'name' until we reach fullstop character.
for (type = 0; type != FILE_TYPE_COUNT; ++type) { // Then we check if it's one from this array by comparing them sequentially.
if (string_compare (name, file_type_data [type]) != 0) { // If it is, we return the value of enumeration of file types.
return (type);
}
}
return (-1); // If it's not in array, we return -1, so we don't access the wrong value in some other array.
}
2023-12-03 11:05:54 -05:00
char * file_record (char * name) {
2023-11-13 21:03:29 -05:00
int file = -1; // You can also initialize local variables in this way.
int size = -1;
char * data = NULL;
fatal_failure (name == NULL, "file_import: Failed to import file, name is null pointer."); // 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) + 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 - 1); // Rest if obvious. This could be implemented smarter. Try to notice why.
file = file_close (file);
return (data); // We return pointer to new memory, but remember, we have to free it later.
}
/*
I won't cover next two functions at this point, because they might be harder to understand, but if you feel confident, try to guess what they do...
*/
2023-11-26 06:34:52 -05:00
char * number_to_string (int number) {
int i, sign;
static char string [32];
for (i = 0; i != 32; ++i) {
string [i] = CHARACTER_NULL;
}
if (number == 0) {
string [0] = '0';
string [1] = CHARACTER_NULL;
return (string);
}
if (number < 0) {
number *= -1;
sign = 1;
} else {
sign = 0;
}
for (i = (string [0] == '-'); number != 0; ++i) {
string [i] = (char) (number % 10) + '0';
number /= 10;
}
if (sign != 0) {
string [i] = '-';
++i;
}
string [i] = CHARACTER_NULL;
string_reverse (string);
return (string);
}
char * format_to_string (int number, int sign, int base, int amount, char character) {
int i;
static char string [32];
for (i = 0; i != 32; ++i) {
string [i] = CHARACTER_NULL;
}
if (number == 0) {
string [0] = '0';
string [1] = CHARACTER_NULL;
string_realign (string, amount, character);
return (string);
}
if (number < 0) {
number *= -1;
}
for (i = (string [0] == '-'); number != 0; ++i) {
string [i] = "0123456789ABCDEF" [number % base];
number /= base;
}
if (sign != 0) {
string [i] = '-';
++i;
}
string [i] = CHARACTER_NULL;
string_reverse (string);
string_realign (string, amount, character);
return (string);
}
int randomize (int minimum, int maximum) { // Now, we're simply returning random integer between 'minimum' and 'maximum', inclusively.
return (rand () % (maximum - minimum + 1) + minimum);
}
2023-11-13 21:03:29 -05:00
#endif