132 Commits

Author SHA1 Message Date
932846362d Add segfault handling to Ground-dependent programs 2026-05-05 08:34:25 +10:00
39b6a49a9c Add segfault handler 2026-05-05 08:31:14 +10:00
159d86b76d Calling functions nesed inside objects 2026-05-05 08:25:33 +10:00
a2b0924018 Modify fields inside fields 2026-05-04 17:58:52 +10:00
9edb6b51ec Remove flag that I accidently set 2026-05-04 13:19:48 +10:00
5f7f2ea152 Start work on moving to Tram 2026-05-04 13:10:37 +10:00
6c293f7c3f Merge pull request 'Fix Subtraction Bug' (#31) from DiamondNether90/cground:master into master
Reviewed-on: ground/ground#31
2026-04-27 11:28:14 +10:00
abe4e9d6f0 Fix conflict 2026-04-27 11:21:08 +10:00
80ee95b918 refactor to facilitate solstice stuff 2026-04-20 19:05:10 +10:00
fca9dd63a0 Allow static linking to Ground 2026-04-17 12:57:47 +10:00
54a0f6058a fixed some issues with the collections lib 2026-04-17 10:09:40 +10:00
2c5c64671c Merge branch 'master' of https://chookspace.com/ground/ground 2026-04-16 17:41:50 +10:00
538cf4c666 fixing bugs with lists and strings 2026-04-16 17:41:37 +10:00
da15a1eff7 most basic tests lib on the planet 2026-04-16 13:10:54 +10:00
731d018857 Fix building without Tram 2026-04-15 15:03:29 +10:00
e4b5aafe35 Add catch support 2026-04-15 10:37:14 +10:00
ca85550c92 Start work on throw and catch 2026-04-14 10:50:27 +10:00
124245f492 Merge branch 'master' of https://chookspace.com/ground/ground 2026-04-13 18:49:10 +10:00
1c752c03db fix tiny bug 2026-04-13 18:48:54 +10:00
7f47491491 Duplicator support 2026-04-13 18:27:52 +10:00
90c91f7c4c Merge branch 'master' of https://chookspace.com/ground/ground 2026-04-13 18:06:39 +10:00
a428b01531 wrote string library 2026-04-13 18:06:33 +10:00
6bcf9d43c8 augh 2026-04-13 13:42:08 +10:00
9a6efed426 Band-aid fix for null type 2026-04-13 13:37:42 +10:00
cca98e3e5d Merge branch 'master' of https://chookspace.com/ground/ground 2026-04-13 12:10:06 +10:00
fb11e75c2a added destructors to List and Hashmap in the collections lib 2026-04-13 12:09:49 +10:00
a38d84ec8c Extlibs can call extlib functions i hope 2026-04-13 11:19:46 +10:00
68bd7f065a I HATE THE GROUND C LIB API RAHHHHHH 2026-04-13 10:44:23 +10:00
90a72a8dd7 stupid fileio bug 2026-04-13 06:47:31 +10:00
9e6a0d5a83 updated fileio and collections libs
rewrote fileio lib to use structs and methods. i also updated collections lib to fix bugs and be more consistent
2026-04-13 06:34:25 +10:00
aa0c71a47b added solstice support to the collections lib 2026-04-12 09:47:27 +10:00
f6c59a61d3 Merge branch 'master' of https://chookspace.com/ground/ground 2026-04-12 07:50:10 +10:00
c64c590abb handle some more memory allocation errors 2026-04-12 07:50:03 +10:00
2d02625e37 Destructor fixes 2026-04-11 20:41:43 +10:00
3427df2643 Merge branches 'master' and 'master' of https://chookspace.com/ground/ground 2026-04-11 19:58:35 +10:00
753e695538 made the collections lib use the "any" type 2026-04-11 19:57:35 +10:00
ead5cdc299 Fix -any again 2026-04-11 19:52:24 +10:00
8a3093c30f added hashmaps and fixed some small issues with lists 2026-04-11 19:47:12 +10:00
a1ad1fe139 fix any again 2026-04-11 19:33:58 +10:00
93c6989890 Fix 2026-04-11 19:26:55 +10:00
7b893dd931 I think any should work now 2026-04-11 19:24:46 +10:00
5b01784011 Destructor 2026-04-11 18:53:28 +10:00
e0bc9261a3 changed __ptr to private_ptr in collections lib 2026-04-11 17:14:21 +10:00
607f95a9c9 added more list functions 2026-04-11 17:04:54 +10:00
5ed3542bd7 Merge branch 'master' of https://chookspace.com/ground/ground 2026-04-11 16:06:40 +10:00
482f85af62 core of lists done 2026-04-11 16:06:34 +10:00
cb6b6a564c Add any type for extlibs 2026-04-11 13:19:04 +10:00
e5776b16dd groundFindVariable function 2026-04-11 13:09:32 +10:00
18ac18bc6d Tram is now an optional dependency 2026-04-11 12:31:37 +10:00
fcf9a13fa1 groundAddFunctionToStruct interface 2026-04-11 12:18:57 +10:00
6f6239f495 Update syntax.md 2026-04-11 10:45:33 +10:00
1342a54771 Methods inside objects 2026-04-11 10:42:53 +10:00
490e782bbf Continue work on compiler 2026-04-11 09:49:14 +10:00
f5c4468d50 Notify of list deprecation 2026-04-11 09:44:48 +10:00
1939ed019b fix makefile 2026-04-10 20:25:38 +10:00
01d3c0ed98 Stuff, also compiler is under construction 2026-04-10 17:43:51 +10:00
c2d6e2fcad Update the header again 2026-04-09 16:04:06 +10:00
c9f6fa5f8a Merge pull request 'Unit tests' (#26) from DiamondNether90/cground:master into master
Reviewed-on: ground/ground#26
2026-04-05 18:21:54 +10:00
93ae22c0db Added the bandaid fix back for variables because apparently it still breaks strings in extlibs??? 2026-03-30 11:47:09 +11:00
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
b348ca7626 More type conversions 2026-01-24 18:18:52 +11:00
1570177de1 Fix type conversions 2026-01-24 16:36:37 +11:00
2da11f854d Struct and object printing 2026-01-24 16:23:01 +11:00
337c6ada24 Actually fix the bug 2026-01-24 15:46:09 +11:00
bb0baba167 Fix bug when defining functions and structs 2026-01-24 15:05:05 +11:00
ca0b87aacd Update docs 2026-01-23 15:30:12 +11:00
e8c49508a0 Band-aid fix for structs, use more memory :\ 2026-01-23 13:48:18 +11:00
68182d916e Satisfy Leo's thirst for structs and extlibs 2026-01-22 20:58:49 +11:00
3f684dad3f I'm tired of ragebaiting Leo 2026-01-22 20:40:27 +11:00
03aab86413 Fix the fix? 2026-01-22 20:36:19 +11:00
c382ecd867 Fix the header again 2026-01-22 10:50:48 +11:00
228d2ac762 I forgot the header again lmao 2026-01-21 18:59:24 +11:00
b289448f56 Add struct field access 2026-01-21 18:41:24 +11:00
a3ca979133 Add not to compiler 2026-01-21 16:02:13 +11:00
3e9ce0dfc0 Expose compiler in groundvm.h 2026-01-21 15:54:18 +11:00
063e85d24a Merge pull request 'Add compiler' (#14) from unstable into master
Reviewed-on: ground/cground#14
2026-01-21 15:53:20 +11:00
07474d4317 Add print and println to compiler 2026-01-21 15:20:00 +11:00
51639e904d Add control flow 2026-01-21 14:35:48 +11:00
c728801bc3 Add comparisons 2026-01-21 14:10:34 +11:00
e3c8a2f453 Add multiply and subtract 2026-01-21 14:02:05 +11:00
bf68f1500c Add add instruction to compiler 2026-01-21 13:50:54 +11:00
31577fcc62 Add set to compiler 2026-01-21 13:25:13 +11:00
925077d55e Merge branch 'master' into unstable 2026-01-21 12:20:51 +11:00
32d6a029dd trying to fix stuff 2026-01-21 11:55:42 +11:00
dac983b684 Update compiler 2026-01-21 11:38:37 +11:00
d3c03b4987 Better cmdline args, start work on compiler 2026-01-21 11:17:19 +11:00
0f155c80be ground library version 1.1.5 2026-01-21 07:23:24 +11:00
4680597065 update to math library (v1.1.0) 2026-01-20 21:29:42 +11:00
c6762a7966 extlibs can now add variables and structs! 2026-01-20 21:26:40 +11:00
792aed13ae Balright time to break master again 2026-01-20 21:17:08 +11:00
08b1edd7a7 Update include header 2026-01-20 20:17:26 +11:00
8eef78c310 Merge pull request 'master' (#11) from master into unstable
Reviewed-on: ground/cground#11
2026-01-20 20:15:00 +11:00
ec23d55f9d Fix bug about passing objs to functions 2026-01-20 19:57:49 +11:00
724162f42e Refactor fn arg checking 2026-01-20 19:56:14 +11:00
6d0dd99406 Merge pull request 'Error function' (#10) from DiamondNether90/cground:testing into master
Reviewed-on: ground/cground#10
2026-01-19 21:21:25 +11:00
7717a40574 Error function 2026-01-19 21:14:48 +11:00
bbf2277b6f Merge pull request 'Add math library' (#9) from unstable into master
Reviewed-on: ground/cground#9
2026-01-19 20:44:56 +11:00
fd6fbbaed5 Merge pull request 'math library' (#8) from math-branch into unstable
Reviewed-on: ground/cground#8
2026-01-19 20:44:04 +11:00
50 changed files with 5252 additions and 634 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,7 @@
CC = gcc
CFLAGS = -Wall -Wextra -Isrc/include -Iinclude
LDFLAGS = -ldl -rdynamic
AR = ar
CFLAGS += -Wall -Wextra -Isrc/include -Iinclude -ggdb
LDFLAGS += -ldl -rdynamic
# Install paths
PREFIX ?= /usr/local
@@ -17,6 +18,7 @@ OBJ_DIR = $(BUILD_DIR)/obj
# Output names
EXECUTABLE = $(BIN_DIR)/ground
SHARED_LIB = $(LIB_DIR)/libgroundvm.so
STATIC_LIB = $(LIB_DIR)/libgroundvm.a
HEADERS = $(INC_DIR)/groundvm.h $(INC_DIR)/groundext.h
# Source files
@@ -27,9 +29,13 @@ EXE_SOURCES = $(wildcard $(SRC_DIR)/*.c)
LIB_OBJECTS = $(LIB_SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/lib_%.o)
EXE_OBJECTS = $(EXE_SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/exe_%.o)
# Default target: build standalone executable
# Default target: build library
.PHONY: all
all: executable
all: both
DEFINE_TRAM:
$(eval CFLAGS += -DGROUND_COMPILE_WITH_TRAM)
$(eval LDFLAGS += -ltram)
# Build standalone executable
.PHONY: executable
@@ -37,12 +43,16 @@ executable: $(EXECUTABLE)
# Build shared library
.PHONY: library
library: $(SHARED_LIB) $(HEADERS)
library: $(SHARED_LIB) $(STATIC_LIB) $(HEADERS)
# Build both
.PHONY: both
both: executable library
# both, with tram
.PHONY: tram_both
tram_both: DEFINE_TRAM both
# Link executable
$(EXECUTABLE): $(EXE_OBJECTS) | $(BIN_DIR)
$(CC) $(EXE_OBJECTS) -o $@ $(LDFLAGS)
@@ -51,6 +61,10 @@ $(EXECUTABLE): $(EXE_OBJECTS) | $(BIN_DIR)
$(SHARED_LIB): $(LIB_OBJECTS) | $(LIB_DIR)
$(CC) -shared $(LIB_OBJECTS) -o $@ $(LDFLAGS)
# Build static library
$(STATIC_LIB): $(LIB_OBJECTS) | $(LIB_DIR)
$(AR) rcs $(STATIC_LIB) $(LIB_OBJECTS)
# Copy header for library distribution
$(INC_DIR)/%.h: include/%.h | $(INC_DIR)
cp $< $@
@@ -80,6 +94,7 @@ install: both
mkdir -p $(DESTDIR)$(PREFIX)/include
cp $(EXECUTABLE) $(DESTDIR)$(PREFIX)/bin/
cp $(SHARED_LIB) $(DESTDIR)$(PREFIX)/lib/
cp $(STATIC_LIB) $(DESTDIR)$(PREFIX)/lib/
cp $(HEADERS) $(DESTDIR)$(PREFIX)/include/
echo "$(DESTDIR)$(PREFIX)/lib" | tee $(DESTDIR)/etc/ld.so.conf.d/cground.conf
ldconfig

View File

@@ -75,15 +75,15 @@ build
- [x] String operations
- [x] Maths
- [x] Comparisions
- [ ] Type conversions
- [x] Type conversions
- [x] Functions
- [x] Define functions
- [x] Call functions
- [x] Return values (type checked)
- [x] Arguments for functions
- [x] Jumping within functions
- [ ] Custom data structures
- [ ] Working with external libraries
- [x] Custom data structures
- [x] Working with external libraries
## Debugger
@@ -98,3 +98,33 @@ Commands:
* eval (code): Runs Ground code in the current scope
* help: Shows a help message
## Compiler
CGround now includes an experimental Ground -> x86_64 Linux ASM compiler. You can try it by adding the `-c` flag when running Ground. This will print the generated ASM to the console.
At present only the `int` data type is supported.
Supported instructions so far:
* set
* add
* subtract
* multiply
* equal
* inequal
* greater
* lesser
* not
* jump
* if
* @ (label creation)
* 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

@@ -348,6 +348,14 @@ Calls a function. After the function reference, a variable amount of value refer
The last argument must be a direct reference which symbolises where to store the function's return value.
#### callmethod &object !methodName $arg1 $arg2 $arg3... &variable
Calls a method stored inside an object. The object can use the "self" variable to refer to itself, and manipulate its fields.
The first argument must be a direct reference to the object, and the second argument must be a function reference to the method name.
The last argument must be a direct reference which symbolises where to store the method's return value.
### Libraries
@@ -362,3 +370,23 @@ Looks in the path $GROUND_LIBS/`$libraryName`.grnd for the library. ($GROUND_LIB
Attempts to import a shared library written in a compiled language like C or C++ for usage within the current program.
Looks in the path $GROUND_LIBS/`$libraryName`.so for the library. ($GROUND_LIBS is a system environment variable.)
### Data Structures
#### struct -structName
Creates a new struct which can be initialised. Until the endstruct keyword, the only valid instructions are init, set, fun, endfun, struct, and endstruct.
Any value created inside the struct will be added to the struct.
#### endstruct
Ends the creation of a struct.
#### getfield $object &fieldName &outputVar
Gets a field from an initialised object. fieldName must be a valid name of a field in the object. Errors if the field does not exist.
#### setfield &object &fieldName $value
Sets a field to a new value in the object. The value must be of the same type as the field's old value.

View File

@@ -14,17 +14,6 @@ extern "C" {
struct GroundScope;
typedef struct GroundScope GroundScope;
/*
* Stores data associated with an error thrown during Ground execution.
*/
typedef struct GroundError {
char* what;
char* type;
struct GroundInstruction* where;
size_t line;
bool hasLine;
} GroundError;
// Creates a GroundValue containing (in), with type ERROR.
GroundValue createErrorGroundValue(GroundError in);
@@ -49,6 +38,7 @@ typedef GroundValue (*NativeGroundFunction)(GroundScope* scope, List args);
* ...: A sequence of (GroundValueType type, char* name) for each argument.
*/
void groundAddNativeFunction(GroundScope* scope, char* name, NativeGroundFunction fn, GroundValueType returnType, int argCount, ...);
void groundAddFunctionToStruct(GroundStruct* gstruct, char* name, NativeGroundFunction fn, GroundValueType returnType, int argCount, ...);
#ifdef __cplusplus
}

View File

@@ -1,5 +1,6 @@
#ifndef LIBGROUND_H
#define LIBGROUND_H
#define MAX_ID_LEN 64
/*
* groundvm.h
@@ -11,13 +12,18 @@
#include <stdbool.h>
#include <stdlib.h>
#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, USE, EXTERN, CREATELABEL, PAUSE, DROP
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, CALLMETHOD, STRUCT, ENDSTRUCT, INIT,
GETFIELD, SETFIELD, USE, EXTERN, CREATELABEL, PAUSE, DROP, LICENSE, ERRORCMD, THROW, CATCH
} GroundInstType;
typedef enum GroundValueType {
INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, CUSTOM, NONE
INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, STRUCTVAL, CUSTOM, ERROR, NONE, ANY
} GroundValueType;
typedef enum GroundArgType {
@@ -30,6 +36,7 @@ typedef enum ListAccessStatus {
struct GroundValue;
struct GroundFunction;
struct GroundStruct;
struct List;
@@ -41,20 +48,34 @@ typedef struct List {
struct GroundValue* values;
} List;
/*
* Stores data associated with an error thrown during Ground execution.
*/
typedef struct GroundError {
char* what;
char* type;
struct GroundInstruction* where;
size_t line;
bool hasLine;
} GroundError;
/*
* Stores literal values created in a Ground program.
*/
typedef struct GroundValue {
GroundValueType type;
union {
struct GroundStruct* customType;
struct {
int64_t intVal;
double doubleVal;
char* stringVal;
char charVal;
bool boolVal;
List listVal;
GroundError errorVal;
struct GroundFunction* fnVal;
void* customVal;
struct GroundStruct* structVal;
struct GroundObject* customVal;
} data;
} GroundValue;
@@ -115,6 +136,57 @@ typedef struct GroundFunction {
size_t startLine;
} GroundFunction;
/*
* Field for a GroundStruct
*/
typedef struct GroundStructField {
char id[64];
GroundValue value;
UT_hash_handle hh;
} GroundStructField;
/*
* Represents a Ground struct.
*/
typedef struct GroundStruct {
GroundStructField* fields;
size_t size;
} GroundStruct;
/*
* Field for a GroundObject
*/
typedef struct GroundObjectField {
char id[64];
GroundValue value;
UT_hash_handle hh;
} GroundObjectField;
/*
* Represents an initialised Ground struct.
*/
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
@@ -123,16 +195,33 @@ GroundProgram groundCreateProgram();
void groundAddInstructionToProgram(GroundProgram* program, GroundInstruction instruction);
GroundValue groundRunProgram(GroundProgram* program);
void groundPrintProgram(GroundProgram* program);
char* groundCompileProgram(GroundProgram* program);
GroundInstruction groundCreateInstruction(GroundInstType type);
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);
GroundStruct groundCreateStruct();
void groundAddFieldToStruct(GroundStruct* gstruct, char* name, GroundValue field);
GroundObjectField* groundFindField(GroundObject head, const char *id);
GroundVariable* groundFindVariable(GroundScope* gs, 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,10 @@
#include <groundext.h>
#include <groundvm.h>
#include "list.h"
#include "hashmap.h"
void ground_init(GroundScope* scope) {
initLists(scope);
initHashmaps(scope);
}

181
libs/collections/hashmap.c Normal file
View File

@@ -0,0 +1,181 @@
#include "hashmap.h"
#include <groundext.h>
#include <groundvm.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uthash.h>
#include <stdio.h>
GroundStruct hashmapStruct = {};
GroundValue hashmapStructSet(GroundScope* scope, List args) {
char* key = args.values[0].data.stringVal;
GroundValue value = args.values[1];
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
HashmapItem* keys = (HashmapItem*)ptrField->value.data.intVal;
HashmapItem* newItem = malloc(sizeof(HashmapItem));
newItem->key = strdup(key);
newItem->value = value;
HASH_ADD_KEYPTR(hh, keys, newItem->key, strlen(newItem->key), newItem);
ptrField->value.data.intVal = (long long)keys;
return groundCreateValue(INT, 0);
}
GroundValue hashmapStructGet(GroundScope* scope, List args) {
char* query = args.values[0].data.stringVal;
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
HashmapItem* keys = (HashmapItem*)ptrField->value.data.intVal;
HashmapItem* out;
HASH_FIND_STR(keys, query, out);
if (out == NULL) {
ERROR("Key was not found in hashmap (tip: use hashmap.getOr if you want to specify a fallback value)", "KeyNotFound");
}
return out->value;
}
GroundValue hashmapStructGetOr(GroundScope* scope, List args) {
char* query = args.values[0].data.stringVal;
GroundValue fallback = args.values[1];
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
HashmapItem* keys = (HashmapItem*)ptrField->value.data.intVal;
HashmapItem* out;
HASH_FIND_STR(keys, query, out);
if (out == NULL)
return fallback;
return out->value;
}
GroundValue hashmapStructRemove(GroundScope* scope, List args) {
char* query = args.values[0].data.stringVal;
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
HashmapItem* keys = (HashmapItem*)ptrField->value.data.intVal;
HashmapItem* out;
HASH_FIND_STR(keys, query, out);
if (out == NULL) {
ERROR("Key was not found in hashmap (tip: use hashmap.removeIfPresent if you want to avoid errors)", "KeyNotFound");
}
HASH_DEL(keys, out);
free(out->key);
free(out);
return groundCreateValue(INT, 0);
}
GroundValue hashmapStructRemoveIfPresent(GroundScope* scope, List args) {
char* query = args.values[0].data.stringVal;
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
HashmapItem* keys = (HashmapItem*)ptrField->value.data.intVal;
HashmapItem* out;
HASH_FIND_STR(keys, query, out);
if (out == NULL) {
return groundCreateValue(BOOL, false);
}
HASH_DEL(keys, out);
free(out->key);
free(out);
return groundCreateValue(BOOL, true);
}
GroundValue hashmapStructConstructor(GroundScope* scope, List args) {
GroundValue value = groundCreateValue(CUSTOM, &hashmapStruct);
value.type = CUSTOM;
return value;
}
GroundValue destroyHashmapStruct(GroundScope* scope, List args) {
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
HashmapItem* keys = (HashmapItem*)ptrField->value.data.intVal;
HashmapItem* current, *temp;
HASH_ITER(hh, keys, current, temp) {
HASH_DEL(keys, current);
free(current->key);
free(current);
}
return groundCreateValue(NONE);
}
GroundValue duplicateHashmapStruct(GroundScope* scope, List args) {
GroundObject* self = args.values[0].data.customVal;
GroundValue newSelf = groundCreateValue(CUSTOM, &hashmapStruct);
HashmapItem* src, *dst = NULL, *item, *tmp;
HASH_ITER(hh, src, item, tmp) {
HashmapItem *copy = malloc(sizeof(HashmapItem));
*copy = *item;
HASH_ADD_KEYPTR(hh, dst, copy->key, strlen(copy->key), copy);
}
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
ptrField->value.data.intVal = (int64_t)src;
return newSelf;
}
void initHashmaps(GroundScope* scope) {
hashmapStruct = groundCreateStruct();
groundAddFieldToStruct(&hashmapStruct, "ptr", groundCreateValue(INT, 0));
groundAddFunctionToStruct(&hashmapStruct, "set", hashmapStructSet, INT, 2, STRING, "key", ANY, "value"); // set a key in the hashmap
groundAddFunctionToStruct(&hashmapStruct, "get", hashmapStructGet, ANY, 1, STRING, "key"); // get a key in the hashmap, throws KeyNotFound if the key does not exist
groundAddFunctionToStruct(&hashmapStruct, "getOr", hashmapStructGetOr, ANY, 2, STRING, "key", ANY, "default"); // get a key in the hashmap, or fallback to a default value
groundAddFunctionToStruct(&hashmapStruct, "remove", hashmapStructRemove, INT, 1, STRING, "key"); // remove a key from the hashmap, throws KeyNotFound if the key does not exist
groundAddFunctionToStruct(&hashmapStruct, "removeIfPresent", hashmapStructRemoveIfPresent, BOOL, 1, STRING, "key"); // remove a key from the hashmap, returns true if the key was removed or false if it didn't exist
groundAddFunctionToStruct(&hashmapStruct, "destructor", destroyHashmapStruct, ANY, 0);
groundAddFunctionToStruct(&hashmapStruct, "duplicator", duplicateHashmapStruct, ANY, 1, CUSTOM);
groundAddNativeFunction(scope, "newHashmap", hashmapStructConstructor, CUSTOM, 0);
groundAddNativeFunction(scope, "Hashmap_SOLS_CONSTRUCTOR", hashmapStructConstructor, CUSTOM, 0);
}

View File

@@ -0,0 +1,16 @@
#ifndef HASHMAP_H
#define HASHMAP_H
#include <groundext.h>
#include <groundvm.h>
#include <uthash.h>
typedef struct {
GroundValue value;
char* key;
UT_hash_handle hh;
} HashmapItem;
void initHashmaps(GroundScope* scope);
#endif

546
libs/collections/list.c Normal file
View File

@@ -0,0 +1,546 @@
#include "list.h"
#include <groundext.h>
#include <groundvm.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const uint8_t STARTING_ELEMENTS = 2;
GroundStruct listStruct = {};
GroundValue appendToListStruct(GroundScope* scope, List args) {
GroundValue newValue = args.values[0];
// grab fields we need from struct
GroundVariable* sizeField = groundFindVariable(scope, "size");
if (sizeField == NULL) {
ERROR("A field called \"size\" was not found", "FieldNotFound");
}
GroundVariable* memSizeField = groundFindVariable(scope, "memSize");
if (memSizeField == NULL) {
ERROR("A field called \"memSize\" was not found", "FieldNotFound");
}
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
GroundVariable* capacityField = groundFindVariable(scope, "capacity");
if (capacityField == NULL) {
ERROR("A field called \"capacity\" was not found", "FieldNotFound");
}
int64_t capacity = capacityField->value.data.intVal;
int64_t size = sizeField->value.data.intVal;
if (size+1 > capacity) {
if (capacity == 0) capacity = 1;
else capacity *= 2;
capacityField->value.data.intVal = capacity;
int64_t newSize = sizeof(GroundValue) * capacity;
memSizeField->value.data.intVal = newSize;
GroundValue* newItems = realloc(items, newSize);
if (newItems == NULL) {
ERROR("Failed to allocate memory when increasing list size!", "AllocFail");
}
items = newItems;
ptrField->value.data.intVal = (int64_t)items;
}
items[size] = newValue;
sizeField->value.data.intVal = size + 1;
return groundCreateValue(INT, 0);
}
GroundValue listStructAt(GroundScope* scope, List args) {
int64_t index = args.values[0].data.intVal;
if (index < 0) {
ERROR("Attempt to access list at negative index", "OutOfBounds");
}
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 (index >= size) {
char buffer[512];
sprintf(buffer, "Attempt to access list at index %ld when list is of length %ld", index, size);
ERROR(buffer, "OutOfBounds");
}
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
return items[index];
}
GroundValue clearListStruct(GroundScope* scope, List args) {
// grab fields we need from struct
GroundVariable* sizeField = groundFindVariable(scope, "size");
if (sizeField == NULL) {
ERROR("A field called \"size\" was not found", "FieldNotFound");
}
GroundVariable* memSizeField = groundFindVariable(scope, "memSize");
if (memSizeField == NULL) {
ERROR("A field called \"memSize\" was not found", "FieldNotFound");
}
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundVariable* capacityField = groundFindVariable(scope, "capacity");
if (capacityField == NULL) {
ERROR("A field called \"capacity\" was not found", "FieldNotFound");
}
GroundValue* newBuffer = calloc(STARTING_ELEMENTS, sizeof(GroundValue));
if (newBuffer == NULL) {
ERROR("Failed to allocate memory when clearing list!", "AllocFail");
}
sizeField->value.data.intVal = 0;
ptrField->value.data.intVal = (int64_t)newBuffer;
capacityField->value.data.intVal = STARTING_ELEMENTS;
memSizeField->value.data.intVal = sizeof(GroundValue) * STARTING_ELEMENTS;
return groundCreateValue(INT, 0);
}
GroundValue insertIntoListStruct(GroundScope* scope, List args) {
GroundValue value = args.values[0];
int64_t index = args.values[1].data.intVal;
if (index < 0) {
ERROR("Attempt to insert element into list at negative index", "OutOfBounds");
}
// grab fields we need from struct
GroundVariable* sizeField = groundFindVariable(scope, "size");
if (sizeField == NULL) {
ERROR("A field called \"size\" was not found", "FieldNotFound");
}
GroundVariable* memSizeField = groundFindVariable(scope, "memSize");
if (memSizeField == NULL) {
ERROR("A field called \"memSize\" was not found", "FieldNotFound");
}
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
GroundVariable* capacityField = groundFindVariable(scope, "capacity");
if (capacityField == NULL) {
ERROR("A field called \"capacity\" was not found", "FieldNotFound");
}
int64_t capacity = capacityField->value.data.intVal;
if (sizeField->value.data.intVal <= index) {
sizeField->value.data.intVal = index+1;
} else {
sizeField->value.data.intVal++;
}
// check if we have enough space to insert the element
if (capacity <= sizeField->value.data.intVal+1) {
// not enough space, double size
capacity = sizeField->value.data.intVal + 1;
capacityField->value.data.intVal = capacity;
}
// allocate new buffer
GroundValue* newBuffer = calloc(capacity, sizeof(GroundValue));
if (newBuffer == NULL) {
ERROR("Failed to allocate memory when increasing list size!", "AllocFail");
}
ptrField->value.data.intVal = (int64_t)newBuffer;
memSizeField->value.data.intVal = sizeof(GroundValue) * capacity;
// copy elements from 0 to i-1 into new buffer
memcpy(newBuffer, items, sizeof(GroundValue) * index);
// insert element at i
newBuffer[index] = value;
// copy elements 0 to i+1 into new buffer
memcpy(newBuffer + (index+1), items + index, sizeof(GroundValue) * (sizeField->value.data.intVal - index));
// free older buffer
free(items);
// retur null
return groundCreateValue(INT, 0);
}
GroundValue listStructDelete(GroundScope* scope, List args) {
int64_t index = args.values[0].data.intVal;
if (index < 0) {
ERROR("Attempt to remove element into list at negative index", "OutOfBounds");
}
// grab fields we need from struct
GroundVariable* sizeField = groundFindVariable(scope, "size");
if (sizeField == NULL) {
ERROR("A field called \"size\" was not found", "FieldNotFound");
}
GroundVariable* memSizeField = groundFindVariable(scope, "memSize");
if (memSizeField == NULL) {
ERROR("A field called \"memSizvoid initLists(GroundScope* scope);e\" was not found", "FieldNotFound");
}
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
GroundVariable* capacityField = groundFindVariable(scope, "capacity");
if (capacityField == NULL) {
ERROR("A field called \"capacity\" was not found", "FieldNotFound");
}
int64_t capacity = capacityField->value.data.intVal;
if (sizeField->value.data.intVal < index) {
char buffer[512];
sprintf(buffer, "Attempt to delete element at index %ld when list is of size %ld", index, sizeField->value.data.intVal);
ERROR(buffer, "OutOfBounds");
} else {
sizeField->value.data.intVal--;
}
int64_t size = sizeField->value.data.intVal;
// check if we have enough space to insert the element
if (capacity > size+1) {
// too much space, make list smaller
capacity = size + 1;
capacityField->value.data.intVal = capacity;
}
// allocate new buffer
GroundValue* newBuffer = calloc(capacity, sizeof(GroundValue));
if (newBuffer == NULL) {
ERROR("Failed to allocate memory when decreasing list size!", "AllocFail");
}
ptrField->value.data.intVal = (int64_t)newBuffer;
memSizeField->value.data.intVal = sizeof(GroundValue) * capacity;
// copy elements from 0 to i-1 into new buffer
if (index > 0)
memcpy(newBuffer, items, sizeof(GroundValue) * index);
// copy elements 0 to i+1 into new buffer
if (index < size)
memcpy(newBuffer + index, items + index + 1, sizeof(GroundValue) * (size - index));
// free older buffer
free(items);
// retur null
return groundCreateValue(INT, 0);
}
GroundValue listStructSet(GroundScope* scope, List args) {
GroundValue value = args.values[0];
int64_t index = args.values[1].data.intVal;
if (index < 0) {
ERROR("Attempt to set element in list at negative index", "OutOfBounds");
}
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* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
if (index >= size) {
insertIntoListStruct(scope, args);
} else {
items[index] = value;
}
return groundCreateValue(INT, 0);
}
GroundValue listStructIsEmpty(GroundScope* scope, List args) {
GroundVariable* sizeField = groundFindVariable(scope, "size");
if (sizeField == NULL) {
ERROR("A field called \"size\" was not found", "FieldNotFound");
}
int64_t size = sizeField->value.data.intVal;
return groundCreateValue(BOOL, size <= 0);
}
bool areGroundValuesEqual(GroundValue a, GroundValue b) {
if (a.type != b.type) {
if (a.type == INT && b.type == DOUBLE) {
return a.data.intVal == b.data.doubleVal;
} else if (a.type == DOUBLE && b.type == INT) {
return a.data.doubleVal == b.data.intVal;
}
} else {
switch (a.type) {
case INT:
return a.data.intVal == b.data.intVal;
case DOUBLE:
return a.data.doubleVal == b.data.doubleVal;
case BOOL:
return a.data.boolVal == b.data.boolVal;
case CHAR:
return a.data.charVal == b.data.charVal;
case STRING:
return strcmp(a.data.stringVal, b.data.stringVal) == 0;
default:
return false;
}
}
return false;
}
GroundValue listStructContains(GroundScope* scope, List args) {
GroundValue targetValue = args.values[0];
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* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
for (int64_t i = 0; i < size; i++) {
if (areGroundValuesEqual(items[i], targetValue)) {
return groundCreateValue(BOOL, true);
}
}
return groundCreateValue(BOOL, false);
}
GroundValue reverseListStruct(GroundScope* scope, List args) {
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* capacityField = groundFindVariable(scope, "capacity");
if (capacityField == NULL) {
ERROR("A field called \"capacity\" was not found", "FieldNotFound");
}
int64_t capacity = capacityField->value.data.intVal;
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
GroundValue* newBuffer = calloc(capacity, sizeof(GroundValue));
if (newBuffer == NULL) {
ERROR("Failed to allocate memory when reversing list!", "AllocFail");
}
int z = 0;
for (int i = size - 1; i >= 0; i--, z++) {
newBuffer[z] = items[i];
}
ptrField->value.data.intVal = (int64_t)newBuffer;
free(items);
return groundCreateValue(INT, 0);
}
GroundValue findListStruct(GroundScope* scope, List args) {
GroundValue targetValue = args.values[0];
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* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
for (int64_t i = 0; i < size; i++) {
if (areGroundValuesEqual(items[i], targetValue)) {
return groundCreateValue(INT, i);
}
}
return groundCreateValue(INT, (int64_t)-1);
}
GroundValue reserveListStruct(GroundScope* scope, List args) {
int64_t amount = args.values[0].data.intVal;
GroundVariable* capacityField = groundFindVariable(scope, "capacity");
if (capacityField == NULL) {
ERROR("A field called \"capacity\" was not found", "FieldNotFound");
}
int64_t capacity = capacityField->value.data.intVal;
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
GroundValue* items = (GroundValue*)ptrField->value.data.intVal;
GroundVariable* memSizeField = groundFindVariable(scope, "memSize");
if (memSizeField == NULL) {
ERROR("A field called \"memSize\" was not found", "FieldNotFound");
}
if (capacity > amount) {
return groundCreateValue(BOOL, false);
}
capacityField->value.data.intVal = amount;
items = realloc(items, sizeof(GroundValue) * amount);
if (items == NULL) {
ERROR("Failed to allocate memory when reserving list space!", "AllocFail");
}
memSizeField->value.data.intVal = sizeof(GroundValue) * amount;
return groundCreateValue(BOOL, true);
}
GroundValue listStructConstructor(GroundScope* scope, List args) {
int64_t startingCapacity = args.values[0].data.intVal;
if (startingCapacity < 1) {
ERROR("List can't be less than 1 element in capacity on initialization!", "OutOfBounds");
}
GroundValue value = groundCreateValue(CUSTOM, &listStruct);
GroundObjectField *sizeField = groundFindField(*value.data.customVal, "size");
GroundObjectField *capacityField = groundFindField(*value.data.customVal, "capacity");
GroundObjectField *memSizeField = groundFindField(*value.data.customVal, "memSize");
GroundObjectField *ptrField = groundFindField(*value.data.customVal, "ptr");
GroundValue* items = calloc(STARTING_ELEMENTS, sizeof(GroundValue));
if (items == NULL) {
ERROR("Failed to allocate memory while creating list!", "AllocFail");
}
sizeField->value.data.intVal = 0;
capacityField->value.data.intVal = startingCapacity;
memSizeField->value.data.intVal = sizeof(GroundValue) * startingCapacity;
ptrField->value.data.intVal = (int64_t)items;
value.type = CUSTOM;
return value;
}
GroundValue destroyListStruct(GroundScope* scope, List args) {
GroundVariable* ptrField = groundFindVariable(scope, "ptr");
if (ptrField == NULL) {
ERROR("A field called \"ptr\" was not found", "FieldNotFound");
}
//free((GroundValue*)ptrField->value.data.intVal);
return groundCreateValue(NONE);
}
GroundValue duplicateListStruct(GroundScope* scope, List args) {
GroundObject* self = args.values[0].data.customVal;
GroundObjectField *sizeField = groundFindField(*self, "size");
GroundObjectField *capacityField = groundFindField(*self, "capacity");
GroundObjectField *memSizeField = groundFindField(*self, "memSize");
GroundObjectField *ptrField = groundFindField(*self, "ptr");
GroundValue newSelf = groundCreateValue(CUSTOM, &listStruct);
newSelf.type = CUSTOM;
GroundObjectField *newSizeField = groundFindField(*newSelf.data.customVal, "size");
GroundObjectField *newCapacityField = groundFindField(*newSelf.data.customVal, "capacity");
GroundObjectField *newMemSizeField = groundFindField(*newSelf.data.customVal, "memSize");
GroundObjectField *newPtrField = groundFindField(*newSelf.data.customVal, "ptr");
newSizeField->value.data.intVal = sizeField->value.data.intVal;
newCapacityField->value.data.intVal = capacityField->value.data.intVal;
newMemSizeField->value.data.intVal = memSizeField->value.data.intVal;
groundAddValueToScope(scope, "size", sizeField->value);
groundAddValueToScope(scope, "capacity", capacityField->value);
groundAddValueToScope(scope, "memSize", memSizeField->value);
GroundValue* newPtr = calloc(capacityField->value.data.intVal, sizeof(GroundValue));
if (!newPtr) {
ERROR("Failed to allocate memory while calling copy constructor of List!", "AllocFail");
}
memcpy(newPtr, (GroundValue*)ptrField->value.data.intVal, sizeField->value.data.intVal * sizeof(GroundValue));
newPtrField->value.data.intVal = (int64_t)newPtr;
return newSelf;
}
void initLists(GroundScope* scope) {
listStruct = groundCreateStruct();
groundAddFieldToStruct(&listStruct, "size", groundCreateValue(INT, 0)); // number of elements
groundAddFieldToStruct(&listStruct, "memSize", groundCreateValue(INT, sizeof(GroundValue) * STARTING_ELEMENTS)); // number of bytes allocated
groundAddFieldToStruct(&listStruct, "capacity", groundCreateValue(INT, STARTING_ELEMENTS)); // number of elements that can fit in the currently allocated space
groundAddFieldToStruct(&listStruct, "ptr", groundCreateValue(INT, 0)); // pointer to internal list struct
groundAddFunctionToStruct(&listStruct, "init", listStructConstructor, INT, 1, INT, "startingCapacity"); // init struct (ground constructor)
groundAddFunctionToStruct(&listStruct, "append", appendToListStruct, INT, 1, ANY, "value"); // append item to end of list
groundAddFunctionToStruct(&listStruct, "insert", insertIntoListStruct, INT, 2, ANY, "value", INT, "index"); // insert value at index
groundAddFunctionToStruct(&listStruct, "remove", listStructDelete, INT, 1, INT, "index"); // delete value at index
groundAddFunctionToStruct(&listStruct, "at", listStructAt, ANY, 1, INT, "index"); // get value at index
groundAddFunctionToStruct(&listStruct, "clear", clearListStruct, INT, 0); // clear list
groundAddFunctionToStruct(&listStruct, "set", listStructSet, INT, 2, ANY, "value", INT, "index"); // replace a value at an index
groundAddFunctionToStruct(&listStruct, "isEmpty", listStructIsEmpty, BOOL, 0); // returns true if list is empty, otherwise false
groundAddFunctionToStruct(&listStruct, "contains", listStructContains, BOOL, 1, ANY, "value"); // returns true if value is in list
groundAddFunctionToStruct(&listStruct, "reverse", reverseListStruct, INT, 0); // return list struct in reverse order
groundAddFunctionToStruct(&listStruct, "find", findListStruct, INT, 1, ANY, "value"); // return index of value in list, if not found, returns -1
groundAddFunctionToStruct(&listStruct, "reserve", reserveListStruct, BOOL, 1, INT, "amount"); // ensure list capacity >= amount. returns true if the list's capacity was expanded
groundAddFunctionToStruct(&listStruct, "destructor", destroyListStruct, ANY,0);
groundAddFunctionToStruct(&listStruct, "duplicator", duplicateListStruct, CUSTOM, 1, CUSTOM, "self");
groundAddNativeFunction(scope, "newList", listStructConstructor, CUSTOM, 1, INT, "startingCapacity");
groundAddNativeFunction(scope, "List_SOLS_CONSTRUCTOR", listStructConstructor, CUSTOM, 1, INT, "startingCapacity");
}

13
libs/collections/list.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef LIST_H
#define LIST_H
#include <groundext.h>
#include <groundvm.h>
#include <stddef.h>
extern const uint8_t STARTING_ELEMENTS;
GroundValue createList(int64_t initialCapacity);
void initLists(GroundScope* scope);
#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,57 +1,258 @@
#include <groundext.h>
#include <groundvm.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "groundext.h"
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
GroundValue native_file_read(GroundScope* scope, List args) {
if (args.size < 1 || args.values[0].type != STRING) {
return groundCreateValue(NONE);
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");
}
char* path = args.values[0].data.stringVal;
FILE* f = fopen(path, "r");
if (!f) {
ERROR("Failed to open file for reading", "FileError");
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;
}
}
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
// opens the given file in read and write mode, the file must exist or openning the file will fail
FILE* file = fopen(filePath, "r+");
char* content = malloc(fsize + 1);
if (!content) {
fclose(f);
ERROR("Failed to allocate memory for file reading", "FileError");
if (file == NULL) {
ERROR("Unkown error while openning file!", "GenericFileError");
}
fread(content, 1, fsize, f);
fclose(f);
content[fsize] = 0;
// seek to end of file to get size
fseek(file, 0, SEEK_END);
int64_t size = ftell(file);
rewind(file);
GroundValue val = groundCreateValue(STRING, content);
free(content);
return val;
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;
}
GroundValue native_file_write(GroundScope* scope, List args) {
if (args.size < 2 || args.values[0].type != STRING || args.values[1].type != STRING) {
return groundCreateValue(BOOL, 0);
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");
}
int64_t size = sizeField->value.data.intVal;
// 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");
}
char* path = args.values[0].data.stringVal;
char* content = args.values[1].data.stringVal;
// read file
size_t readSize = fread(buffer, 1, size, file);
buffer[readSize] = 0; // null terminate buffer
FILE* f = fopen(path, "w");
if (!f) {
ERROR("Failed to open file for writing", "FileError");
return groundCreateValue(STRING, buffer);
}
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");
}
fprintf(f, "%s", content);
fclose(f);
GroundVariable* tellField = groundFindVariable(scope, "tell");
if (tellField == NULL) {
ERROR("A field called \"tell\" was not found", "FieldNotFound");
}
return groundCreateValue(BOOL, 1);
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);
}
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) {
groundAddNativeFunction(scope, "file_Read", native_file_read, STRING, 1, STRING, "path");
groundAddNativeFunction(scope, "file_Write", native_file_write, BOOL, 2, STRING, "path", STRING, "content");
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");
}

View File

@@ -1,5 +1,12 @@
#include <groundext.h>
#include <math.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
#include <float.h>
GroundValue ground_sin(GroundScope* scope, List args) {
return groundCreateValue(
@@ -21,19 +28,15 @@ GroundValue ground_tan(GroundScope* scope, List args) {
}
GroundValue rad_to_deg(GroundScope* scope, List args) {
double radians = args.values[0].data.doubleVal;
return groundCreateValue(
DOUBLE,
radians * (180.0 / M_PI)
args.values[0].data.doubleVal * (180.0 / M_PI)
);
}
GroundValue deg_to_rad(GroundScope* scope, List args) {
double deg = args.values[0].data.doubleVal;
return groundCreateValue(
DOUBLE,
deg * (M_PI / 180.0)
args.values[0].data.doubleVal * (M_PI / 180.0)
);
}
@@ -62,16 +65,149 @@ GroundValue ground_sqrt(GroundScope* scope, List args) {
);
}
GroundValue ground_abs(GroundScope* scope, List args) {
return groundCreateValue(
DOUBLE,
abs(args.values[0].data.doubleVal)
);
}
GroundValue ground_min(GroundScope* scope, List args) {
double a = args.values[0].data.doubleVal;
double b = args.values[1].data.doubleVal;
return groundCreateValue(
DOUBLE,
(a < b) ? a : b
);
}
GroundValue ground_max(GroundScope* scope, List args) {
double a = args.values[0].data.doubleVal;
double b = args.values[1].data.doubleVal;
return groundCreateValue(
DOUBLE,
(a > b) ? a : b
);
}
GroundValue ground_clamp(GroundScope* scope, List args) {
double number = args.values[0].data.doubleVal;
double min = args.values[1].data.doubleVal;
double max = args.values[2].data.doubleVal;
if (number < min) number = min;
if (number > max) number = max;
return groundCreateValue(
DOUBLE,
number
);
}
GroundValue ground_round(GroundScope* scope, List args) {
double x = args.values[0].data.doubleVal;
if (x >= 0.0)
return groundCreateValue(
INT,
(long long)(x + 0.5)
);
else
return groundCreateValue(
INT,
(long long)(x - 0.5)
);
}
GroundValue ground_floor(GroundScope* scope, List args) {
double x = args.values[0].data.doubleVal;
if (x >= (double)LLONG_MAX) return groundCreateValue(INT, LLONG_MAX);
if (x <= (double)LLONG_MIN) return groundCreateValue(INT, LLONG_MIN);
long long i = (long long)x; // truncates toward zero
if (x < 0 && x != (double)i) {
return groundCreateValue(
INT,
i-1
);
}
return groundCreateValue(
INT,
i
);
}
GroundValue ground_ceil(GroundScope* scope, List args) {
double x = args.values[0].data.doubleVal;
if (x >= (double)LLONG_MAX) return groundCreateValue(INT, LLONG_MAX);
if (x <= (double)LLONG_MIN) return groundCreateValue(INT, LLONG_MIN);
long long i = (long long)x; // truncates toward zero
if (x > 0 && x != (double)i) {
return groundCreateValue(
INT,
i+1
);
}
return groundCreateValue(
INT,
i
);
}
GroundValue ground_random(GroundScope* scope, List args) {
int64_t min = args.values[0].data.intVal;
int64_t max = args.values[1].data.intVal;
return groundCreateValue(
INT,
min + rand() % (max - min)
);
}
GroundValue ground_random_double(GroundScope* scope, List args) {
double min = args.values[0].data.doubleVal;
double max = args.values[1].data.doubleVal;
return groundCreateValue(
DOUBLE,
min + (double)rand() / RAND_MAX * (max - min)
);
}
GroundValue ground_random_set_seed(GroundScope* scope, List args) {
srand(args.values[0].data.intVal);
return groundCreateValue(
INT,
0
);
}
void ground_init(GroundScope* scope) {
srand((unsigned)time(NULL) ^ (unsigned)clock());
groundAddNativeFunction(scope, "math_Sin", ground_sin, DOUBLE, 1, DOUBLE, "radians");
groundAddNativeFunction(scope, "math_Cos", ground_cos, DOUBLE, 1, DOUBLE, "radians");
groundAddNativeFunction(scope, "math_Tan", ground_tan, DOUBLE, 1, DOUBLE, "radians");
groundAddNativeFunction(scope, "math_DegreesToRadians", deg_to_rad, DOUBLE, 1, DOUBLE, "degrees");
groundAddNativeFunction(scope, "math_RadiansToDegrees", rad_to_deg, DOUBLE, 1, DOUBLE, "radians");
groundAddNativeFunction(scope, "math_Modulos", ground_modulos, DOUBLE, 2, DOUBLE, "number1", DOUBLE, "number2");
groundAddNativeFunction(scope, "math_Pow", ground_pow, DOUBLE, 2, DOUBLE, "number1", DOUBLE, "number2");
groundAddNativeFunction(scope, "math_Sqrt", ground_sqrt, DOUBLE, 1, DOUBLE, "number");
groundAddNativeFunction(scope, "math_Abs", ground_abs, DOUBLE, 1, DOUBLE, "number");
groundAddNativeFunction(scope, "math_Min", ground_min, DOUBLE, 2, DOUBLE, "number1", DOUBLE, "number2");
groundAddNativeFunction(scope, "math_Max", ground_max, DOUBLE, 2, DOUBLE, "number1", DOUBLE, "number2");
groundAddNativeFunction(scope, "math_Clamp", ground_clamp, DOUBLE, 2, DOUBLE, "number", DOUBLE, "min", DOUBLE, "max");
groundAddNativeFunction(scope, "math_Round", ground_round, INT, 1, DOUBLE, "number");
groundAddNativeFunction(scope, "math_Floor", ground_floor, INT, 1, DOUBLE, "number");
groundAddNativeFunction(scope, "math_Ceil", ground_ceil, INT, 1, DOUBLE, "number");
groundAddNativeFunction(scope, "math_Random", ground_random, INT, 2, INT, "min", INT, "max");
groundAddNativeFunction(scope, "math_RandomDouble", ground_random_double, DOUBLE, 2, DOUBLE, "min", DOUBLE, "max");
groundAddNativeFunction(scope, "math_RandomSetSeed", ground_random_set_seed, INT, 1, INT, "seed");
}

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();
GroundValue ground_request(GroundScope* scope, List args) {
if (!handle) {
ERROR("CURL failed to init", "GenericRequestError");
GroundObject obj = *args.values[0].data.customVal;
// 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");
}
// 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;
}
}
if (!requestTypeOk)
ERROR("Invalid request type! Choices: GET, POST", "ValueError");
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);
// 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);
CURLcode result = curl_easy_perform(handle);
// 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_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, httpHeaderList);
if (result != CURLE_OK) {
char buf[256];
snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result));
ERROR(buf, "ActionRequestError");
CURLcode curlStatus = curl_easy_perform(curlHandle);
curl_slist_free_all(httpHeaderList); // dont want em' memory leaks :P - spooopy
// 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_easy_cleanup(handle);
// get the http response code
long httpCode = 0;
curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpCode);
GroundValue ret = groundCreateValue(STRING, buffer.data);
return ret;
}
// create return value and return it
GroundValue value = groundCreateValue(CUSTOM, &template);
// POST request
GroundValue post_request(GroundScope* scope, List args) {
curl_setup();
CURL* handle = curl_easy_init();
GroundObjectField* status = groundFindField(*value.data.customVal, "statusCode");
status->value.data.intVal = httpCode;
if (!handle) {
ERROR("CURL failed to init", "GenericRequestError");
}
GroundObjectField* data = groundFindField(*value.data.customVal, "data");
data->value.data.stringVal = buffer.data;
ResponseBuffer buffer = {0};
buffer.data = malloc(1);
buffer.data[0] = '\0';
buffer.size = 0;
value.type = CUSTOM;
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;
}
// ============== WebSocket Functions ==============
// 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;
}
}
return -1;
}
// 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);
}
// 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);
}
// 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");
}
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);
}
// 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);
}
// 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");
}
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);
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");
// init libcurl
curl_global_init(CURL_GLOBAL_ALL);
curlHandle = curl_easy_init();
// 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");
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");
}

