231 lines
12 KiB
C
231 lines
12 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_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 <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);
|
|
|
|
// 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
|