2026-04-13 06:34:25 +10:00
|
|
|
#include <groundext.h>
|
|
|
|
|
#include <groundvm.h>
|
|
|
|
|
#include <stddef.h>
|
|
|
|
|
#include <stdint.h>
|
2026-01-02 20:53:07 +11:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
2026-04-13 06:34:25 +10:00
|
|
|
#include <string.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
GroundStruct fileStruct = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GroundValue fileStructConstructor(GroundScope* scope, List args) {
|
|
|
|
|
GroundValue value = groundCreateValue(CUSTOM, &fileStruct);
|
|
|
|
|
char* filePath = args.values[0].data.stringVal;
|
|
|
|
|
|
|
|
|
|
// do a check to make sure the file is actually a file first
|
|
|
|
|
struct stat st;
|
|
|
|
|
if (0 != lstat(filePath, &st)) {
|
|
|
|
|
// lstat failed, file probably doesn't exist
|
|
|
|
|
char buffer[256];
|
|
|
|
|
sprintf(buffer, "The file \"%s\" does not exist!", filePath);
|
|
|
|
|
ERROR(buffer, "FileNotFound");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (S_IFREG != (st.st_mode & S_IFMT)) {
|
|
|
|
|
switch (st.st_mode & S_IFMT) {
|
|
|
|
|
case S_IFSOCK:
|
|
|
|
|
ERROR("Attempt to open socket as file!", "NotAFile");
|
|
|
|
|
case S_IFBLK:
|
|
|
|
|
ERROR("Attempt to open block device as file!", "NotAFile");
|
|
|
|
|
case S_IFDIR:
|
|
|
|
|
ERROR("Attempt to open directory as file!", "NotAFile");
|
|
|
|
|
case S_IFCHR:
|
|
|
|
|
ERROR("Attempt to open character device as file!", "NotAFile");
|
|
|
|
|
case S_IFIFO:
|
|
|
|
|
ERROR("Attempt to open FIFO (pipe) as file!", "NotAFile");
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// opens the given file in read and write mode, the file must exist or openning the file will fail
|
|
|
|
|
FILE* file = fopen(filePath, "r+");
|
|
|
|
|
|
|
|
|
|
if (file == NULL) {
|
|
|
|
|
ERROR("Unkown error while openning file!", "GenericFileError");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// seek to end of file to get size
|
|
|
|
|
fseek(file, 0, SEEK_END);
|
|
|
|
|
int64_t size = ftell(file);
|
|
|
|
|
rewind(file);
|
|
|
|
|
|
|
|
|
|
GroundObjectField *handleField = groundFindField(*value.data.customVal, "fileHandle");
|
|
|
|
|
GroundObjectField *sizeField = groundFindField(*value.data.customVal, "size");
|
|
|
|
|
GroundObjectField *filePathField = groundFindField(*value.data.customVal, "filePath");
|
|
|
|
|
|
|
|
|
|
handleField->value.data.intVal = (int64_t)file;
|
|
|
|
|
sizeField->value.data.intVal = size;
|
|
|
|
|
filePathField->value.data.stringVal = filePath;
|
|
|
|
|
|
|
|
|
|
value.type = CUSTOM;
|
|
|
|
|
|
|
|
|
|
return value;
|
2026-01-02 20:53:07 +11:00
|
|
|
}
|
|
|
|
|
|
2026-04-13 06:34:25 +10:00
|
|
|
GroundValue fileStructRead(GroundScope* scope, List args) {
|
|
|
|
|
GroundVariable* handleField = groundFindVariable(scope, "fileHandle");
|
|
|
|
|
if (handleField == NULL) {
|
|
|
|
|
ERROR("A field called \"fileHandle\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
FILE* file = (FILE*)handleField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* sizeField = groundFindVariable(scope, "size");
|
|
|
|
|
if (sizeField == NULL) {
|
|
|
|
|
ERROR("A field called \"size\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
2026-04-13 06:47:31 +10:00
|
|
|
int64_t size = sizeField->value.data.intVal;
|
2026-04-13 06:34:25 +10:00
|
|
|
|
|
|
|
|
// allocate buffer to read file into
|
|
|
|
|
char* buffer = malloc(size + 1);
|
|
|
|
|
if (buffer == NULL) {
|
|
|
|
|
ERROR("Failed to allocate memory for buffer while reading file!", "AllocFail");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// read file
|
|
|
|
|
size_t readSize = fread(buffer, 1, size, file);
|
|
|
|
|
buffer[readSize] = 0; // null terminate buffer
|
|
|
|
|
|
|
|
|
|
return groundCreateValue(STRING, buffer);
|
2026-01-02 20:53:07 +11:00
|
|
|
}
|
|
|
|
|
|
2026-04-13 06:34:25 +10:00
|
|
|
GroundValue fileStructWrite(GroundScope* scope, List args) {
|
|
|
|
|
char* buffer = args.values[0].data.stringVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* handleField = groundFindVariable(scope, "fileHandle");
|
|
|
|
|
if (handleField == NULL) {
|
|
|
|
|
ERROR("A field called \"fileHandle\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
FILE* file = (FILE*)handleField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* sizeField = groundFindVariable(scope, "size");
|
|
|
|
|
if (sizeField == NULL) {
|
|
|
|
|
ERROR("A field called \"size\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundVariable* tellField = groundFindVariable(scope, "tell");
|
|
|
|
|
if (tellField == NULL) {
|
|
|
|
|
ERROR("A field called \"tell\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ftruncate(fileno(file), 0);
|
|
|
|
|
rewind(file);
|
|
|
|
|
fprintf(file, "%s", buffer);
|
|
|
|
|
|
|
|
|
|
sizeField->value.data.intVal = strlen(buffer);
|
|
|
|
|
tellField->value.data.intVal = strlen(buffer);
|
|
|
|
|
|
|
|
|
|
return groundCreateValue(INT, 0);
|
2026-01-02 20:53:07 +11:00
|
|
|
}
|
2026-04-13 06:34:25 +10:00
|
|
|
|
|
|
|
|
GroundValue fileStructAppend(GroundScope* scope, List args) {
|
|
|
|
|
char* buffer = args.values[0].data.stringVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* handleField = groundFindVariable(scope, "fileHandle");
|
|
|
|
|
if (handleField == NULL) {
|
|
|
|
|
ERROR("A field called \"fileHandle\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
FILE* file = (FILE*)handleField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* sizeField = groundFindVariable(scope, "size");
|
|
|
|
|
if (sizeField == NULL) {
|
|
|
|
|
ERROR("A field called \"size\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
int64_t size = sizeField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* tellField = groundFindVariable(scope, "tell");
|
|
|
|
|
if (tellField == NULL) {
|
|
|
|
|
ERROR("A field called \"tell\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(file, "%s", buffer);
|
|
|
|
|
sizeField->value.data.intVal = size + strlen(buffer);
|
|
|
|
|
tellField->value.data.intVal = tellField->value.data.intVal + strlen(buffer);
|
|
|
|
|
|
|
|
|
|
return groundCreateValue(INT, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundValue fileStructFlush(GroundScope* scope, List args) {
|
|
|
|
|
GroundVariable* handleField = groundFindVariable(scope, "fileHandle");
|
|
|
|
|
if (handleField == NULL) {
|
|
|
|
|
ERROR("A field called \"fileHandle\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
FILE* file = (FILE*)handleField->value.data.intVal;
|
|
|
|
|
fflush(file);
|
|
|
|
|
|
|
|
|
|
return groundCreateValue(INT, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundValue fileStructSeek(GroundScope* scope, List args) {
|
|
|
|
|
int64_t offset = args.values[0].data.intVal;
|
|
|
|
|
if (offset < 0) {
|
|
|
|
|
ERROR("Can't seek to negative offset!", "OutOfBounds");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundVariable* handleField = groundFindVariable(scope, "fileHandle");
|
|
|
|
|
if (handleField == NULL) {
|
|
|
|
|
ERROR("A field called \"fileHandle\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
FILE* file = (FILE*)handleField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* tellField = groundFindVariable(scope, "tell");
|
|
|
|
|
if (tellField == NULL) {
|
|
|
|
|
ERROR("A field called \"tell\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundVariable* sizeField = groundFindVariable(scope, "size");
|
|
|
|
|
if (sizeField == NULL) {
|
|
|
|
|
ERROR("A field called \"size\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
int64_t size = sizeField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
if (offset >= size) {
|
|
|
|
|
ERROR("Attempt to seek past file size!", "OutOfBounds");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int result = fseek(file, offset, SEEK_SET);
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
char buffer[100];
|
|
|
|
|
sprintf(buffer, "fseek failed with error code: \"%d\"", result);
|
|
|
|
|
ERROR(buffer, "SeekError");
|
|
|
|
|
}
|
|
|
|
|
tellField->value.data.intVal = offset;
|
|
|
|
|
|
|
|
|
|
return groundCreateValue(INT, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundValue fileStructSeekEnd(GroundScope* scope, List args) {
|
|
|
|
|
int64_t offset = args.values[0].data.intVal;
|
|
|
|
|
if (offset < 0) {
|
|
|
|
|
ERROR("Can't seek to negative offset!", "OutOfBounds");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundVariable* handleField = groundFindVariable(scope, "fileHandle");
|
|
|
|
|
if (handleField == NULL) {
|
|
|
|
|
ERROR("A field called \"fileHandle\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
FILE* file = (FILE*)handleField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
GroundVariable* tellField = groundFindVariable(scope, "tell");
|
|
|
|
|
if (tellField == NULL) {
|
|
|
|
|
ERROR("A field called \"tell\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GroundVariable* sizeField = groundFindVariable(scope, "size");
|
|
|
|
|
if (sizeField == NULL) {
|
|
|
|
|
ERROR("A field called \"size\" was not found", "FieldNotFound");
|
|
|
|
|
}
|
|
|
|
|
int64_t size = sizeField->value.data.intVal;
|
|
|
|
|
|
|
|
|
|
if (offset >= size) {
|
|
|
|
|
ERROR("Attempt to seek past file size!", "OutOfBounds");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int result = fseek(file, offset, SEEK_END);
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
char buffer[100];
|
|
|
|
|
sprintf(buffer, "fseek failed with error code: \"%d\"", result);
|
|
|
|
|
ERROR(buffer, "SeekError");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tellField->value.data.intVal = size - offset;
|
|
|
|
|
|
|
|
|
|
return groundCreateValue(INT, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ground_init(GroundScope* scope) {
|
|
|
|
|
fileStruct = groundCreateStruct();
|
|
|
|
|
|
|
|
|
|
groundAddFieldToStruct(&fileStruct, "fileHandle", groundCreateValue(INT, 0));
|
|
|
|
|
groundAddFieldToStruct(&fileStruct, "filePath", groundCreateValue(STRING, ""));
|
|
|
|
|
groundAddFieldToStruct(&fileStruct, "size", groundCreateValue(INT, 0));
|
|
|
|
|
groundAddFieldToStruct(&fileStruct, "tell", groundCreateValue(INT, 0));
|
|
|
|
|
|
|
|
|
|
groundAddFunctionToStruct(&fileStruct, "read", fileStructRead, STRING, 0); // reads the file and returns the contents as a string
|
|
|
|
|
// readLines function implemented in solstice
|
|
|
|
|
groundAddFunctionToStruct(&fileStruct, "write", fileStructWrite, INT, 1, STRING, "buffer"); // wipes the file before writing the buffer to the file
|
|
|
|
|
groundAddFunctionToStruct(&fileStruct, "append", fileStructAppend, INT, 1, STRING, "buffer"); // appends the buffer to end of the contents in the file
|
|
|
|
|
groundAddFunctionToStruct(&fileStruct, "flush", fileStructFlush, INT, 0); // flush file buffer (not THAT useful tbh)
|
|
|
|
|
groundAddFunctionToStruct(&fileStruct, "seek", fileStructSeek, INT, 1, INT, "offset"); // jump to offset relative to start of file
|
|
|
|
|
groundAddFunctionToStruct(&fileStruct, "seekEnd", fileStructSeekEnd, INT, 1, INT, "offset"); // jump to offset relative to end of file
|
|
|
|
|
|
|
|
|
|
groundAddNativeFunction(scope, "openFile", fileStructConstructor, CUSTOM, 1, STRING, "filePath");
|
|
|
|
|
groundAddNativeFunction(scope, "File_SOLS_CONSTRUCTOR", fileStructConstructor, CUSTOM, 1, STRING, "filePath");
|
|
|
|
|
}
|