455
libs/string/string.c Normal file
View File

@@ -0,0 +1,455 @@
#include <ctype.h>
#include <groundext.h>
#include <groundvm.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
GroundValue stringUpper(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
char* uppercase = malloc(sizeof(char) * (strlen(string) + 1));
if (!uppercase)
ERROR("Failed to allocate memory while getting uppercase of string!", "AllocFail");
for (size_t i = 0; i < strlen(string); i++) {
uppercase[i] = toupper(string[i]);
}
return groundCreateValue(STRING, uppercase);
}
GroundValue stringLower(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
char* lowercase = malloc(sizeof(char) * (strlen(string) + 1));
if (!lowercase)
ERROR("Failed to allocate memory while getting uppercase of string!", "AllocFail");
for (size_t i = 0; i < strlen(string); i++) {
lowercase[i] = tolower(string[i]);
}
return groundCreateValue(STRING, lowercase);
}
GroundValue stringStartsWith(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
char* prefix = args.values[1].data.stringVal;
return groundCreateValue(BOOL, strncmp(prefix, string, strlen(prefix)) == 0);
}
GroundValue stringEndsWith(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
char* suffix = args.values[1].data.stringVal;
size_t strLen = strlen(string);
size_t suffixLen = strlen(suffix);
if (suffixLen > strLen) return groundCreateValue(BOOL, false); // can't start with suffix if suffix is longer
return groundCreateValue(
BOOL,
strcmp(string + strLen - suffixLen, suffix) == 0
);
}
GroundValue stringSubstring(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
int64_t start = args.values[1].data.intVal;
int64_t end = args.values[2].data.intVal;
if (end < start) {
ERROR("End can't be less than start when getting substring!", "EndBeforeStart");
}
size_t inputLen = strlen(string);
if (start >= inputLen) {
ERROR("Start is outside string!", "OutOfBounds");
} else if (end >= inputLen || end < 0) {
ERROR("End is outside string!", "OutOfBounds");
}
char* buffer = malloc(inputLen + 1);
if (!buffer) {
ERROR("Failed to allocate memory while getting substring of string!", "AllocFail");
}
// string_Substring("Hello!", 1, 3) -> "ell"
// amount = 3 - 1 + 1 = 3
strncpy(buffer, string + start, end - start + 1);
buffer[end - start + 1] = 0;
return groundCreateValue(STRING, buffer);
}
GroundValue stringCharAt(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
int64_t index = args.values[1].data.intVal;
if (index < 0)
ERROR("Index can't be less than 0!", "OutOfBounds");
int64_t stringLen = strlen(string);
if (index >= stringLen) {
char buffer[256];
sprintf(buffer, "Attempt to get char at index %ld when string is of length %ld.", index, stringLen);
ERROR(buffer, "OutOfBounds");
}
char buffer[2] = {string[index], 0};
return groundCreateValue(STRING, buffer);
}
GroundValue stringFind(GroundScope* scope, List args) {
char* haystack = args.values[0].data.stringVal;
char* needle = args.values[1].data.stringVal;
char* result = strstr(haystack, needle);
if (!result) {
return groundCreateValue(INT, (int64_t)-1);
}
return groundCreateValue(INT, result - haystack);
}
GroundValue stringFindLast(GroundScope* scope, List args) {
char* haystack = args.values[0].data.stringVal;
char* needle = args.values[1].data.stringVal;
if (!*needle) {
return groundCreateValue(INT, (int64_t)strlen(haystack)-1);
}
const char* p = haystack;
while (*p) p++;
const char* q = needle;
while (*q) q++;
bool found = false;
while (!found && (p - haystack) >= (q - needle)) {
const char* s = p;
const char* t = q;
while (t != needle && s != haystack && *(s - 1) == *(t - 1)) {
s--;
t--;
}
found = (t == needle);
if (found) p = s;
else p--;
}
return groundCreateValue(INT, (int64_t)(found ? p - haystack : -1));
}
GroundValue stringContains(GroundScope* scope, List args) {
char* haystack = args.values[0].data.stringVal;
char* needle = args.values[1].data.stringVal;
char* result = strstr(haystack, needle);
return groundCreateValue(BOOL, result != NULL);
}
GroundValue stringCount(GroundScope* scope, List args) {
char* haystack = args.values[0].data.stringVal;
char* needle = args.values[1].data.stringVal;
int64_t count = 0;
const char* tmp = haystack;
while ((tmp = strstr(tmp, needle))) {
count++;
tmp++;
}
return groundCreateValue(INT, count);
}
GroundValue stringTrim(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
// trip whitespace from start
char* start = string;
while (*start && isspace((unsigned char)*start)) start++;
// trim whitespace from end
char* end = string + strlen(string);
while (end > start && isspace((unsigned char)*(end - 1))) end--;
size_t len = end - start;
char* buffer = malloc(len + 1);
if (!buffer)
ERROR("Failed to allocate memory while trimming string!", "AllocFail");
memcpy(buffer, start, len);
buffer[len] = 0;
return groundCreateValue(STRING, buffer);
}
GroundValue stringTrimLeft(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
// trip whitespace from start
char* start = string;
while (*start && isspace((unsigned char)*start)) start++;
size_t len = strlen(start);
char* buffer = malloc(len + 1);
if (!buffer)
ERROR("Failed to allocate memory while trimming string!", "AllocFail");
memcpy(buffer, start, len);
buffer[len] = 0;
return groundCreateValue(STRING, buffer);
}
GroundValue stringTrimRight(GroundScope* scope, List args) {
char* str = args.values[0].data.stringVal;
char* end = str + strlen(str);
while (end > str && isspace((unsigned char)*(end - 1))) {
end--;
}
size_t len = end - str;
char* buffer = malloc(len + 1);
if (!buffer)
ERROR("Failed to allocate memory while trimming string!", "AllocFail");
memcpy(buffer, str, len);
buffer[len] = '\0';
return groundCreateValue(STRING, buffer);
}
GroundValue stringReplace(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
char* from = args.values[1].data.stringVal;
char* to = args.values[2].data.stringVal;
// === THANK YOU TO https://creativeandcritical.net FOR THEIR repl_str() FUNCTION === \\
/* Adjust each of the below values to suit your needs. */
/* Increment positions cache size initially by this number. */
size_t cache_sz_inc = 16;
/* Thereafter, each time capacity needs to be increased,
* multiply the increment by this factor. */
const size_t cache_sz_inc_factor = 3;
/* But never increment capacity by more than this number. */
const size_t cache_sz_inc_max = 1048576;
char *pret, *ret = NULL;
const char *pstr2, *pstr = string;
size_t i, count = 0;
#if (__STDC_VERSION__ >= 199901L)
uintptr_t *pos_cache_tmp, *pos_cache = NULL;
#else
ptrdiff_t *pos_cache_tmp, *pos_cache = NULL;
#endif
size_t cache_sz = 0;
size_t cpylen, orglen, retlen, tolen, fromlen = strlen(from);
/* Find all matches and cache their positions. */
while ((pstr2 = strstr(pstr, from)) != NULL) {
count++;
/* Increase the cache size when necessary. */
if (cache_sz < count) {
cache_sz += cache_sz_inc;
pos_cache_tmp = realloc(pos_cache, sizeof(*pos_cache) * cache_sz);
if (pos_cache_tmp == NULL) {
goto end_repl_str;
} else pos_cache = pos_cache_tmp;
cache_sz_inc *= cache_sz_inc_factor;
if (cache_sz_inc > cache_sz_inc_max) {
cache_sz_inc = cache_sz_inc_max;
}
}
pos_cache[count-1] = pstr2 - string;
pstr = pstr2 + fromlen;
}
orglen = pstr - string + strlen(pstr);
/* Allocate memory for the post-replacement string. */
if (count > 0) {
tolen = strlen(to);
retlen = orglen + (tolen - fromlen) * count;
} else retlen = orglen;
ret = malloc(retlen + 1);
if (ret == NULL) {
goto end_repl_str;
}
if (count == 0) {
/* If no matches, then just duplicate the string. */
strcpy(ret, string);
} else {
/* Otherwise, duplicate the string whilst performing
* the replacements using the position cache. */
pret = ret;
memcpy(pret, string, pos_cache[0]);
pret += pos_cache[0];
for (i = 0; i < count; i++) {
memcpy(pret, to, tolen);
pret += tolen;
pstr = string + pos_cache[i] + fromlen;
cpylen = (i == count-1 ? orglen : pos_cache[i+1]) - pos_cache[i] - fromlen;
memcpy(pret, pstr, cpylen);
pret += cpylen;
}
ret[retlen] = '\0';
}
end_repl_str:
/* Free the cache and return the post-replacement string,
* which will be NULL in the event of an error. */
free(pos_cache);
return groundCreateValue(STRING, ret);
}
GroundValue stringRepeat(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
int64_t times = args.values[1].data.intVal;
if (times < 0)
ERROR("Can't repeat string negative number of times!", "ValueError");
char* out = malloc(strlen(string) * times + 1);
if (!out)
ERROR("Failed to allocate memory while repeating string!", "AllocFail");
strcpy(out, string);
while (--times > 0) {
strcat(out, string);
}
return groundCreateValue(STRING, out);
}
GroundValue stringReverse(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
size_t stringLen = strlen(string);
char* out = malloc(stringLen + 1);
if (!out)
ERROR("Failed to allocate memory while reversing string!", "AllocFail");
unsigned int i = 0;
while (stringLen > 0) out[i++] = string[stringLen-- - 1];
out[i] = 0;
return groundCreateValue(STRING, out);
}
GroundValue stringIsAlpha(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
const char* p = string;
bool isAlpha = true;
while (*p) {
if (!isalpha(*p)) {
isAlpha = false;
break;
}
p++;
}
return groundCreateValue(BOOL, isAlpha);
}
GroundValue stringIsDigit(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
const char* p = string;
bool isDigit = true;
while (*p) {
if (!isdigit(*p)) {
isDigit = false;
break;
}
p++;
}
return groundCreateValue(BOOL, isDigit);
}
GroundValue stringIsAlnum(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
const char* p = string;
bool isAlnum = true;
while (*p) {
if (!isalnum(*p)) {
isAlnum = false;
break;
}
p++;
}
return groundCreateValue(BOOL, isAlnum);
}
GroundValue stringIsSpace(GroundScope* scope, List args) {
char* string = args.values[0].data.stringVal;
const char* p = string;
bool isSpace = true;
while (*p) {
if (!isspace(*p)) {
isSpace = false;
break;
}
p++;
}
return groundCreateValue(BOOL, isSpace);
}
void ground_init(GroundScope* scope) {
// modify string
groundAddNativeFunction(scope, "string_Upper", stringUpper, STRING, 1, STRING, "str");
groundAddNativeFunction(scope, "string_Lower", stringLower, STRING, 1, STRING, "str");
groundAddNativeFunction(scope, "string_Trim", stringTrim, STRING, 1, STRING, "str");
groundAddNativeFunction(scope, "string_TrimLeft", stringTrimLeft, STRING, 1, STRING, "str");
groundAddNativeFunction(scope, "string_TrimRight", stringTrimRight, STRING, 1, STRING, "str");
groundAddNativeFunction(scope, "string_Substring", stringSubstring, STRING, 3, STRING, "str", INT, "start", INT, "end");
groundAddNativeFunction(scope, "string_Replace", stringReplace, STRING, 3, STRING, "str", STRING, "from", STRING, "to");
groundAddNativeFunction(scope, "string_Repeat", stringRepeat, STRING, 2, STRING, "str", INT, "times");
groundAddNativeFunction(scope, "string_Reverse", stringReverse, STRING, 1, STRING, "str");
// check for substring
groundAddNativeFunction(scope, "string_StartsWith", stringStartsWith, BOOL, 2, STRING, "str", STRING, "prefix");
groundAddNativeFunction(scope, "string_EndsWith", stringEndsWith, BOOL, 2, STRING, "str", STRING, "suffix");
groundAddNativeFunction(scope, "string_Contains", stringContains, BOOL, 2, STRING, "haystack", STRING, "needle");
// character classification
groundAddNativeFunction(scope, "string_IsAlpha", stringIsAlpha, BOOL, 1, STRING, "str");
groundAddNativeFunction(scope, "string_IsDigit", stringIsDigit, BOOL, 1, STRING, "str");
groundAddNativeFunction(scope, "string_IsAlnum", stringIsAlnum, BOOL, 1, STRING, "str");
groundAddNativeFunction(scope, "string_IsSpace", stringIsSpace, BOOL, 1, STRING, "str");
// get char
groundAddNativeFunction(scope, "string_CharAt", stringCharAt, STRING, 2, STRING, "str", INT, "index");
// find index substring
groundAddNativeFunction(scope, "string_Find", stringFind, INT, 2, STRING, "haystack", STRING, "needle");
groundAddNativeFunction(scope, "string_FindLast", stringFindLast, INT, 2, STRING, "haystack", STRING, "needle");
groundAddNativeFunction(scope, "string_Count", stringCount, INT, 2, STRING, "haystack", STRING, "needle");
}

