More debug features, update README, new makefile

This commit is contained in:
2025-12-08 15:06:29 +11:00
parent bd1a47d118
commit 2ca3789024
8 changed files with 354 additions and 21 deletions

View File

@@ -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)

77
Makefile Normal file
View File

@@ -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)"

View File

@@ -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 *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] Lexer
- [x] Parser - [x] Parser

130
include/groundvm.h Normal file
View File

@@ -0,0 +1,130 @@
#ifndef LIBGROUND_H
#define LIBGROUND_H
/*
* groundvm.h
* Provides an interface for external programs wanting to run Ground code.
*
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.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
} 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

81
src/interface.c Normal file
View File

@@ -0,0 +1,81 @@
#include "lexer.h"
#include "parser.h"
#include "interpreter.h"
#include "types.h"
#include <stdlib.h>
#include <stdarg.h>
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);
}

View File

@@ -190,6 +190,10 @@ GroundDebugInstruction parseDebugInstruction(char* in) {
gdi.type = CONTINUE; gdi.type = CONTINUE;
} else if (strcmp(instruction, "exit") == 0) { } else if (strcmp(instruction, "exit") == 0) {
gdi.type = EXIT; 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) { } else if (strcmp(instruction, "help") == 0) {
gdi.type = HELP; gdi.type = HELP;
} else { } else {
@@ -209,6 +213,7 @@ GroundDebugInstruction parseDebugInstruction(char* in) {
GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) {
GroundLabel* labels = NULL; GroundLabel* labels = NULL;
GroundVariable* variables = NULL; GroundVariable* variables = NULL;
int instructionsToPause = -1;
GroundScope scope; GroundScope scope;
if (inScope != NULL) { 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("Paused execution\n");
printf("Previous instruction: "); printf("Previous instruction: ");
if (i > 0) { if (i > 0) {
@@ -341,6 +346,19 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) {
freeGroundProgram(&program); freeGroundProgram(&program);
break; 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: { case HELP: {
printf("Ground Debugger Help\n"); printf("Ground Debugger Help\n");
printf("Didn't mean to end up here? Type \"continue\" to escape this prompt.\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(" inspect (variable): Shows the contents of a variable\n");
printf(" eval (code): Runs Ground code in the current scope\n"); printf(" eval (code): Runs Ground code in the current scope\n");
printf(" help: Shows this help message"); printf(" help: Shows this help message");
break;
} }
case UNKNOWN: { case UNKNOWN: {
printf("Unknown instruction (type \"help\" for help)"); printf("Unknown instruction (type \"help\" for help)");
@@ -361,8 +380,11 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) {
break; break;
} }
} }
if (in->instructions[i].type == PAUSE) {
continue; continue;
} }
}
instructionsToPause --;
int ci = currentInstruction; int ci = currentInstruction;
GroundValue gv = interpretGroundInstruction(in->instructions[i], &scope); GroundValue gv = interpretGroundInstruction(in->instructions[i], &scope);
if (gv.type != NONE) { if (gv.type != NONE) {

View File

@@ -11,7 +11,7 @@ typedef enum GroundRuntimeError {
} GroundRuntimeError; } GroundRuntimeError;
typedef enum GroundDebugInstructionType { typedef enum GroundDebugInstructionType {
DUMP, INSPECT, EVAL, CONTINUE, EXIT, HELP, UNKNOWN DUMP, INSPECT, EVAL, CONTINUE, EXIT, STEP, VIEW, HELP, UNKNOWN
} GroundDebugInstructionType; } GroundDebugInstructionType;
typedef struct GroundLabel { typedef struct GroundLabel {

View File

@@ -6,3 +6,6 @@ set &x 5
set &y "dingle" set &y "dingle"
PAUSE PAUSE
println "continuing" println "continuing"
println "step through here"
println "step again"
println "and again"