319 lines
15 KiB
C
319 lines
15 KiB
C
#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.
|
|
|
|
#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.
|
|
|
|
@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);
|
|
|
|
// Some commonly used file 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.");
|
|
// 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!
|
|
|
|
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 = (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...
|
|
|
|
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.
|
|
}
|
|
|
|
char * file_record (char * name) {
|
|
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...
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
#endif
|