50
libs/tests/tests.c Normal file
View File

@@ -0,0 +1,50 @@
#include "uthash.h"
#include <stdio.h>
#include <groundvm.h>
#include <groundext.h>
#include <ansii.h>
GroundValue testsRun(GroundScope* scope, List args) {
GroundObject* testsStruct = args.values[0].data.customVal;
int totalTests = 0, successful = 0, fail = 0;
GroundObjectField* entry, *temp;
HASH_ITER(hh, testsStruct->fields, entry, temp) {
if (entry) {
totalTests++;
if (entry->value.type != FUNCTION) {
ERROR("The given tests struct can't have any struct fields, only methods!", "WrongType");
}
GroundValue success = groundRunFunction(entry->value.data.fnVal, 0);
if (success.type != BOOL) {
ERROR("Your tests should return a boolean.", "ReturnTypeMismatch");
}
if (success.data.boolVal) {
successful++;
printf(ESC_GREEN_FG ESC_BOLD ".");
} else {
fail++;
printf(ESC_RED_FG ESC_BOLD ".");
}
}
}
printf(ESC_RESET "\n\n/============================\\\n");
printf("- Total : %d\n", totalTests);
printf("- Successful: %d\n", successful);
printf("- Fail : %d\n", fail);
printf("\\============================/\n");
return groundCreateValue(INT, 0);
}
void ground_init(GroundScope* scope) {
groundAddNativeFunction(scope, "tests_Run", testsRun, INT, 1, CUSTOM, "testsStruct");
}

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");
}

