/* Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation. And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version. It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3. */ #ifndef CHAPTER_1_SOURCE #define CHAPTER_1_SOURCE #include "chapter_1.h" // Get used to this... We're pasting macros, enumerations and function declarations again... int character_is_uppercase (char character) { // Returns a boolean value, aka FALSE or TRUE, aka 0 or 1... return ((int) ((character >= 'A') && (character <= 'Z'))); } int character_is_lowercase (char character) { return ((int) ((character >= 'a') && (character <= 'z'))); } int character_is_digit (char character) { return ((int) ((character >= '0') && (character <= '9'))); } int character_is_blank (char character) { // Standard implementation also considers vertical tab and form feed as blank, we don't... return ((int) ((character == ' ') || (character == CHARACTER_TAB_HORIZONTAL) || (character == CHARACTER_CARRIAGE_RETURN) || (character == CHARACTER_LINE_FEED))); // If you like smaller line length limit, you can align it like this: // return ((character == ' ') // || (character == CHARACTER_TAB_HORIZONTAL) // || (character == CHARACTER_CARRIAGE_RETURN) // || (character == CHARACTER_LINE_FEED)); // Or: // return ((character == ' ') || // (character == CHARACTER_TAB_HORIZONTAL) || // (character == CHARACTER_CARRIAGE_RETURN) || // (character == CHARACTER_LINE_FEED)); // Or even use literal characters: // return ((character == ' ') || // (character == '\t') || // (character == '\r') || // (character == '\n')); } int character_is_alpha (char character) { // Returns TRUE / 1 or FALSE / 0 depending on if the character is either uppercase or lowercase. return ((character_is_uppercase (character) != 0) || (character_is_lowercase (character) != 0)); } int character_is_symbol (char character) { // Returns TRUE / 1 if character is one of the characters in that string (array of characters), otherwise it returns FALSE / 0. return (character_compare_array (character, "~!@#$%^&*()+{}|:\"<>?`-=[]\\;',./")); } int character_is_visible (char character) { // This is visible (printable) character range, and space is included in there. return ((int) ((character >= ' ') && (character <= '~'))); } int character_is_invisible (char character) { // If character is not visible, then guess what? It's invisible. return (character_is_visible (character) == FALSE); } int character_is_escape (char character) { // We might use this function... return ((int) (character == CHARACTER_ESCAPE)); } int character_is_underscore (char character) { // I don't even know if I'll ever use this one, we'll see, I'm in the process of writing this "book"... return ((int) (character == '_')); } int character_is_hexadecimal (char character) { // Same as function 'character_is_symbol', but for hexadecimal digits. return (character_compare_array (character, "0123456789ABCDEF")); } /* Now, we can see how function 'character_compare_array' was implemented, but know that it could be even shorter, like you see below. However, I really think it's for the best to use curly and round braces, when even the compiler won't warn about them. You can easily see the scope of something if you have a text editor capable of highlighting matching braces, and almost all of them have that feature. @C int character_compare_array (char character, char * character_array) { for (; * character_array != CHARACTER_NULL; ++character_array) if (character == * character_array) return (TRUE); return (FALSE); } @ */ int character_compare_array (char character, char * character_array) { // I didn't use name "string", but "character_array", to explicitly show the intention of argument. int offset; for (offset = 0; offset != string_length (character_array); ++offset) { // We iterate through string (character array!) and return TRUE / 1 if we found it. if (character == character_array [offset]) { // If we don't find it in that string, we return FALSE / 0 since it's not there. return (TRUE); // Note that we could do this without the variable 'offset', similar to string functions. } } return (FALSE); } /* You can see important information about some functions on manual pages in every Linux distro, with 'man ([optional] number) function_name', for example 'man 2 open', and I'll list few important ones down below with some copy & paste magic, but I'll keep it short. @C #include // Core types. #include // Core something, I don't even know... #include // Few system calls. int open (const char *pathname, int flags); int open (const char *pathname, int flags, mode_t mode); int creat (const char *pathname, mode_t mode); int openat (int dirfd, const char *pathname, int flags); int openat (int dirfd, const char *pathname, int flags, mode_t mode); // Flags (modes, one of the first three must always be present in mode mask): // - O_RDONLY: Open or create file as 'read only', prohibit writing to that file. // - O_WRONLY: Open or create file as 'write only', so you have permission to modify it. // - O_RDWR: Open or create file as 'read and write', so you can do whatever you want with it... // - O_APPEND: Before each write system call, the file offset is positioned at the end of the file, as if with lseek system call. Like, continue writing... // - O_CREAT: If path name doesn't exist, create a new file with the name you specified. The owner of the new file is set to the effective user ID of the process. // - O_TRUNC: If the file already exists and is a regular file and the access mode allows writing (is O_RDWR or O_WRONLY) it will be truncated to length 0. @ There's a lot more to read in manual pages, about various functions and libraries, they are old (and somewhat outdated, since not everyone use them nowdays, and some don't even update them) source of information, but can be good for standard library. Also keep in mind that most functions below return -1 on error. */ int file_open (char * name, int mode) { int descriptor = -1; // Assume error value as default. fatal_failure (name == NULL, "file_open: Failed to open file, name is null pointer."); // We must provide non-null address. fatal_failure ((descriptor = open (name, mode)) == -1, "file_open: Failed to open file, function open returned invalid descriptor."); // We abort of 'open' error... // We could write something like this too: // descriptor = open (name, mode); // fatal_failure (descriptor == -1, "file_open: Failed to open file, function open returned invalid descriptor."); // Or align it to break two longer function arguments: // fatal_failure ((descriptor = open (name, mode)) == -1, // "file_open: Failed to open file, function open returned invalid descriptor."); return (descriptor); // Return opened file descriptor. } int file_close (int file) { fatal_failure (file == -1, "file_close: Failed to close file, invalid file descriptor."); // If 'file' was already closed or corrupted, we abort. fatal_failure (close (file) == -1, "file_close: Failed to close file, function close returned invalid code."); // Keep in mind that this isn't always safe. return (-1); } void file_read (int file, void * data, int size) { fatal_failure (file == -1, "file_read: Failed to read from file, invalid descriptor."); // We'll comment this out once, since it's all similar with 'file_write'. fatal_failure (data == NULL, "file_read: Failed to read from file, data is null pointer."); // This function is very similar to 'in', but it accepts a file descriptor. fatal_failure (size == 0, "file_read: Failed to read from file, size is zero."); // That means we handle the files, not standard input or output. (void) read (file, data, (unsigned long int) size); // If there was no errors, we read, and don't check for errors at all... } void file_write (int file, void * data, int size) { fatal_failure (file == -1, "file_write: Failed to write to file, invalid descriptor."); fatal_failure (data == NULL, "file_write: Failed to write to file, data is null pointer."); fatal_failure (size == 0, "file_write: Failed to write to file, size is zero."); (void) write (file, data, (unsigned long int) size); } int file_seek (int file, int whence) { fatal_failure (file == -1, "file_seek: Failed to seek in file, invalid descriptor."); // Make sure we have a valid file descriptor (it's also unsafe to assume it)... return ((int) lseek (file, 0, whence)); // Keep in mind that C isn't safe language. It's safe only if you use your brain. } int file_size (char * name) { int size = -1; // Lets just assume that everything is wrong, everything falls apart... int file = -1; // Everything is just -1 around us... file = file_open (name, O_RDONLY); // We open a file to read it. size = lseek (file, 0, SEEK_END); // We set the offset to the end of the file. fatal_failure (size == -1, "file_size: Failed to get size of file, invalid file size."); // Again, error of 'lseek' would be -1, so we check for that... file = file_close (file); // We close the file, meaning we didn't edit it. return (size); // And we return file size in bytes. } /* Lets pretend that each file type has only one extension and save our selves from headaches. Fuck C++ with it's 10 extensions like '.cpp', '.c++', '.cxx', '.cc', and variations for header files, with prefix 'h', it's cancer. Why the extension wasn't just '.c=c+1', huh? */ int file_type (char * name) { char * file_type_data [FILE_TYPE_COUNT] = { ".txt", ".s", ".fasm", ".gasm", ".nasm", ".yasm", ".c", ".h", ".adb", ".ads", ".cpp", ".hpp" }; int type; while (* name != '.') { ++name; } for (type = 0; type != FILE_TYPE_COUNT; ++type) { if (string_compare (name, file_type_data [type]) != 0) { return (type); } } return (-1); } void * file_record (char * name) { int file = -1; int size = -1; char * data = NULL; fatal_failure (name == NULL, "file_import: Failed to import file, name is null pointer."); file = file_open (name, O_RDONLY); // Again, we open the file just in order to read it. size = file_size (name); // We do it again, but only to get it's size. data = allocate (size); // And we allocate new memory for data in that file. file_read (file, data, size); // Rest if obvious. This could be implemented smarter. Try to notice why. file = file_close (file); return (data); // We return pointer to new memory, but remember, we have to free it later. } #endif