35 Commits

Author SHA1 Message Date
5c5b0bd26f Add bitwise or 2026-03-25 21:04:44 +11:00
be7db9d837 Fix memory leak in Ground REPL, remove persistent labels to avoid UB 2026-03-20 09:37:37 +11:00
3bfea76f7d Merge branch 'master' of https://chookspace.com/ground/ground 2026-03-20 09:07:16 +11:00
a8097c9cf7 made the _ToEpoch functions in the datetime lib return INTs instead of DOUBLEs 2026-03-20 07:53:29 +11:00
f9c333a1bc Merge branch 'master' of https://chookspace.com/ground/ground 2026-03-20 06:29:01 +11:00
ccc8a61f66 first version of datetime lib 2026-03-20 06:28:55 +11:00
fd118369d3 Ground REPL 2026-03-19 16:00:46 +11:00
2df8d2848d Merge branch 'master' of https://chookspace.com/ground/ground 2026-03-19 13:04:15 +11:00
fb6dd62a42 Leak a little memory to fix list issue 2026-03-19 13:03:54 +11:00
c60e53a1a8 Updated old code 2026-03-19 09:11:42 +11:00
8f34705965 Fix extra space in ErrorInstruction line of errors 2026-03-18 15:26:50 +11:00
e58eec6f08 Merge branch 'master' of https://chookspace.com/ground/ground 2026-03-18 15:25:19 +11:00
7443722dd5 Unit tests 2026-03-18 15:21:58 +11:00
e73fdddb4c Merge branch 'master' of https://chookspace.com/ground/ground 2026-03-18 05:53:40 +11:00
dc2781559d request lib 2.0.0 2026-03-18 05:53:04 +11:00
3f678e0cd7 Fix code skipping when using use 2026-03-17 19:45:16 +11:00
76f342adf8 Fix buffer sizes 2026-03-17 18:45:16 +11:00
832c6c7bf9 Fix use segfault 2026-03-17 18:35:37 +11:00
e3a0f16d2e Fix segfaults returning objects from extlibs 2026-03-14 14:47:14 +11:00
6fecf42e0e Add groundFindField to groundvm.h 2026-03-14 14:10:51 +11:00
ae17165254 Treat extlib functions differently 2026-03-14 13:52:42 +11:00
cd59281f0c Stuff 2026-03-14 13:48:04 +11:00
4be8eeefdf Fix some typos and that 2026-03-07 16:26:00 +11:00
da9976c4c0 Ground -> unistd interface 2026-03-07 15:56:26 +11:00
d0b39eb972 Add serialization functions to groundvm.h 2026-03-04 10:44:40 +11:00
ede2f06ef8 .grbc file serialization/deserialization 2026-03-04 10:40:54 +11:00
d5d79d4b5b Fix groundRunProgram 2026-03-02 18:26:20 +11:00
ce75981c58 Update Readme 2026-03-02 10:14:35 +11:00
5577679ded Closures in Ground 2026-03-02 10:09:10 +11:00
d011d2beb4 Expose GroundScope struct 2026-02-02 13:07:02 +11:00
e3702f46f3 Command line arguments via CMDLINE_ARGS list var 2026-01-31 09:08:38 +11:00
ca861f7e4b Copy custom values and structs properly 2026-01-27 14:10:12 +11:00
9796e309cf Fix the header file 2026-01-25 20:53:24 +11:00
a18479b21c It compiles on windows now I guess 2026-01-25 14:38:37 +11:00
b502184478 Merge pull request 'More type conversions' (#19) from DiamondNether90/cground:master into master
Reviewed-on: ground/cground#19
2026-01-25 13:12:42 +11:00
31 changed files with 1924 additions and 466 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ build
ground
groundc
.idea
Makefile

View File

@@ -121,3 +121,10 @@ Supported instructions so far:
* print
* println
* end
## Closures
So I decided to add closures to Ground. Here's how they work:
* When you define a function, it makes a copy of the scope it's currently in. Variables are statically captured
* Use those variables in functions. Closure achieved!

View File

@@ -1,5 +1,6 @@
#ifndef LIBGROUND_H
#define LIBGROUND_H
#define MAX_ID_LEN 64
/*
* groundvm.h
@@ -13,8 +14,9 @@
#include <stdarg.h>
#include <uthash.h>
typedef enum GroundInstType {
IF, JUMP, END, INPUT, PRINT, PRINTLN, SET, GETTYPE, EXISTS, SETLIST, SETLISTAT, GETLISTAT, GETLISTSIZE, LISTAPPEND, GETSTRSIZE, GETSTRCHARAT, ADD, SUBTRACT, MULTIPLY, DIVIDE, EQUAL, INEQUAL, NOT, GREATER, LESSER, STOI, STOD, TOSTRING, FUN, RETURN, ENDFUN, PUSHARG, CALL, STRUCT, ENDSTRUCT, INIT, GETFIELD, SETFIELD, USE, EXTERN, CREATELABEL, PAUSE, DROP, ERRORCMD
IF, JUMP, END, INPUT, PRINT, PRINTLN, SET, GETTYPE, EXISTS, SETLIST, SETLISTAT, GETLISTAT, GETLISTSIZE, LISTAPPEND, GETSTRSIZE, GETSTRCHARAT, ADD, SUBTRACT, MULTIPLY, DIVIDE, EQUAL, INEQUAL, NOT, GREATER, LESSER, STOI, STOD, ITOC, CTOI, TOSTRING, FUN, RETURN, ENDFUN, PUSHARG, CALL, STRUCT, ENDSTRUCT, INIT, GETFIELD, SETFIELD, USE, EXTERN, CREATELABEL, PAUSE, DROP, ERRORCMD
} GroundInstType;
typedef enum GroundValueType {
@@ -164,6 +166,24 @@ typedef struct GroundObject {
GroundObjectField* fields;
} GroundObject;
typedef struct GroundLabel {
char id[MAX_ID_LEN];
int lineNum;
UT_hash_handle hh;
} GroundLabel;
typedef struct GroundVariable {
char id[MAX_ID_LEN];
GroundValue value;
UT_hash_handle hh;
} GroundVariable;
typedef struct GroundScope {
GroundLabel** labels;
GroundVariable** variables;
bool isMainScope;
} GroundScope;
#ifdef __cplusplus
extern "C" {
#endif
@@ -179,6 +199,8 @@ void groundAddValueToInstruction(GroundInstruction* inst, GroundValue value);
void groundAddReferenceToInstruction(GroundInstruction* inst, GroundArg value);
GroundArg groundCreateReference(GroundArgType type, char* ref);
void groundAddValueToScope(GroundScope* gs, const char* name, GroundValue value);
GroundValue groundCreateValue(GroundValueType type, ...);
GroundProgram groundParseFile(const char* code);
@@ -186,6 +208,16 @@ GroundProgram groundParseFile(const char* code);
GroundStruct groundCreateStruct();
void groundAddFieldToStruct(GroundStruct* gstruct, char* name, GroundValue field);
GroundObjectField* groundFindField(GroundObject head, const char *id);
// Run function returned by Ground code
// Use argc to set count of args, accepts that amount of GroundValue's after
GroundValue groundRunFunction(GroundFunction* function, size_t argc, ...);
bool serializeProgramToFile(const char* path, const GroundProgram* prog);
bool deserializeProgramFromFile(const char* path, GroundProgram* out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,422 @@
#include "date_functions.h"
#include <stdio.h>
GroundValue tmToGroundValue(struct tm t) {
GroundStruct timeStruct = groundCreateStruct();
groundAddFieldToStruct(&timeStruct, "year", groundCreateValue(INT, t.tm_year + 1900));
groundAddFieldToStruct(&timeStruct, "month", groundCreateValue(INT, t.tm_mon + 1));
groundAddFieldToStruct(&timeStruct, "day", groundCreateValue(INT, t.tm_mday));
groundAddFieldToStruct(&timeStruct, "hour", groundCreateValue(INT, t.tm_hour));
groundAddFieldToStruct(&timeStruct, "minute", groundCreateValue(INT, t.tm_min));
groundAddFieldToStruct(&timeStruct, "second", groundCreateValue(INT, t.tm_sec));
groundAddFieldToStruct(&timeStruct, "weekDay", groundCreateValue(INT, t.tm_wday));
groundAddFieldToStruct(&timeStruct, "yearDay", groundCreateValue(INT, t.tm_yday + 1));
groundAddFieldToStruct(&timeStruct, "isDaylightSavingsTime", groundCreateValue(BOOL, t.tm_isdst));
GroundValue value = groundCreateValue(CUSTOM, &timeStruct);
value.type = CUSTOM;
return value;
}
GroundValue datetime_Now(GroundScope* scope, List args) {
time_t now = time(NULL);
struct tm t = {0};
localtime_r(&now, &t);
// return a -datetime struct
return tmToGroundValue(t);
}
GroundValue datetime_FromEpochLocal(GroundScope* scope, List args) {
struct tm t = {0};
time_t ts = args.values[0].data.doubleVal;
localtime_r(&ts, &t);
return tmToGroundValue(t);
}
GroundValue datetime_FromEpochUTC(GroundScope* scope, List args) {
struct tm t = {0};
time_t ts = args.values[0].data.doubleVal;
gmtime_r(&ts, &t);
return tmToGroundValue(t);
}
GroundValue datetime_ToEpochLocal(GroundScope* scope, List args) {
GroundObject obj = *args.values[0].data.customVal;
// check args
GroundObjectField* year = groundFindField(obj, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
time_t ts = mktime(&t);
return groundCreateValue(INT, (long long)ts);
}
GroundValue datetime_ToEpochUTC(GroundScope* scope, List args) {
GroundObject obj = *args.values[0].data.customVal;
// check args
GroundObjectField* year = groundFindField(obj, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
time_t ts = timegm(&t);
return groundCreateValue(INT, (long long)ts);
}
GroundValue formatDatetimeObj(GroundObject obj, char* formatString) {
// check args
GroundObjectField* year = groundFindField(obj, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
mktime(&t); // normalise time
// create a buffer for the string
char* buffer = (char*)malloc(128);
strftime(buffer, 128, formatString, &t);
// return ground version of string
return groundCreateValue(STRING, buffer);
}
GroundValue datetime_Format(GroundScope* scope, List args) {
return formatDatetimeObj(
*args.values[0].data.customVal,
args.values[1].data.stringVal
);
}
GroundValue datetime_FromFormatted(GroundScope* scope, List args) {
char* timeString = args.values[0].data.stringVal;
char* formatString = args.values[1].data.stringVal;
struct tm t = {0};
t.tm_isdst = -1;
char* result = strptime(timeString, formatString, &t);
if (result == NULL) {
ERROR("Time string does not match format!", "ValueError");
}
mktime(&t);
return tmToGroundValue(t);
}
GroundValue datetime_ToISO8601UTC(GroundScope* scope, List args) {
GroundObject obj = *args.values[0].data.customVal;
// check args
GroundObjectField* year = groundFindField(obj, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
time_t ts = mktime(&t);
gmtime_r(&ts, &t);
char* buffer = (char*)malloc(128);
strftime(buffer, 128, "%Y-%m-%dT%H:%M:%SZ", &t);
return groundCreateValue(STRING, buffer);
}
GroundValue datetime_ToISO8601Local(GroundScope* scope, List args) {
GroundObject obj = *args.values[0].data.customVal;
// check args
GroundObjectField* year = groundFindField(obj, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
time_t ts = mktime(&t);
localtime_r(&ts, &t);
char* buffer = (char*)malloc(128);
strftime(buffer, 128, "%Y-%m-%dT%H:%M:%S%z", &t);
return groundCreateValue(STRING, buffer);
}
GroundValue datetime_Diff(GroundScope* scope, List args) {
GroundObject obj1 = *args.values[0].data.customVal;
GroundObject obj2 = *args.values[1].data.customVal;
// check first timedate object
GroundObjectField* year = groundFindField(obj1, "year");
if (year == NULL || year->value.type != INT) ERROR("Object 1 does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj1, "month");
if (month == NULL || month->value.type != INT) ERROR("Object 1 does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj1, "day");
if (day == NULL || day->value.type != INT) ERROR("Object 1 does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj1, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object 1 does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj1, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object 1 does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj1, "second");
if (second == NULL || second->value.type != INT) ERROR("Object 1 does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj1, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object 1 does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj1, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object 1 does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj1, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object 1 does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t1 = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
// check second timedate object
year = groundFindField(obj2, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
month = groundFindField(obj2, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
day = groundFindField(obj2, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
hour = groundFindField(obj2, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
minute = groundFindField(obj2, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
second = groundFindField(obj2, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
weekDay = groundFindField(obj2, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
yearDay = groundFindField(obj2, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
isDaylightSavingsTime = groundFindField(obj2, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t2 = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
time_t ts1 = mktime(&t1);
time_t ts2 = mktime(&t2);
return groundCreateValue(INT, ts2 - ts1);
}
GroundValue datetime_Add(GroundScope* scope, List args) {
GroundObject obj = *args.values[0].data.customVal;
long long secs = args.values[1].data.intVal;
long long mins = args.values[2].data.intVal;
long long hours = args.values[3].data.intVal;
long long days = args.values[4].data.intVal;
// check args
GroundObjectField* year = groundFindField(obj, "year");
if (year == NULL || year->value.type != INT) ERROR("Object does not have year field as int", "ValueError");
GroundObjectField* month = groundFindField(obj, "month");
if (month == NULL || month->value.type != INT) ERROR("Object does not have month field as int", "ValueError");
GroundObjectField* day = groundFindField(obj, "day");
if (day == NULL || day->value.type != INT) ERROR("Object does not have day field as int", "ValueError");
GroundObjectField* hour = groundFindField(obj, "hour");
if (hour == NULL || hour->value.type != INT) ERROR("Object does not have hour field as int", "ValueError");
GroundObjectField* minute = groundFindField(obj, "minute");
if (minute == NULL || minute->value.type != INT) ERROR("Object does not have minute field as int", "ValueError");
GroundObjectField* second = groundFindField(obj, "second");
if (second == NULL || second->value.type != INT) ERROR("Object does not have second field as int", "ValueError");
GroundObjectField* weekDay = groundFindField(obj, "weekDay");
if (weekDay == NULL || weekDay->value.type != INT) ERROR("Object does not have weekDay field as int", "ValueError");
GroundObjectField* yearDay = groundFindField(obj, "yearDay");
if (yearDay == NULL || yearDay->value.type != INT) ERROR("Object does not have yearDay field as int", "ValueError");
GroundObjectField* isDaylightSavingsTime = groundFindField(obj, "isDaylightSavingsTime");
if (isDaylightSavingsTime == NULL || isDaylightSavingsTime->value.type != BOOL) ERROR("Object does not have isDaylightSavingsTime field as bool", "ValueError");
// construct tm struct from our ground struct
struct tm t = {
.tm_sec = second->value.data.intVal,
.tm_min = minute->value.data.intVal,
.tm_hour = hour->value.data.intVal,
.tm_mday = day->value.data.intVal,
.tm_mon = month->value.data.intVal - 1,
.tm_year = year->value.data.intVal - 1900,
.tm_wday = 0,
.tm_yday = 0,
.tm_isdst = -1
};
time_t base = mktime(&t);
long long totalSeconds =
secs +
mins * 60 +
hours * 3600 +
days * 86400;
base += totalSeconds;
struct tm newT;
localtime_r(&base, &newT);
return tmToGroundValue(newT);
}

View File

@@ -0,0 +1,18 @@
#pragma once
#define _XOPEN_SOURCE // to make gcc happy lol
#include <groundext.h>
#include <groundvm.h>
#include <time.h>
GroundValue datetime_Now(GroundScope* scope, List args);
GroundValue datetime_Format(GroundScope* scope, List args);
GroundValue datetime_FromFormatted(GroundScope* scope, List args);
GroundValue datetime_ToISO8601UTC(GroundScope* scope, List args);
GroundValue datetime_ToISO8601Local(GroundScope* scope, List args);
GroundValue datetime_FromEpochLocal(GroundScope* scope, List args);
GroundValue datetime_FromEpochUTC(GroundScope* scope, List args);
GroundValue datetime_ToEpochLocal(GroundScope* scope, List args);
GroundValue datetime_ToEpochUTC(GroundScope* scope, List args);
GroundValue datetime_Diff(GroundScope* scope, List args);
GroundValue datetime_Add(GroundScope* scope, List args);

18
libs/datetime/datetime.c Normal file
View File

@@ -0,0 +1,18 @@
#include "time_functions.h"
#include "date_functions.h"
void ground_init(GroundScope* scope) {
groundAddNativeFunction(scope, "datetime_NowEpoch", datetime_NowEpoch, DOUBLE, 0);
groundAddNativeFunction(scope, "datetime_Now", datetime_Now, CUSTOM, 0);
groundAddNativeFunction(scope, "datetime_Format", datetime_Format, STRING, 2, CUSTOM, "datetime", STRING, "format");
groundAddNativeFunction(scope, "datetime_FromFormatted", datetime_FromFormatted, CUSTOM, 2, STRING, "datetimeString", STRING, "format");
groundAddNativeFunction(scope, "datetime_ToISO8601UTC", datetime_ToISO8601UTC, STRING, 1, CUSTOM, "datetime");
groundAddNativeFunction(scope, "datetime_ToISO8601Local", datetime_ToISO8601Local, STRING, 1, CUSTOM, "datetime");
groundAddNativeFunction(scope, "datetime_FromEpochUTC", datetime_FromEpochUTC, CUSTOM, 1, DOUBLE, "epoch");
groundAddNativeFunction(scope, "datetime_FromEpochLocal", datetime_FromEpochLocal, CUSTOM, 1, DOUBLE, "epoch");
groundAddNativeFunction(scope, "datetime_ToEpochUTC", datetime_ToEpochUTC, INT, 1, CUSTOM, "datetime");
groundAddNativeFunction(scope, "datetime_ToEpochLocal", datetime_ToEpochLocal, INT, 1, CUSTOM, "datetime");
groundAddNativeFunction(scope, "datetime_Diff", datetime_Diff, INT, 2, CUSTOM, "datetime1", CUSTOM, "datetime2");
groundAddNativeFunction(scope, "datetime_Add", datetime_Add, CUSTOM, 5, CUSTOM, "datetime", INT, "seconds", INT, "minutes", INT, "hours", INT, "days");
}

View File

@@ -0,0 +1,12 @@
#include "time_functions.h"
GroundValue datetime_NowEpoch(GroundScope* scope, List args) {
// grab time from system clock
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
// convert it to secs and return it
double secsSinceEpoch = (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
return groundCreateValue(DOUBLE, secsSinceEpoch);
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <groundext.h>
#include <time.h>
GroundValue datetime_NowEpoch(GroundScope* scope, List args);

View File

@@ -1,35 +1,23 @@
#include <groundext.h>
#include <curl/curl.h>
#include <groundvm.h>
#include <curl/curl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
bool curl_init = false;
CURL* curlHandle = NULL;
GroundStruct template;
const char* VALID_REQUEST_TYPES[] = {
"GET",
"POST"
};
typedef struct {
char* data;
size_t size;
} ResponseBuffer;
typedef struct {
CURL* handle;
bool connected;
char* url;
} WebSocketConnection;
// Global storage for WebSocket connections (simple implementation)
#define MAX_WS_CONNECTIONS 10
WebSocketConnection ws_connections[MAX_WS_CONNECTIONS] = {0};
void curl_setup() {
if (!curl_init) {
curl_global_init(CURL_GLOBAL_ALL);
curl_init = true;
}
}
// Proper write callback that accumulates data
size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
size_t total_size = size * nmemb;
ResponseBuffer* buffer = (ResponseBuffer*)userdata;
@@ -48,295 +36,117 @@ size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
return total_size;
}
// GET request
GroundValue get_request(GroundScope* scope, List args) {
curl_setup();
CURL* handle = curl_easy_init();
if (!handle) {
ERROR("CURL failed to init", "GenericRequestError");
}
ResponseBuffer buffer = {0};
buffer.data = malloc(1);
buffer.data[0] = '\0';
buffer.size = 0;
curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal);
curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode result = curl_easy_perform(handle);
if (result != CURLE_OK) {
char buf[256];
snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result));
ERROR(buf, "ActionRequestError");
}
curl_easy_cleanup(handle);
GroundValue ret = groundCreateValue(STRING, buffer.data);
return ret;
}
GroundValue ground_request(GroundScope* scope, List args) {
// POST request
GroundValue post_request(GroundScope* scope, List args) {
curl_setup();
CURL* handle = curl_easy_init();
if (!handle) {
ERROR("CURL failed to init", "GenericRequestError");
}
ResponseBuffer buffer = {0};
buffer.data = malloc(1);
buffer.data[0] = '\0';
buffer.size = 0;
curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal);
curl_easy_setopt(handle, CURLOPT_POST, 1L);
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, args.values[1].data.stringVal);
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode result = curl_easy_perform(handle);
if (result != CURLE_OK) {
free(buffer.data);
char buf[256];
snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result));
curl_easy_cleanup(handle);
ERROR(buf, "ActionRequestError");
}
curl_easy_cleanup(handle);
GroundValue ret = groundCreateValue(STRING, buffer.data);
return ret;
}
GroundObject obj = *args.values[0].data.customVal;
// ============== WebSocket Functions ==============
// check arg types
GroundObjectField* address = groundFindField(obj, "address");
if (address == NULL || address->value.type != STRING) {
ERROR("Object does not have address field as string", "ValueError");
}
GroundObjectField* type = groundFindField(obj, "type");
if (type == NULL || type->value.type != STRING) {
ERROR("Object does not have type field as string", "ValueError");
}
GroundObjectField* userAgent = groundFindField(obj, "userAgent");
if (userAgent == NULL || userAgent->value.type != STRING) {
ERROR("Object does not have userAgent field as string", "ValueError");
}
GroundObjectField* httpHeaders = groundFindField(obj, "httpHeaders");
if (httpHeaders == NULL || httpHeaders->value.type != LIST) {
ERROR("Object does not have httpHeaders field as list<string>", "ValueError");
}
GroundObjectField* verbose = groundFindField(obj, "verbose");
if (verbose == NULL || verbose->value.type != BOOL) {
ERROR("Object does not have verbose field as bool", "ValueError");
}
// Find an empty slot for a new WebSocket connection
int find_ws_slot() {
for (int i = 0; i < MAX_WS_CONNECTIONS; i++) {
if (!ws_connections[i].connected) {
return i;
// check request type string
bool requestTypeOk = false;
for (int i = 0; i < sizeof(VALID_REQUEST_TYPES)/sizeof(VALID_REQUEST_TYPES[0]); i++) {
if (strcmp(type->value.data.stringVal, VALID_REQUEST_TYPES[i]) == 0) {
requestTypeOk = true;
break;
}
}
return -1;
}
if (!requestTypeOk)
ERROR("Invalid request type! Choices: GET, POST", "ValueError");
// WebSocket Connect - returns connection ID as INT
GroundValue ws_connect(GroundScope* scope, List args) {
curl_setup();
int slot = find_ws_slot();
if (slot == -1) {
ERROR("Maximum WebSocket connections reached", "OverflowError");
}
CURL* handle = curl_easy_init();
if (!handle) {
ERROR("CURL failed to init", "GenericWSError");
}
curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal);
curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 2L); // WebSocket mode
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 10L);
CURLcode result = curl_easy_perform(handle);
if (result != CURLE_OK) {
char buf[256];
snprintf(buf, sizeof(buf) - 1, "Curl WebSocket failed: %s\n", curl_easy_strerror(result));
curl_easy_cleanup(handle);
ERROR(buf, "ActionWSError");
}
ws_connections[slot].handle = handle;
ws_connections[slot].connected = true;
ws_connections[slot].url = strdup(args.values[0].data.stringVal);
return groundCreateValue(INT, (int64_t)slot);
}
ResponseBuffer buffer = {0};
// WebSocket Send - sends a text message, returns bytes sent as INT
GroundValue ws_send(GroundScope* scope, List args) {
int conn_id = (int)args.values[0].data.intVal;
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
ERROR("Invalid WebSocket connection ID", "GenericWSError");
}
CURL* handle = ws_connections[conn_id].handle;
const char* message = args.values[1].data.stringVal;
size_t sent;
CURLcode result = curl_ws_send(handle, message, strlen(message), &sent, 0, CURLWS_TEXT);
if (result != CURLE_OK) {
char buf[256];
snprintf(buf, sizeof(buf) - 1, "WebSocket send failed: %s\n", curl_easy_strerror(result));
ERROR(buf, "ActionWSError");
}
return groundCreateValue(INT, (int64_t)sent);
}
// set curl params
curl_easy_setopt(curlHandle, CURLOPT_URL, address->value.data.stringVal);
curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, (void*)&buffer);
curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, userAgent->value.data.stringVal);
curl_easy_setopt(curlHandle, CURLOPT_VERBOSE, verbose->value.data.boolVal);
// WebSocket Receive - receives a message (blocking)
GroundValue ws_receive(GroundScope* scope, List args) {
int conn_id = (int)args.values[0].data.intVal;
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
ERROR("Invalid WebSocket connection ID", "GenericWSError");
// append headers to http request
struct curl_slist* httpHeaderList = NULL;
GroundValue* httpHeaderStrings = httpHeaders->value.data.listVal.values;
for (int i = 0; i < httpHeaders->value.data.listVal.size; i++) {
httpHeaderList = curl_slist_append(httpHeaderList, httpHeaderStrings[i].data.stringVal);
}
CURL* handle = ws_connections[conn_id].handle;
char buffer[4096];
size_t received;
const struct curl_ws_frame* meta;
CURLcode result = curl_ws_recv(handle, buffer, sizeof(buffer) - 1, &received, &meta);
if (result != CURLE_OK) {
if (result == CURLE_AGAIN) {
// No data available right now
return groundCreateValue(STRING, "");
}
char buf[256];
snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result));
ERROR(buf, "ActionWSError");
}
buffer[received] = '\0';
// Handle different frame types
if (meta->flags & CURLWS_CLOSE) {
printf("WebSocket close frame received\n");
ws_connections[conn_id].connected = false;
return groundCreateValue(STRING, "[CLOSED]");
}
if (meta->flags & CURLWS_PING) {
// Automatically respond to ping with pong
curl_ws_send(handle, "", 0, &received, 0, CURLWS_PONG);
return groundCreateValue(STRING, "[PING]");
}
char* data = malloc(received + 1);
memcpy(data, buffer, received);
data[received] = '\0';
return groundCreateValue(STRING, data);
}
curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, httpHeaderList);
// WebSocket Receive with timeout (non-blocking version)
GroundValue ws_receive_timeout(GroundScope* scope, List args) {
int conn_id = (int)args.values[0].data.intVal;
long timeout_ms = (long)args.values[1].data.intVal;
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
ERROR("Invalid WebSocket connection ID", "GenericWSError");
}
CURL* handle = ws_connections[conn_id].handle;
// Set socket to non-blocking mode
curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms);
char buffer[4096];
size_t received;
const struct curl_ws_frame* meta;
CURLcode result = curl_ws_recv(handle, buffer, sizeof(buffer) - 1, &received, &meta);
if (result == CURLE_AGAIN || result == CURLE_OPERATION_TIMEDOUT) {
return groundCreateValue(STRING, "");
}
if (result != CURLE_OK) {
char buf[256];
snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result));
ERROR(buf, "ActionWSError");
}
buffer[received] = '\0';
if (meta->flags & CURLWS_CLOSE) {
ws_connections[conn_id].connected = false;
return groundCreateValue(STRING, "[CLOSED]");
}
if (meta->flags & CURLWS_PING) {
curl_ws_send(handle, "", 0, &received, 0, CURLWS_PONG);
return groundCreateValue(STRING, "[PING]");
}
char* data = malloc(received + 1);
memcpy(data, buffer, received);
data[received] = '\0';
return groundCreateValue(STRING, data);
}
// WebSocket Close - properly close a connection, returns BOOL success
GroundValue ws_close(GroundScope* scope, List args) {
int conn_id = (int)args.values[0].data.intVal;
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
ERROR("Invalid or already connected websocket", "GenericWSError");
}
CURL* handle = ws_connections[conn_id].handle;
size_t sent;
// Send close frame
curl_ws_send(handle, "", 0, &sent, 0, CURLWS_CLOSE);
// Cleanup
curl_easy_cleanup(handle);
free(ws_connections[conn_id].url);
ws_connections[conn_id].handle = NULL;
ws_connections[conn_id].connected = false;
ws_connections[conn_id].url = NULL;
return groundCreateValue(BOOL, true);
}
CURLcode curlStatus = curl_easy_perform(curlHandle);
curl_slist_free_all(httpHeaderList); // dont want em' memory leaks :P - spooopy
// WebSocket Send Binary data, returns bytes sent as INT
GroundValue ws_send_binary(GroundScope* scope, List args) {
int conn_id = (int)args.values[0].data.intVal;
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
ERROR("Invalid WebSocket connection ID", "GenericWSError");
// check for any curl errors
if (curlStatus != CURLE_OK) {
char curlErrorMsg[255] = {0};
snprintf(curlErrorMsg, 255, "%s\n", curl_easy_strerror(curlStatus));
ERROR(curlErrorMsg, "CurlError");
}
CURL* handle = ws_connections[conn_id].handle;
const char* data = args.values[1].data.stringVal;
size_t sent;
CURLcode result = curl_ws_send(handle, data, strlen(data), &sent, 0, CURLWS_BINARY);
if (result != CURLE_OK) {
char buf[256];
snprintf(buf, sizeof(buf) - 1, "WebSocket binary send failed: %s\n", curl_easy_strerror(result));
ERROR(buf, "ActionWSError");
}
return groundCreateValue(INT, (int64_t)sent);
// get the http response code
long httpCode = 0;
curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpCode);
// create return value and return it
GroundValue value = groundCreateValue(CUSTOM, &template);
GroundObjectField* status = groundFindField(*value.data.customVal, "statusCode");
status->value.data.intVal = httpCode;
GroundObjectField* data = groundFindField(*value.data.customVal, "data");
data->value.data.stringVal = buffer.data;
value.type = CUSTOM;
return value;
}
void ground_init(GroundScope* scope) {
// HTTP Functions
groundAddNativeFunction(scope, "request_Get", get_request, STRING, 1, STRING, "url");
groundAddNativeFunction(scope, "request_Post", post_request, STRING, 2, STRING, "url", STRING, "data");
// WebSocket Functions
groundAddNativeFunction(scope, "ws_Connect", ws_connect, INT, 1, STRING, "url");
groundAddNativeFunction(scope, "ws_Send", ws_send, INT, 2, INT, "conn_id", STRING, "message");
groundAddNativeFunction(scope, "ws_Receive", ws_receive, STRING, 1, INT, "conn_id");
groundAddNativeFunction(scope, "ws_ReceiveTimeout", ws_receive_timeout, STRING, 2, INT, "conn_id", INT, "timeout_ms");
groundAddNativeFunction(scope, "ws_Close", ws_close, BOOL, 1, INT, "conn_id");
groundAddNativeFunction(scope, "ws_SendBinary", ws_send_binary, INT, 2, INT, "conn_id", STRING, "data");
}
// init libcurl
curl_global_init(CURL_GLOBAL_ALL);
curlHandle = curl_easy_init();
groundAddValueToScope(scope, "requestInitSuccess", groundCreateValue(BOOL, curlHandle != NULL));
if (curlHandle == NULL) {
return;
}
// create struct
template = groundCreateStruct();
groundAddFieldToStruct(&template, "statusCode", groundCreateValue(INT, 200));
groundAddFieldToStruct(&template, "data", groundCreateValue(STRING, ""));
groundAddFieldToStruct(&template, "ok", groundCreateValue(BOOL, 1));
// create struct that users can pass to this library
GroundStruct requestStruct = groundCreateStruct();
groundAddFieldToStruct(&requestStruct, "address", groundCreateValue(STRING, ""));
groundAddFieldToStruct(&requestStruct, "type", groundCreateValue(STRING, "GET"));
groundAddFieldToStruct(&requestStruct, "userAgent", groundCreateValue(STRING, "Ground/1.0"));
groundAddFieldToStruct(&requestStruct, "httpHeaders", groundCreateValue(LIST, 0));
groundAddFieldToStruct(&requestStruct, "verbose", groundCreateValue(BOOL, 0));
groundAddValueToScope(scope, "request", groundCreateValue(STRUCTVAL, requestStruct));
// create functions
groundAddNativeFunction(scope, "request_Request", ground_request, CUSTOM, 1, CUSTOM, "requestInfo");
}

316
libs/unistd/unistd.c Normal file
View File

@@ -0,0 +1,316 @@
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <groundvm.h>
#include <groundext.h>
// Allows Ground to access the POSIX standard library.
// Read more here: https://en.wikipedia.org/wiki/Unistd.h
// Misc functions
GroundValue groundCrypt(GroundScope* scope, List args) {
char* hash = crypt(args.values[0].data.stringVal, args.values[1].data.stringVal);
if (hash == NULL) {
ERROR(strerror(errno), "CryptError");
}
return groundCreateValue(STRING, hash);
}
GroundValue groundGetHostId(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) gethostid());
}
GroundValue groundSetHostId(GroundScope* scope, List args) {
int result = sethostid((long) args.values[0].data.intVal);
if (result < 0) {
ERROR(strerror(errno), "SetHostIdError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundGetHostname(GroundScope* scope, List args) {
char* buf = malloc(256);
if (buf == NULL) {
ERROR("Couldn't allocate memory to store hostname", "GetHostnameError");
}
int result = gethostname(buf, 255);
if (result < 0) {
ERROR(strerror(errno), "GetHostnameError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSetHostname(GroundScope* scope, List args) {
int result = sethostname(args.values[0].data.stringVal, strlen(args.values[0].data.stringVal));
if (result < 0) {
ERROR(strerror(errno), "SetHostnameError");
}
return groundCreateValue(INT, (int64_t) result);
}
// Signals
GroundValue groundAlarm(GroundScope* scope, List args) {
unsigned result = alarm((unsigned) args.values[0].data.intVal);
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundPause(GroundScope* scope, List args) {
int result = pause();
ERROR(strerror(errno), "PauseError");
}
// Filesystem
GroundValue groundAccess(GroundScope* scope, List args) {
int result = access(args.values[0].data.stringVal, (int) args.values[1].data.intVal);
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundChdir(GroundScope* scope, List args) {
int result = chdir(args.values[0].data.stringVal);
if (result < 0) {
ERROR(strerror(errno), "ChdirError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundChown(GroundScope* scope, List args) {
int result = chown(args.values[0].data.stringVal, (uid_t) args.values[1].data.intVal, (gid_t) args.values[2].data.intVal);
if (result < 0) {
ERROR(strerror(errno), "ChownError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundLink(GroundScope* scope, List args) {
int result = link(args.values[0].data.stringVal, args.values[1].data.stringVal);
if (result < 1) {
ERROR(strerror(errno), "LinkError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundRmdir(GroundScope* scope, List args) {
int result = rmdir(args.values[0].data.stringVal);
if (result < 1) {
ERROR(strerror(errno), "RmdirError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSymlink(GroundScope* scope, List args) {
int result = symlink(args.values[0].data.stringVal, args.values[1].data.stringVal);
if (result < 1) {
ERROR(strerror(errno), "SymlinkError");
}
return groundCreateValue(INT, (int64_t) result);
}
// Process
GroundValue groundExit(GroundScope* scope, List args) {
_exit(args.values[0].data.intVal);
ERROR("Couldn't exit (huh?)", "ExitError");
}
GroundValue groundExecv(GroundScope* scope, List args) {
char** argv = malloc(sizeof(char*) * args.values[1].data.listVal.size + 1);
if (argv == NULL) {
ERROR("Couldn't allocate memory for execv list", "ExecvError");
}
for (size_t i = 0; i < args.values[1].data.listVal.size; i++) {
if (args.values[1].data.listVal.values[i].type != STRING) {
ERROR("Expecting all arguments in list to be of String type", "ExecvError");
}
argv[i] = args.values[1].data.listVal.values[i].data.stringVal;
}
argv[args.values[1].data.listVal.size] = NULL;
int result = execv(args.values[0].data.stringVal, argv);
ERROR(strerror(errno), "ExecvError");
}
GroundValue groundExecvp(GroundScope* scope, List args) {
char** argv = malloc(sizeof(char*) * args.values[1].data.listVal.size + 1);
if (argv == NULL) {
ERROR("Couldn't allocate memory for execv list", "ExecvpError");
}
for (size_t i = 0; i < args.values[1].data.listVal.size; i++) {
if (args.values[1].data.listVal.values[i].type != STRING) {
ERROR("Expecting all arguments in list to be of String type", "ExecvpError");
}
argv[i] = args.values[1].data.listVal.values[i].data.stringVal;
}
argv[args.values[1].data.listVal.size] = NULL;
int result = execvp(args.values[0].data.stringVal, argv);
ERROR(strerror(errno), "ExecvpError");
}
GroundValue groundFork(GroundScope* scope, List args) {
pid_t id = fork();
if (id < 0) {
ERROR(strerror(errno), "ForkError");
}
return groundCreateValue(INT, id);
}
GroundValue groundGetPid(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) getpid());
}
GroundValue groundGetPPid(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) getppid());
}
GroundValue groundGetSid(GroundScope* scope, List args) {
pid_t result = getsid((pid_t) args.values[0].data.intVal);
if (result < 0) {
ERROR(strerror(errno), "GetSidError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundNice(GroundScope* scope, List args) {
int result = nice((int) args.values[0].data.intVal);
if (result < 0) {
ERROR(strerror(errno), "NiceError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSetSid(GroundScope* scope, List args) {
pid_t result = setsid();
if (result < 0) {
ERROR(strerror(errno), "SetSidError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSleep(GroundScope* scope, List args) {
unsigned int result = sleep((unsigned int) args.values[0].data.intVal);
return groundCreateValue(INT, (int64_t) result);
}
// User/Group
GroundValue groundGetGid(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) getgid());
}
GroundValue groundGetEGid(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) getegid());
}
GroundValue groundGetUid(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) getuid());
}
GroundValue groundGetEUid(GroundScope* scope, List args) {
return groundCreateValue(INT, (int64_t) geteuid());
}
GroundValue groundGetLogin(GroundScope* scope, List args) {
char* login = getlogin();
if (login == NULL) {
ERROR(strerror(errno), "GetLoginError");
}
return groundCreateValue(STRING, login);
}
GroundValue groundSetEUid(GroundScope* scope, List args) {
int result = seteuid((uid_t) args.values[0].data.intVal);
if (result < -1) {
ERROR(strerror(errno), "SetEUidError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSetEGid(GroundScope* scope, List args) {
int result = setegid((uid_t) args.values[0].data.intVal);
if (result < -1) {
ERROR(strerror(errno), "SetEGidError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSetREUid(GroundScope* scope, List args) {
int result = setreuid((uid_t) args.values[0].data.intVal, (uid_t) args.values[1].data.intVal);
if (result < -1) {
ERROR(strerror(errno), "SetREUidError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSetREGid(GroundScope* scope, List args) {
int result = setregid((gid_t) args.values[0].data.intVal, (gid_t) args.values[1].data.intVal);
if (result < -1) {
ERROR(strerror(errno), "SetREGidError");
}
return groundCreateValue(INT, (int64_t) result);
}
GroundValue groundSetUid(GroundScope* scope, List args) {
int result = setuid((uid_t) args.values[0].data.intVal);
if (result < -1) {
ERROR(strerror(errno), "SetUidError");
}
return groundCreateValue(INT, (int64_t) result);
}
void ground_init(GroundScope* scope) {
// Misc
groundAddNativeFunction(scope, "unistd_Crypt", groundCrypt, STRING, 2, STRING, "key", STRING, "value");
groundAddNativeFunction(scope, "unistd_GetHostId", groundGetHostId, INT, 0);
groundAddNativeFunction(scope, "unistd_SetHostId", groundSetHostId, INT, 1, INT, "hostid");
groundAddNativeFunction(scope, "unistd_GetHostname", groundGetHostname, STRING, 0);
groundAddNativeFunction(scope, "unistd_SetHostname", groundSetHostname, INT, 1, STRING, "name");
// Signals
groundAddNativeFunction(scope, "unistd_Alarm", groundAlarm, INT, 1, INT, "seconds");
groundAddNativeFunction(scope, "unistd_Pause", groundPause, INT, 0);
// Filesystem
groundAddValueToScope(scope, "unistd_F_OK", groundCreateValue(INT, F_OK));
groundAddValueToScope(scope, "unistd_R_OK", groundCreateValue(INT, R_OK));
groundAddValueToScope(scope, "unistd_W_OK", groundCreateValue(INT, W_OK));
groundAddValueToScope(scope, "unistd_X_OK", groundCreateValue(INT, X_OK));
groundAddNativeFunction(scope, "unistd_Access", groundAccess, INT, 2, STRING, "path", INT, "mode");
groundAddNativeFunction(scope, "unistd_Chdir", groundAccess, INT, 1, STRING, "path");
groundAddNativeFunction(scope, "unistd_Chown", groundChown, INT, 3, STRING, "path", INT, "owner", INT, "group");
groundAddNativeFunction(scope, "unistd_Link", groundLink, INT, 2, STRING, "oldpath", STRING, "newpath");
groundAddNativeFunction(scope, "unistd_Rmdir", groundRmdir, INT, 1, STRING, "path");
groundAddNativeFunction(scope, "unistd_Symlink", groundSymlink, INT, 2, STRING, "target", STRING, "linkpath");
// Process
groundAddNativeFunction(scope, "unistd_Exit", groundExit, INT, 1, INT, "status");
groundAddNativeFunction(scope, "unistd_Execv", groundExecv, INT, 2, STRING, "path", LIST, "argv");
groundAddNativeFunction(scope, "unistd_Execvp", groundExecvp, INT, 2, STRING, "path", LIST, "argv");
groundAddNativeFunction(scope, "unistd_Fork", groundFork, INT, 0);
groundAddNativeFunction(scope, "unistd_GetPid", groundGetPid, INT, 0);
groundAddNativeFunction(scope, "unistd_GetPPid", groundGetPPid, INT, 0);
groundAddNativeFunction(scope, "unistd_GetSid", groundGetSid, INT, 1, INT, "pid");
groundAddNativeFunction(scope, "unistd_Nice", groundNice, INT, 1, INT, "inc");
groundAddNativeFunction(scope, "unistd_SetSid", groundSetSid, INT, 0);
groundAddNativeFunction(scope, "unistd_Sleep", groundSleep, INT, 1, INT, "seconds");
// User/Group
groundAddNativeFunction(scope, "unistd_GetGid", groundGetGid, INT, 0);
groundAddNativeFunction(scope, "unistd_GetEGid", groundGetEGid, INT, 0);
groundAddNativeFunction(scope, "unistd_GetUid", groundGetUid, INT, 0);
groundAddNativeFunction(scope, "unistd_GetEUid", groundGetEUid, INT, 0);
groundAddNativeFunction(scope, "unistd_GetLogin", groundGetLogin, STRING, 0);
groundAddNativeFunction(scope, "unistd_SetEUid", groundSetEUid, INT, 1, INT, "euid");
groundAddNativeFunction(scope, "unistd_SetEGid", groundSetEGid, INT, 1, INT, "egid");
groundAddNativeFunction(scope, "unistd_SetREUid", groundSetEGid, INT, 1, INT, "ruid", INT, "euid");
groundAddNativeFunction(scope, "unistd_SetREGid", groundSetEGid, INT, 1, INT, "rgid", INT, "egid");
}

View File

@@ -43,60 +43,87 @@ GroundValue groundCreateValue(GroundValueType type, ...) {
va_list args;
va_start(args, type);
GroundValue gv;
switch (type) {
case INT: {
return createIntGroundValue(va_arg(args, int64_t));
gv = createIntGroundValue(va_arg(args, int64_t));
break;
}
case DOUBLE: {
return createDoubleGroundValue(va_arg(args, double));
gv = createDoubleGroundValue(va_arg(args, double));
break;
}
case STRING: {
return createStringGroundValue(va_arg(args, char*));
gv = createStringGroundValue(va_arg(args, char*));
break;
}
case CHAR: {
return createCharGroundValue((char)va_arg(args, int));
gv = createCharGroundValue((char)va_arg(args, int));
break;
}
case BOOL: {
return createBoolGroundValue((bool)va_arg(args, int));
gv = createBoolGroundValue((bool)va_arg(args, int));
break;
}
case LIST: {
return createListGroundValue(va_arg(args, List));
gv = createListGroundValue(va_arg(args, List));
break;
}
case FUNCTION: {
return createFunctionGroundValue(va_arg(args, GroundFunction*));
gv = createFunctionGroundValue(va_arg(args, GroundFunction*));
break;
}
case STRUCTVAL: {
GroundValue gv;
gv.type = STRUCTVAL;
gv.data.structVal = malloc(sizeof(GroundStruct));
*gv.data.structVal = va_arg(args, GroundStruct);
return gv;
}
case NONE: {
return createNoneGroundValue();
break;
}
case ERROR:
case NONE: {
gv = createNoneGroundValue();
break;
}
case CUSTOM: {
// CUSTOM values are created from structs
GroundStruct* gstruct = va_arg(args, GroundStruct*);
gv.type = CUSTOM;
gv.data.customVal = malloc(sizeof(GroundObject));
*gv.data.customVal = createObject(*gstruct);
// Deep copy the struct definition so it stays valid
gv.customType = malloc(sizeof(GroundStruct));
gv.customType->size = gstruct->size;
gv.customType->fields = malloc(gstruct->size * sizeof(GroundStructField));
for (size_t i = 0; i < gstruct->size; i++) {
strncpy(gv.customType->fields[i].id, gstruct->fields[i].id, 64);
gv.customType->fields[i].value = copyGroundValue(&gstruct->fields[i].value);
}
break;
}
case ERROR: {
// FIXME
gv = createNoneGroundValue();
break;
}
default: {
gv = createNoneGroundValue();
break;
}
}
return createNoneGroundValue();
va_end(args);
return gv;
}
GroundValue groundRunProgram(GroundProgram* program) {
return interpretGroundProgram(program, NULL);
GroundVariable* variables = NULL;
GroundLabel* labels = NULL;
GroundScope scope = {
.variables = &variables,
.labels = &labels,
.isMainScope = true
};
return interpretGroundProgram(program, &scope);
}
void groundPrintProgram(GroundProgram* program) {
@@ -128,3 +155,44 @@ void groundAddFieldToStruct(GroundStruct* gstruct, char* name, GroundValue field
char* groundCompileProgram(GroundProgram* program) {
return compileGroundProgram(program);
}
GroundValue groundRunFunction(GroundFunction* function, size_t argc, ...) {
va_list args;
va_start(args, argc);
if (function == NULL) {
return createErrorGroundValue(createGroundError("Null function passed to groundRunFunction", "valueError", NULL, NULL));
}
// Seems to crash some functions
// GroundScope callScope = copyGroundScope(&function->closure);
GroundScope callScope = {
.labels = malloc(sizeof(GroundLabel*)),
.variables = malloc(sizeof(GroundVariable*)),
.isMainScope = false
};
*callScope.variables = NULL;
*callScope.labels = NULL;
if (argc != function->argSize) {
return createErrorGroundValue(createGroundError("Too few or too many arguments for function", "callError", NULL, NULL));
}
for (size_t i = 0; i < argc; i++) {
GroundValue gv = va_arg(args, GroundValue);
if (checkFnTypes(&gv, &function->args[i]) == false) {
return createErrorGroundValue(createGroundError("Mismatched argument type", "callError", NULL, NULL));
}
addVariable(callScope.variables, function->args[i].name, gv);
}
va_end(args);
return interpretGroundProgram(&function->program, &callScope);
}
GroundObjectField* groundFindField(GroundObject head, const char *id) {
GroundObjectField* s;
HASH_FIND_STR(head.fields, id, s);
return s;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,5 @@
#ifndef INTERPRETER_H
#define INTERPRETER_H
#define MAX_ID_LEN 64
#include "types.h"
#include "parser.h"
@@ -14,24 +13,6 @@ typedef enum GroundDebugInstructionType {
DUMP, INSPECT, EVAL, CONTINUE, EXIT, STEP, VIEW, HELP, UNKNOWN
} GroundDebugInstructionType;
typedef struct GroundLabel {
char id[MAX_ID_LEN];
int lineNum;
UT_hash_handle hh;
} GroundLabel;
typedef struct GroundVariable {
char id[MAX_ID_LEN];
GroundValue value;
UT_hash_handle hh;
} GroundVariable;
typedef struct GroundScope {
GroundLabel** labels;
GroundVariable** variables;
bool isMainScope;
} GroundScope;
typedef struct GroundDebugInstruction {
GroundDebugInstructionType type;
char* arg;
@@ -42,7 +23,6 @@ GroundFunction* parseFunction(GroundProgram* in, size_t errorOffset);
GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope);
GroundValue interpretGroundInstruction(GroundInstruction inst, GroundScope* scope);
void addVariable(GroundVariable **head, const char *id, GroundValue data);

View File

@@ -1,7 +1,11 @@
#include "parser.h"
#include "interpreter.h"
#include "compiler.h"
#include "types.h"
#include "serialize.h"
#include "repl.h"
#include <stdio.h>
#include <string.h>
char* getFileContents(const char* filename) {
// https://stackoverflow.com/questions/3747086/reading-the-whole-text-file-into-a-char-array-in-c
@@ -40,57 +44,86 @@ char* getFileContents(const char* filename) {
}
int main(int argc, char** argv) {
/*
if (argc < 2) {
printf("Usage: ground [file]\n");
exit(1);
}
char* file = getFileContents(argv[1]);
GroundProgram program = parseFile(file);
free(file);
char* compiled = compileGroundProgram(&program);
printf("%s\n", compiled);
interpretGroundProgram(&program, NULL);
*/
if (argc < 2) {
printf("Usage: %s <file> [-c] [--compile] [-h] [--help]\n", argv[0]);
exit(1);
if (argc == 1) {
exit(repl());
}
bool compile = false;
bool writeBytecode = false;
bool readBytecode = false;
char* fileName = NULL;
char* outFileName = NULL;
List groundArgs = createList();
for (int i = 1; i < argc; i++) {
if (strcmp("--compile", argv[i]) == 0 || strcmp("-c", argv[i]) == 0) {
if (writeBytecode) {
printf("Cannot choose both bytecode and compilation");
exit(1);
}
compile = true;
}
else if (strcmp("--help", argv[i]) == 0 || strcmp("-h", argv[i]) == 0) {
printf("GroundVM help\n");
printf("Usage: %s <file> [-c] [--compile] [-h] [--help]\n", argv[0]);
printf("Usage: %s <file> [-c] [--compile] [-h] [--help] [-w <output>] [--writeBytecode <output>] [-b] [--bytecode]\n", argv[0]);
printf("Options:\n");
printf(" -c or --compile: Outputs Linux x86_64 assembly instead of interpreting (WIP)\n");
printf(" -h or --help: Shows this help message\n");
printf(" -w <output> or --writebytecode <output>: Outputs binary Ground bytecode");
printf(" -b <output> or --bytecode <output>: Inputs binary Ground bytecode");
exit(0);
} else if (strcmp("--writeBytecode", argv[i]) == 0 || strcmp("-w", argv[i]) == 0) {
if (compile) {
printf("Cannot choose both bytecode and compilation");
exit(1);
}
writeBytecode = true;
if (i + 1 >= argc) {
printf("Usage: %s %s <output>", argv[0], argv[i]);
exit(1);
}
i++;
outFileName = argv[i];
} else if (strcmp("--bytecode", argv[i]) == 0 || strcmp("-b", argv[i]) == 0) {
readBytecode = true;
} else {
fileName = argv[i];
if (fileName == NULL) {
fileName = argv[i];
} else {
appendToList(&groundArgs, createStringGroundValue(argv[i]));
}
}
}
if (fileName == NULL) {
printf("Usage: %s <file> [-c] [--compile] [-h] [--help]\n", argv[0]);
printf("Usage: %s <file> [-c] [--compile] [-h] [--help] [-w <output>] [--writeBytecode <output>] [-b] [--bytecode]\n", argv[0]);
printf("Error: No file name provided\n");
exit(1);
}
char* file = getFileContents(fileName);
GroundProgram program = parseFile(file);
free(file);
GroundProgram program;
if (readBytecode) {
deserializeProgramFromFile(fileName, &program);
} else {
char* file = getFileContents(fileName);
program = parseFile(file);
free(file);
}
if (compile) {
char* compiled = compileGroundProgram(&program);
printf("%s\n", compiled);
} else if (writeBytecode) {
serializeProgramToFile(outFileName, &program);
} else {
interpretGroundProgram(&program, NULL);
GroundVariable* variables = NULL;
GroundLabel* labels = NULL;
GroundScope scope;
scope.variables = &variables;
scope.labels = &labels;
scope.isMainScope = true;
addVariable(scope.variables, "CMDLINE_ARGS", createListGroundValue(groundArgs));
interpretGroundProgram(&program, &scope);
}
}

View File

@@ -5,6 +5,25 @@
#include <string.h>
#include <ctype.h>
#ifdef _WIN32
size_t strnlen(const char *src, size_t n) {
size_t len = 0;
while (len < n && src[len])
len++;
return len;
}
char* strndup(const char *s, size_t n) {
size_t len = strnlen(s, n);
char *p = malloc(len + 1);
if (p) {
memcpy(p, s, len);
p[len] = '\0';
}
return p;
}
#endif
GroundProgram createGroundProgram() {
GroundProgram gp;
gp.size = 0;
@@ -157,6 +176,11 @@ static GroundInstType getInstructionType(const char* inst) {
if (strcmp(inst, "not") == 0) return NOT;
if (strcmp(inst, "greater") == 0) return GREATER;
if (strcmp(inst, "lesser") == 0) return LESSER;
if (strcmp(inst, "and") == 0) return AND;
if (strcmp(inst, "or") == 0) return OR;
if (strcmp(inst, "xor") == 0) return XOR;
if (strcmp(inst, "neg") == 0) return NEG;
if (strcmp(inst, "shift") == 0) return SHIFT;
if (strcmp(inst, "stoi") == 0) return STOI;
if (strcmp(inst, "stod") == 0) return STOD;
if (strcmp(inst, "ctoi") == 0) return CTOI;
@@ -175,6 +199,7 @@ static GroundInstType getInstructionType(const char* inst) {
if (strcmp(inst, "use") == 0) return USE;
if (strcmp(inst, "extern") == 0) return EXTERN;
if (strcmp(inst, "drop") == 0) return DROP;
if (strcmp(inst, "license") == 0) return LICENSE;
if (strcmp(inst, "PAUSE") == 0) return PAUSE;
fprintf(stderr, "Error: Unknown instruction: %s\n", inst);

60
src/repl.c Normal file
View File

@@ -0,0 +1,60 @@
/*
* Ground REPL (shows when ground is ran without any arguments)
* Copyright (C) 2026 DiamondNether90
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "interpreter.h"
#include "include/estr.h"
#include "parser.h"
#define BUFFER_SIZE 1024
int repl() {
printf("Ground REPL\n");
printf("Copyright (C) DiamondNether90 2026\n");
printf("Distributed under the GNU GPL-3.0 license; type \"license\" for more info\n");
char input[BUFFER_SIZE];
GroundVariable* variables = NULL;
GroundLabel* labels = NULL;
GroundScope scope;
scope.variables = &variables;
scope.labels = &labels;
scope.isMainScope = true;
while (true) {
Estr programString = CREATE_ESTR("");
*scope.labels = NULL;
// Get program
printf(">>> ");
while (true) {
fgets(input, BUFFER_SIZE, stdin);
if (strcmp(input, "\n") == 0) {
break;
}
APPEND_ESTR(programString, input);
printf("...");
}
// Interpret program
GroundProgram program = createGroundProgram();
program = parseFile(programString.str);
interpretGroundProgram(&program, &scope);
freeGroundProgram(&program);
DESTROY_ESTR(programString);
}
}

1
src/repl.h Normal file
View File

@@ -0,0 +1 @@
int repl();

302
src/serialize.c Normal file
View File

@@ -0,0 +1,302 @@
#include "serialize.h"
#include "types.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
static bool writeBytes(FILE* f, const void* data, size_t n) {
return fwrite(data, 1, n, f) == n;
}
static bool readBytes(FILE* f, void* data, size_t n) {
return fread(data, 1, n, f) == n;
}
/* Convenience macros for writing/reading a single typed value. */
#define WRITE(f, val) writeBytes((f), &(val), sizeof(val))
#define READ(f, val) readBytes((f), &(val), sizeof(val))
bool serializeWriteString(FILE* f, const char* s) {
if (s == NULL) {
uint32_t sentinel = UINT32_MAX;
return WRITE(f, sentinel);
}
uint32_t len = (uint32_t)strlen(s);
if (!WRITE(f, len)) return false;
if (len > 0 && !writeBytes(f, s, len)) return false;
return true;
}
char* serializeReadString(FILE* f) {
uint32_t len;
if (!READ(f, len)) return NULL;
if (len == UINT32_MAX) return NULL;
char* s = malloc(len + 1);
if (!s) return NULL;
if (len > 0 && !readBytes(f, s, len)) {
free(s);
return NULL;
}
s[len] = '\0';
return s;
}
/* -----------------------------------------------------------------------
* GroundValue
*
* Format:
* uint32_t type
* <payload depending on type>
*
* Only INT, DOUBLE, CHAR, BOOL, STRING, NONE are expected here.
* Any other type is treated as a serialization error.
* -----------------------------------------------------------------------
*/
bool serializeWriteValue(FILE* f, const GroundValue* gv) {
uint32_t type = (uint32_t)gv->type;
if (!WRITE(f, type)) return false;
switch (gv->type) {
case INT:
return WRITE(f, gv->data.intVal);
case DOUBLE:
return WRITE(f, gv->data.doubleVal);
case CHAR:
return WRITE(f, gv->data.charVal);
case BOOL:
return WRITE(f, gv->data.boolVal);
case STRING:
return serializeWriteString(f, gv->data.stringVal);
case NONE:
return true;
default:
/* LIST, FUNCTION, STRUCTVAL, CUSTOM, ERROR:
* These don't exist at serialization time. If you're hitting
* this, something has gone wrong well before we got here. */
fprintf(stderr, "serializeWriteValue: unexpected type %d\n", gv->type);
return false;
}
}
bool serializeReadValue(FILE* f, GroundValue* out) {
memset(out, 0, sizeof(*out));
uint32_t type;
if (!READ(f, type)) return false;
out->type = (GroundValueType)type;
switch (out->type) {
case INT:
return READ(f, out->data.intVal);
case DOUBLE:
return READ(f, out->data.doubleVal);
case CHAR:
return READ(f, out->data.charVal);
case BOOL:
return READ(f, out->data.boolVal);
case STRING: {
char* s = serializeReadString(f);
/* NULL is a valid encoded value (the sentinel case), but
* we only wrote non-NULL strings, so treat NULL-read as error. */
if (!s) return false;
out->data.stringVal = s;
return true;
}
case NONE:
return true;
default:
fprintf(stderr, "serializeReadValue: unexpected type %d\n", out->type);
return false;
}
}
/* -----------------------------------------------------------------------
* GroundArg
*
* Format:
* uint32_t argType
* <if VALUE> serialized GroundValue
* <otherwise> length-prefixed refName string
* -----------------------------------------------------------------------
*/
bool serializeWriteArg(FILE* f, const GroundArg* ga) {
uint32_t type = (uint32_t)ga->type;
if (!WRITE(f, type)) return false;
if (ga->type == VALUE) {
return serializeWriteValue(f, &ga->value.value);
} else {
/* VALREF, DIRREF, LINEREF, LABEL, FNREF, TYPEREF — all carry a refName */
return serializeWriteString(f, ga->value.refName);
}
}
bool serializeReadArg(FILE* f, GroundArg* out) {
memset(out, 0, sizeof(*out));
uint32_t type;
if (!READ(f, type)) return false;
out->type = (GroundArgType)type;
if (out->type == VALUE) {
return serializeReadValue(f, &out->value.value);
} else {
char* ref = serializeReadString(f);
if (!ref) return false;
out->value.refName = ref;
return true;
}
}
/* -----------------------------------------------------------------------
* GroundInstruction
*
* Format:
* uint32_t instType
* uint64_t argCount
* <argCount x serialized GroundArg>
* -----------------------------------------------------------------------
*/
bool serializeWriteInstruction(FILE* f, const GroundInstruction* gi) {
uint32_t type = (uint32_t)gi->type;
if (!WRITE(f, type)) return false;
uint64_t argc = (uint64_t)gi->args.length;
if (!WRITE(f, argc)) return false;
for (size_t i = 0; i < gi->args.length; i++) {
if (!serializeWriteArg(f, &gi->args.args[i])) return false;
}
return true;
}
bool serializeReadInstruction(FILE* f, GroundInstruction* out) {
uint32_t type;
if (!READ(f, type)) return false;
*out = createGroundInstruction((GroundInstType)type);
uint64_t argc;
if (!READ(f, argc)) return false;
for (uint64_t i = 0; i < argc; i++) {
GroundArg arg;
if (!serializeReadArg(f, &arg)) {
/* Free whatever args we've already read before bailing. */
freeGroundInstruction(out);
return false;
}
addArgToInstruction(out, arg);
}
return true;
}
/* -----------------------------------------------------------------------
* GroundProgram — top-level
*
* File layout:
* uint32_t magic (GROUND_MAGIC)
* uint32_t version (GROUND_VERSION)
* uint64_t instrCount
* <instrCount x serialized GroundInstruction>
* -----------------------------------------------------------------------
*/
bool serializeProgramToFile(const char* path, const GroundProgram* prog) {
FILE* f = fopen(path, "wb");
if (!f) {
perror("serializeProgramToFile: fopen");
return false;
}
bool ok = true;
/* Header */
uint32_t magic = GROUND_MAGIC;
uint32_t version = GROUND_VERSION;
ok = ok && WRITE(f, magic);
ok = ok && WRITE(f, version);
/* Instruction count, then instructions */
uint64_t count = (uint64_t)prog->size;
ok = ok && WRITE(f, count);
for (size_t i = 0; i < prog->size && ok; i++) {
ok = serializeWriteInstruction(f, &prog->instructions[i]);
}
if (!ok) {
fprintf(stderr, "serializeProgramToFile: write error\n");
}
fclose(f);
return ok;
}
bool deserializeProgramFromFile(const char* path, GroundProgram* out) {
memset(out, 0, sizeof(*out));
FILE* f = fopen(path, "rb");
if (!f) {
perror("deserializeProgramFromFile: fopen");
return false;
}
bool ok = true;
/* Validate header */
uint32_t magic, version;
ok = ok && READ(f, magic);
ok = ok && READ(f, version);
if (!ok || magic != GROUND_MAGIC) {
fprintf(stderr, "deserializeProgramFromFile: bad magic (got 0x%08X)\n", magic);
fclose(f);
return false;
}
if (version != GROUND_VERSION) {
fprintf(stderr, "deserializeProgramFromFile: unsupported version %u\n", version);
fclose(f);
return false;
}
/* Read instruction count */
uint64_t count;
if (!READ(f, count)) {
fclose(f);
return false;
}
out->instructions = malloc(sizeof(GroundInstruction) * count);
if (!out->instructions && count > 0) {
fprintf(stderr, "deserializeProgramFromFile: malloc failed\n");
fclose(f);
return false;
}
out->size = 0; /* incremented as we go so partial frees are safe */
for (uint64_t i = 0; i < count; i++) {
if (!serializeReadInstruction(f, &out->instructions[i])) {
fprintf(stderr, "deserializeProgramFromFile: failed at instruction %llu\n",
(unsigned long long)i);
/* Free everything successfully read so far. */
for (size_t j = 0; j < out->size; j++)
freeGroundInstruction(&out->instructions[j]);
free(out->instructions);
memset(out, 0, sizeof(*out));
fclose(f);
return false;
}
out->size++;
}
fclose(f);
return true;
}

48
src/serialize.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include <stdio.h>
#include <stdbool.h>
#include "types.h"
/*
* Magic number and version for Ground bytecode files.
* The magic bytes spell 'GRND'
*/
#define GROUND_MAGIC 0x47524E44u
#define GROUND_VERSION 1u
/*
* File header written at the start of every .grbc file.
*/
typedef struct GroundBytecodeHeader {
uint32_t magic;
uint32_t version;
} GroundBytecodeHeader;
/*
* Writes a length-prefixed UTF-8 string to (f).
* NULL is encoded as a sentinel length (UINT32_MAX).
* Returns true on success.
*/
bool serializeWriteString(FILE* f, const char* s);
char* serializeReadString(FILE* f);
bool serializeWriteValue(FILE* f, const GroundValue* gv);
bool serializeReadValue(FILE* f, GroundValue* out);
bool serializeWriteArg(FILE* f, const GroundArg* ga);
bool serializeReadArg(FILE* f, GroundArg* out);
bool serializeWriteInstruction(FILE* f, const GroundInstruction* gi);
bool serializeReadInstruction(FILE* f, GroundInstruction* out);
bool serializeProgramToFile(const char* path, const GroundProgram* prog);
bool deserializeProgramFromFile(const char* path, GroundProgram* out);
#endif

View File

@@ -2,6 +2,20 @@
#include <inttypes.h>
#include <stdlib.h>
#ifdef __EMSCRIPTEN__
static char out_buf[65536];
static int out_pos = 0;
void wasm_print(const char* str) {
int len = strlen(str);
if (out_pos + len < (int)sizeof(out_buf) - 1) {
memcpy(out_buf + out_pos, str, len);
out_pos += len;
out_buf[out_pos] = '\0';
}
}
#endif
GroundValue createIntGroundValue(int64_t in) {
GroundValue gv;
gv.data.intVal = in;
@@ -73,11 +87,14 @@ GroundValue copyGroundValue(const GroundValue* gv) {
case CHAR: newGv.data.charVal = gv->data.charVal; break;
case BOOL: newGv.data.boolVal = gv->data.boolVal; break;
case STRING:
/*
if (gv->data.stringVal != NULL) {
newGv.data.stringVal = strdup(gv->data.stringVal);
} else {
newGv.data.stringVal = NULL;
}
*/
newGv.data.stringVal = gv->data.stringVal;
break;
case LIST: {
List newList = createList();
@@ -90,16 +107,63 @@ GroundValue copyGroundValue(const GroundValue* gv) {
}
case FUNCTION: newGv.data.fnVal = gv->data.fnVal; break;
case STRUCTVAL: {
// do this later lmao
// FIXME
newGv.data.structVal = gv->data.structVal;
newGv.data.structVal = malloc(sizeof(GroundStruct));
if (newGv.data.structVal == NULL) {
printf("Couldn't allocate memory for GroundStruct copy\n");
exit(1);
}
newGv.data.structVal->size = gv->data.structVal->size;
newGv.data.structVal->fields = malloc(gv->data.structVal->size * sizeof(GroundStructField));
if (newGv.data.structVal->fields == NULL && gv->data.structVal->size > 0) {
printf("Couldn't allocate memory for GroundStruct fields copy\n");
exit(1);
}
for (size_t i = 0; i < gv->data.structVal->size; i++) {
strncpy(newGv.data.structVal->fields[i].id,
gv->data.structVal->fields[i].id,
sizeof(newGv.data.structVal->fields[i].id) - 1);
newGv.data.structVal->fields[i].id[sizeof(newGv.data.structVal->fields[i].id) - 1] = '\0';
newGv.data.structVal->fields[i].value = copyGroundValue(&gv->data.structVal->fields[i].value);
}
break;
}
case CUSTOM:
// also do this later
// FIXME
newGv.data.customVal = gv->data.customVal;
case CUSTOM: {
newGv.data.customVal = malloc(sizeof(GroundObject));
if (newGv.data.customVal == NULL) {
printf("Couldn't allocate memory for GroundObject copy\n");
exit(1);
}
newGv.data.customVal->fields = NULL;
// Deep copy the struct definition as well
newGv.customType = malloc(sizeof(GroundStruct));
newGv.customType->size = gv->customType->size;
newGv.customType->fields = malloc(gv->customType->size * sizeof(GroundStructField));
for (size_t i = 0; i < gv->customType->size; i++) {
strncpy(newGv.customType->fields[i].id, gv->customType->fields[i].id, 64);
newGv.customType->fields[i].value = copyGroundValue(&gv->customType->fields[i].value);
}
GroundObjectField *field, *tmp;
HASH_ITER(hh, gv->data.customVal->fields, field, tmp) {
GroundObjectField* newField = malloc(sizeof(GroundObjectField));
if (newField == NULL) {
printf("Couldn't allocate memory for GroundObjectField copy\n");
exit(1);
}
strncpy(newField->id, field->id, sizeof(newField->id) - 1);
newField->id[sizeof(newField->id) - 1] = '\0';
newField->value = copyGroundValue(&field->value);
HASH_ADD_STR(newGv.data.customVal->fields, id, newField);
}
break;
}
case NONE:
default: {
@@ -197,7 +261,8 @@ void printGroundValue(GroundValue* gv) {
void freeGroundValue(GroundValue* gv) {
if (gv->type == STRING && gv->data.stringVal != NULL) {
free(gv->data.stringVal);
// leak some memory for now
// free(gv->data.stringVal);
gv->data.stringVal = NULL;
}
if (gv->type == LIST && gv->data.listVal.values != NULL) {
@@ -214,6 +279,20 @@ void freeGroundValue(GroundValue* gv) {
freeGroundStruct(gstruct);
free(gstruct);
}
if (gv->type == CUSTOM && gv->data.customVal != NULL) {
freeGroundObject(gv->data.customVal);
free(gv->data.customVal);
gv->data.customVal = NULL;
if (gv->customType != NULL) {
for (size_t i = 0; i < gv->customType->size; i++) {
freeGroundValue(&gv->customType->fields[i].value);
}
free(gv->customType->fields);
free(gv->customType);
gv->customType = NULL;
}
}
if (gv->type == ERROR) {
GroundError* error = &gv->data.errorVal;
if (error->type != NULL) {
@@ -407,6 +486,21 @@ void printGroundInstruction(GroundInstruction* gi) {
case LESSER:
printf("lesser");
break;
case AND:
printf("and");
break;
case OR:
printf("or");
break;
case XOR:
printf("xor");
break;
case NEG:
printf("neg");
break;
case SHIFT:
printf("shift");
break;
case STOI:
printf("stoi");
break;
@@ -466,6 +560,7 @@ void printGroundInstruction(GroundInstruction* gi) {
}
if (gi->type != CREATELABEL) printf(" ");
for (size_t i = 0; i < gi->args.length; i++) {
if (i != 0) printf(" ");
if (gi->args.args[i].type == VALUE && gi->args.args[i].value.value.type == STRING) {
printf("\"");
printGroundArg(&gi->args.args[i]);
@@ -473,7 +568,6 @@ void printGroundInstruction(GroundInstruction* gi) {
} else {
printGroundArg(&gi->args.args[i]);
}
printf(" ");
}
}
@@ -486,12 +580,12 @@ List createList() {
void appendToList(List* list, GroundValue value) {
if (list == NULL) {
printf("Expecting a List ptr, got a null pointer instead.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/cground\n");
printf("Expecting a List ptr, got a null pointer instead.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/ground\n");
exit(EXIT_FAILURE);
}
GroundValue* ptr = realloc(list->values, (list->size + 1) * sizeof(GroundValue));
if (ptr == NULL) {
printf("There was an error allocating memory for a list.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/cground\n");
printf("There was an error allocating memory for a list.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/ground\n");
exit(EXIT_FAILURE);
}
list->size++;
@@ -501,7 +595,7 @@ void appendToList(List* list, GroundValue value) {
ListAccess getListAt(List* list, size_t idx) {
if (list == NULL) {
printf("Expecting a List ptr, got a null pointer instead.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/cground\n");
printf("Expecting a List ptr, got a null pointer instead.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/ground\n");
exit(EXIT_FAILURE);
}
if (idx < list->size) {
@@ -519,7 +613,7 @@ ListAccess getListAt(List* list, size_t idx) {
ListAccessStatus setListAt(List* list, size_t idx, GroundValue value) {
if (list == NULL) {
printf("Expecting a List ptr, got a null pointer instead.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/cground\n");
printf("Expecting a List ptr, got a null pointer instead.\nThis is likely not an error with your Ground program.\nPlease report this issue to https://chsp.au/ground/ground\n");
exit(EXIT_FAILURE);
}
if (idx < list->size) {
@@ -670,3 +764,24 @@ bool checkTypes(GroundValue* left, GroundValue* right) {
}
return true;
}
GroundScope copyGroundScope(GroundScope* scope) {
GroundScope newScope = {
.labels = malloc(sizeof(GroundLabel*)),
.variables = malloc(sizeof(GroundVariable*)),
.isMainScope = false
};
*newScope.variables = NULL;
*newScope.labels = NULL;
if (scope == NULL) {
printf("oh no scope is null\n");
}
GroundVariable *var, *tmp = NULL;
HASH_ITER(hh, *scope->variables, var, tmp) {
addVariable(newScope.variables, var->id, var->value);
}
return newScope;
}

View File

@@ -8,8 +8,28 @@
#include <string.h>
#include "include/uthash.h"
#define MAX_ID_LEN 64
// If targeting WASM, define WASM specific stuff
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
void wasm_print(const char* str);
#undef printf
#define printf(fmt, ...) do { \
int __needed = snprintf(NULL, 0, fmt, ##__VA_ARGS__) + 1; \
char* __buf = malloc(__needed); \
if (__buf) { \
snprintf(__buf, __needed, fmt, ##__VA_ARGS__); \
wasm_print(__buf); \
free(__buf); \
} \
} while(0)
#endif
typedef enum GroundInstType {
IF, JUMP, END, INPUT, PRINT, PRINTLN, SET, GETTYPE, EXISTS, SETLIST, SETLISTAT, GETLISTAT, GETLISTSIZE, LISTAPPEND, GETSTRSIZE, GETSTRCHARAT, ADD, SUBTRACT, MULTIPLY, DIVIDE, EQUAL, INEQUAL, NOT, GREATER, LESSER, STOI, STOD, ITOC, CTOI, TOSTRING, FUN, RETURN, ENDFUN, PUSHARG, CALL, STRUCT, ENDSTRUCT, INIT, GETFIELD, SETFIELD, USE, EXTERN, CREATELABEL, PAUSE, DROP, ERRORCMD
IF, JUMP, END, INPUT, PRINT, PRINTLN, SET, GETTYPE, EXISTS, SETLIST, SETLISTAT, GETLISTAT, GETLISTSIZE, LISTAPPEND, GETSTRSIZE, GETSTRCHARAT, ADD, SUBTRACT, MULTIPLY, DIVIDE, EQUAL, INEQUAL, NOT, GREATER, LESSER, AND, OR, XOR, NEG, SHIFT, STOI, STOD, ITOC, CTOI, TOSTRING, FUN, RETURN, ENDFUN, PUSHARG, CALL, STRUCT, ENDSTRUCT, INIT, GETFIELD, SETFIELD, USE, EXTERN, CREATELABEL, PAUSE, DROP, LICENSE, ERRORCMD
} GroundInstType;
typedef enum GroundValueType {
@@ -63,7 +83,7 @@ typedef struct GroundError {
typedef struct GroundValue {
GroundValueType type;
struct GroundStruct* customType;
struct {
union {
int64_t intVal;
double doubleVal;
char* stringVal;
@@ -77,6 +97,24 @@ typedef struct GroundValue {
} data;
} GroundValue;
typedef struct GroundLabel {
char id[MAX_ID_LEN];
int lineNum;
UT_hash_handle hh;
} GroundLabel;
typedef struct GroundVariable {
char id[MAX_ID_LEN];
GroundValue value;
UT_hash_handle hh;
} GroundVariable;
typedef struct GroundScope {
GroundLabel** labels;
GroundVariable** variables;
bool isMainScope;
} GroundScope;
/*
* Indicates status when accessing a list.
* Associated functions:
@@ -145,6 +183,7 @@ typedef struct GroundFunction {
GroundProgram program;
size_t startLine;
bool isNative;
GroundScope closure;
NativeGroundFunction nativeFn;
} GroundFunction;
@@ -284,5 +323,10 @@ bool checkFnTypes(GroundValue* left, GroundFunctionArgs* arg);
// Compares types of two values.
bool checkTypes(GroundValue* left, GroundValue* right);
// Adds variable to GroundScope
void addVariable(GroundVariable **head, const char *id, GroundValue data);
// Deep copies a GroundScope
GroundScope copyGroundScope(GroundScope* scope);
#endif

15
tests/closure.grnd Normal file
View File

@@ -0,0 +1,15 @@
set &x 5
fun !dingle -function -int &a
fun !capture -int -int &b
add $a $b &tmp
add $tmp $x &tmp
return $tmp
endfun
return $capture
endfun
set &x 10
call !dingle 3 &result
call !result 5 &y
println $y $x

View File

@@ -1,4 +1,4 @@
set &x "dingus"
PAUSE
println $x
drop &x
PAUSE
println $x

2
tests/end.grnd Normal file
View File

@@ -0,0 +1,2 @@
set &x 10
end $x

4
tests/lib.grnd Normal file
View File

@@ -0,0 +1,4 @@
fun !lib_PrintHello -int
println "Hello"
return 0
endfun

View File

@@ -1,5 +1,6 @@
# A cool list
setlist &favWords "hello" "there" "general" "kenobi"
setlist &favWords "hello" "there" "general"
listappend &favWords "kenobi"
println $favWords
set &count 0

View File

@@ -1,11 +1,2 @@
fun !dingle -int
endfun
set &x 5
set &y "dingle"
PAUSE
println "continuing"
println "step through here"
println "step again"
println "and again"
PAUSE

View File

@@ -18,7 +18,7 @@ fun !fib -int -int &n -function &fib
endfun
# Main program
println "Computing fib(30) recursively..."
call !fib 30 $fib &answer
println "Computing fib(20) recursively..."
call !fib 20 $fib &answer
println "Result:" $answer
end

View File

@@ -2,10 +2,10 @@ input &str
getstrsize $str &size
set &idx 0
@loop
getstrcharat $str $idx &char
println $char
add 1 $idx &idx
equal $idx $size &cond
if $cond %loopend
jump %loop
getstrcharat $str $idx &char
println $char
add 1 $idx &idx
equal $idx $size &cond
if $cond %loopend
jump %loop
@loopend

82
tests/unit.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
echo "" > log.txt
for f in *.grnd; do
[ -e "$f" ] || continue # skip if no files match
# Files to skip over
if [[ "$f" == "lib.grnd" ]] ||
[[ "$f" == "string.grnd" ]] ||
[[ "$f" == "test.grnd" ]] ||
[[ "$f" == "to1000.grnd" ]] ||
[[ "$f" == "uhoh.grnd" ]] ||
[[ "$f" == "pause.grnd" ]];
then continue
fi
echo "Running $f"
ground "$f" > log.txt
FILE="log.txt"
FAILED="\033[31mFailed\n\033[0m"
if [[ "$f" == "closure.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "13 10\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "convs.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "32\n12\n3.140000\na\n97\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "drop.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "dingus\nGround runtime error:\n ErrorType: UnknownVariable\n ErrorInstruction: println \$x\n ErrorLine: 4\n"));
then printf "\033[31mFailed\n\033[0m"
exit 1
fi
elif [[ "$f" == "error.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "Ground runtime error:\n ErrorType: Hello\n ErrorContext: [1, 2, 3, Hi!]\n ErrorInstruction: error \"Hello\" [1, 2, 3, Hi!] 1\n ErrorLine: 2\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "fib.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "Fibonacci result: 7540113804746346429\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "function.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "dingle\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "list.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "[hello, there, general, kenobi]\nhello\nthere\ngeneral\nkenobi\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "recursivefib.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "Computing fib(20) recursively...\nResult: 6765\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "simple.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "dingus\ndinglefart\n5.840000\n464773025\n5164.120000\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "struct.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "53\n32\n"));
then printf $FAILED
exit 1
fi
elif [[ "$f" == "use.grnd" ]]; then
if !(cmp -s "$FILE" <(printf "Hello\n"));
then printf $FAILED
exit 1
fi
else
printf "\033[31mCould not find test case\n\033[0m"
exit 1
fi
done
rm log.txt
printf "\033[32mAll tests passed!\n\033[0m"
exit 0

2
tests/use.grnd Normal file
View File

@@ -0,0 +1,2 @@
use "lib"
call !lib_PrintHello &tmp