559
src/compiler.c Normal file
View File

@@ -0,0 +1,559 @@
#ifdef GROUND_COMPILE_WITH_TRAM
#include "compiler.h"
#include "types.h"
#include "include/uthash.h"
#include <tram.h>
// Helper macros to reduce repetition
#define CHECK_ARGC(expected) \
if (instruction->args.length != (expected)) { \
fprintf(stderr, "Error at instruction %zu (%s): expected %d argument(s), got %zu\n", \
i, #expected, (expected), instruction->args.length); \
return false; \
}
#define CHECK_ARGC_MIN(min) \
if (instruction->args.length < (min)) { \
fprintf(stderr, "Error at instruction %zu: expected at least %d argument(s), got %zu\n", \
i, (min), instruction->args.length); \
return false; \
}
#define CHECK_ARG_TYPE(idx, expected_type) \
if (instruction->args.args[(idx)].type != (expected_type)) { \
fprintf(stderr, "Error at instruction %zu: argument %d should be " #expected_type ", got %d\n", \
i, (idx), instruction->args.args[(idx)].type); \
return false; \
}
bool checkForErrors(GroundProgram* program) {
for (size_t i = 0; i < program->size; i++) {
GroundInstruction* instruction = &program->instructions[i];
switch (instruction->type) {
// if $condition %label
case IF:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, LINEREF);
break;
// jump %label
case JUMP:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, LINEREF);
break;
// end $status
case END:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, VALREF);
break;
// input &variable
case INPUT:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, DIRREF);
break;
// print $value
case PRINT:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, VALREF);
break;
// println $value
case PRINTLN:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, VALREF);
break;
// set &variable $value
case SET:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, VALREF);
break;
// gettype $value &variable
case GETTYPE:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// exists &variable &output
case EXISTS:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// setlist &varname $val1 $val2 ... (at least 1 arg, rest are valrefs)
case SETLIST:
CHECK_ARGC_MIN(1);
CHECK_ARG_TYPE(0, DIRREF);
for (size_t j = 1; j < instruction->args.length; j++) {
if (instruction->args.args[j].type != VALREF) {
fprintf(stderr, "Error at instruction %zu (SETLIST): argument %zu should be VALREF, got %d\n",
i, j, instruction->args.args[j].type);
return false;
}
}
break;
// setlistat &listname $index $value
case SETLISTAT:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, VALREF);
break;
// getlistat &list $index &variable
case GETLISTAT:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// getlistsize &list &variable
case GETLISTSIZE:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// listappend $value &list
case LISTAPPEND:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// getstrsize $string &variable
case GETSTRSIZE:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// getstrcharat $string $index &variable
case GETSTRCHARAT:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// add $value $value &variable
case ADD:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// subtract $value $value &variable
case SUBTRACT:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// multiply $value $value &variable
case MULTIPLY:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// divide $value $value &variable
case DIVIDE:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// equal $value $value &variable
case EQUAL:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// inequal $value $value &variable
case INEQUAL:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// not $value &variable
case NOT:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// greater $value $value &variable
case GREATER:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// lesser $value $value &variable
case LESSER:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, VALREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// stoi $value &variable
case STOI:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// stod $value &variable
case STOD:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// itoc, ctoi $value &variable
case ITOC:
case CTOI:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// tostring $value &variable
case TOSTRING:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
break;
// fun !functionName -returnType [-argType &arg ...]
// Minimum 2 args: the function ref and return type
case FUN:
CHECK_ARGC_MIN(2);
CHECK_ARG_TYPE(0, FNREF);
CHECK_ARG_TYPE(1, TYPEREF);
// Remaining args must be alternating TYPEREF, DIRREF pairs
if ((instruction->args.length - 2) % 2 != 0) {
fprintf(stderr, "Error at instruction %zu (FUN): argument pairs after return type must be -type &name pairs\n", i);
return false;
}
for (size_t j = 2; j < instruction->args.length; j += 2) {
CHECK_ARG_TYPE(j, TYPEREF);
CHECK_ARG_TYPE(j + 1, DIRREF);
}
break;
// return $value
case RETURN:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, VALREF);
break;
// endfun - no args
case ENDFUN:
CHECK_ARGC(0);
break;
// call !function [$arg1 $arg2 ...] &returnVal
// Minimum 2 args: function ref and return variable
case CALL:
CHECK_ARGC_MIN(2);
CHECK_ARG_TYPE(0, FNREF);
// Middle args are valrefs, last arg is dirref
for (size_t j = 1; j < instruction->args.length - 1; j++) {
if (instruction->args.args[j].type != VALREF) {
fprintf(stderr, "Error at instruction %zu (CALL): argument %zu should be VALREF, got %d\n",
i, j, instruction->args.args[j].type);
return false;
}
}
CHECK_ARG_TYPE(instruction->args.length - 1, DIRREF);
break;
// callmethod &object !methodName [$arg1 $arg2 ...] &returnVal
// Minimum 3 args: object ref, method name, return variable
case CALLMETHOD:
CHECK_ARGC_MIN(3);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, FNREF);
// Middle args are valrefs, last arg is dirref
for (size_t j = 2; j < instruction->args.length - 1; j++) {
if (instruction->args.args[j].type != VALREF) {
fprintf(stderr, "Error at instruction %zu (CALL): argument %zu should be VALREF, got %d\n",
i, j, instruction->args.args[j].type);
return false;
}
}
CHECK_ARG_TYPE(instruction->args.length - 1, DIRREF);
break;
// struct -structName
case STRUCT:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, TYPEREF);
break;
// endstruct - no args
case ENDSTRUCT:
CHECK_ARGC(0);
break;
// init &var -type
case INIT:
CHECK_ARGC(2);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, TYPEREF);
break;
// getfield $object &fieldName &outputVar
case GETFIELD:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, VALREF);
CHECK_ARG_TYPE(1, DIRREF);
CHECK_ARG_TYPE(2, DIRREF);
break;
// setfield &object &fieldName $value
case SETFIELD:
CHECK_ARGC(3);
CHECK_ARG_TYPE(0, DIRREF);
CHECK_ARG_TYPE(1, DIRREF);
CHECK_ARG_TYPE(2, VALREF);
break;
// use $libraryName
case USE:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, VALREF);
break;
// extern $libraryName
case EXTERN:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, VALREF);
break;
// createlabel @label
case CREATELABEL:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, LABEL);
break;
// pause, licence - no args
case PAUSE:
case LICENSE:
break;
// drop &variable
case DROP:
CHECK_ARGC(1);
CHECK_ARG_TYPE(0, DIRREF);
break;
}
}
return true;
}
GroundState createGroundState() {
GroundState state = {
.variables = NULL,
.labels = NULL
};
return state;
}
GroundCompilerVariable* cFindVariable(GroundState* state, const char* id) {
GroundCompilerVariable* var;
HASH_FIND_STR(state->variables, id, var);
return var;
}
void cDeleteVariable(GroundState* state, const char* id) {
GroundCompilerVariable* var;
HASH_FIND_STR(state->variables, id, var);
HASH_DEL(state->variables, var);
}
GroundCompilerVariable* cAddVariable(GroundState* state, const char* id, GroundValueType type) {
GroundCompilerVariable* var = malloc(sizeof(GroundCompilerVariable));
snprintf(var->id, MAX_ID_LEN, "%s", id);
var->type = type;
HASH_ADD_STR(state->variables, id, var);
return var;
}
void compileGroundProgram(GroundProgram* program, char* name) {
if (!checkForErrors(program)) {
printf("Error: Invalid program\n");
return;
}
GroundState state = createGroundState();
Tram_Program tramProgram = Tram_Program_Create();
Tram_ParameterList parameters = Tram_ParameterList_Create(1, (Tram_Parameter[]){Tram_Parameter_Variable("main")});
Tram_Program_AddInstruction(&tramProgram, Tram_Instruction_Create(Tram_InstructionType_CreateLabel, parameters));
// Resolve labels
for (size_t i = 0; i < program->size; i++) {
if (program->instructions[i].type == CREATELABEL) {
Tram_ParameterList parameters = Tram_ParameterList_Create(1, (Tram_Parameter[]){Tram_Parameter_Variable(program->instructions[i].args.args[0].value.refName)});
Tram_Program_AddInstruction(&tramProgram, Tram_Instruction_Create(Tram_InstructionType_CreateLabel, parameters));
}
}
for (size_t i = 0; i < program->size; i++) {
compileGroundInstruction(&program->instructions[i], &tramProgram, &state);
}
Tram_Compiler* compiler = Tram_Compiler_Create(tramProgram);
Tram_Compiler_SetLogLevel(compiler, Tram_LogLevel_Debug);
Tram_Compiler_SetTarget(compiler, Tram_Target_x86_64_Linux_libc);
Tram_Compiler_Compile(compiler);
if (Tram_Compiler_HasCompiled(compiler)) {
Tram_Compiler_AssembleAndLink(compiler, name, (Tram_LinkerArgs){.size = 0, .data = NULL});
} else {
printf("Compilation failed\n");
exit(EXIT_FAILURE);
}
}
void compileGroundInstruction(GroundInstruction* instruction, Tram_Program* program, GroundState* state) {
switch (instruction->type) {
case IF:
break;
case JUMP:
break;
case END:
break;
case INPUT:
break;
case PRINT:
break;
case PRINTLN:
break;
case SET:
break;
case GETTYPE:
break;
case EXISTS:
break;
case SETLIST:
break;
case SETLISTAT:
break;
case GETLISTAT:
break;
case GETLISTSIZE:
break;
case LISTAPPEND:
break;
case GETSTRSIZE:
break;
case GETSTRCHARAT:
break;
case ADD:
break;
case SUBTRACT:
break;
case MULTIPLY:
break;
case DIVIDE:
break;
case EQUAL:
break;
case INEQUAL:
break;
case NOT:
break;
case GREATER:
break;
case LESSER:
break;
case AND:
break;
case OR:
break;
case XOR:
break;
case NEG:
break;
case SHIFT:
break;
case STOI:
break;
case STOD:
break;
case ITOC:
break;
case CTOI:
break;
case TOSTRING:
break;
case FUN:
break;
case RETURN:
break;
case ENDFUN:
break;
case PUSHARG:
break;
case CALL:
break;
case STRUCT:
break;
case ENDSTRUCT:
break;
case INIT:
break;
case GETFIELD:
break;
case SETFIELD:
break;
case USE:
break;
case EXTERN:
break;
case CREATELABEL: {
Tram_ParameterList parameters = Tram_ParameterList_Create(1, (Tram_Parameter[]){Tram_Parameter_Variable(instruction->args.args[0].value.refName)});
Tram_Program_AddInstruction(program, Tram_Instruction_Create(Tram_InstructionType_CreateLabel, parameters));
break;
}
case PAUSE:
break;
case DROP:
break;
case LICENSE:
break;
case ERRORCMD:
break;
}
}
#endif

