diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 45473f6..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 4.0) -project(groundc C) - -set(CMAKE_C_STANDARD 11) - -include_directories(src) - -add_executable(groundc - src/main.c - src/interpreter.c - src/interpreter.h - src/parser.c - src/parser.h - src/lexer.c - src/lexer.h - src/types.c - src/types.h) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cb3c46b --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +CC = gcc +CFLAGS = -Wall -Wextra -O3 -Isrc/include -Iinclude +LDFLAGS = + +# Directories +SRC_DIR = src +BUILD_DIR = build +BIN_DIR = $(BUILD_DIR)/bin +LIB_DIR = $(BUILD_DIR)/lib +INC_DIR = $(BUILD_DIR)/include +OBJ_DIR = $(BUILD_DIR)/obj + +# Output names +EXECUTABLE = $(BIN_DIR)/ground +SHARED_LIB = $(LIB_DIR)/libgroundvm.so +HEADER = $(INC_DIR)/groundvm.h + +# Source files +LIB_SOURCES = $(filter-out $(SRC_DIR)/main.c, $(wildcard $(SRC_DIR)/*.c)) +EXE_SOURCES = $(wildcard $(SRC_DIR)/*.c) + +# Object files +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 +.PHONY: all +all: executable + +# Build standalone executable +.PHONY: executable +executable: $(EXECUTABLE) + +# Build shared library +.PHONY: library +library: $(SHARED_LIB) $(HEADER) + +# Build both +.PHONY: both +both: executable library + +# Link executable +$(EXECUTABLE): $(EXE_OBJECTS) | $(BIN_DIR) + $(CC) $(EXE_OBJECTS) -o $@ $(LDFLAGS) + +# Build shared library +$(SHARED_LIB): $(LIB_OBJECTS) | $(LIB_DIR) + $(CC) -shared $(LIB_OBJECTS) -o $@ $(LDFLAGS) + +# Copy header for library distribution +$(HEADER): include/groundvm.h | $(INC_DIR) + cp $< $@ + +# Compile object files for executable +$(OBJ_DIR)/exe_%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +# Compile object files for shared library (with -fPIC) +$(OBJ_DIR)/lib_%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +# Create directories +$(BUILD_DIR) $(BIN_DIR) $(LIB_DIR) $(INC_DIR) $(OBJ_DIR): + mkdir -p $@ + +# Clean build artifacts +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + +# Debug: print variables +.PHONY: debug +debug: + @echo "LIB_SOURCES: $(LIB_SOURCES)" + @echo "EXE_SOURCES: $(EXE_SOURCES)" + @echo "LIB_OBJECTS: $(LIB_OBJECTS)" + @echo "EXE_OBJECTS: $(EXE_OBJECTS)" diff --git a/README.md b/README.md index cbab038..38285e2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,44 @@ Now that Ground's features have mostly been finalised, this interpreter can be b *so far, only tested on Linux, but hopefully should work on other platforms as well -Progress marker: +## Building + +To build, make sure Make is installed. Then, run: + +```bash +make +``` + +Ground can also be embedded as a library in other programs. Run this code to generate library files: +```bash +make library +``` + +File structure of the build directory: +``` +build +├── bin +│   └── ground +├── include +│   └── groundvm.h +├── lib +│   └── libgroundvm.so +└── obj + ├── exe_interface.o + ├── exe_interpreter.o + ├── exe_lexer.o + ├── exe_main.o + ├── exe_parser.o + ├── exe_types.o + ├── lib_interface.o + ├── lib_interpreter.o + ├── lib_lexer.o + ├── lib_parser.o + └── lib_types.o +``` + + +## Progress marker - [x] Lexer - [x] Parser diff --git a/include/groundvm.h b/include/groundvm.h new file mode 100644 index 0000000..14916d3 --- /dev/null +++ b/include/groundvm.h @@ -0,0 +1,130 @@ +#ifndef LIBGROUND_H +#define LIBGROUND_H + +/* + * groundvm.h + * Provides an interface for external programs wanting to run Ground code. + * + */ + +#include +#include +#include +#include + +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 +} GroundInstType; + +typedef enum GroundValueType { + INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, CUSTOM, NONE +} GroundValueType; + +typedef enum GroundArgType { + VALUE, VALREF, DIRREF, LINEREF, LABEL, FNREF, TYPEREF +} GroundArgType; + +typedef enum ListAccessStatus { + LIST_OKAY, LIST_OUT_OF_BOUNDS, LIST_FIXME +} ListAccessStatus; + +struct GroundValue; +struct GroundFunction; + +struct List; + +/* + * Custom data type that stores Ground values. + */ +typedef struct List { + size_t size; + struct GroundValue* values; +} List; + +/* + * Stores literal values created in a Ground program. + */ +typedef struct GroundValue { + GroundValueType type; + union { + int64_t intVal; + double doubleVal; + char* stringVal; + char charVal; + bool boolVal; + List listVal; + struct GroundFunction* fnVal; + void* customVal; + } data; +} GroundValue; + +/* + * Indicates status when accessing a list. + */ +typedef struct ListAccess { + ListAccessStatus status; + GroundValue* value; +} ListAccess; + +/* + * Stores arguments for the GroundInstruction struct. + */ +typedef struct GroundArg { + GroundArgType type; + union { + GroundValue value; + char* refName; + } value; +} GroundArg; + +/* + * Represents a Ground instruction. + */ +typedef struct GroundInstruction { + GroundInstType type; + struct { + GroundArg* args; + size_t length; + } args; +} GroundInstruction; + +/* + * Represents a Ground program or function. + */ +typedef struct GroundProgram { + GroundInstruction* instructions; + size_t size; +} GroundProgram; + +/* + * Represents the argument typing for a GroundFunction. + */ +typedef struct GroundFunctionArgs { + GroundValueType type; + char* name; +} GroundFunctionArgs; + +/* + * Represents a Ground function. + */ +typedef struct GroundFunction { + GroundFunctionArgs* args; + size_t argSize; + GroundValueType returnType; + GroundProgram program; + size_t startLine; +} GroundFunction; + +GroundProgram groundCreateProgram(); +void groundAddInstructionToProgram(GroundProgram* program, GroundInstruction instruction); +GroundValue groundRunProgram(GroundProgram* program); + +GroundInstruction groundCreateInstruction(GroundInstType type); +void groundAddValueToInstruction(GroundInstruction* inst, GroundValue value); +void groundAddReferenceToInstruction(GroundInstruction* inst, GroundArg value); +GroundArg groundCreateReference(GroundArgType type, char* ref); + +GroundValue groundCreateValue(GroundValueType type, ...); + + +#endif diff --git a/src/interface.c b/src/interface.c new file mode 100644 index 0000000..0346ed9 --- /dev/null +++ b/src/interface.c @@ -0,0 +1,81 @@ +#include "lexer.h" +#include "parser.h" +#include "interpreter.h" +#include "types.h" + +#include +#include + +GroundProgram groundCreateProgram() { + return (GroundProgram) { + .instructions = malloc(sizeof(GroundInstruction)), + .size = 0 + }; +} +void groundAddInstructionToProgram(GroundProgram* program, GroundInstruction instruction) { + addInstructionToProgram(program, instruction); +} + +GroundInstruction groundCreateInstruction(GroundInstType type) { + return (GroundInstruction) { + .type = type, + .args.args = malloc(sizeof(GroundArg)), + .args.length = 0 + }; +} + +void groundAddValueToInstruction(GroundInstruction* inst, GroundValue value) { + addArgToInstruction(inst, createValueGroundArg(value)); +} + +void groundAddReferenceToInstruction(GroundInstruction* inst, GroundArg value) { + addArgToInstruction(inst, value); +} + +GroundArg groundCreateReference(GroundArgType type, char* ref) { + return (GroundArg) { + .type = type, + .value.refName = ref + }; +} + +GroundValue groundCreateValue(GroundValueType type, ...) { + va_list args; + va_start(args, type); + + switch (type) { + case INT: { + return createIntGroundValue(va_arg(args, int)); + break; + } + case DOUBLE: { + return createDoubleGroundValue(va_arg(args, double)); + break; + } + case STRING: { + return createStringGroundValue(va_arg(args, char*)); + break; + } + case CHAR: { + return createCharGroundValue((char)va_arg(args, int)); + break; + } + case BOOL: { + return createBoolGroundValue((bool)va_arg(args, int)); + break; + } + case LIST: { + return createListGroundValue(va_arg(args, List)); + break; + } + default: { + return createNoneGroundValue(); + } + } + + va_end(args); +} + +GroundValue groundRunProgram(GroundProgram* program) { + return interpretGroundProgram(program, NULL); +} diff --git a/src/interpreter.c b/src/interpreter.c index 92dfe11..47da9b8 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -190,6 +190,10 @@ GroundDebugInstruction parseDebugInstruction(char* in) { gdi.type = CONTINUE; } else if (strcmp(instruction, "exit") == 0) { gdi.type = EXIT; + } else if (strcmp(instruction, "step") == 0) { + gdi.type = STEP; + } else if (strcmp(instruction, "view") == 0) { + gdi.type = VIEW; } else if (strcmp(instruction, "help") == 0) { gdi.type = HELP; } else { @@ -209,6 +213,7 @@ GroundDebugInstruction parseDebugInstruction(char* in) { GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { GroundLabel* labels = NULL; GroundVariable* variables = NULL; + int instructionsToPause = -1; GroundScope scope; if (inScope != NULL) { @@ -277,7 +282,7 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { } } } - if (in->instructions[i].type == PAUSE) { + if (in->instructions[i].type == PAUSE || instructionsToPause == 0) { printf("Paused execution\n"); printf("Previous instruction: "); if (i > 0) { @@ -341,6 +346,19 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { freeGroundProgram(&program); break; } + case STEP: { + if (gdi.arg != NULL && strlen(gdi.arg) > 0) { + instructionsToPause = atoi(gdi.arg); + } else { + instructionsToPause = 1; + } + instructionsToPause++; + shouldBreak = true; + break; + } + case VIEW: { + break; + } case HELP: { printf("Ground Debugger Help\n"); printf("Didn't mean to end up here? Type \"continue\" to escape this prompt.\n"); @@ -351,6 +369,7 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { printf(" inspect (variable): Shows the contents of a variable\n"); printf(" eval (code): Runs Ground code in the current scope\n"); printf(" help: Shows this help message"); + break; } case UNKNOWN: { printf("Unknown instruction (type \"help\" for help)"); @@ -361,8 +380,11 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { break; } } - continue; + if (in->instructions[i].type == PAUSE) { + continue; + } } + instructionsToPause --; int ci = currentInstruction; GroundValue gv = interpretGroundInstruction(in->instructions[i], &scope); if (gv.type != NONE) { diff --git a/src/interpreter.h b/src/interpreter.h index 62897f3..b3a3db6 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -11,7 +11,7 @@ typedef enum GroundRuntimeError { } GroundRuntimeError; typedef enum GroundDebugInstructionType { - DUMP, INSPECT, EVAL, CONTINUE, EXIT, HELP, UNKNOWN + DUMP, INSPECT, EVAL, CONTINUE, EXIT, STEP, VIEW, HELP, UNKNOWN } GroundDebugInstructionType; typedef struct GroundLabel { diff --git a/tests/pause.grnd b/tests/pause.grnd index 7709eac..a7faa7b 100644 --- a/tests/pause.grnd +++ b/tests/pause.grnd @@ -6,3 +6,6 @@ set &x 5 set &y "dingle" PAUSE println "continuing" +println "step through here" +println "step again" +println "and again"