20
src/compiler.h Normal file
View File

@@ -0,0 +1,20 @@
#include "types.h"
#ifdef GROUND_COMPILE_WITH_TRAM
#include <tram.h>
#endif
typedef struct GroundCompilerVariable {
GroundValueType type;
char id[MAX_ID_LEN];
UT_hash_handle hh;
} GroundCompilerVariable;
typedef struct GroundState {
GroundCompilerVariable* variables;
GroundLabel* labels;
} GroundState;
void compileGroundProgram(GroundProgram* program, char* name);
#ifdef GROUND_COMPILE_WITH_TRAM
void compileGroundInstruction(GroundInstruction* instruction, Tram_Program* program, GroundState* state);
#endif

52
src/include/estr.h Normal file
View File

@@ -0,0 +1,52 @@
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef ESTR_H
#define ESTR_H
/*
estr.h - Easy string manipulation
This library has macros to allow easier manipulation of strings. No longer shall
you have to malloc and realloc away to keep adding to your strings.
Usage:
Estr myString = CREATE_ESTR("my awesome string");
APPEND_ESTR(myString, " is so cool");
printf("%s\n", myString.str);
*/
#define CREATE_ESTR(instr) \
(Estr) { \
.str = instr,\
.size = strlen(instr),\
.shouldBeFreed = 0, \
.destroyed = 0 \
}
#define APPEND_ESTR(estr, instr) { \
estr.size = estr.size + strlen(instr); \
char* tmp_ptr = malloc(estr.size + 1); \
if (tmp_ptr == NULL) printf("WARNING: Could not realloc estr " #estr "\n"); \
else { \
snprintf(tmp_ptr, estr.size + 1, "%s%s", estr.str, instr); \
if (estr.shouldBeFreed > 0) free(estr.str); \
estr.shouldBeFreed = 1; \
estr.str = tmp_ptr; \
} \
}
#define DESTROY_ESTR(estr) if (estr.shouldBeFreed > 0 && estr.destroyed < 1) free(estr.str);
typedef struct Estr {
char* str;
size_t size;
int8_t shouldBeFreed;
int8_t destroyed;
} Estr;
#endif // ESTR_H

View File

@@ -1,10 +1,47 @@
#include "lexer.h"
#include "parser.h"
#include "interpreter.h"
#include "compiler.h"
#include "types.h"
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
char* getFileContents(const char* filename) {
// https://stackoverflow.com/questions/3747086/reading-the-whole-text-file-into-a-char-array-in-c
FILE* fp;
long lSize;
char* file;
fp = fopen(filename, "rb");
if (!fp) {
perror(filename);
exit(1);
}
fseek(fp, 0L, SEEK_END);
lSize = ftell(fp);
rewind(fp);
file = calloc(1, lSize + 1);
if (!file) {
fclose(fp);
fprintf(stderr, "memory allocation fail when reading file %s\n", filename);
exit(1);
}
if (1!=fread(file, lSize, 1, fp)) {
fclose(fp);
free(file);
fputs("couldn't read entire file", stderr);
exit(1);
}
// we done
fclose(fp);
return file;
}
GroundProgram groundCreateProgram() {
return (GroundProgram) {
@@ -43,41 +80,90 @@ 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: {
gv = createFunctionGroundValue(va_arg(args, GroundFunction*));
break;
}
case STRUCTVAL: {
gv.type = STRUCTVAL;
gv.data.structVal = malloc(sizeof(GroundStruct));
*gv.data.structVal = va_arg(args, GroundStruct);
break;
}
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: {
return createNoneGroundValue();
gv = createNoneGroundValue();
break;
}
}
va_end(args);
return gv;
}
void segfaultHandle(int signal);
GroundValue groundRunProgram(GroundProgram* program) {
return interpretGroundProgram(program, NULL);
signal(SIGSEGV, segfaultHandle);
GroundVariable* variables = NULL;
GroundLabel* labels = NULL;
GroundScope scope = {
.variables = &variables,
.labels = &labels,
.isMainScope = true
};
return interpretGroundProgram(program, &scope);
}
void groundPrintProgram(GroundProgram* program) {
@@ -90,3 +176,103 @@ void groundPrintProgram(GroundProgram* program) {
GroundProgram groundParseFile(const char* code) {
return parseFile(code);
}
GroundStruct groundCreateStruct() {
GroundStruct gs;
gs.size = 0;
gs.fields = malloc(sizeof(GroundStructField));
return gs;
}
void groundAddValueToScope(GroundScope* gs, const char* name, GroundValue value) {
addVariable(gs->variables, name, value);
}
void groundAddFieldToStruct(GroundStruct* gstruct, char* name, GroundValue field) {
addFieldToStruct(gstruct, name, field);
}
GroundFunction* createGroundFunction();
void addArgsToGroundFunction(GroundFunction* function, GroundValueType type, char* name);
void groundAddFunctionToStruct(GroundStruct* gstruct, char* name, NativeGroundFunction fn, GroundValueType returnType, int argCount, ...) {
GroundFunction* gf = createGroundFunction();
gf->isNative = true;
gf->nativeFn = fn;
gf->returnType = returnType;
va_list args;
va_start(args, argCount);
for(int i = 0; i < argCount; i++) {
GroundValueType type = va_arg(args, GroundValueType);
char* argName = va_arg(args, char*);
addArgsToGroundFunction(gf, type, argName);
}
va_end(args);
GroundValue gv = createFunctionGroundValue(gf);
addFieldToStruct(gstruct, name, gv);
}
void groundCompileProgram(GroundProgram* program) {
#ifdef GROUND_COMPILE_WITH_TRAM
compileGroundProgram(program, "program.gexe");
#else
(void)program;
printf("This version of Ground has been compiled without Tram, so compilation is not supported.\n");
#endif
}
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);
if (function->isNative) {
List argsList = createList();
for (size_t i = 0; i < argc; i++) {
appendToList(&argsList, va_arg(args, GroundValue));
}
return function->nativeFn(NULL, argsList);
}
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;
}
GroundVariable* groundFindVariable(GroundScope* gs, const char *id) {
GroundVariable* var;
HASH_FIND_STR(*gs->variables, id, var);
return var;
}

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;

View File

@@ -1,50 +1,106 @@
#include "parser.h"
#include "interpreter.h"
#include "compiler.h"
#include "types.h"
#include "serialize.h"
#include "repl.h"
#include <signal.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
FILE* fp;
long lSize;
char* file;
void segfaultHandle(int signal);
fp = fopen(filename, "rb");
if (!fp) {
perror(filename);
exit(1);
}
fseek(fp, 0L, SEEK_END);
lSize = ftell(fp);
rewind(fp);
file = calloc(1, lSize + 1);
if (!file) {
fclose(fp);
fputs("memory allocation fail when reading file", stderr);
exit(1);
}
if (1!=fread(file, lSize, 1, fp)) {
fclose(fp);
free(file);
fputs("couldn't read entire file", stderr);
exit(1);
}
// we done
fclose(fp);
return file;
}
char* getFileContents(const char* filename);
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: ground [file]\n");
signal(SIGSEGV, segfaultHandle);
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);
}
char* file = getFileContents(argv[1]);
GroundProgram program = parseFile(file);
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] [-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 {
if (fileName == NULL) {
fileName = argv[i];
} else {
appendToList(&groundArgs, createStringGroundValue(argv[i]));
}
}
}
if (fileName == NULL) {
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);
}
GroundProgram program;
if (readBytecode) {
deserializeProgramFromFile(fileName, &program);
} else {
char* file = getFileContents(fileName);
program = parseFile(file);
free(file);
interpretGroundProgram(&program, NULL);
}
if (compile) {
#ifdef GROUND_COMPILE_WITH_TRAM
compileGroundProgram(&program, "program.gexe");
#else
printf("This version of Ground has been compiled without Tram, so compilation is not supported.\n");
#endif
} else if (writeBytecode) {
serializeProgramToFile(outFileName, &program);
} else {
GroundVariable* variables = NULL;
GroundLabel* labels = NULL;
GroundCatch* catches = NULL;
GroundScope scope;
scope.variables = &variables;
scope.labels = &labels;
scope.catches = &catches;
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;
@@ -109,6 +128,9 @@ static GroundArg parseArgument(const char* token) {
else if (token[0] == '-') {
// Could be type reference or negative number
if (strlen(token) > 1 && !isdigit(token[1])) {
if (strcmp(token, "-any") == 0) {
printf("Note: please avoid using \"any\" type outside of extlibs.\n");
}
// Type reference (e.g., -int, -string)
return createRefGroundArg(TYPEREF, token + 1);
}
@@ -137,6 +159,7 @@ static GroundInstType getInstructionType(const char* inst) {
if (strcmp(inst, "input") == 0 || strcmp(inst, "stdin") == 0) return INPUT;
if (strcmp(inst, "print") == 0 || strcmp(inst, "stdout") == 0) return PRINT;
if (strcmp(inst, "println") == 0 || strcmp(inst, "stdlnout") == 0) return PRINTLN;
if (strcmp(inst, "error") == 0) return ERRORCMD;
if (strcmp(inst, "set") == 0) return SET;
if (strcmp(inst, "gettype") == 0) return GETTYPE;
if (strcmp(inst, "exists") == 0) return EXISTS;
@@ -156,20 +179,33 @@ 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;
if (strcmp(inst, "itoc") == 0) return ITOC;
if (strcmp(inst, "tostring") == 0) return TOSTRING;
if (strcmp(inst, "fun") == 0) return FUN;
if (strcmp(inst, "return") == 0) return RETURN;
if (strcmp(inst, "endfun") == 0) return ENDFUN;
if (strcmp(inst, "pusharg") == 0) return PUSHARG;
if (strcmp(inst, "call") == 0) return CALL;
if (strcmp(inst, "callmethod") == 0) return CALLMETHOD;
if (strcmp(inst, "struct") == 0) return STRUCT;
if (strcmp(inst, "endstruct") == 0) return ENDSTRUCT;
if (strcmp(inst, "init") == 0) return INIT;
if (strcmp(inst, "getfield") == 0) return GETFIELD;
if (strcmp(inst, "setfield") == 0) return SETFIELD;
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, "throw") == 0) return THROW;
if (strcmp(inst, "catch") == 0) return CATCH;
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,22 @@
#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
bool groundDisableTypeChecking = false;
GroundValue createIntGroundValue(int64_t in) {
GroundValue gv;
gv.data.intVal = in;
@@ -73,11 +89,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 +109,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: {
@@ -149,8 +215,51 @@ void printGroundValue(GroundValue* gv) {
printf("<function>");
break;
}
default: {
printf("FIXME");
case STRUCTVAL: {
printf("<struct fields: { ");
for (size_t i = 0; i < gv->data.structVal->size; i++) {
if (i != 0) {
printf(", ");
}
printf("%s: ", gv->data.structVal->fields[i].id);
if (gv->data.structVal->fields[i].value.type == STRING) {
printf("\"");
printGroundValue(&gv->data.structVal->fields[i].value);
printf("\"");
} else {
printGroundValue(&gv->data.structVal->fields[i].value);
}
}
printf(" }>");
break;
}
case CUSTOM: {
printf("<object fields: { ");
for (size_t i = 0; i < gv->customType->size; i++) {
if (i != 0) {
printf(", ");
}
printf("%s: ", gv->customType->fields[i].id);
GroundObjectField* field = findField(*gv->data.customVal, gv->customType->fields[i].id);
if (field == NULL) {
printf("<missing>");
} else {
printGroundValue(&field->value);
}
}
printf(" }>");
break;
}
case ANY: {
printf("<any>");
break;
}
case ERROR: {
printf("<error type: %s, what: %s>", gv->data.errorVal.type, gv->data.errorVal.what);
break;
}
case NONE: {
printf("<none>");
break;
}
}
@@ -158,7 +267,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) {
@@ -175,6 +285,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) {
@@ -238,6 +362,10 @@ void printGroundArg(GroundArg* ga) {
break;
}
case TYPEREF: {
if (ga->value.refName == NULL) {
printf("-any");
break;
}
printf("-%s", ga->value.refName);
break;
}
@@ -368,12 +496,33 @@ 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;
case STOD:
printf("stod");
break;
case CTOI:
printf("ctoi");
break;
case ITOC:
printf("itoc");
break;
case TOSTRING:
printf("tostring");
break;
@@ -392,6 +541,9 @@ void printGroundInstruction(GroundInstruction* gi) {
case CALL:
printf("call");
break;
case CALLMETHOD:
printf("callmethod");
break;
case STRUCT:
printf("struct");
break;
@@ -412,12 +564,31 @@ void printGroundInstruction(GroundInstruction* gi) {
break;
case CREATELABEL:
break;
default:
printf("FIXME");
case ERRORCMD:
printf("error");
break;
case GETFIELD:
printf("getfield");
break;
case SETFIELD:
printf("setfield");
break;
case PAUSE:
printf("pause");
break;
case LICENSE:
printf("license");
break;
case THROW:
printf("throw");
break;
case CATCH:
printf("catch");
break;
}
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]);
@@ -425,7 +596,6 @@ void printGroundInstruction(GroundInstruction* gi) {
} else {
printGroundArg(&gi->args.args[i]);
}
printf(" ");
}
}
@@ -438,12 +608,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++;
@@ -453,7 +623,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) {
@@ -471,7 +641,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) {
@@ -582,3 +752,69 @@ GroundError createGroundError(char* what, char* type, GroundInstruction* where,
return ge;
}
bool checkFnTypes(GroundValue* left, GroundFunctionArgs* right) {
if (groundDisableTypeChecking) return true;
if (left->type == ANY || right->type == ANY) return true;
if (left->type != right->type) {
return false;
}
if (left->type == CUSTOM) {
if (left->customType->size != right->customType->size) {
return false;
}
for (size_t i = 0; i < left->customType->size; i++) {
if (strcmp(left->customType->fields[i].id, right->customType->fields[i].id) != 0) {
return false;
}
if (!checkTypes(&left->customType->fields[i].value, &right->customType->fields[i].value)) {
return false;
}
}
}
return true;
}
bool checkTypes(GroundValue* left, GroundValue* right) {
if (groundDisableTypeChecking) return true;
if (left->type != right->type) {
return false;
}
if (left->type == CUSTOM) {
if (left->customType->size != right->customType->size) {
return false;
}
for (size_t i = 0; i < left->customType->size; i++) {
if (strcmp(left->customType->fields[i].id, right->customType->fields[i].id) != 0) {
return false;
}
if (!checkTypes(&left->customType->fields[i].value, &right->customType->fields[i].value)) {
return false;
}
}
}
return true;
}
GroundScope copyGroundScope(GroundScope* scope) {
GroundScope newScope = {
.labels = malloc(sizeof(GroundLabel*)),
.variables = malloc(sizeof(GroundVariable*)),
.catches = malloc(sizeof(GroundCatch*)),
.isMainScope = false
};
*newScope.variables = NULL;
*newScope.labels = NULL;
*newScope.catches = 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,12 +8,35 @@
#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, TOSTRING, FUN, RETURN, ENDFUN, PUSHARG, CALL, STRUCT, ENDSTRUCT, INIT, USE, EXTERN, CREATELABEL, PAUSE, DROP
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, CALLMETHOD, STRUCT, ENDSTRUCT, INIT,
GETFIELD, SETFIELD, USE, EXTERN, CREATELABEL, PAUSE, DROP, LICENSE, ERRORCMD, THROW, CATCH
} GroundInstType;
typedef enum GroundValueType {
INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, STRUCTVAL, CUSTOM, ERROR, NONE
INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, STRUCTVAL, CUSTOM, ERROR, NONE, ANY
} GroundValueType;
typedef enum GroundArgType {
@@ -62,7 +85,8 @@ typedef struct GroundError {
*/
typedef struct GroundValue {
GroundValueType type;
union {
struct GroundStruct* customType;
struct {
int64_t intVal;
double doubleVal;
char* stringVal;
@@ -76,6 +100,32 @@ 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;
bool freed;
} GroundVariable;
typedef struct GroundCatch {
char id[MAX_ID_LEN];
GroundLabel* label;
UT_hash_handle hh;
} GroundCatch;
typedef struct GroundScope {
GroundLabel** labels;
GroundVariable** variables;
GroundCatch** catches;
bool isMainScope;
} GroundScope;
/*
* Indicates status when accessing a list.
* Associated functions:
@@ -125,6 +175,7 @@ typedef struct GroundProgram {
*/
typedef struct GroundFunctionArgs {
GroundValueType type;
struct GroundStruct* customType;
char* name;
} GroundFunctionArgs;
@@ -143,6 +194,7 @@ typedef struct GroundFunction {
GroundProgram program;
size_t startLine;
bool isNative;
GroundScope closure;
NativeGroundFunction nativeFn;
} GroundFunction;
@@ -276,6 +328,16 @@ void freeGroundObject(GroundObject* object);
// Creates a GroundError.
GroundError createGroundError(char* what, char* type, GroundInstruction* where, size_t* line);
// Compares types of a value and function args.
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

13
tests/catch.grnd Normal file
View File

@@ -0,0 +1,13 @@
fun !throws -int
throw "MyError" "longer error message"
endfun
catch "MyError" %catcher
call !throws &res
end 0
@catcher
println "ruh roh"
end 1

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,3 +1,11 @@
tostring 32 &int
tostring 32 &str
stoi "12" &int
stod "3.14" &dou
itoc 353 &chr
ctoi 'a' &it2
println $str
println $int
println $dou
println $chr
println $it2

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

2
tests/error.grnd Normal file
View File

@@ -0,0 +1,2 @@
setlist &dat 1 2 3 "Hi!"
error "Hello" $dat 1

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"

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

17
tests/struct.grnd Normal file
View File

@@ -0,0 +1,17 @@
struct -point
init &x -int
init &y -int
endstruct
init &mypoint -point
setfield &mypoint &x 53
setfield &mypoint &y 32
getfield $mypoint &x &value
println $value
getfield $mypoint &y &value
println $value

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