From 38473f0e015c293b6864290002ce064a2e573424 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sun, 1 Mar 2026 16:00:03 +1100 Subject: [PATCH] Friendship ended with C++, C is my new best friend --- Makefile | 12 +- src/argparser.cpp | 79 -- src/argparser.h | 29 - src/codegen/SolsScope.c | 49 ++ src/codegen/SolsScope.h | 32 + src/codegen/codegen.c | 1036 ++++++++++++++++++++++ src/codegen/codegen.h | 34 + src/error.cpp | 49 -- src/error.h | 15 - src/include/ansii.h | 67 ++ src/include/error.h | 83 ++ src/include/estr.h | 52 ++ src/include/nothing.h | 10 + src/include/uthash.h | 1137 ++++++++++++++++++++++++ src/lexer.cpp | 204 ----- src/lexer.h | 29 - src/lexer/SolsLiteral.c | 51 ++ src/lexer/SolsLiteral.h | 46 + src/lexer/SolsToken.c | 93 ++ src/lexer/SolsToken.h | 83 ++ src/lexer/SolsType.c | 176 ++++ src/lexer/SolsType.h | 105 +++ src/lexer/lexer.c | 844 ++++++++++++++++++ src/lexer/lexer.h | 76 ++ src/main.c | 117 +++ src/main.cpp | 90 -- src/parser.cpp | 1850 --------------------------------------- src/parser.h | 167 ---- src/parser/SolsNode.c | 61 ++ src/parser/SolsNode.h | 62 ++ src/parser/parser.c | 1677 +++++++++++++++++++++++++++++++++++ src/parser/parser.h | 99 +++ 32 files changed, 5996 insertions(+), 2518 deletions(-) delete mode 100644 src/argparser.cpp delete mode 100644 src/argparser.h create mode 100644 src/codegen/SolsScope.c create mode 100644 src/codegen/SolsScope.h create mode 100644 src/codegen/codegen.c create mode 100644 src/codegen/codegen.h delete mode 100644 src/error.cpp delete mode 100644 src/error.h create mode 100644 src/include/ansii.h create mode 100644 src/include/error.h create mode 100644 src/include/estr.h create mode 100644 src/include/nothing.h create mode 100644 src/include/uthash.h delete mode 100644 src/lexer.cpp delete mode 100644 src/lexer.h create mode 100644 src/lexer/SolsLiteral.c create mode 100644 src/lexer/SolsLiteral.h create mode 100644 src/lexer/SolsToken.c create mode 100644 src/lexer/SolsToken.h create mode 100644 src/lexer/SolsType.c create mode 100644 src/lexer/SolsType.h create mode 100644 src/lexer/lexer.c create mode 100644 src/lexer/lexer.h create mode 100644 src/main.c delete mode 100644 src/main.cpp delete mode 100644 src/parser.cpp delete mode 100644 src/parser.h create mode 100644 src/parser/SolsNode.c create mode 100644 src/parser/SolsNode.h create mode 100644 src/parser/parser.c create mode 100644 src/parser/parser.h diff --git a/Makefile b/Makefile index a05d3a2..e25a2b0 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -CXX = g++ -CXXFLAGS = -std=c++17 -Wall -Isrc +CXX = gcc +CXXFLAGS = -Wall -Wextra -pedantic -O3 -ggdb LDFLAGS = -lgroundvm BUILD_DIR = build @@ -9,8 +9,8 @@ PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin LIBDIR = /usr/lib -SRCS = $(SRC_DIR)/main.cpp $(SRC_DIR)/argparser.cpp $(SRC_DIR)/lexer.cpp $(SRC_DIR)/parser.cpp $(SRC_DIR)/error.cpp -OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(BUILD_DIR)/%.o, $(SRCS)) +SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/codegen/SolsScope.c $(SRC_DIR)/codegen/codegen.c $(SRC_DIR)/lexer/SolsLiteral.c $(SRC_DIR)/lexer/SolsToken.c $(SRC_DIR)/lexer/SolsType.c $(SRC_DIR)/lexer/lexer.c $(SRC_DIR)/parser/SolsNode.c $(SRC_DIR)/parser/parser.c +OBJS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS)) TARGET = solstice all: $(TARGET) @@ -18,7 +18,7 @@ all: $(TARGET) $(TARGET): $(OBJS) $(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS) -$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR) +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) $(CXX) $(CXXFLAGS) -c $< -o $@ install: $(TARGET) @@ -29,7 +29,7 @@ install: $(TARGET) cp -r libs/* $(LIBDIR)/solstice $(BUILD_DIR): - mkdir -p $(BUILD_DIR) + mkdir -p $(BUILD_DIR) $(BUILD_DIR)/codegen $(BUILD_DIR)/lexer $(BUILD_DIR)/parser clean: rm -rf $(BUILD_DIR) $(TARGET) diff --git a/src/argparser.cpp b/src/argparser.cpp deleted file mode 100644 index b1a03ba..0000000 --- a/src/argparser.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include "argparser.h" - -namespace ArgParser { - - bool nostdlib = false; - - void printHelp() { - std::cout << "Solstice compiler\n"; - std::cout << "This program takes a Solstice source file (.sols) and compiles it to Ground\n"; - std::cout << "Usage:\n"; - std::cout << " solstice inputFile [-h] [--help] [-o file] [--output file] [-t type] [--type type]\n"; - std::cout << "Options:\n"; - std::cout << " -h or --help\n"; - std::cout << " Shows this help message\n"; - std::cout << " -o file or --output file\n"; - std::cout << " Specifies where to output the compiled Ground program.\n"; - std::cout << " If this option is omitted, the program is automatically run.\n"; - std::cout << " -t type or --type type\n"; - std::cout << " Specifies the type of output.\n"; - std::cout << " Currently supported options:\n"; - std::cout << " ground, native (BETA)\n"; - std::cout << " Choosing the 'ground' option outputs the compiled program as a .grnd textual representation file.\n"; - std::cout << " Choosing the 'native' option uses Ground's ground->asm compiler to create a native executable.\n"; - std::cout << " This feature is currently in beta, as Ground's ground->asm compiler is not fully complete.\n"; - std::cout << " See https://sols.dev/docs#nativecompiler for more details\n"; - } - - Args parseArgs(int argc, char** argv) { - if (argc < 2) { - printHelp(); - exit(1); - } - Args args; - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { - printHelp(); - exit(1); - } - else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) { - i++; - if (i < argc) { - args.output = Argument(ArgType::OUTPUT, std::string(argv[i])); - continue; - } else { - std::cout << "(error) Please provide a filename to output to\n"; - exit(1); - } - } - else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--type") == 0) { - i++; - if (i < argc) { - args.outputtype = Argument(ArgType::OUTPUTTYPE, std::string(argv[i])); - continue; - } else { - std::cout << "(error) Please provide an output type\n"; - exit(1); - } - } - else { - if (args.file.has_value()) { - std::cout << "(error) Multiple input files provided\n"; - exit(1); - } - args.file = Argument(ArgType::FILE, std::string(argv[i])); - } - } - if (!args.file.has_value()) { - std::cout << "(error) No input file provided\n"; - exit(1); - } - return args; - } - -} // namespace ArgParser diff --git a/src/argparser.h b/src/argparser.h deleted file mode 100644 index c910efd..0000000 --- a/src/argparser.h +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include -#include - -namespace ArgParser { - enum class ArgType { - FILE, OUTPUT, OUTPUTTYPE - }; - - class Argument { - public: - ArgType type; - std::string value; - Argument() = default; - Argument(ArgType type, std::string value) : type(type), value(value) {} - }; - - class Args { - public: - std::optional output = {}; - std::optional outputtype = {}; - std::optional file = {}; - }; - - void printHelp(); - Args parseArgs(int argc, char** argv); - -} // namespace ArgParser diff --git a/src/codegen/SolsScope.c b/src/codegen/SolsScope.c new file mode 100644 index 0000000..54e81d9 --- /dev/null +++ b/src/codegen/SolsScope.c @@ -0,0 +1,49 @@ +#include "SolsScope.h" + +#include "../include/uthash.h" +#include "../lexer/SolsType.h" + +void addVariableToScope(SolsScope* scope, const char* name, SolsType type) { + SolsVariable* s = malloc(sizeof(SolsVariable)); + + strncpy(s->id, name, sizeof(s->id) - 1); + s->id[sizeof(s->id) - 1] = '\0'; + + s->typeinfo = type; + + HASH_ADD_STR(scope->variables, id, s); +} + +SolsVariable* findSolsVariable(SolsScope* scope, const char* name) { + if (scope == NULL || scope->variables == NULL || name == NULL) { + return NULL; + } + SolsVariable* s; + HASH_FIND_STR(scope->variables, name, s); + return s; +} + +SolsScope copySolsScope(SolsScope* scope) { + SolsScope newScope = { + .variables = NULL, + .tmpCounter = scope->tmpCounter, + .returnType = scope->returnType + }; + + SolsVariable *var, *tmp; + + HASH_ITER(hh, scope->variables, var, tmp) { + addVariableToScope(&newScope, var->id, var->typeinfo); + } + + return newScope; +} + +void destroySolsScope(SolsScope* scope) { + SolsVariable *var, *tmp; + + HASH_ITER(hh, scope->variables, var, tmp) { + HASH_DEL(scope->variables, var); + free(var); + } +} diff --git a/src/codegen/SolsScope.h b/src/codegen/SolsScope.h new file mode 100644 index 0000000..1f4d12e --- /dev/null +++ b/src/codegen/SolsScope.h @@ -0,0 +1,32 @@ +#ifndef SOLSSCOPE_H +#define SOLSSCOPE_H + +#include "../include/uthash.h" +#include "../lexer/SolsType.h" + +// Stores type information for variables in a UTHash table. +typedef struct SolsVariable { + char id[256]; + UT_hash_handle hh; + SolsType typeinfo; +} SolsVariable; + +typedef struct SolsScope { + SolsVariable* variables; + size_t tmpCounter; + SolsType returnType; +} SolsScope; + +// Adds a variable to the SolsScope. +void addVariableToScope(SolsScope* scope, const char* name, SolsType type); + +// Finds a variable in the SolsScope. +SolsVariable* findSolsVariable(SolsScope* scope, const char* name); + +// Deep copies a SolsScope, usually for being inside a code block +SolsScope copySolsScope(SolsScope* scope); + +// Destroys everything in the SolsScope +void destroySolsScope(SolsScope* scope); + +#endif diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c new file mode 100644 index 0000000..5235639 --- /dev/null +++ b/src/codegen/codegen.c @@ -0,0 +1,1036 @@ +#include "codegen.h" + +#include "SolsScope.h" + +#include +#include +#include +#include + +#include "../lexer/lexer.h" +#include "../parser/parser.h" +#include "../parser/SolsNode.h" +#include "../include/estr.h" +#include "../include/uthash.h" +#include "../include/ansii.h" + +// FIXME show multiple lines for the error +char* createCodegenError(SolsNode* node, char* what) { + if (node->line.content == NULL) { + return what; + } + Estr err = CREATE_ESTR(ESC_BOLD ESC_RED_FG "error: " ESC_RESET ESC_BOLD); + APPEND_ESTR(err, what); + APPEND_ESTR(err, "\n" ESC_RESET); + APPEND_ESTR(err, ESC_CYAN_FG "-> " ESC_RESET); + APPEND_ESTR(err, "on line "); + + char line_buf[16]; + snprintf(line_buf, sizeof(line_buf), "%zu", node->line.num); + APPEND_ESTR(err, line_buf); + APPEND_ESTR(err, "\n\n"); + + APPEND_ESTR(err, node->line.content); + APPEND_ESTR(err, "\n"); + + return err.str; +} + +ResultType(SolsType, charptr) getNodeType(SolsNode* node, SolsScope* scope) { + switch (node->type) { + case SNT_PUTS: + case SNT_IF: + case SNT_WHILE: + case SNT_CODE_BLOCK: + case SNT_RETURN: { + return Error(SolsType, charptr, "Specified node does not return data"); + } + case SNT_OP_SET: { + if (node->children.count < 2) { + return Error(SolsType, charptr, "Not enough children to determine type"); + } + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[0], scope); + if (type.error) { + return Error(SolsType, charptr, type.as.error); + } + return Success(SolsType, charptr, type.as.success); + } + case SNT_OP_ADD: { + if (node->children.count < 2) { + return Error(SolsType, charptr, "Not enough children to determine type"); + } + + ResultType(SolsType, charptr) leftType = getNodeType(&node->children.at[0], scope); + if (leftType.error) { + return Error(SolsType, charptr, leftType.as.error); + } + ResultType(SolsType, charptr) rightType = getNodeType(&node->children.at[1], scope); + if (rightType.error) { + return Error(SolsType, charptr, rightType.as.error); + } + + if (!(leftType.as.success.type == STT_INT || leftType.as.success.type == STT_DOUBLE || leftType.as.success.type == STT_STRING)) { + return Error(SolsType, charptr, "Cannot add left type"); + } + if (!(rightType.as.success.type == STT_INT || rightType.as.success.type == STT_DOUBLE || rightType.as.success.type == STT_STRING)) { + return Error(SolsType, charptr, "Cannot add right type"); + } + + if (leftType.as.success.type == rightType.as.success.type) { + return Success(SolsType, charptr, leftType.as.success); + } + + if ((leftType.as.success.type == STT_INT && rightType.as.success.type == STT_DOUBLE) || (leftType.as.success.type == STT_DOUBLE && rightType.as.success.type == STT_INT)) { + ResultType(SolsType, charptr) type = createSolsType(STT_DOUBLE); + if (type.error) { + return Error(SolsType, charptr, type.as.error); + } + return Success(SolsType, charptr, type.as.success); + } + return Error(SolsType, charptr, "Cannot add these types together"); + } + case SNT_OP_MUL: + case SNT_OP_DIV: + case SNT_OP_SUB: { + if (node->children.count < 2) { + return Error(SolsType, charptr, "Not enough children to determine type"); + } + + ResultType(SolsType, charptr) leftType = getNodeType(&node->children.at[0], scope); + if (leftType.error) { + return Error(SolsType, charptr, leftType.as.error); + } + ResultType(SolsType, charptr) rightType = getNodeType(&node->children.at[1], scope); + if (rightType.error) { + return Error(SolsType, charptr, rightType.as.error); + } + + if (!(leftType.as.success.type == STT_INT || leftType.as.success.type == STT_DOUBLE)) { + return Error(SolsType, charptr, "Cannot operate on left type"); + } + if (!(rightType.as.success.type == STT_INT || rightType.as.success.type == STT_DOUBLE)) { + return Error(SolsType, charptr, "Cannot operate on right type"); + } + + if (leftType.as.success.type == rightType.as.success.type) { + return Success(SolsType, charptr, leftType.as.success); + } + + ResultType(SolsType, charptr) type = createSolsType(STT_DOUBLE); + if (type.error) { + return Error(SolsType, charptr, type.as.error); + } + return Success(SolsType, charptr, type.as.success); + } + case SNT_OP_EQUAL: + case SNT_OP_INEQUAL: { + if (node->children.count < 2) { + return Error(SolsType, charptr, "Not enough children to determine type"); + } + + ResultType(SolsType, charptr) leftType = getNodeType(&node->children.at[0], scope); + if (leftType.error) { + return Error(SolsType, charptr, leftType.as.error); + } + ResultType(SolsType, charptr) rightType = getNodeType(&node->children.at[1], scope); + if (rightType.error) { + return Error(SolsType, charptr, rightType.as.error); + } + + if (leftType.as.success.type == STT_FUN || leftType.as.success.type == STT_TEMPLATE || leftType.as.success.type == STT_OBJECT) { + return Error(SolsType, charptr, "Cannot compare with left type"); + } + + if (rightType.as.success.type == STT_FUN || rightType.as.success.type == STT_TEMPLATE || rightType.as.success.type == STT_OBJECT) { + return Error(SolsType, charptr, "Cannot compare with right type"); + } + + if (leftType.as.success.type == rightType.as.success.type) { + return Success(SolsType, charptr, {STT_BOOL}); + } + if ((leftType.as.success.type == STT_INT && rightType.as.success.type == STT_DOUBLE) || (rightType.as.success.type == STT_INT && leftType.as.success.type == STT_DOUBLE)) { + return Success(SolsType, charptr, {STT_BOOL}); + } + + return Error(SolsType, charptr, ""); + } + case SNT_OP_GREATER: + case SNT_OP_LESSER: + case SNT_OP_EQGREATER: + case SNT_OP_EQLESSER: { + if (node->children.count < 2) { + return Error(SolsType, charptr, "Not enough children to determine type"); + } + + ResultType(SolsType, charptr) leftType = getNodeType(&node->children.at[0], scope); + if (leftType.error) { + return Error(SolsType, charptr, leftType.as.error); + } + ResultType(SolsType, charptr) rightType = getNodeType(&node->children.at[1], scope); + if (rightType.error) { + return Error(SolsType, charptr, rightType.as.error); + } + + if (!(leftType.as.success.type == STT_INT || leftType.as.success.type == STT_DOUBLE)) { + return Error(SolsType, charptr, "Cannot compare with left type"); + } + + if (!(rightType.as.success.type == STT_INT || rightType.as.success.type == STT_DOUBLE)) { + return Error(SolsType, charptr, "Cannot compare with right type"); + } + return Success(SolsType, charptr, STT_BOOL); + } + case SNT_LITERAL: { + switch (node->as.literal.type) { + case SLT_INT: { + return Success(SolsType, charptr, {STT_INT}); + } + case SLT_DOUBLE: { + return Success(SolsType, charptr, {STT_DOUBLE}); + } + case SLT_STRING: { + return Success(SolsType, charptr, {STT_STRING}); + } + case SLT_BOOL: { + return Success(SolsType, charptr, {STT_BOOL}); + } + case SLT_CHAR: { + return Success(SolsType, charptr, {STT_CHAR}); + } + } + break; + } + case SNT_IDENTIFIER: { + SolsVariable* var = findSolsVariable(scope, node->as.idName); + if (var == NULL) { + Estr estr = CREATE_ESTR("Unable to find variable "); + APPEND_ESTR(estr, node->as.idName); + return Error(SolsType, charptr, estr.str); + } + return Success(SolsType, charptr, var->typeinfo); + } + case SNT_LAMBDA: { + return Success(SolsType, charptr, node->as.type); + } + case SNT_FUNCTION_CALL: { + SolsVariable* var = findSolsVariable(scope, node->as.idName); + if (var == NULL) { + Estr estr = CREATE_ESTR("Unable to find function "); + APPEND_ESTR(estr, node->as.idName); + return Error(SolsType, charptr, estr.str); + } + if (var->typeinfo.type != STT_FUN) { + Estr estr = CREATE_ESTR(node->as.idName); + APPEND_ESTR(estr, " is not a callable function"); + return Error(SolsType, charptr, estr.str); + } + return Success(SolsType, charptr, *var->typeinfo.returnType); + } + } + return Error(SolsType, charptr, "Not yet implemented"); +} + +static inline ResultType(GroundProgram, charptr) generateLiteralNode(SolsNode* node, SolsScope* scope) { + // We don't even need to do anything lmao + return Success(GroundProgram, charptr, groundCreateProgram()); +} + +static inline ResultType(GroundProgram, charptr) generatePutsNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 1) { + return Error(GroundProgram, charptr, "puts requires arguments"); + } + GroundInstruction inst = groundCreateInstruction(PRINTLN); + for (size_t i = 0; i < node->children.count; i++) { + // Validate arg + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[i], scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + groundAddReferenceToInstruction(&inst, node->children.at[i].accessArg); + } + GroundProgram program = groundCreateProgram(); + groundAddInstructionToProgram(&program, inst); + return Success(GroundProgram, charptr, program); +} + +static inline ResultType(GroundProgram, charptr) generateSetNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "set requires arguments"); + } + if (node->children.at[0].type != SNT_IDENTIFIER) { + return Error(GroundProgram, charptr, "set requires an identifier before '='"); + } + SolsVariable* var = findSolsVariable(scope, node->children.at[0].as.idName); + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[1], scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + if (var == NULL) { + addVariableToScope(scope, node->children.at[0].as.idName, type.as.success); + } else { + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[0], scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + if (compareTypes(&var->typeinfo, &type.as.success) == false) { + return Error(GroundProgram, charptr, "Type of variable cannot be changed"); + } + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction gi = groundCreateInstruction(SET); + groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, node->children.at[0].as.idName)); + groundAddReferenceToInstruction(&gi, node->children.at[1].accessArg); + groundAddInstructionToProgram(&gp, gi); + + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateAddNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "add requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(ADD); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in add"); + } + snprintf(tmpId, 64, "__SOLS_TMP_ADD_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateSubNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "sub requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(SUBTRACT); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in subtract"); + } + snprintf(tmpId, 64, "__SOLS_TMP_SUB_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateMulNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "mul requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(MULTIPLY); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in multiply"); + } + snprintf(tmpId, 64, "__SOLS_TMP_MUL_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateDivNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "div requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(DIVIDE); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in divide"); + } + snprintf(tmpId, 64, "__SOLS_TMP_DIV_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateEqualNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "equal requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(EQUAL); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in equal"); + } + snprintf(tmpId, 64, "__SOLS_TMP_EQUAL_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateInequalNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "inequal requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(INEQUAL); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in inequal"); + } + snprintf(tmpId, 64, "__SOLS_TMP_INEQUAL_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateGreaterNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "greater requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(GREATER); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in greater"); + } + snprintf(tmpId, 64, "__SOLS_TMP_GREATER_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateEqGreaterNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "eqgreater requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(LESSER); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in eqgreater"); + } + snprintf(tmpId, 64, "__SOLS_TMP_EQGREATER_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + + GroundInstruction not = groundCreateInstruction(NOT); + groundAddReferenceToInstruction(¬, groundCreateReference(VALREF, tmpId)); + groundAddReferenceToInstruction(¬, groundCreateReference(DIRREF, tmpId)); + + groundAddInstructionToProgram(&gp, not); + + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateLesserNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "lesser requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(LESSER); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in lesser"); + } + snprintf(tmpId, 64, "__SOLS_TMP_LESSER_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateEqLesserNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 2) { + return Error(GroundProgram, charptr, "eqlesser requires arguments"); + } + + // Use this function for type checking + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + GroundProgram gp = groundCreateProgram(); + GroundInstruction add = groundCreateInstruction(GREATER); + groundAddReferenceToInstruction(&add, node->children.at[0].accessArg); + groundAddReferenceToInstruction(&add, node->children.at[1].accessArg); + + char* tmpId = malloc(sizeof(char) * 64); + if (tmpId == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for temporary identifier in eqlesser"); + } + snprintf(tmpId, 64, "__SOLS_TMP_LESSER_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&add, groundCreateReference(DIRREF, tmpId)); + + node->accessArg = groundCreateReference(VALREF, tmpId); + + groundAddInstructionToProgram(&gp, add); + + GroundInstruction not = groundCreateInstruction(NOT); + groundAddReferenceToInstruction(¬, groundCreateReference(VALREF, tmpId)); + groundAddReferenceToInstruction(¬, groundCreateReference(DIRREF, tmpId)); + + groundAddInstructionToProgram(&gp, not); + + return Success(GroundProgram, charptr, gp); +} + +ResultType(GroundProgram, charptr) generateCodeBlockNode(SolsNode* node, SolsScope* scope) { + // Nothing needs to be done, as children are handled by the generateCode function + (void)node; (void)scope; + return Success(GroundProgram, charptr, groundCreateProgram()); +} + +static inline ResultType(GroundProgram, charptr) generateWhileNode(SolsNode* node, SolsScope* scope) { + GroundProgram gp = groundCreateProgram(); + + char* start_label = malloc(64); + snprintf(start_label, 64, "__SOLS_WHILE_START_%zu", scope->tmpCounter++); + + char* end_label = malloc(64); + snprintf(end_label, 64, "__SOLS_WHILE_END_%zu", scope->tmpCounter++); + + GroundInstruction start_label_inst = groundCreateInstruction(CREATELABEL); + groundAddReferenceToInstruction(&start_label_inst, groundCreateReference(LABEL, start_label)); + groundAddInstructionToProgram(&gp, start_label_inst); + + ResultType(GroundProgram, charptr) cond_code = generateCode(&node->children.at[0], scope); + if (cond_code.error) return cond_code; + for (size_t i = 0; i < cond_code.as.success.size; i++) { + groundAddInstructionToProgram(&gp, cond_code.as.success.instructions[i]); + } + + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[0], scope); + if (type.error) return Error(GroundProgram, charptr, type.as.error); + if (type.as.success.type != STT_BOOL) { + return Error(GroundProgram, charptr, "While condition must be a boolean"); + } + + char* tmp_inverted_cond = malloc(64); + snprintf(tmp_inverted_cond, 64, "__SOLS_IF_COND_NOT_%zu", scope->tmpCounter++); + GroundInstruction not_inst = groundCreateInstruction(NOT); + groundAddReferenceToInstruction(¬_inst, node->children.at[0].accessArg); + groundAddReferenceToInstruction(¬_inst, groundCreateReference(DIRREF, tmp_inverted_cond)); + groundAddInstructionToProgram(&gp, not_inst); + + GroundInstruction if_inst = groundCreateInstruction(IF); + groundAddReferenceToInstruction(&if_inst, groundCreateReference(VALREF, tmp_inverted_cond)); + groundAddReferenceToInstruction(&if_inst, groundCreateReference(LINEREF, end_label)); + groundAddInstructionToProgram(&gp, if_inst); + + ResultType(GroundProgram, charptr) body_code = generateCode(&node->children.at[1], scope); + if (body_code.error) return body_code; + for (size_t i = 0; i < body_code.as.success.size; i++) { + groundAddInstructionToProgram(&gp, body_code.as.success.instructions[i]); + } + + GroundInstruction jump_inst = groundCreateInstruction(JUMP); + groundAddReferenceToInstruction(&jump_inst, groundCreateReference(LINEREF, start_label)); + groundAddInstructionToProgram(&gp, jump_inst); + + GroundInstruction end_label_inst = groundCreateInstruction(CREATELABEL); + groundAddReferenceToInstruction(&end_label_inst, groundCreateReference(LABEL, end_label)); + groundAddInstructionToProgram(&gp, end_label_inst); + + return Success(GroundProgram, charptr, gp); +} + +static inline ResultType(GroundProgram, charptr) generateIfNode(SolsNode* node, SolsScope* scope) { + GroundProgram gp = groundCreateProgram(); + + ResultType(GroundProgram, charptr) cond_code = generateCode(&node->children.at[0], scope); + if (cond_code.error) return cond_code; + for (size_t i = 0; i < cond_code.as.success.size; i++) { + groundAddInstructionToProgram(&gp, cond_code.as.success.instructions[i]); + } + + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[0], scope); + if (type.error) return Error(GroundProgram, charptr, type.as.error); + if (type.as.success.type != STT_BOOL) { + return Error(GroundProgram, charptr, "If condition must be a boolean"); + } + + char* end_label = malloc(64); + snprintf(end_label, 64, "__SOLS_IF_END_%zu", scope->tmpCounter++); + + char* tmp_inverted_cond = malloc(64); + snprintf(tmp_inverted_cond, 64, "__SOLS_IF_COND_NOT_%zu", scope->tmpCounter++); + GroundInstruction not_inst = groundCreateInstruction(NOT); + groundAddReferenceToInstruction(¬_inst, node->children.at[0].accessArg); + groundAddReferenceToInstruction(¬_inst, groundCreateReference(DIRREF, tmp_inverted_cond)); + groundAddInstructionToProgram(&gp, not_inst); + + GroundInstruction if_inst = groundCreateInstruction(IF); + groundAddReferenceToInstruction(&if_inst, groundCreateReference(VALREF, tmp_inverted_cond)); + groundAddReferenceToInstruction(&if_inst, groundCreateReference(LINEREF, end_label)); + groundAddInstructionToProgram(&gp, if_inst); + + ResultType(GroundProgram, charptr) body_code = generateCode(&node->children.at[1], scope); + if (body_code.error) return body_code; + for (size_t i = 0; i < body_code.as.success.size; i++) { + groundAddInstructionToProgram(&gp, body_code.as.success.instructions[i]); + } + + GroundInstruction label_inst = groundCreateInstruction(CREATELABEL); + groundAddReferenceToInstruction(&label_inst, groundCreateReference(LABEL, end_label)); + groundAddInstructionToProgram(&gp, label_inst); + + return Success(GroundProgram, charptr, gp); +} + +ResultType(GroundProgram, charptr) generateLambdaNode(SolsNode* node, SolsScope* scope) { + GroundProgram gp = groundCreateProgram(); + + // Generate function signature + GroundInstruction signature = groundCreateInstruction(FUN); + char* lambdaId = malloc(sizeof(char) * 64); + snprintf(lambdaId, 64, "__SOLS_LAMBDA_%zu", scope->tmpCounter++); + + node->accessArg = groundCreateReference(VALREF, lambdaId); + + groundAddReferenceToInstruction(&signature, groundCreateReference(FNREF, lambdaId)); + ResultType(GroundArg, charptr) arg = createGroundArgFromSolsType(node->as.type.returnType); + if (arg.error) { + return Error(GroundProgram, charptr, arg.as.error); + } + groundAddReferenceToInstruction(&signature, arg.as.success); + for (size_t i = 0; i < node->as.type.children.count; i++) { + // Add type + ResultType(GroundArg, charptr) arg = createGroundArgFromSolsType(&node->as.type.children.at[i].type); + if (arg.error) { + return Error(GroundProgram, charptr, arg.as.error); + } + groundAddReferenceToInstruction(&signature, arg.as.success); + + // Add arg name + groundAddReferenceToInstruction(&signature, groundCreateReference(DIRREF, node->as.type.children.at[i].name)); + } + + groundAddInstructionToProgram(&gp, signature); + + // Create a scope for lambda arguments + // Lambdas do NOT have access to external state + SolsScope lambdaScope = { + .variables = NULL, + .tmpCounter = 0, + .returnType = *node->as.type.returnType + }; + + for (size_t i = 0; i < node->as.type.children.count; i++) { + addVariableToScope(&lambdaScope, node->as.type.children.at[i].name, node->as.type.children.at[i].type); + } + + // Generate children and add then to this program + ResultType(GroundProgram, charptr) bodyCode = generateCode(&node->children.at[0], &lambdaScope); + if (bodyCode.error) return bodyCode; + for (size_t i = 0; i < bodyCode.as.success.size; i++) { + groundAddInstructionToProgram(&gp, bodyCode.as.success.instructions[i]); + } + + // End the function + groundAddInstructionToProgram(&gp, groundCreateInstruction(ENDFUN)); + + return Success(GroundProgram, charptr, gp); +} + +ResultType(GroundProgram, charptr) generateDefNode(SolsNode* node, SolsScope* scope) { + GroundProgram gp = groundCreateProgram(); + + // Register the function in the current scope so calls can resolve it + // node->children.at[0] is the identifier node for the function name + if (node->children.count < 2 || node->children.at[0].type != SNT_IDENTIFIER) { + return Error(GroundProgram, charptr, "Invalid def node shape (expected name + body)"); + } + + char* fnName = node->children.at[0].as.idName; + SolsVariable* existing = findSolsVariable(scope, fnName); + if (existing == NULL) { + addVariableToScope(scope, fnName, node->as.type); + } else { + if (existing->typeinfo.type != STT_FUN) { + return Error(GroundProgram, charptr, "A non-function variable already exists with this name"); + } + if (compareTypes(&existing->typeinfo, &node->as.type) == false) { + return Error(GroundProgram, charptr, "Function already exists with a different type signature"); + } + } + + // Generate function signature + GroundInstruction signature = groundCreateInstruction(FUN); + + node->accessArg = groundCreateReference(VALREF, fnName); + + groundAddReferenceToInstruction(&signature, groundCreateReference(FNREF, fnName)); + ResultType(GroundArg, charptr) arg = createGroundArgFromSolsType(node->as.type.returnType); + if (arg.error) { + return Error(GroundProgram, charptr, arg.as.error); + } + groundAddReferenceToInstruction(&signature, arg.as.success); + for (size_t i = 0; i < node->as.type.children.count; i++) { + // Add type + ResultType(GroundArg, charptr) arg = createGroundArgFromSolsType(&node->as.type.children.at[i].type); + if (arg.error) { + return Error(GroundProgram, charptr, arg.as.error); + } + groundAddReferenceToInstruction(&signature, arg.as.success); + + // Add arg name + groundAddReferenceToInstruction(&signature, groundCreateReference(DIRREF, node->as.type.children.at[i].name)); + } + + groundAddInstructionToProgram(&gp, signature); + + // Create a scope for function arguments + SolsScope functionScope = copySolsScope(scope); + + // Set the scope's return type + functionScope.returnType = *node->as.type.returnType; + + for (size_t i = 0; i < node->as.type.children.count; i++) { + addVariableToScope(&functionScope, node->as.type.children.at[i].name, node->as.type.children.at[i].type); + } + + // Generate children and add then to this program + ResultType(GroundProgram, charptr) bodyCode = generateCode(&node->children.at[1], &functionScope); + if (bodyCode.error) return bodyCode; + for (size_t i = 0; i < bodyCode.as.success.size; i++) { + groundAddInstructionToProgram(&gp, bodyCode.as.success.instructions[i]); + } + + // End the function + groundAddInstructionToProgram(&gp, groundCreateInstruction(ENDFUN)); + + return Success(GroundProgram, charptr, gp); +} + +ResultType(GroundProgram, charptr) generateFunctionCallNode(SolsNode* node, SolsScope* scope) { + + // Check whether the function exists and is callable + ResultType(SolsType, charptr) type = getNodeType(node, scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + SolsVariable* var = findSolsVariable(scope, node->as.idName); + if (var == NULL) { + return Error(GroundProgram, charptr, "Could not find variable"); + } + + // Ensure the argument types match the function types + if (node->children.count != var->typeinfo.children.count) { + return Error(GroundProgram, charptr, "Incorrect amount of arguments for function"); + } + + for (size_t i = 0; i < node->children.count; i++) { + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[i], scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + if (compareTypes(&type.as.success, &var->typeinfo.children.at[i].type) == false) { + return Error(GroundProgram, charptr, "Types incorrect for function call"); + } + } + + // Now that everything seems to be fine, call the function + GroundInstruction gi = groundCreateInstruction(CALL); + groundAddReferenceToInstruction(&gi, groundCreateReference(FNREF, node->as.idName)); + + for (size_t i = 0; i < node->children.count; i++) { + groundAddReferenceToInstruction(&gi, node->children.at[i].accessArg); + } + + char* returnStr = malloc(sizeof(char) * 64); + if (returnStr == NULL) { + return Error(GroundProgram, charptr, "Failed to allocate memory for tmp identifier"); + } + snprintf(returnStr, 64, "__SOLS_TMP_CALL_%zu", scope->tmpCounter++); + + groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, returnStr)); + + GroundProgram program = groundCreateProgram(); + groundAddInstructionToProgram(&program, gi); + + node->accessArg = groundCreateReference(VALREF, returnStr); + + return Success(GroundProgram, charptr, program); + +} + +ResultType(GroundProgram, charptr) generateReturnNode(SolsNode* node, SolsScope* scope) { + if (node->children.count < 1) { + return Error(GroundProgram, charptr, "Expecting a value in return"); + } + + ResultType(SolsType, charptr) type = getNodeType(&node->children.at[0], scope); + if (type.error) { + return Error(GroundProgram, charptr, type.as.error); + } + + if (compareTypes(&type.as.success, &scope->returnType) == false) { + return Error(GroundProgram, charptr, "Mismatched return type"); + } + + GroundProgram program = groundCreateProgram(); + GroundInstruction inst = groundCreateInstruction(RETURN); + + groundAddReferenceToInstruction(&inst, node->children.at[0].accessArg); + + groundAddInstructionToProgram(&program, inst); + + return Success(GroundProgram, charptr, program); +} + +// For generateUseNode function, implemented in src/main.c +char* getFileContents(const char* filename); + +ResultType(GroundProgram, charptr) generateUseNode(SolsNode* node, SolsScope* scope) { + char* libraryName = node->as.idName; + char* libPath = getenv("SOLSTICE_LIBS"); + if (libPath == NULL) { + libPath = "/usr/lib/solstice"; + } + + Estr filePath = CREATE_ESTR(libPath); + APPEND_ESTR(filePath, "/"); + APPEND_ESTR(filePath, libraryName); + APPEND_ESTR(filePath, ".sols"); + + // Steal the main function for use here + char* file = getFileContents(filePath.str); + if (file == NULL) { + return Error(GroundProgram, charptr, "Couldn't read contents of imported file"); + } + ResultType(SolsLexer, charptr) lexer = createLexer(file); + if (lexer.error) { + printf("While lexing file %s:\n", filePath.str); + printf("Error while creating lexer: %s", lexer.as.error); + exit(1); + } + ResultType(Nothing, charptr) lexed = lex(&lexer.as.success); + if (lexed.error) { + printf("While lexing file %s:\n", filePath.str); + printf("%s\n", lexed.as.error); + exit(1); + } + + // Parse file + ResultType(SolsParser, charptr) parser = createSolsParser(&lexer.as.success.output); + if (parser.error) { + printf("While parsing file %s:\n", filePath.str); + printf("Error while creating parser: %s\n", parser.as.error); + exit(1); + } + ResultType(Nothing, charptr) parsed = parse(&parser.as.success); + if (parsed.error) { + printf("While parsing file %s:\n", filePath.str); + printf("%s\n", parsed.as.error); + exit(1); + } + + SolsScope newScope = { + .variables = NULL, + .tmpCounter = 0, + .returnType = createSolsType(STT_INT).as.success + }; + + // Do codegen on root node + ResultType(GroundProgram, charptr) codegen = generateCode(&parser.as.success.output, &newScope); + if (codegen.error) { + printf("While generating code for file %s:\n", filePath.str); + printf("%s\n", codegen.as.error); + exit(1); + } + + // Insert all the stuff into our scope + SolsVariable *var, *tmp; + HASH_ITER(hh, newScope.variables, var, tmp) { + addVariableToScope(scope, var->id, var->typeinfo); + } + + return Success(GroundProgram, charptr, codegen.as.success); +} + +ResultType(GroundProgram, charptr) generateInlineGroundNode(SolsNode* node, SolsScope* scope) { + return Success(GroundProgram, charptr, groundParseFile(node->as.inlineGround)); +} + +ResultType(GroundProgram, charptr) generateCode(SolsNode* node, SolsScope* scope) { + + GroundProgram program = groundCreateProgram(); + + SolsScope backupScope = {NULL, 0}; + + if (node->type != SNT_IF && node->type != SNT_WHILE && node->type != SNT_LAMBDA && node->type != SNT_DEF) { + if (node->type == SNT_CODE_BLOCK) { + backupScope = *scope; + SolsScope newScope = copySolsScope(scope); + *scope = newScope; + } + // Generate code for all children before generating this node's code + for (size_t i = 0; i < node->children.count; i++) { + ResultType(GroundProgram, charptr) generated = generateCode(&node->children.at[i], scope); + if (generated.error) { + return Error(GroundProgram, charptr, createCodegenError(&node->children.at[i], generated.as.error)); + } + for (size_t j = 0; j < generated.as.success.size; j++) { + groundAddInstructionToProgram(&program, generated.as.success.instructions[j]); + } + } + if (node->type == SNT_CODE_BLOCK) { + destroySolsScope(scope); + *scope = backupScope; + } + } + + // Now generate code for this node + switch (node->type) { + case SNT_PUTS: generate(Puts); + case SNT_LITERAL: generate(Literal); + case SNT_OP_SET: generate(Set); + case SNT_OP_ADD: generate(Add); + case SNT_OP_SUB: generate(Sub); + case SNT_OP_MUL: generate(Mul); + case SNT_OP_DIV: generate(Div); + case SNT_OP_EQUAL: generate(Equal); + case SNT_OP_INEQUAL: generate(Inequal); + case SNT_OP_GREATER: generate(Greater); + case SNT_OP_EQGREATER: generate(EqGreater); + case SNT_OP_LESSER: generate(Lesser); + case SNT_OP_EQLESSER: generate(EqLesser); + case SNT_CODE_BLOCK: generate(CodeBlock); + case SNT_IF: generate(If); + case SNT_WHILE: generate(While); + case SNT_LAMBDA: generate(Lambda); + case SNT_DEF: generate(Def); + case SNT_FUNCTION_CALL: generate(FunctionCall); + case SNT_RETURN: generate(Return); + case SNT_USE: generate(Use); + case SNT_GROUND: generate(InlineGround); + } + return Success(GroundProgram, charptr, program); +} diff --git a/src/codegen/codegen.h b/src/codegen/codegen.h new file mode 100644 index 0000000..fa98498 --- /dev/null +++ b/src/codegen/codegen.h @@ -0,0 +1,34 @@ +#ifndef CODEGEN_H +#define CODEGEN_H + +#include + +#include "SolsScope.h" + +#include "../parser/SolsNode.h" + +Result(GroundProgram, charptr); + +// Generates a GroundProgram (from the Ground VM header) from +// a provided SolsNode. +// Returns: +// Success: Generated GroundProgram +// Failure: charptr detailing what happened +ResultType(GroundProgram, charptr) generateCode(SolsNode* node, SolsScope* scope); + +// Gets the type of a node generated by the parser for the type checker. +ResultType(SolsType, charptr) getNodeType(SolsNode* node, SolsScope* scope); + +// Macro to help with code generation (and soon error handling) +#define generate(nodetype) {\ + ResultType(GroundProgram, charptr) __result = generate##nodetype##Node(node, scope);\ + if (__result.error) {\ + return Error(GroundProgram, charptr, __result.as.error);\ + }\ + for (size_t i = 0; i < __result.as.success.size; i++) {\ + groundAddInstructionToProgram(&program, __result.as.success.instructions[i]);\ + }\ + break;\ +} + +#endif diff --git a/src/error.cpp b/src/error.cpp deleted file mode 100644 index 2cd326a..0000000 --- a/src/error.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "error.h" -#include -#include - -namespace Solstice { - namespace Error { - // ANSI Color Codes - const std::string RED = "\033[31m"; - const std::string YELLOW = "\033[33m"; - const std::string CYAN = "\033[36m"; - const std::string RESET = "\033[0m"; - const std::string BOLD = "\033[1m"; - - int amountOfErrors = 0; - - void syntaxError(std::string what, int line, std::string lineContent) { - std::cout << BOLD << RED << "Syntax Error" << RESET; - if (line > 0) std::cout << " on line " << BOLD << line << RESET; - std::cout << ":\n"; - - if (!lineContent.empty()) { - std::cout << "\n " << CYAN << lineContent << RESET << "\n\n"; - } else { - std::cout << "\n"; - } - std::cout << YELLOW << "-> " << what << RESET << "\n"; - amountOfErrors++; - } - void typingError(std::string what, int line, std::string lineContent) { - std::cout << BOLD << RED << "Typing Error" << RESET; - if (line > 0) std::cout << " on line " << BOLD << line << RESET; - std::cout << ":\n"; - - if (!lineContent.empty()) { - std::cout << "\n " << CYAN << lineContent << RESET << "\n\n"; - } else { - std::cout << "\n"; - } - std::cout << YELLOW << "-> " << what << RESET << "\n"; - amountOfErrors++; - } - void summariseErrors() { - if (amountOfErrors > 0) { - std::cout << amountOfErrors << " errors generated.\n"; - exit(1); - } - } - } -} diff --git a/src/error.h b/src/error.h deleted file mode 100644 index 7ecd480..0000000 --- a/src/error.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ERROR_H -#define ERROR_H - -#include - -namespace Solstice { - namespace Error { - void syntaxError(std::string what, int line = 0, std::string lineContent = ""); - void typingError(std::string what, int line = 0, std::string lineContent = ""); - void summariseErrors(); - extern int amountOfErrors; - } -} - -#endif diff --git a/src/include/ansii.h b/src/include/ansii.h new file mode 100644 index 0000000..fc40739 --- /dev/null +++ b/src/include/ansii.h @@ -0,0 +1,67 @@ +// ansii.h - made by SpookyDervish +// version 1.0.0 +// do with this whatever you want +// +// example usage with printf: printf(ESC_BOLD ESC_RED_FG "hi\n"); + +#ifndef ANSII_H +#define ANSII_H + +#define ESC_RESET "\x1b[0m" +#define ESC_BOLD "\x1b[1m" +#define ESC_DIM "\x1b[2m" +#define ESC_ITALIC "\x1b[3m" +#define ESC_UNDERLINE "\x1b[4m" +#define ESC_BLINKING "\x1b[5m" +#define ESC_REVERSE "\x1b[7m" +#define ESC_HIDDEN "\x1b[8m" +#define ESC_STRIKETHROUGH "\x1b[8m" + +#define ESC_TERMINAL_BELL "\a" + +#define ESC_BLACK_FG "\x1b[30m" +#define ESC_RED_FG "\x1b[31m" +#define ESC_GREEN_FG "\x1b[32m" +#define ESC_YELLOW_FG "\x1b[33m" +#define ESC_BLUE_FG "\x1b[34m" +#define ESC_MAGENTA_FG "\x1b[35m" +#define ESC_CYAN_FG "\x1b[36m" +#define ESC_WHITE_FG "\x1b[37m" + +#define ESC_BLACK_FG "\x1b[30m" +#define ESC_RED_FG "\x1b[31m" +#define ESC_GREEN_FG "\x1b[32m" +#define ESC_YELLOW_FG "\x1b[33m" +#define ESC_BLUE_FG "\x1b[34m" +#define ESC_MAGENTA_FG "\x1b[35m" +#define ESC_CYAN_FG "\x1b[36m" +#define ESC_WHITE_FG "\x1b[37m" +#define ESC_BRIGHT_BLACK_FG "\x1b[90m" +#define ESC_BRIGHT_RED_FG "\x1b[91m" +#define ESC_BRIGHT_GREEN_FG "\x1b[92m" +#define ESC_BRIGHT_YELLOW_FG "\x1b[93m" +#define ESC_BRIGHT_BLUE_FG "\x1b[94m" +#define ESC_BRIGHT_MAGENTA_FG "\x1b[95m" +#define ESC_BRIGHT_CYAN_FG "\x1b[96m" +#define ESC_BRIGHT_WHITE_FG "\x1b[97m" + +#define ESC_BLACK_BG "\x1b[40m" +#define ESC_RED_BG "\x1b[41m" +#define ESC_GREEN_BG "\x1b[42m" +#define ESC_YELLOW_BG "\x1b[43m" +#define ESC_BLUE_BG "\x1b[44m" +#define ESC_MAGENTA_BG "\x1b[45m" +#define ESC_CYAN_BG "\x1b[46m" +#define ESC_WHITE_BG "\x1b[47m" +#define ESC_BRIGHT_BLACK_BG "\x1b[100m" +#define ESC_BRIGHT_RED_BG "\x1b[101m" +#define ESC_BRIGHT_GREEN_BG "\x1b[102m" +#define ESC_BRIGHT_YELLOW_BG "\x1b[103m" +#define ESC_BRIGHT_BLUE_BG "\x1b[104m" +#define ESC_BRIGHT_MAGENTA_BG "\x1b[105m" +#define ESC_BRIGHT_CYAN_BG "\x1b[106m" +#define ESC_BRIGHT_WHITE_BG "\x1b[107m" + +#define ESC_DEFAULT_FG "\x1b[39m" + +#endif // !ANSII_H \ No newline at end of file diff --git a/src/include/error.h b/src/include/error.h new file mode 100644 index 0000000..eb78829 --- /dev/null +++ b/src/include/error.h @@ -0,0 +1,83 @@ +#include +#include + +#ifndef ERROR_H +#define ERROR_H + +/* + * error.h - First class errors for C + * Have you ever wanted to have a Rust-like error experience in C? + * Look no further than this library! Using a couple simple macros, + * we can emulate their complicated enum system, and I'd argue that + * we do it better. Besides, it's in a better programming language. + * + * Enjoy! + * + * Licenced to you under the MIT license - see below. +*/ + +/* + * Example usage: + * + * #include "error.h" + * #include + * + * // You can't write char*, you have to define it with a typedef + * typedef char* charptr; + * + * Result(int, charptr) myFn(int x) { + * if (x > 5) { + * return Error(int, charptr, "Your number is too big"); + * } + * return Success(int, charptr, x); + * } + * + * int main() { + * ResultType(int, charptr) res = myFn(10); + * if (res.error) { + * printf("Uh oh, error is: %s\n", res.as.error); + * } else { + * printf("Got a result! It is %d\n", res.as.success); + * } + * } + * + */ + +/* + * Copyright 2026 Maxwell Jeffress + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. +*/ + +// Creates a new struct with the a (success) and b (error) types. +// If Result(a, b) has already been called with the same paramaters, please +// use ResultType(a, b) instead. +#define Result(a, b) struct __ResultType_##a##_##b { bool error; union {a success; b error;} as; } + +// Uses an existing Result(a, b) struct. +#define ResultType(a, b) struct __ResultType_##a##_##b + + +// Creates a __ResultType_a_b struct, with .error as false and .as.success as res. +#define Success(a, b, res) (ResultType(a, b)) { .error = false, .as.success = res } + +// Creates a __ResultType_a_b struct, with .error as true and .as.error as res. +#define Error(a, b, res) (ResultType(a, b)) { .error = true, .as.error = res } + +#endif diff --git a/src/include/estr.h b/src/include/estr.h new file mode 100644 index 0000000..df5d172 --- /dev/null +++ b/src/include/estr.h @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#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 diff --git a/src/include/nothing.h b/src/include/nothing.h new file mode 100644 index 0000000..cfa91b7 --- /dev/null +++ b/src/include/nothing.h @@ -0,0 +1,10 @@ +// nothing.h - ever needed to return nothing (but not void)? +// boy do I have the solution for you + +#ifndef NOTHING_H +#define NOTHING_H + +// Behold, it is nothing! +typedef struct Nothing {} Nothing; + +#endif diff --git a/src/include/uthash.h b/src/include/uthash.h new file mode 100644 index 0000000..06c2eeb --- /dev/null +++ b/src/include/uthash.h @@ -0,0 +1,1137 @@ +/* +Copyright (c) 2003-2025, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_NO_STDINT) && HASH_NO_STDINT +/* The user doesn't have , and must figure out their own way + to provide definitions for uint8_t and uint32_t. */ +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) ((bv[(idx)/8U] & (1U << ((idx)%8U))) != 0) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) 1 +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/src/lexer.cpp b/src/lexer.cpp deleted file mode 100644 index 3177889..0000000 --- a/src/lexer.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include -#include -#include -#include -#include "lexer.h" - -namespace Solstice { - - std::optional Lexer::peek(int ahead) { - if (current + ahead < size) { - return input[current + ahead]; - } else { - return {}; - } - } - - std::optional Lexer::consume() { - if (current < size) { - return input[current++]; - } else { - return {}; - } - } - - Lexer::Lexer(std::string in) : input(in), size(in.size()) { - std::stringstream ss(in); - std::string line; - while (std::getline(ss, line)) { - lines.push_back(line); - } - if (lines.empty()) lines.push_back(""); - } - - std::vector Lexer::lex() { - current = 0; - std::vector tokens; - std::string buf; - bool inString = false; - size_t currentLine = 1; - - auto addToken = [&](std::string val) { - std::string content = ""; - if (currentLine <= lines.size()) { - content = lines[currentLine - 1]; - } - tokens.push_back({val, currentLine, content}); - }; - - while (auto copt = consume()) { - char c = copt.value(); - - if (inString) { - buf.push_back(c); - if (c == '"') { - addToken(buf); - buf.clear(); - inString = false; - } - if (c == '\n') currentLine++; - continue; - } - - switch (c) { - // double quotes are special - case '"': { - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - buf.push_back(c); - inString = true; - break; - } - // slash is special - case '/': { - if (peek(0).has_value() && peek(0).value() == '/') { - while (auto copt = consume()) { - char c = copt.value(); - if (c == '\n') { - break; - } - } - continue; - } - std::string newToken(1, c); - auto tokenopt = peek(0); - if (tokenopt) { - char token = tokenopt.value(); - if (token == '=') { - newToken += token; - consume(); - } - } - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - addToken(newToken); - break; - } - // tokens which are not followed by anything - case '\n': - { - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - addToken(std::string(1, c)); - currentLine++; - break; - } - case '(': - case ')': - case '}': - case ',': - case ':': - case '.': - { - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - addToken(std::string(1, c)); - break; - } - // tokens which may be followed by either themselves - // or an equals sign - case '+': - case '-': - { - std::string newToken(1, c); - auto tokenopt = peek(0); - if (tokenopt) { - char token = tokenopt.value(); - if (token == c || token == '=') { - newToken += token; - consume(); - } - } - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - addToken(newToken); - break; - } - // tokens which may be followed by an equals sign - case '!': - case '*': - case '=': - case '>': - case '<': - { - std::string newToken(1, c); - auto tokenopt = peek(0); - if (tokenopt) { - char token = tokenopt.value(); - if (token == '=') { - newToken += token; - consume(); - } - } - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - addToken(newToken); - break; - } - // tokens which need a newline inserted for them - case '{': - { - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - addToken("\n"); - addToken(std::string(1, c)); - break; - } - // tokens which do not need to be included - case ' ': - case '\r': - { - if (!buf.empty()) { - addToken(buf); - buf.clear(); - } - break; - } - default: - { - buf += c; - } - } - } - - if (!buf.empty()) { - addToken(buf); - } - return tokens; - } - -} diff --git a/src/lexer.h b/src/lexer.h deleted file mode 100644 index 161f7f8..0000000 --- a/src/lexer.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef LEXER_H -#define LEXER_H - -#include -#include -#include - -namespace Solstice { - struct Token { - std::string value; - size_t line; - std::string lineContent; - }; - - class Lexer { - std::string input; - std::vector lines; - size_t size; - size_t current; - std::optional peek(int ahead = 1); - std::optional consume(); - public: - Lexer(std::string in); - std::vector lex(); - - }; -} - -#endif diff --git a/src/lexer/SolsLiteral.c b/src/lexer/SolsLiteral.c new file mode 100644 index 0000000..afddf09 --- /dev/null +++ b/src/lexer/SolsLiteral.c @@ -0,0 +1,51 @@ +#include "SolsLiteral.h" +#include +#include + +ResultType(SolsLiteral, charptr) createSolsLiteral(SolsLiteralType type, ...) { + va_list args; + va_start(args, type); + SolsLiteral literal = { + .type = type + }; + switch (type) { + case SLT_INT: { + literal.as.intv = va_arg(args, int64_t); + break; + } + case SLT_DOUBLE: { + literal.as.doublev = va_arg(args, double); + break; + } + case SLT_BOOL: { + literal.as.boolv = (bool) va_arg(args, int); + break; + } + case SLT_CHAR: { + literal.as.charv = (char) va_arg(args, int); + break; + } + case SLT_STRING: { + char* input = va_arg(args, char*); + if (input == NULL) { + va_end(args); + return Error(SolsLiteral, charptr, "Unexpected NULL value (in createSolsLiteral() function)"); + } + literal.as.stringv = malloc(strlen(input) + 1); + if (literal.as.stringv == NULL) { + va_end(args); + return Error(SolsLiteral, charptr, "Couldn't allocate memory (in createSolsLiteral() function)"); + } + strcpy(literal.as.stringv, input); + break; + } + } + va_end(args); + return Success(SolsLiteral, charptr, literal); +} + +void freeSolsLiteral(SolsLiteral* lit) { + if (lit->type == SLT_STRING && lit->as.stringv != NULL) { + free(lit->as.stringv); + } +} diff --git a/src/lexer/SolsLiteral.h b/src/lexer/SolsLiteral.h new file mode 100644 index 0000000..7c97ac9 --- /dev/null +++ b/src/lexer/SolsLiteral.h @@ -0,0 +1,46 @@ +#ifndef SOLSLITERAL_H +#define SOLSLITERAL_H + +#include +#include + +#include "../include/error.h" +#include "../include/nothing.h" + +typedef char* charptr; + +typedef enum SolsLiteralType { + SLT_INT, SLT_STRING, SLT_DOUBLE, SLT_BOOL, SLT_CHAR +} SolsLiteralType; + +// Stores literal values which will be added to the Ground code. +// Not much explaining needed here. +typedef struct SolsLiteral { + SolsLiteralType type; + union { + int64_t intv; + char* stringv; + double doublev; + bool boolv; + char charv; + } as; +} SolsLiteral; + +Result(SolsLiteral, charptr); + +// Creates a SolsLiteral, based on the type provided. +// SolsLiteralType -> C type: +// SLT_INT -> int64_t +// SLT_STRING -> char* +// SLT_DOUBLE -> double +// SLT_BOOL -> bool +// SL_CHAR -> char +// An error will only be returned if there is an issue copying a provided char*. +// There is no way to detect incorrectly provided types, so ensure that the right type +// is provided!!!! +ResultType(SolsLiteral, charptr) createSolsLiteral(SolsLiteralType type, ...); + +// Frees a SolsLiteral. Primarily concerned with freeing .as.stringv +void freeSolsLiteral(SolsLiteral* lit); + +#endif diff --git a/src/lexer/SolsToken.c b/src/lexer/SolsToken.c new file mode 100644 index 0000000..c667d3f --- /dev/null +++ b/src/lexer/SolsToken.c @@ -0,0 +1,93 @@ +#include "SolsToken.h" +#include "SolsLiteral.h" +#include "../include/error.h" +#include +#include + +ResultType(SolsToken, charptr) createSolsToken(SolsTokenType type, ...) { + va_list args; + va_start(args, type); + SolsToken token = { + .type = type + }; + + if (type == STT_IDENTIFIER) { + char* name = va_arg(args, char*); + if (name == NULL) { + va_end(args); + return Error(SolsToken, charptr, "String passed is NULL (in createSolsToken() function)"); + } + token.as.idName = malloc(strlen(name) + 1); + if (token.as.idName == NULL) { + va_end(args); + return Error(SolsToken, charptr, "Couldn't allocate memory (in createSolsToken() function)"); + } + strcpy(token.as.idName, name); + } + + if (type == STT_KW_GROUND) { + char* ground = va_arg(args, char*); + if (ground == NULL) { + va_end(args); + return Error(SolsToken, charptr, "String passed is NULL (in createSolsToken() function)"); + } + token.as.inlineGround = malloc(strlen(ground) + 1); + if (token.as.inlineGround == NULL) { + va_end(args); + return Error(SolsToken, charptr, "Couldn't allocate memory (in createSolsToken() function)"); + } + strcpy(token.as.inlineGround, ground); + } + + if (type == STT_LITERAL) { + token.as.literal = va_arg(args, SolsLiteral); + } + + if (type == STT_TYPE) { + token.as.type = va_arg(args, SolsType); + } + + va_end(args); + return Success(SolsToken, charptr, token); +} + +void freeSolsToken(SolsToken* token) { + if (token->type == STT_IDENTIFIER && token->as.idName != NULL) { + free(token->as.idName); + } + if (token->type == STT_KW_GROUND && token->as.inlineGround != NULL) { + free(token->as.inlineGround); + } + if (token->type == STT_LITERAL) { + freeSolsLiteral(&token->as.literal); + } + if (token->type == STT_TYPE) { + freeSolsType(&token->as.type); + } +} + +ResultType(SolsTokens, charptr) createSolsTokens() { + SolsTokens tokens = { + .at = malloc(sizeof(SolsToken) * 32), + .capacity = 32, + .count = 0 + }; + if (tokens.at == NULL) { + return Error(SolsTokens, charptr, "Failed to allocate memory (in createSolsTokens() function)"); + } + return Success(SolsTokens, charptr, tokens); +} + +ResultType(Nothing, charptr) addTokenToSolsTokens(SolsTokens* tokens, SolsToken token) { + if (tokens->capacity < tokens->count + 1) { + tokens->capacity *= 2; + SolsToken* tmp = realloc(tokens->at, sizeof(SolsToken) * tokens->capacity); + if (tmp == NULL) { + return Error(Nothing, charptr, "Failed to allocate memory (in addTokenToSolsTokens() function)"); + } + tokens->at = tmp; + } + tokens->at[tokens->count] = token; + tokens->count++; + return Success(Nothing, charptr, {}); +} diff --git a/src/lexer/SolsToken.h b/src/lexer/SolsToken.h new file mode 100644 index 0000000..f87a7fe --- /dev/null +++ b/src/lexer/SolsToken.h @@ -0,0 +1,83 @@ +#ifndef SOLSTOKEN_H +#define SOLSTOKEN_H + +#include + +#include "../include/error.h" +#include "../include/nothing.h" + +#include "SolsType.h" +#include "SolsLiteral.h" + +typedef enum SolsTokenType { + STT_IDENTIFIER, STT_LITERAL, STT_TYPE, STT_DOT, STT_OPEN_CURLY, STT_CLOSE_CURLY, STT_OPEN_PAREN, STT_CLOSE_PAREN, STT_OP_ADD, STT_OP_SUB, STT_OP_MUL, STT_OP_DIV, STT_OP_ADDTO, STT_OP_SUBTO, STT_OP_MULTO, STT_OP_DIVTO, STT_OP_INCREMENT, STT_OP_DECREMENT, STT_OP_SET, STT_OP_GREATER, STT_OP_LESSER, STT_OP_EQUAL, STT_OP_INEQUAL, STT_OP_EQGREATER, STT_OP_EQLESSER, STT_KW_DEF, STT_KW_LAMBDA, STT_KW_RETURN, STT_KW_USE, STT_KW_STRUCT, STT_KW_PUTS, STT_KW_IF, STT_KW_WHILE, STT_KW_NEW, STT_KW_GROUND, STT_LINE_END, STT_COMMA +} SolsTokenType; + +typedef char* charptr; + +// Stores information about the line that the token/node is on, for printing if an error +// occurs. +// .num is the line number, .content is the line's contents. +typedef struct LineInfo { + size_t num; + char* content; +} LineInfo; + +// Represents a token lexed by the lex() function. +// Most token types exclusively use the .type field, however some tokens require storing +// more data, inside the .as union. +// Those tokens are: +// STT_LITERAL: A literal value. Uses field .as.literal +// STT_TYPE: A type descriptor. Uses field .as.type +// STT_IDENTIFIER: An identifier. Uses field .as.idName +// STT_KW_GROUND: Ground code embedded inside Solstice. Uses field .as.inlineGround +typedef struct SolsToken { + SolsTokenType type; + union { + SolsLiteral literal; + SolsType type; + char* idName; + char* inlineGround; + } as; + LineInfo line; +} SolsToken; + +Result(SolsToken, charptr); + +// Creates a SolsToken. If the type passed in is STT_LITERAL, STT_TYPE, STT_IDENTIFIER or +// STT_KW_GROUND, the function expects another argument, corresponding to the data type +// the token holds. See the SolsToken struct for more information. +// Returns: +// Success: The created SolsToken +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsToken, charptr) createSolsToken(SolsTokenType type, ...); + +// Frees a SolsToken, specifically the .as field elements. +void freeSolsToken(SolsToken* token); + +// Represents a Solstice program, seperated into tokens. +// .at is a pointer to the tokens +// .count is how many tokens are currently being stored +// .capacity is how many tokens worth of memory is allocated +typedef struct SolsTokens { + SolsToken* at; + size_t count; + size_t capacity; +} SolsTokens; + +Result(SolsTokens, charptr); + +// Creates a SolsTokens holder. +// Returns: +// Success: Constructed SolsTokens +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsTokens, charptr) createSolsTokens(); + +// Adds a token to SolsTokens. Used by the lex() function. +// Returns: +// Success: Nothing +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(Nothing, charptr) addTokenToSolsTokens(SolsTokens* tokens, SolsToken token); + + +#endif diff --git a/src/lexer/SolsType.c b/src/lexer/SolsType.c new file mode 100644 index 0000000..e77fb36 --- /dev/null +++ b/src/lexer/SolsType.c @@ -0,0 +1,176 @@ +#include "SolsType.h" +#include "lexer.h" +#include "../include/error.h" +#include "../include/estr.h" +#include +#include + +ResultType(SolsType, charptr) createSolsType(SolsTypeType in) { + SolsTypeField* ptr = malloc(sizeof(SolsTypeField) * 32); + if (ptr == NULL) { + return Error(SolsType, charptr, "Couldn't allocate memory (in createSolsType() function)"); + } + SolsType type = { .type = in, .children.capacity = 32, .children.at = ptr }; + return Success(SolsType, charptr, type); +} + +ResultType(SolsType, charptr) copySolsType(SolsType* type) { + SolsType ret = { .type = type->type, .children.count = type->children.count, .children.capacity = type->children.capacity}; + + // Allocate memory + SolsTypeField* ptr = malloc(sizeof(SolsTypeField) * type->children.capacity); + if (ptr == NULL) { + return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)"); + } + ret.children.at = ptr; + + // Deep copy values + for (size_t i = 0; i < type->children.count; i++) { + // Copy the SolsType value + ResultType(SolsType, charptr) copied = copySolsType(&type->children.at[i].type); + if (copied.error) { + Estr err = CREATE_ESTR(copied.as.error); + APPEND_ESTR(err, " (in addChildToSolsType() function)"); + return Error(SolsType, charptr, err.str); + } + ret.children.at[i].type = copied.as.success; + + // Copy the name + if (type->children.at[i].name == NULL) { + ret.children.at[i].name = NULL; + } else { + ret.children.at[i].name = malloc(strlen(type->children.at[i].name) + 1); + if (ret.children.at[i].name == NULL) { + return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)"); + } + strcpy(ret.children.at[i].name, type->children.at[i].name); + } + } + return Success(SolsType, charptr, ret); +} + +ResultType(Nothing, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name) { + if (type->children.capacity < type->children.count + 1) { + type->children.capacity *= 2; + SolsTypeField* ptr = realloc(type->children.at, sizeof(SolsTypeField) * type->children.capacity); + if (ptr == NULL) { + return Error(Nothing, charptr, "Couldn't allocate memory (in addChildToSolsType() function)"); + } + type->children.at = ptr; + } + ResultType(SolsType, charptr) copied = copySolsType(&child); + if (copied.error) { + Estr err = CREATE_ESTR(copied.as.error); + APPEND_ESTR(err, " (in addChildToSolsType() function)"); + return Error(Nothing, charptr, err.str); + } + type->children.at[type->children.count].type = copied.as.success; + if (name == NULL) { + type->children.at[type->children.count].name = NULL; + } else { + type->children.at[type->children.count].name = malloc(strlen(name) + 1); + strcpy(type->children.at[type->children.count].name, name); + } + type->children.count++; + + return Success(Nothing, charptr, {}); +} + +void freeSolsType(SolsType* type) { + for (size_t i = 0; i < type->children.count; i++) { + // Free the name + if (type->children.at[i].name != NULL) { + free(type->children.at[i].name); + } + + // Free the child SolsTypes + freeSolsType(&type->children.at[i].type); + } + // Free the field itself + free(type->children.at); + type->children.at = NULL; + + // Set count and capacity to zero + type->children.count = 0; + type->children.capacity = 0; +} + +bool compareTypes(SolsType* left, SolsType* right) { + if (left->type != right->type) { + return false; + } + switch (left->type) { + case STT_OBJECT: { + if (left->children.count != right->children.count) { + return false; + } + for (size_t i = 0; i < left->children.count; i++) { + if (strcmp(left->children.at[i].name, right->children.at[i].name) != 0) { + return false; + } + if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) { + return false; + } + } + return true; + } + case STT_TEMPLATE: { + if (left->children.count != right->children.count) { + return false; + } + for (size_t i = 0; i < left->children.count; i++) { + if (strcmp(left->children.at[i].name, right->children.at[i].name) != 0) { + return false; + } + if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) { + return false; + } + } + return true; + + } + case STT_FUN: { + if (left->children.count != right->children.count) { + return false; + } + for (size_t i = 0; i < left->children.count; i++) { + if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) { + return false; + } + } + return true; + } + default: return true; + } +} + +ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type) { + switch (type->type) { + case STT_INT: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "int")); + } + case STT_DOUBLE: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "double")); + } + case STT_STRING: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "string")); + } + case STT_BOOL: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "bool")); + } + case STT_CHAR: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "char")); + } + case STT_FUN: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "function")); + } + case STT_TEMPLATE: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "struct")); + } + case STT_OBJECT: { + // FIXME Do this later + return Error(GroundArg, charptr, "FIXME"); + } + } + return Error(GroundArg, charptr, "How did we get here?"); +} diff --git a/src/lexer/SolsType.h b/src/lexer/SolsType.h new file mode 100644 index 0000000..0194c0d --- /dev/null +++ b/src/lexer/SolsType.h @@ -0,0 +1,105 @@ +#ifndef SOLSTYPE_H +#define SOLSTYPE_H + +#include +#include + +#include "../include/error.h" +#include "../include/nothing.h" + +typedef enum SolsTypeType { + STT_INT, STT_STRING, STT_DOUBLE, STT_BOOL, STT_CHAR, STT_FUN, STT_TEMPLATE, STT_OBJECT +} SolsTypeType; + +// Definition of charptr for Result() and ResultType() macros +typedef char* charptr; + +struct SolsTypeField; + +// Holds type information for a struct, object or function. +// Say, for example, your type signature looks like this: +// object(string x, fun(int) y) +// This is stored like this: +// SolsType { +// type: STT_OBJECT +// children: [ +// { +// type: { +// type: STT_STRING +// } +// name: "x" +// } +// { +// type: { +// type: STT_FUN +// children: [ +// { +// type: { +// type: STT_INT +// } +// } +// ] +// } +// name: "y" +// } +// ] +// } +// +// (Sorry for the long explaination, but it's worth it so you know how the type system works.) +// +typedef struct SolsType { + SolsTypeType type; + + // For use when type is identified with a name + char* identifierType; + + // For use in functions + struct SolsType* returnType; + + // For use by fun, template, object + struct { + struct SolsTypeField* at; + size_t count; + size_t capacity; + } children; +} SolsType; + +// Assists with holding child types in the SolsType struct. +typedef struct SolsTypeField { + SolsType type; + char* name; +} SolsTypeField; + + +Result(SolsType, charptr); + +// Creates a SolsType, with the provided type type. +// Use the "addChildToSolsType()" function to add children, in case this type has children. +// Returns: +// Success: The constructed SolsType +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsType, charptr) createSolsType(SolsTypeType in); + +Result(Nothing, charptr); + +// Adds a child SolsType to a given SolsType. +// Returns: +// Success: Nothing +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(Nothing, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name); + +// Makes a deep copy of a SolsType. +ResultType(SolsType, charptr) copySolsType(SolsType* type); + +Result(GroundArg, charptr); + +// Represents a SolsType as a GroundArg (in typeref form) +ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type); + +// Frees a SolsType +void freeSolsType(SolsType* type); + +// Compares two SolsTypes +bool compareTypes(SolsType* left, SolsType* right); + +#endif diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c new file mode 100644 index 0000000..babcb45 --- /dev/null +++ b/src/lexer/lexer.c @@ -0,0 +1,844 @@ +#include "lexer.h" +#include "SolsLiteral.h" +#include "SolsToken.h" +#include "../include/error.h" +#include "../include/estr.h" +#include "../include/ansii.h" +#include + +struct _SolsTokenTypeMap SolsTokenTypeMap[] = { + {"puts", STT_KW_PUTS}, + {"if", STT_KW_IF}, + {"while", STT_KW_WHILE}, + {"def", STT_KW_DEF}, + {"lambda", STT_KW_LAMBDA}, + {"return", STT_KW_RETURN}, + {"use", STT_KW_USE}, + {"struct", STT_KW_STRUCT}, + {"ground", STT_KW_GROUND}, + {"{", STT_OPEN_CURLY}, + {"}", STT_CLOSE_CURLY}, + {"(", STT_OPEN_PAREN}, + {")", STT_CLOSE_PAREN}, + {"+", STT_OP_ADD}, + {"-", STT_OP_SUB}, + {"*", STT_OP_MUL}, + {"/", STT_OP_DIV}, + {"=", STT_OP_SET}, + {"+=", STT_OP_ADDTO}, + {"-=", STT_OP_SUBTO}, + {"*=", STT_OP_MULTO}, + {"/=", STT_OP_DIVTO}, + {"++", STT_OP_INCREMENT}, + {"--", STT_OP_DECREMENT}, + {"==", STT_OP_EQUAL}, + {"!=", STT_OP_INEQUAL}, + {">", STT_OP_GREATER}, + {"<", STT_OP_LESSER}, + {">=", STT_OP_EQGREATER}, + {"<=", STT_OP_EQLESSER}, + {"\n", STT_LINE_END}, + {";", STT_LINE_END}, + {",", STT_COMMA}, + // Shh, this is our little secret + // Your reward for actually reading the source code + // Enable this by adding -DSUPER_SILLY_MODE to your + // compile flags (not recommended for production) + #ifdef SUPER_SILLY_MODE + {"plus", STT_OP_ADD}, + {"minus", STT_OP_SUB}, + {"times", STT_OP_MUL}, + {"dividedby", STT_OP_DIV}, + {"then", STT_OPEN_CURLY}, + {"do", STT_OPEN_CURLY}, + {"end", STT_CLOSE_CURLY}, + {"is", STT_OP_SET}, + {"equals", STT_OP_EQUAL}, + {"greaterthan", STT_OP_GREATER}, + {"lesserthan", STT_OP_LESSER}, + {"increment", STT_OP_INCREMENT}, + {"decrement", STT_OP_DECREMENT}, + {"adds", STT_OP_ADDTO}, + {"subtracts", STT_OP_SUBTO}, + {"multiplies", STT_OP_MULTO}, + {"divides", STT_OP_DIVTO}, + #endif +}; + +ResultType(SolsTokenType, Nothing) getTokenType(const char* input) { + size_t mapsize = sizeof(SolsTokenTypeMap) / sizeof(struct _SolsTokenTypeMap); + for (size_t i = 0; i < mapsize; i++) { + if (strcmp(input, SolsTokenTypeMap[i].str) == 0) { + return Success(SolsTokenType, Nothing, SolsTokenTypeMap[i].type); + } + } + return Error(SolsTokenType, Nothing, {}); +} + +static ResultType(Nothing, charptr) handleGround(SolsLexer* lexer, SolsToken* token, size_t* lineNum, Estr* currentLine, char currentChr, bool* skipDelimiter) { + bool foundBrace = false; + if (currentChr == '{') { + foundBrace = true; + *skipDelimiter = true; + } else { + while (true) { + ResultType(char, Nothing) peek = lexerPeek(lexer, 1); + if (peek.error) break; + if (isspace(peek.as.success)) { + char c = lexerConsume(lexer).as.success; + if (c == '\n') { + (*lineNum)++; + DESTROY_ESTR((*currentLine)); + *currentLine = CREATE_ESTR(""); + size_t lineStart = lexer->current; + for (size_t i = lineStart; i < lexer->inputsize; i++) { + if (lexer->input[i] == '\n') break; + char buf_tmp[] = {lexer->input[i], '\0'}; + APPEND_ESTR((*currentLine), buf_tmp); + } + } + } else if (peek.as.success == '{') { + lexerConsume(lexer); + foundBrace = true; + break; + } else { + break; + } + } + } + + if (!foundBrace) { + return Error(Nothing, charptr, "Expected '{' after 'ground'"); + } + + Estr groundBuf = CREATE_ESTR(""); + int depth = 1; + while (depth > 0) { + ResultType(char, Nothing) next = lexerConsume(lexer); + if (next.error) { + DESTROY_ESTR(groundBuf); + return Error(Nothing, charptr, "Unterminated 'ground' block"); + } + if (next.as.success == '{') depth++; + if (next.as.success == '}') { + depth--; + if (depth == 0) break; + } + + char tmp[] = {next.as.success, '\0'}; + APPEND_ESTR(groundBuf, tmp); + + if (next.as.success == '\n') { + (*lineNum)++; + DESTROY_ESTR((*currentLine)); + *currentLine = CREATE_ESTR(""); + size_t lineStart = lexer->current; + for (size_t i = lineStart; i < lexer->inputsize; i++) { + if (lexer->input[i] == '\n') break; + char buf_tmp[] = {lexer->input[i], '\0'}; + APPEND_ESTR((*currentLine), buf_tmp); + } + } + } + + token->as.inlineGround = malloc(strlen(groundBuf.str) + 1); + if (token->as.inlineGround == NULL) { + DESTROY_ESTR(groundBuf); + return Error(Nothing, charptr, "Memory allocation failed (in handleGround() function)"); + } + strcpy(token->as.inlineGround, groundBuf.str); + DESTROY_ESTR(groundBuf); + return Success(Nothing, charptr, {}); +} + +static ResultType(Nothing, charptr) identifyAndAdd(SolsLexer* lexer, Estr* buf, size_t* lineNum, Estr* currentLine, char currentChr, bool* skipDelimiter) { + if (strcmp(buf->str, "") == 0) return Success(Nothing, charptr, {}); + + ResultType(SolsToken, charptr) result = identifyToken(buf->str); + if (result.error) { + return Error(Nothing, charptr, result.as.error); + } + result.as.success.line.num = *lineNum; + result.as.success.line.content = malloc(strlen(currentLine->str) + 1); + if (result.as.success.line.content == NULL) { + return Error(Nothing, charptr, "Couldn't allocate memory to store line information in token (in identifyAndAdd() function)"); + } + strcpy(result.as.success.line.content, currentLine->str); + + if (result.as.success.type == STT_KW_GROUND) { + ResultType(Nothing, charptr) res = handleGround(lexer, &result.as.success, lineNum, currentLine, currentChr, skipDelimiter); + if (res.error) return res; + } + + addTokenToSolsTokens(&lexer->output, result.as.success); + DESTROY_ESTR((*buf)); + *buf = CREATE_ESTR(""); + return Success(Nothing, charptr, {}); +} + + +ResultType(SolsLexer, charptr) createLexer(char* input) { + + // Copy input into the new lexer struct + char* inputcopy = malloc(strlen(input) + 1); + if (inputcopy == NULL) { + return Error(SolsLexer, charptr, "Couldn't copy string into lexer (in createLexer() function)"); + } + strcpy(inputcopy, input); + + // Create SolsTokens + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + Estr e = CREATE_ESTR(tokens.as.error); + APPEND_ESTR(e, " (in createLexer() function)"); + return Error(SolsLexer, charptr, e.str); + } + + // Construct and return lexer + SolsLexer lexer = { + .input = inputcopy, + .inputsize = strlen(inputcopy), + .output = tokens.as.success, + .current = 0, + }; + return Success(SolsLexer, charptr, lexer); +} + +ResultType(char, Nothing) lexerPeek(SolsLexer* lexer, size_t ahead) { + + // Reduce by 1 so peeking at the next token with 1 works + ahead--; + + // Bounds and null checking + if (lexer->input == NULL) { + return Error(char, Nothing, {}); + } + if (lexer->current + ahead >= lexer->inputsize) { + return Error(char, Nothing, {}); + } + + // Char is within bounds, return it + return Success(char, Nothing, lexer->input[lexer->current + ahead]); +} + + +ResultType(char, Nothing) lexerConsume(SolsLexer* lexer) { + + // Bounds and null checking + if (lexer->input == NULL) { + return Error(char, Nothing, {}); + } + if (lexer->current + 1 > lexer->inputsize) { + return Error(char, Nothing, {}); + } + + // Char is within bounds, return and increment + return Success(char, Nothing, lexer->input[lexer->current++]); +} + +ResultType(SolsToken, charptr) identifyToken(const char* token) { + // Process strings + if (token[0] == '"') { + if (token[strlen(token) - 1] == '"') { + // Cut out the quotes + char* tokencopy = malloc(strlen(token) + 1); + strncpy(tokencopy, token + 1, strlen(token) - 2); + tokencopy[strlen(token) - 2] = '\0'; + + // Create a literal + ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_STRING, tokencopy); + // Free our copy of the string, createSolsLiteral creates a copy + free(tokencopy); + if (literal.error) { + Estr str = CREATE_ESTR(literal.as.error); + APPEND_ESTR(str, " (in identifyToken() function)"); + return Error(SolsToken, charptr, str.str); + } + + // Construct and return the token + SolsToken tok = { + .type = STT_LITERAL, + .as.literal = literal.as.success + }; + return Success(SolsToken, charptr, tok); + } + return Error(SolsToken, charptr, "Unterminated string (in identifyToken() function)"); + } + + // Process characters + if (token[0] == '\'') { + if (strlen(token) != 3) { + return Error(SolsToken, charptr, "Characters can only hold one character at a time (try using \"this\" for strings?)"); + } + if (token[2] == '\'') { + ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_CHAR, token[1]); + if (literal.error) { + Estr str = CREATE_ESTR(literal.as.error); + APPEND_ESTR(str, " (in identifyToken() function)"); + return Error(SolsToken, charptr, str.str); + } + SolsToken tok = { + .type = STT_LITERAL, + .as.literal = literal.as.success + }; + return Success(SolsToken, charptr, tok); + } else { + return Error(SolsToken, charptr, "Unterminated character (in identifyToken() function)"); + } + } + + // Process integers and floats + if (isdigit(token[0]) || (token[0] == '-' && strlen(token) > 1 && (isdigit(token[1]) || token[1] == '.'))) { + size_t len = strlen(token); + bool isInt = true; + bool isDouble = false; + for (size_t i = 1; i < len; i++) { + if (isInt && token[i] == '.') { + isInt = false; + isDouble = true; + continue; + } + if (!isdigit(token[i])) { + isInt = false; + isDouble = false; + } + } + if (isInt) { + int64_t newInt = atoll(token); + ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_INT, newInt); + if (literal.error) { + Estr str = CREATE_ESTR(literal.as.error); + APPEND_ESTR(str, " (in identifyToken() function)"); + return Error(SolsToken, charptr, str.str); + } + SolsToken tok = { + .type = STT_LITERAL, + .as.literal = literal.as.success + }; + return Success(SolsToken, charptr, tok); + } + + if (isDouble) { + double newDouble = atof(token); + ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_DOUBLE, newDouble); + if (literal.error) { + Estr str = CREATE_ESTR(literal.as.error); + APPEND_ESTR(str, " (in identifyToken() function)"); + return Error(SolsToken, charptr, str.str); + } + SolsToken tok = { + .type = STT_LITERAL, + .as.literal = literal.as.success + }; + return Success(SolsToken, charptr, tok); + } + } + + // Handle boolean (true/false) + if (strcmp(token, "true") == 0) { + ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_BOOL, true); + if (literal.error) { + Estr str = CREATE_ESTR(literal.as.error); + APPEND_ESTR(str, " (in identifyToken() function)"); + return Error(SolsToken, charptr, str.str); + } + SolsToken tok = { + .type = STT_LITERAL, + .as.literal = literal.as.success + }; + return Success(SolsToken, charptr, tok); + } + if (strcmp(token, "false") == 0) { + ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_BOOL, false); + if (literal.error) { + Estr str = CREATE_ESTR(literal.as.error); + APPEND_ESTR(str, " (in identifyToken() function)"); + return Error(SolsToken, charptr, str.str); + } + SolsToken tok = { + .type = STT_LITERAL, + .as.literal = literal.as.success + }; + return Success(SolsToken, charptr, tok); + } + + // Process base types + if (strcmp(token, "int") == 0) { + ResultType(SolsType, charptr) type = createSolsType(STT_INT); + if (type.error) { + Estr e = CREATE_ESTR(type.as.error); + APPEND_ESTR(e, " (in identifyToken() function)"); + return Error(SolsToken, charptr, e.str); + } + SolsToken tok = { + .type = STT_TYPE, + .as.type = type.as.success + }; + return Success(SolsToken, charptr, tok); + } + + if (strcmp(token, "double") == 0) { + ResultType(SolsType, charptr) type = createSolsType(STT_DOUBLE); + if (type.error) { + Estr e = CREATE_ESTR(type.as.error); + APPEND_ESTR(e, " (in identifyToken() function)"); + return Error(SolsToken, charptr, e.str); + } + SolsToken tok = { + .type = STT_TYPE, + .as.type = type.as.success + }; + return Success(SolsToken, charptr, tok); + } + + if (strcmp(token, "string") == 0) { + ResultType(SolsType, charptr) type = createSolsType(STT_STRING); + if (type.error) { + Estr e = CREATE_ESTR(type.as.error); + APPEND_ESTR(e, " (in identifyToken() function)"); + return Error(SolsToken, charptr, e.str); + } + SolsToken tok = { + .type = STT_TYPE, + .as.type = type.as.success + }; + return Success(SolsToken, charptr, tok); + } + + if (strcmp(token, "char") == 0) { + ResultType(SolsType, charptr) type = createSolsType(STT_CHAR); + if (type.error) { + Estr e = CREATE_ESTR(type.as.error); + APPEND_ESTR(e, " (in identifyToken() function)"); + return Error(SolsToken, charptr, e.str); + } + SolsToken tok = { + .type = STT_TYPE, + .as.type = type.as.success + }; + return Success(SolsToken, charptr, tok); + } + + if (strcmp(token, "bool") == 0) { + ResultType(SolsType, charptr) type = createSolsType(STT_BOOL); + if (type.error) { + Estr e = CREATE_ESTR(type.as.error); + APPEND_ESTR(e, " (in identifyToken() function)"); + return Error(SolsToken, charptr, e.str); + } + SolsToken tok = { + .type = STT_TYPE, + .as.type = type.as.success + }; + return Success(SolsToken, charptr, tok); + } + + // Find if it's a reserved keyword/operator + ResultType(SolsTokenType, Nothing) result = getTokenType(token); + if (!result.error) { + return Success(SolsToken, charptr, {result.as.success}); + } + + // No appropriate token found, it's an identifier (I hope) + SolsToken id = { + .type = STT_IDENTIFIER, + .as.idName = malloc(strlen(token) + 1) + }; + + if (id.as.idName == NULL) { + return Error(SolsToken, charptr, "Couldn't allocate memory to copy string (in identifyToken() function)"); + } + strcpy(id.as.idName, token); + + return Success(SolsToken, charptr, id); + +} + +char* createLexingError(size_t lineNum, char* line, char* why) { + Estr error = CREATE_ESTR(ESC_RESET ESC_BOLD ESC_RED_FG "Lexing Error " ESC_RESET ESC_YELLOW_FG "on line "); + char buf[256]; + snprintf(buf, sizeof(buf), "%zu", lineNum); + APPEND_ESTR(error, buf); + APPEND_ESTR(error, ":\n\n" ESC_RESET ESC_BLUE_FG " "); + APPEND_ESTR(error, line); + APPEND_ESTR(error, "\n\n"); + APPEND_ESTR(error, ESC_RESET ESC_MAGENTA_FG "-> "); + APPEND_ESTR(error, why); + APPEND_ESTR(error, "\n"); + return error.str; +} + +ResultType(Nothing, charptr) lex(SolsLexer* lexer) { + if (lexer->input == NULL) { + return Error(Nothing, charptr, "Lexer is not initialised"); + } + + Estr buf = CREATE_ESTR(""); + bool inString = false; + + size_t lineNum = 1; + size_t lineStart = 0; + Estr currentLine = CREATE_ESTR(""); + + for (; lineStart < lexer->inputsize; lineStart++) { + if (lexer->input[lineStart] == '\n') { + break; + } + char tmp[] = {lexer->input[lineStart], '\0'}; + APPEND_ESTR(currentLine, tmp); + } + + bool skipDelimiter = false; + for (;;) { + ResultType(char, Nothing) chr = lexerConsume(lexer); + + if (chr.error) { + break; + } + + skipDelimiter = false; + + if (chr.as.success == '/' && !inString) { + ResultType(char, Nothing) peek = lexerPeek(lexer, 1); + if (!peek.error && peek.as.success == '/') { + // Consume characters until \n or EOF + while (true) { + ResultType(char, Nothing) next = lexerPeek(lexer, 1); + if (next.error || next.as.success == '\n') break; + lexerConsume(lexer); + } + continue; + } else if (!peek.error && peek.as.success == '*') { + // Skip the * + lexerConsume(lexer); + // Consume characters until */ or EOF + while (true) { + ResultType(char, Nothing) next = lexerConsume(lexer); + if (next.error) break; + if (next.as.success == '\n') { + lineNum++; + DESTROY_ESTR(currentLine); + currentLine = CREATE_ESTR(""); + lineStart = lexer->current; + for (size_t i = lineStart; i < lexer->inputsize; i++) { + if (lexer->input[i] == '\n') break; + char tmp[] = {lexer->input[i], '\0'}; + APPEND_ESTR(currentLine, tmp); + } + } + if (next.as.success == '*') { + ResultType(char, Nothing) peek2 = lexerPeek(lexer, 1); + if (!peek2.error && peek2.as.success == '/') { + lexerConsume(lexer); // skip / + break; + } + } + } + continue; + } + } + if (chr.as.success == '#' && !inString) { + while (true) { + ResultType(char, Nothing) next = lexerPeek(lexer, 1); + if (next.error || next.as.success == '\n') break; + lexerConsume(lexer); + } + continue; + } + + if (chr.as.success == '\n') { + lineNum++; + DESTROY_ESTR(currentLine); + currentLine = CREATE_ESTR(""); + lineStart = lexer->current; + for (size_t i = lineStart; i < lexer->inputsize; i++) { + if (lexer->input[i] == '\n') { + break; + } + char buf_tmp[] = {lexer->input[i], '\0'}; + APPEND_ESTR(currentLine, buf_tmp); + } + } + + if (inString) { + char str[2] = { chr.as.success, '\0' }; + APPEND_ESTR(buf, str); + if (chr.as.success == '"') { + inString = false; + } + continue; + } + + switch (chr.as.success) { + case '"': { + inString = true; + APPEND_ESTR(buf, "\""); + break; + } + + // These characters require themselves added seperately from the previous token. + case '{': + case '}': + case '(': + case ')': + case ',': + case ':': + case ';': + case '\n': + { + ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter); + if (res.error) { + char* err = createLexingError(lineNum, currentLine.str, res.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + + if (skipDelimiter) break; + + char tmp[] = {chr.as.success, '\0'}; + ResultType(SolsToken, charptr) result = identifyToken(tmp); + if (result.error) { + char* err = createLexingError(lineNum, currentLine.str, result.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + result.as.success.line.num = lineNum; + result.as.success.line.content = malloc(strlen(currentLine.str) + 1); + if (result.as.success.line.content == NULL) { + char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + strcpy(result.as.success.line.content, currentLine.str); + + addTokenToSolsTokens(&lexer->output, result.as.success); + break; + } + + // These characters may be repeated, or followed by an equals sign. + case '+': + case '-': { + ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter); + if (res.error) { + char* err = createLexingError(lineNum, currentLine.str, res.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + // skipDelimiter is unlikely here but handled just in case + if (skipDelimiter) break; + + ResultType(char, Nothing) next = lexerPeek(lexer, 1); + if (next.error || (next.as.success != chr.as.success && next.as.success != '=')) { + char tmp[] = {chr.as.success, '\0'}; + ResultType(SolsToken, charptr) result = identifyToken(tmp); + if (result.error) { + char* err = createLexingError(lineNum, currentLine.str, result.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + result.as.success.line.num = lineNum; + result.as.success.line.content = malloc(strlen(currentLine.str) + 1); + if (result.as.success.line.content == NULL) { + char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + strcpy(result.as.success.line.content, currentLine.str); + addTokenToSolsTokens(&lexer->output, result.as.success); + } + if (next.as.success == '=') { + char tmp[] = {chr.as.success, '=', '\0'}; + ResultType(SolsToken, charptr) result = identifyToken(tmp); + if (result.error) { + char* err = createLexingError(lineNum, currentLine.str, result.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + result.as.success.line.num = lineNum; + result.as.success.line.content = malloc(strlen(currentLine.str) + 1); + if (result.as.success.line.content == NULL) { + char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + strcpy(result.as.success.line.content, currentLine.str); + addTokenToSolsTokens(&lexer->output, result.as.success); + lexerConsume(lexer); + } + if (next.as.success == chr.as.success) { + char tmp[] = {chr.as.success, chr.as.success, '\0'}; + ResultType(SolsToken, charptr) result = identifyToken(tmp); + if (result.error) { + char* err = createLexingError(lineNum, currentLine.str, result.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + result.as.success.line.num = lineNum; + result.as.success.line.content = malloc(strlen(currentLine.str) + 1); + if (result.as.success.line.content == NULL) { + char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + strcpy(result.as.success.line.content, currentLine.str); + addTokenToSolsTokens(&lexer->output, result.as.success); + lexerConsume(lexer); + } + break; + } + + // These characters may be followed by an equals sign, or nothing else. + case '=': + case '!': + case '>': + case '<': + case '*': + case '/': { + ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter); + if (res.error) { + char* err = createLexingError(lineNum, currentLine.str, res.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + if (skipDelimiter) break; + + ResultType(char, Nothing) next = lexerPeek(lexer, 1); + if (next.error || next.as.success != '=') { + char tmp[] = {chr.as.success, '\0'}; + ResultType(SolsToken, charptr) result = identifyToken(tmp); + if (result.error) { + char* err = createLexingError(lineNum, currentLine.str, result.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + result.as.success.line.num = lineNum; + result.as.success.line.content = malloc(strlen(currentLine.str) + 1); + if (result.as.success.line.content == NULL) { + char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + strcpy(result.as.success.line.content, currentLine.str); + addTokenToSolsTokens(&lexer->output, result.as.success); + } + if (next.as.success == '=') { + char tmp[] = {chr.as.success, '=', '\0'}; + ResultType(SolsToken, charptr) result = identifyToken(tmp); + if (result.error) { + char* err = createLexingError(lineNum, currentLine.str, result.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + result.as.success.line.num = lineNum; + result.as.success.line.content = malloc(strlen(currentLine.str) + 1); + if (result.as.success.line.content == NULL) { + char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + strcpy(result.as.success.line.content, currentLine.str); + addTokenToSolsTokens(&lexer->output, result.as.success); + lexerConsume(lexer); + } + break; + } + + + // '.' requires checking whether it's a number or an identifier after + case '.': { + ResultType(char, Nothing) peek = lexerPeek(lexer, 1); + // If the next character is a digit, then this is a literal, not a member access dot. + if (!peek.error && isdigit(peek.as.success)) { + APPEND_ESTR(buf, "."); + } else { + ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter); + if (res.error) { + char* err = createLexingError(lineNum, currentLine.str, res.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + if (!skipDelimiter) { + addTokenToSolsTokens(&lexer->output, (SolsToken) {.type = STT_DOT}); + } + } + break; + } + + // This whitespace splits the program and does not get appended as it's own token. + case '\t': + case ' ': { + ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter); + if (res.error) { + char* err = createLexingError(lineNum, currentLine.str, res.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + break; + } + default: { + char newchar[] = {chr.as.success, '\0'}; + APPEND_ESTR(buf, newchar); + break; + } + } + + // Check whether we need to parse types + if (strcmp(buf.str, "fun") == 0) { + if (!lexerPeek(lexer, 1).error && lexerPeek(lexer, 1).as.success == '(') { + // do stuff + } + } + + if (strcmp(buf.str, "template") == 0 ) { + if (!lexerPeek(lexer, 1).error && lexerPeek(lexer, 1).as.success == '(') { + + } + } + + if (strcmp(buf.str, "object") == 0 ) { + if (!lexerPeek(lexer, 1).error && lexerPeek(lexer, 1).as.success == '(') { + + } + } + } + + ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, '\0', &skipDelimiter); + if (res.error) { + char* err = createLexingError(lineNum, currentLine.str, res.as.error); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + + if (inString) { + char* err = createLexingError(lineNum, currentLine.str, "Unterminated string"); + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Error(Nothing, charptr, err); + } + + DESTROY_ESTR(buf); + DESTROY_ESTR(currentLine); + return Success(Nothing, charptr, (Nothing){}); +} + +ResultType(Nothing, charptr) processTypeSignature(SolsLexer* lexer) { + return Error(Nothing, charptr, "WIP (in processTypeSignature() function)"); +} diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h new file mode 100644 index 0000000..6efa42b --- /dev/null +++ b/src/lexer/lexer.h @@ -0,0 +1,76 @@ +#ifndef LEXER_H +#define LEXER_H + +#include +#include +#include + +#include "../include/error.h" +#include "../include/nothing.h" + +#include "SolsType.h" +#include "SolsToken.h" +#include "SolsLiteral.h" + +// A map containing all corresponding strs and token types. +// Use the getTokenType() function to search this +extern struct _SolsTokenTypeMap {char* str; SolsTokenType type;} SolsTokenTypeMap[]; + +// Represents the current state of the lexer. +// .input is the Solstice program as written by the user. +// .output is the lexed Solstice program, which is constructed by the lex() function. +// .current represents the current character from .input being lexed. +typedef struct SolsLexer { + char* input; + size_t inputsize; + SolsTokens output; + size_t current; +} SolsLexer; + +Result(SolsLexer, charptr); + +// Creates a lexer for use by the lex() function. +// Returns: +// Success: Constructed SolsLexer +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsLexer, charptr) createLexer(char* input); + +// Uses the provided lexer to scan the code, and create tokens. +// Returne: +// Success: Nothing +// Failure: char* detailing what went wrong (usually user failure or memory failure) +ResultType(Nothing, charptr) lex(SolsLexer* lexer); + +Result(char, Nothing); + +// Peeks at the next token in the lexer. +// Returns: +// Success: The token with offset ahead +// Failure: Nothing (requested character is out of bounds) +ResultType(char, Nothing) lexerPeek(SolsLexer* lexer, size_t ahead); + +// Consumes the next token in the lexer. +// Success: The token that has just been consumed +// Failure: Nothing (requested character is out of bounds) +ResultType(char, Nothing) lexerConsume(SolsLexer* lexer); + +// Helper function to classify tokens +// Returns: +// Success: A SolsToken which has all information needed from the token. +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsToken, charptr) identifyToken(const char* token); + +Result(SolsTokenType, Nothing); + +// Helper function to convert a char* into a SolsTokenType using the SolsTokenTypeMap. +// Returns: +// Success: The corresponding SolsTokenType +// Failure: Nothing (meaning the token is likely an identifier) +ResultType(SolsTokenType, Nothing) getTokenType(const char* input); + +// Helper function to lex type signatures into tokens +// FIXME this function is a work in progress +ResultType(Nothing, charptr) processTypeSignature(SolsLexer* lexer); + + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2f6cdde --- /dev/null +++ b/src/main.c @@ -0,0 +1,117 @@ +#include "lexer/SolsType.h" +#include "lexer/lexer.h" +#include "parser/parser.h" +#include "codegen/codegen.h" + +#include +#include + +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); + return NULL; + } + + if (1!=fread(file, lSize, 1, fp)) { + fclose(fp); + free(file); + return NULL; + } + + // we done + fclose(fp); + + return file; +} + +int main(int argc, char** argv) { + if (argc < 2) { + printf("Usage: %s [file]\n", argv[0]); + exit(1); + } + + char* fileContents = NULL; + bool printProgram = false; + + if (strcmp(argv[1], "-p") == 0) { + if (argc < 3) { + printf("Usage: %s -p [file]\n", argv[0]); + exit(1); + } + printProgram = true; + fileContents = getFileContents(argv[2]); + } else { + fileContents = getFileContents(argv[1]); + } + + if (fileContents == NULL) { + printf("Couldn't read that file :(\n"); + exit(1); + } + + // Lex file + ResultType(SolsLexer, charptr) lexer = createLexer(fileContents); + if (lexer.error) { + printf("Error while creating lexer: %s", lexer.as.error); + exit(1); + } + ResultType(Nothing, charptr) lexed = lex(&lexer.as.success); + if (lexed.error) { + printf("%s\n", lexed.as.error); + exit(1); + } + + // Parse file + ResultType(SolsParser, charptr) parser = createSolsParser(&lexer.as.success.output); + if (parser.error) { + printf("Error while creating parser: %s\n", parser.as.error); + exit(1); + } + ResultType(Nothing, charptr) parsed = parse(&parser.as.success); + if (parsed.error) { + printf("%s\n", parsed.as.error); + exit(1); + } + + SolsScope scope = { + .variables = NULL, + .tmpCounter = 0, + .returnType = createSolsType(STT_INT).as.success + }; + + // Do codegen on root node + ResultType(GroundProgram, charptr) codegen = generateCode(&parser.as.success.output, &scope); + if (codegen.error) { + printf("%s\n", codegen.as.error); + exit(1); + } + + if (printProgram) { + groundPrintProgram(&codegen.as.success); + exit(0); + } + + // Run program on GroundVM + GroundValue retval = groundRunProgram(&codegen.as.success); + if (retval.type == INT) { + return retval.data.intVal; + } else { + return 0; + } +} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 2631dca..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lexer.h" -#include "parser.h" -#include "argparser.h" -#include "error.h" - -#define parseOneToken(token) Parser({token.value()}).parse().children[0] - -namespace Solstice { - - int tmpIdIterator = 0; - int labelIterator = 0; - - -} // namespace Solstice - - -int main(int argc, char** argv) { - auto args = ArgParser::parseArgs(argc, argv); - std::ifstream file(args.file->value); - std::ostringstream ss; - ss << file.rdbuf(); - auto lexed = Solstice::Lexer(ss.str()).lex(); - auto parsed = Solstice::Parser::Parser(lexed).parse(); - GroundProgram program = Solstice::Parser::assembleProgram(parsed); - Solstice::Error::summariseErrors(); - if (args.output.has_value()) { - if (!args.outputtype.has_value() || args.outputtype.value().value == "ground") { - std::FILE* originalStdout = stdout; - std::FILE* tmp = std::tmpfile(); - if (!tmp) { - std::cout << "Failed to create tmp file\n"; - exit(1); - } - stdout = tmp; - groundPrintProgram(&program); - std::fflush(tmp); - std::fseek(tmp, 0, SEEK_END); - long size = std::ftell(tmp); - std::fseek(tmp, 0, SEEK_SET); - - std::string result(size, '\0'); - std::fread(&result[0], 1, size, tmp); - - stdout = originalStdout; - std::fclose(tmp); - - std::ofstream outputFile(args.output.value().value); - if (!outputFile) { - std::cout << "Failed to write to " << args.output.value().value << "\n"; - } - outputFile << "#!/usr/bin/env ground\n" << result; - outputFile.close(); - system(std::string("chmod +x " + args.output.value().value).c_str()); - exit(0); - } else if (args.outputtype.value().value == "native") { - std::string assembly(groundCompileProgram(&program)); - std::string folder = "." + args.output.value().value + "_solsbuild"; - std::filesystem::create_directory(folder); - std::ofstream asmfile(folder + "/program.s"); - if (asmfile) { - asmfile << assembly; - asmfile.close(); - } else { - std::cout << "Failed to create temporary file for assembly\n"; - exit(1); - } - std::string command = "nasm -f elf64 -o " + folder + "/program.o " + folder + "/program.s"; - std::system(command.c_str()); - command = "ld -o " + args.output.value().value + " " + folder + "/program.o"; - std::system(command.c_str()); - exit(0); - } - } else { - GroundValue gv = groundRunProgram(&program); - if (gv.type == INT) { - return gv.data.intVal; - } else { - return 0; - } - } -} diff --git a/src/parser.cpp b/src/parser.cpp deleted file mode 100644 index 3318f16..0000000 --- a/src/parser.cpp +++ /dev/null @@ -1,1850 +0,0 @@ -#include "parser.h" -#include "error.h" -#include "lexer.h" -#include "argparser.h" -#include -#include -#include -#include -#include -#include - -#define parseOneToken(token) Parser({token}).parse().children[0] - -namespace Solstice { - namespace Parser { - - bool operator==(const SolStruct& left, const SolStruct& right) { - return left.signature == right.signature; - } - - SolStruct::SolStruct(SolNode in) { - signature = "template("; - genSignature = "object("; - bool first = true; - for (auto& node : in.children) { - if (!first) { - signature += ", "; - genSignature += ", "; - } else { - first = false; - } - if (node.nodeType == SolNodeType::FunctionDef) { - // FIXME do this later - } - if (node.nodeType == SolNodeType::Identifier) { - if (node.children[0].nodeType == SolNodeType::Value) { - signature += node.children[0].data.getTypeString(); - genSignature += node.children[0].data.getTypeString(); - } else if (node.children[0].nodeType == SolNodeType::Identifier) { - if (variables.find(node.children[0].outputId) != variables.end()) { - signature += variables[node.children[0].outputId]; - genSignature += variables[node.children[0].outputId]; - } else { - return; - } - } else { - return; - } - signature += " " + node.outputId; - genSignature += " " + node.outputId; - } - } - signature += ")"; - genSignature += ")"; - } - - char* copyString(std::string s) { - char* str = (char*) malloc(s.size() + 1); - strcpy(str, s.c_str()); - return str; - } - - bool isTemp(std::string id) { - return id.rfind("tmp_", 0) == 0; - } - - // Contains the type information for all variables. - // The first string is the variable name, the second is the type. - std::map variables; - - // Contains type information for all templates. - std::map templates; - - std::string checkNodeReturnType(SolNode i) { - switch (i.nodeType) { - case SolNodeType::In: - case SolNodeType::Identifier: { - if (variables.find(i.outputId) != variables.end()) { - return variables[i.outputId]; - } else { - Error::syntaxError("Unknown variable " + i.outputId, i.line, i.lineContent); - } - break; - } - case SolNodeType::Value: { - return i.data.getTypeString(); - } - case SolNodeType::Equal: - case SolNodeType::Inequal: - case SolNodeType::Greater: - case SolNodeType::Lesser: - case SolNodeType::EqGreater: - case SolNodeType::EqLesser: - return "bool"; - break; - case SolNodeType::Add: - { - if (checkNodeReturnType(i.children[0]) == "string" && checkNodeReturnType(i.children[1]) == "string") { - return "string"; - } - } - case SolNodeType::Subtract: - case SolNodeType::Multiply: - case SolNodeType::Divide: - { - if (checkNodeReturnType(i.children[0]) == "double") { - return "double"; - } - if (checkNodeReturnType(i.children[1]) == "double") { - return "double"; - } - return "int"; - break; - } - case SolNodeType::FunctionCall: { - std::string funcName = i.children[0].outputId; - if (variables.find(funcName) == variables.end()) { - Error::syntaxError("Unknown function " + funcName, i.line, i.lineContent); - } - std::string signature = variables[funcName]; - - // Parse signature: fun(arg1, arg2) retType - if (signature.rfind("fun(", 0) != 0) { - // Not a function signature, maybe it's a variable being called? - Error::typingError(funcName + " is not a function", i.line, i.lineContent); - } - - size_t endParen = signature.find(')'); - if (endParen == std::string::npos) { - Error::typingError("Invalid function signature for " + funcName, i.line, i.lineContent); - } - - std::string argsStr = signature.substr(4, endParen - 4); - std::string retType = signature.substr(endParen + 2); // skip ") " - - std::vector expectedArgs; - if (!argsStr.empty()) { - size_t start = 0; - size_t end = argsStr.find(", "); - while (end != std::string::npos) { - expectedArgs.push_back(argsStr.substr(start, end - start)); - start = end + 2; - end = argsStr.find(", ", start); - } - expectedArgs.push_back(argsStr.substr(start)); - } - - // Check arguments - // children[0] is function name, children[1..] are args - if (i.children.size() - 1 != expectedArgs.size()) { - Error::typingError("Expected " + std::to_string(expectedArgs.size()) + " arguments for " + funcName + ", got " + std::to_string(i.children.size() - 1), i.line, i.lineContent); - return "none"; - } - - for (size_t k = 0; k < expectedArgs.size(); ++k) { - std::string argType = checkNodeReturnType(i.children[k + 1]); - if (argType != expectedArgs[k]) { - Error::typingError("Expected argument " + std::to_string(k + 1) + " of " + funcName + " to be " + expectedArgs[k] + ", got " + argType, i.children[k + 1].line, i.children[k + 1].lineContent); - } - } - - return retType; - break; - } - case SolNodeType::New: { - if (templates.find(i.children[0].outputId) == templates.end()) { - Error::typingError("Cannot find template " + i.children[0].outputId, i.line, i.lineContent); - } - return templates[i.children[0].outputId].genSignature; - } - case SolNodeType::Puts: - case SolNodeType::If: - case SolNodeType::While: - case SolNodeType::CodeBlock: - case SolNodeType::Set: - case SolNodeType::Root: - case SolNodeType::None: - case SolNodeType::FunctionDef: - return "none"; - break; - } - return "none"; - } - - // SolData Implementation - SolData::SolData(int64_t in) : data(in), type(SolDataType::Int) {} - SolData::SolData(double in) : data(in), type(SolDataType::Double) {} - SolData::SolData(std::string in) : data(in), type(SolDataType::String) {} - SolData::SolData(char in) : data(in), type(SolDataType::Char) {} - SolData::SolData(bool in) : data(in), type(SolDataType::Bool) {} - SolData::SolData(SolFunction in) : data(in), type(SolDataType::Function) {} - - std::optional SolData::getInt() { - if (type == SolDataType::Int) { - return std::get(data); - } else { - return {}; - } - } - std::optional SolData::getDouble() { - if (type == SolDataType::Double) { - return std::get(data); - } else { - return {}; - } - } - std::optional SolData::getString() { - if (type == SolDataType::String) { - return std::get(data); - } else { - return {}; - } - } - std::optional SolData::getChar() { - if (type == SolDataType::Char) { - return std::get(data); - } else { - return {}; - } - } - std::optional SolData::getBool() { - if (type == SolDataType::Bool) { - return std::get(data); - } else { - return {}; - } - } - std::optional SolData::getFunction() { - if (type == SolDataType::Function) { - return std::get(data); - } else { - return {}; - } - } - - std::string SolData::getTypeString() { - switch (type) { - case SolDataType::Int: return "int"; - case SolDataType::Double: return "double"; - case SolDataType::String: return "string"; - case SolDataType::Char: return "char"; - case SolDataType::Bool: return "bool"; - case SolDataType::Function: return "function"; - case SolDataType::None: return "none"; - } - return "none"; - } - - // SolNode Implementation - SolNode::SolNode(SolNodeType nodeType) : nodeType(nodeType) {} - SolNode::SolNode(SolNodeType nodeType, SolData data) : data(data), nodeType(nodeType) {} - - void SolNode::addNode(SolNode in) { - children.push_back(in); - } - void SolNode::setValue(SolData in) { - data = in; - } - - std::string currentFunctionRetType = ""; - - const std::vector SolNode::generateCode() { - std::vector code; - // Process object member access - for (size_t i = 0; i < children.size(); i++) { - SolNode& child = children[i]; - if (child.nodeType == SolNodeType::In) { - if (nodeType == SolNodeType::Set && i == 0) continue; - if (variables.find(child.outputId) == variables.end()) { - Error::syntaxError(child.outputId + " could not be found", child.line, child.lineContent); - } - std::string type = variables[child.outputId]; - if (type.substr(0, 7) != "object(") { - Error::typingError(child.outputId + " is not an object", child.line, child.lineContent); - } - std::regex pattern("([a-zA-Z0-9_:<>*&]+)\\s+\\b" + child.children[0].outputId + "\\b"); - std::smatch match; - - if (std::regex_search(type, match, pattern)) { - if (i + 1 < children.size() && children[i + 1].nodeType == SolNodeType::Set) { - // handle stuff in the equals - } else { - SolGroundCodeBlock codeBlock; - GroundInstruction gi = groundCreateInstruction(GETFIELD); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(child.outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(child.children[0].outputId))); - child.outputId = "obj_access_" + std::to_string(tmpIdIterator++); - variables[child.outputId] = match[1]; - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(child.outputId))); - codeBlock.code.push_back(gi); - code.push_back(codeBlock); - } - } else { - Error::typingError("Couldn't find field " + child.children[0].outputId + " in " + child.outputId, child.line, child.lineContent); - } - } - } - - // Process other stuff - if (nodeType != SolNodeType::If && nodeType != SolNodeType::While && nodeType != SolNodeType::Struct) for (auto& child : children) { - auto childCode = child.generateCode(); - code.insert(code.end(), childCode.begin(), childCode.end()); - } - switch (nodeType) { - case SolNodeType::Value: { - outputId = "tmp_" + std::to_string(tmpIdIterator++); - SolGroundCodeBlock codeBlock; - GroundInstruction gi = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - switch (data.type) { - case SolDataType::Int: { - auto dataopt = data.getInt(); - if (dataopt) { - groundAddValueToInstruction(&gi, groundCreateValue(INT, dataopt.value())); - } - break; - } - case SolDataType::Double: { - auto dataopt = data.getDouble(); - if (dataopt) { - groundAddValueToInstruction(&gi, groundCreateValue(DOUBLE, dataopt.value())); - } - break; - } - case SolDataType::String: { - auto dataopt = data.getString(); - if (dataopt) { - char* str = (char*) malloc(dataopt.value().size() + 1); - strcpy(str, dataopt.value().c_str()); - groundAddValueToInstruction(&gi, groundCreateValue(STRING, str)); - } - break; - } - case SolDataType::Char: { - auto dataopt = data.getChar(); - if (dataopt) { - groundAddValueToInstruction(&gi, groundCreateValue(CHAR, dataopt.value())); - } - break; - } - case SolDataType::Bool: { - auto dataopt = data.getBool(); - if (dataopt) { - groundAddValueToInstruction(&gi, groundCreateValue(BOOL, dataopt.value())); - } - break; - } - } - codeBlock.code.push_back(gi); - code.push_back(codeBlock); - break; - } - case SolNodeType::Add: { - ensure3(children[0], "int", "double", "string", "operator '+'"); - ensure3(children[1], "int", "double", "string", "operator '+'"); - ensuresame(children[0], children[1], "operator '+'"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(ADD); - if (children.size() < 2) { - Error::typingError("Need more stuff to add", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Subtract: { - ensure2(children[0], "int", "double", "operator '-'"); - ensure2(children[1], "int", "double", "operator '-'"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(SUBTRACT); - if (children.size() < 2) { - Error::typingError("Need more stuff to subtract", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Multiply: { - ensure2(children[0], "int", "double", "operator '*'"); - ensure2(children[1], "int", "double", "operator '*'"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(MULTIPLY); - if (children.size() < 2) { - Error::typingError("Need more stuff to multiply", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Divide: { - ensure2(children[0], "int", "double", "operator '/'"); - ensure2(children[1], "int", "double", "operator '/'"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(DIVIDE); - if (children.size() < 2) { - Error::typingError("Need more stuff to divide", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Equal: { - ensuresame(children[0], children[1], "operator '=='"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(EQUAL); - if (children.size() < 2) { - Error::typingError("Need more stuff to equal", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Inequal: { - ensuresame(children[0], children[1], "operator '!='"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(INEQUAL); - if (children.size() < 2) { - Error::typingError("Need more stuff to inequal", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Greater: { - ensuresame(children[0], children[1], "operator '>'"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(GREATER); - if (children.size() < 2) { - Error::typingError("Need more stuff to greater", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Lesser: { - ensuresame(children[0], children[1], "operator '<'"); - SolGroundCodeBlock codeBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - GroundInstruction gi = groundCreateInstruction(LESSER); - if (children.size() < 2) { - Error::typingError("Need more stuff to lesser", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::EqGreater: { - ensuresame(children[0], children[1], "operator '>='"); - SolGroundCodeBlock codeBlock; - if (children.size() < 2) { - Error::typingError("Need more stuff to inequal", children[0].line, children[0].lineContent); - } - outputId = "tmp_" + std::to_string(tmpIdIterator++); - std::string trueLabelIdString = "internal_true" + std::to_string(labelIterator++); - std::string falseLabelIdString = "internal_false" + std::to_string(labelIterator); - std::string endLabelIdString = "internal_end" + std::to_string(labelIterator); - char* trueLabelId = (char*) malloc(sizeof(char) * (trueLabelIdString.size() + 1)); - strcpy(trueLabelId, trueLabelIdString.data()); - char* falseLabelId = (char*) malloc(sizeof(char) * (falseLabelIdString.size() + 1)); - strcpy(falseLabelId, falseLabelIdString.data()); - char* endLabelId = (char*) malloc(sizeof(char) * (endLabelIdString.size() + 1)); - strcpy(endLabelId, endLabelIdString.data()); - // greater $tmp_0 $tmp_1 &cond - GroundInstruction gi = groundCreateInstruction(GREATER); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, (char*) "internal_cond0")); - codeBlock.code.push_back(gi); - // if &cond %true - gi = groundCreateInstruction(IF); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, (char*) "internal_cond0")); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, trueLabelId)); - codeBlock.code.push_back(gi); - // equal $tmp_0 $tmp_1 &cond - gi = groundCreateInstruction(EQUAL); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, (char*) "internal_cond1")); - codeBlock.code.push_back(gi); - // if $cond %true - gi = groundCreateInstruction(IF); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, (char*) "internal_cond1")); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, trueLabelId)); - codeBlock.code.push_back(gi); - // jump %false - gi = groundCreateInstruction(JUMP); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, falseLabelId)); - codeBlock.code.push_back(gi); - // @true - gi = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi, groundCreateReference(LABEL, trueLabelId)); - codeBlock.code.push_back(gi); - // set &tmp_x true - gi = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - groundAddValueToInstruction(&gi, groundCreateValue(BOOL, true)); - codeBlock.code.push_back(gi); - // jump %end - gi = groundCreateInstruction(JUMP); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, endLabelId)); - codeBlock.code.push_back(gi); - // @false - gi = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi, groundCreateReference(LABEL, falseLabelId)); - codeBlock.code.push_back(gi); - // set &tmp_x false - gi = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - groundAddValueToInstruction(&gi, groundCreateValue(BOOL, false)); - codeBlock.code.push_back(gi); - // @end - gi = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi, groundCreateReference(LABEL, endLabelId)); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::EqLesser: { - ensuresame(children[0], children[1], "operator '<='"); - SolGroundCodeBlock codeBlock; - if (children.size() < 2) { - Error::typingError("Need more stuff to inequal", children[0].line, children[0].lineContent); - } - outputId = "tmp_" + std::to_string(tmpIdIterator++); - std::string trueLabelIdString = "internal_true" + std::to_string(labelIterator++); - std::string falseLabelIdString = "internal_false" + std::to_string(labelIterator); - std::string endLabelIdString = "internal_end" + std::to_string(labelIterator); - char* trueLabelId = (char*) malloc(sizeof(char) * (trueLabelIdString.size() + 1)); - strcpy(trueLabelId, trueLabelIdString.data()); - char* falseLabelId = (char*) malloc(sizeof(char) * (falseLabelIdString.size() + 1)); - strcpy(falseLabelId, falseLabelIdString.data()); - char* endLabelId = (char*) malloc(sizeof(char) * (endLabelIdString.size() + 1)); - strcpy(endLabelId, endLabelIdString.data()); - // lesser $tmp_0 $tmp_1 &cond - GroundInstruction gi = groundCreateInstruction(LESSER); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, (char*) "internal_cond0")); - codeBlock.code.push_back(gi); - // if &cond %true - gi = groundCreateInstruction(IF); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, (char*) "internal_cond0")); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, trueLabelId)); - codeBlock.code.push_back(gi); - // equal $tmp_0 $tmp_1 &cond - gi = groundCreateInstruction(EQUAL); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[1].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, (char*) "internal_cond1")); - codeBlock.code.push_back(gi); - // if $cond %true - gi = groundCreateInstruction(IF); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, (char*) "internal_cond1")); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, trueLabelId)); - codeBlock.code.push_back(gi); - // jump %false - gi = groundCreateInstruction(JUMP); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, falseLabelId)); - codeBlock.code.push_back(gi); - // @true - gi = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi, groundCreateReference(LABEL, trueLabelId)); - codeBlock.code.push_back(gi); - // set &tmp_x true - gi = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - groundAddValueToInstruction(&gi, groundCreateValue(BOOL, true)); - codeBlock.code.push_back(gi); - // jump %end - gi = groundCreateInstruction(JUMP); - groundAddReferenceToInstruction(&gi, groundCreateReference(LINEREF, endLabelId)); - codeBlock.code.push_back(gi); - // @false - gi = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi, groundCreateReference(LABEL, falseLabelId)); - codeBlock.code.push_back(gi); - // set &tmp_x false - gi = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - groundAddValueToInstruction(&gi, groundCreateValue(BOOL, false)); - codeBlock.code.push_back(gi); - // @end - gi = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi, groundCreateReference(LABEL, endLabelId)); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::Puts: { - SolGroundCodeBlock codeBlock; - exists(children[0]); - GroundInstruction gi = groundCreateInstruction(PRINTLN); - if (children.size() < 1) { - Error::typingError("Need more stuff to puts", children[0].line, children[0].lineContent); - } - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - codeBlock.code.push_back(gi); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - code.push_back(codeBlock); - break; - } - case SolNodeType::If: { - ensure(children[0], "bool", "if condition"); - auto conditionCode = children[0].generateCode(); - code.insert(code.end(), conditionCode.begin(), conditionCode.end()); - outputId = "tmp_" + std::to_string(tmpIdIterator++); - SolGroundCodeBlock codeBlock; - codeBlock.toBeDropped.push_back(outputId); - if (isTemp(children[0].outputId)) codeBlock.toBeDropped.push_back(children[0].outputId); - GroundInstruction gi = groundCreateInstruction(NOT); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(gi); - std::string labelIdString = "if_" + std::to_string(labelIterator++); - char* labelId = (char*) malloc(sizeof(char) * labelIdString.size() + 1); - strcpy(labelId, labelIdString.data()); - GroundInstruction gi2 = groundCreateInstruction(IF); - groundAddReferenceToInstruction(&gi2, groundCreateReference(VALREF, copyString(outputId))); - groundAddReferenceToInstruction(&gi2, groundCreateReference(LINEREF, labelId)); - codeBlock.code.push_back(gi2); - code.push_back(codeBlock); - for (size_t i = 1; i < children.size(); i++) { - auto childCode = children[i].generateCode(); - code.insert(code.end(), childCode.begin(), childCode.end()); - } - codeBlock.code.clear(); - codeBlock.toBeDropped.clear(); - GroundInstruction gi3 = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi3, groundCreateReference(LABEL, labelId)); - codeBlock.code.push_back(gi3); - code.push_back(codeBlock); - break; - } - case SolNodeType::While: { - ensure(children[0], "bool", "while condition"); - SolGroundCodeBlock startLabelBlock; - std::string startLabelIdString = "whilestart_" + std::to_string(labelIterator++); - std::string endLabelIdString = "whileend_" + std::to_string(labelIterator); - char* startLabelId = (char*) malloc(sizeof(char) * (startLabelIdString.size() + 1)); - strcpy(startLabelId, startLabelIdString.data()); - char* endLabelId = (char*) malloc(sizeof(char) * (endLabelIdString.size() + 1)); - strcpy(endLabelId, endLabelIdString.data()); - - GroundInstruction startLabel = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&startLabel, groundCreateReference(LABEL, startLabelId)); - startLabelBlock.code.push_back(startLabel); - code.push_back(startLabelBlock); - - auto conditionCode = children[0].generateCode(); - code.insert(code.end(), conditionCode.begin(), conditionCode.end()); - - SolGroundCodeBlock checkBlock; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - checkBlock.toBeDropped.push_back(outputId); - if (isTemp(children[0].outputId)) checkBlock.toBeDropped.push_back(children[0].outputId); - GroundInstruction gi = groundCreateInstruction(NOT); - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - checkBlock.code.push_back(gi); - GroundInstruction gi2 = groundCreateInstruction(IF); - groundAddReferenceToInstruction(&gi2, groundCreateReference(VALREF, copyString(outputId))); - groundAddReferenceToInstruction(&gi2, groundCreateReference(LINEREF, endLabelId)); - checkBlock.code.push_back(gi2); - code.push_back(checkBlock); - - for (size_t i = 1; i < children.size(); i++) { - auto childCode = children[i].generateCode(); - code.insert(code.end(), childCode.begin(), childCode.end()); - } - - SolGroundCodeBlock endBlock; - GroundInstruction gi3 = groundCreateInstruction(JUMP); - groundAddReferenceToInstruction(&gi3, groundCreateReference(LINEREF, startLabelId)); - endBlock.code.push_back(gi3); - GroundInstruction gi4 = groundCreateInstruction(CREATELABEL); - groundAddReferenceToInstruction(&gi4, groundCreateReference(LABEL, endLabelId)); - endBlock.code.push_back(gi4); - code.push_back(endBlock); - break; - } - case SolNodeType::Identifier: { - break; - } - case SolNodeType::Set: { - if (children[0].nodeType == SolNodeType::In) { - if (variables.find(children[0].outputId) == variables.end()) { - Error::syntaxError(children[0].outputId + " could not be found", children[0].line, children[0].lineContent); - } - std::string type = variables[children[0].outputId]; - if (type.substr(0, 7) != "object(") { - Error::typingError(children[0].outputId + " is not an object", children[0].line, children[0].lineContent); - } - std::regex pattern("([a-zA-Z0-9_:<>*&]+)\\s+\\b" + children[0].children[0].outputId + "\\b"); - std::smatch match; - - if (!std::regex_search(type, match, pattern)) { - Error::typingError("Couldn't find field " + children[0].children[0].outputId + " in " + children[0].outputId, children[0].line, children[0].lineContent); - } - std::string fieldType = match[1]; - if (fieldType != checkNodeReturnType(children[1])) { - Error::typingError("Cannot assign type " + checkNodeReturnType(children[1]) + " to field of type " + fieldType, children[0].line, children[0].lineContent); - } - - exists(children[1]); - SolGroundCodeBlock codeBlock; - GroundInstruction setInstruction = groundCreateInstruction(SETFIELD); - groundAddReferenceToInstruction(&setInstruction, groundCreateReference(DIRREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&setInstruction, groundCreateReference(DIRREF, copyString(children[0].children[0].outputId))); - groundAddReferenceToInstruction(&setInstruction, groundCreateReference(VALREF, copyString(children[1].outputId))); - codeBlock.code.push_back(setInstruction); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - } else { - if (variables.find(children[0].outputId) != variables.end()) { - if (variables[children[0].outputId] != checkNodeReturnType(children[1])) { - Error::typingError("Cannot change type of this variable", children[0].line, children[0].lineContent); - } - } - exists(children[1]); - SolGroundCodeBlock codeBlock; - GroundInstruction setInstruction = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&setInstruction, groundCreateReference(DIRREF, copyString(children[0].outputId))); - groundAddReferenceToInstruction(&setInstruction, groundCreateReference(VALREF, copyString(children[1].outputId))); - codeBlock.code.push_back(setInstruction); - if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId); - code.push_back(codeBlock); - // Make sure we know what the variable type is - variables[children[0].outputId] = checkNodeReturnType(children[1]); - } - break; - } - case SolNodeType::FunctionCall: { - checkNodeReturnType(*this); - exists(children[0]); - std::string fnToCall = children[0].outputId; - outputId = "tmp_" + std::to_string(tmpIdIterator++); - SolGroundCodeBlock codeBlock; - GroundInstruction callInstruction = groundCreateInstruction(CALL); - groundAddReferenceToInstruction(&callInstruction, groundCreateReference(FNREF, copyString(children[0].outputId))); - for (int i = 1; i < children.size(); i++) { - groundAddReferenceToInstruction(&callInstruction, groundCreateReference(VALREF, copyString(children[i].outputId))); - } - groundAddReferenceToInstruction(&callInstruction, groundCreateReference(DIRREF, copyString(outputId))); - codeBlock.code.push_back(callInstruction); - code.push_back(codeBlock); - break; - } - case SolNodeType::Return: { - std::string retType = checkNodeReturnType(children[0]); - if (currentFunctionRetType != "" && retType != currentFunctionRetType) { - Error::typingError("Expected return type " + currentFunctionRetType + " but got " + retType, children[0].line, children[0].lineContent); - } - SolGroundCodeBlock codeBlock; - GroundInstruction returnInstruction = groundCreateInstruction(RETURN); - groundAddReferenceToInstruction(&returnInstruction, groundCreateReference(VALREF, copyString(children[0].outputId))); - codeBlock.code.push_back(returnInstruction); - code.push_back(codeBlock); - break; - } - case SolNodeType::FunctionDef: { - auto functionopt = data.getFunction(); - if (functionopt.has_value()) { - SolFunction function = functionopt.value(); - SolGroundCodeBlock codeBlock; - GroundInstruction inst = groundCreateInstruction(FUN); - char* fnName = (char*) malloc(sizeof(char) * (function.name.size() + 1)); - strcpy(fnName, function.name.c_str()); - char* retType = (char*) malloc(sizeof(char) * (function.returnType.size() + 1)); - strcpy(retType, function.returnType.c_str()); - groundAddReferenceToInstruction(&inst, groundCreateReference(FNREF, fnName)); - groundAddReferenceToInstruction(&inst, groundCreateReference(TYPEREF, retType)); - - // Scope Management - auto variablebackup = variables; - std::string oldRetType = currentFunctionRetType; - - variables.clear(); - currentFunctionRetType = function.returnType; - - for (auto& pair : function.parameters) { - groundAddReferenceToInstruction(&inst, groundCreateReference(TYPEREF, copyString(pair.second))); - groundAddReferenceToInstruction(&inst, groundCreateReference(DIRREF, copyString(pair.first))); - variables[pair.first] = pair.second; // Correctly map Name -> Type - } - - codeBlock.code.push_back(inst); - code.push_back(codeBlock); - codeBlock.code.clear(); - - // Generate body code with local variables visible - auto childCode = function.content->generateCode(); - code.insert(code.end(), childCode.begin(), childCode.end()); - - codeBlock.code.push_back(groundCreateInstruction(ENDFUN)); - code.push_back(codeBlock); - - // Restore Scope - variables = variablebackup; - currentFunctionRetType = oldRetType; - } - break; - } - case SolNodeType::InlineGround: { - GroundProgram program = groundParseFile(ground.c_str()); - SolGroundCodeBlock codeBlock; - for (size_t i = 0; i < program.size; i++) { - codeBlock.code.push_back(program.instructions[i]); - } - code.push_back(codeBlock); - break; - } - case SolNodeType::Struct: { - //SolGroundCodeBlock preProcessCodeBlock; - SolGroundCodeBlock codeBlock; - GroundInstruction gi = groundCreateInstruction(STRUCT); - - SolStruct newStruct(*this); - - variables[outputId] = newStruct.signature; - templates[outputId] = newStruct; - - // struct -name - groundAddReferenceToInstruction(&gi, groundCreateReference(TYPEREF, copyString(outputId))); - codeBlock.code.push_back(gi); - - // contents of struct - for (SolNode& child : children) { - if (child.nodeType == SolNodeType::Identifier) { - gi = groundCreateInstruction(SET); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(child.outputId))); - - SolNode& valNode = child.children[0]; - if (valNode.nodeType == SolNodeType::Value) { - GroundValue gv; - switch (valNode.data.type) { - case SolDataType::String: gv = groundCreateValue(STRING, copyString(valNode.data.getString().value())); break; - case SolDataType::Int: gv = groundCreateValue(INT, valNode.data.getInt().value()); break; - case SolDataType::Double: gv = groundCreateValue(DOUBLE, valNode.data.getDouble().value()); break; - case SolDataType::Char: gv = groundCreateValue(CHAR, valNode.data.getChar().value()); break; - case SolDataType::Bool: gv = groundCreateValue(BOOL, valNode.data.getBool().value()); break; - default: Error::syntaxError("Struct member must be assigned a value or identifier", valNode.line, valNode.lineContent); break; - } - groundAddValueToInstruction(&gi, gv); - } else if (child.nodeType == SolNodeType::Identifier) { - groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, copyString(valNode.outputId))); - } else { - Error::syntaxError("Struct member must be assigned a value or identifier", valNode.line, valNode.lineContent); - } - codeBlock.code.push_back(gi); - } - if (child.nodeType == SolNodeType::FunctionDef) { - auto fnBlock = child.generateCode(); - for (const auto& code : fnBlock) { - codeBlock.code.insert(codeBlock.code.end(), code.code.begin(), code.code.end()); - } - } - } - codeBlock.code.push_back(groundCreateInstruction(ENDSTRUCT)); - code.push_back(codeBlock); - break; - } - case SolNodeType::New: { - SolGroundCodeBlock codeBlock; - GroundInstruction gi = groundCreateInstruction(INIT); - if (templates.find(children[0].outputId) == templates.end()) { - Error::typingError("Unknown template " + children[0].outputId, line, lineContent); - } - outputId = "tmp_" + std::to_string(tmpIdIterator); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, copyString(outputId))); - groundAddReferenceToInstruction(&gi, groundCreateReference(TYPEREF, copyString(children[0].outputId))); - codeBlock.code.push_back(gi); - code.push_back(codeBlock); - break; - } - case SolNodeType::Use: { - SolGroundCodeBlock codeBlock; - char* solsticeLibsEnv = getenv("SOLSTICE_LIBS"); - std::string filePath; - if (solsticeLibsEnv == NULL) { - filePath = "/usr/lib/solstice/" + outputId + ".sols"; - } else { - filePath = std::string(solsticeLibsEnv) + "/" + outputId + ".sols"; - } - - std::ifstream file(filePath); - - if (file.good()) { - std::ostringstream ss; - ss << file.rdbuf(); - SolNode fileNode = Parser(Lexer(ss.str()).lex()).parse(); - fileNode.nodeType = SolNodeType::None; - auto fileCode = fileNode.generateCode(); - code.insert(code.end(), fileCode.begin(), fileCode.end()); - } else { - file.close(); - Error::syntaxError("Library " + outputId + " does not exist"); - } - break; - } - default: {} - } - return code; - } - - // Parser Implementation - Parser::Parser(std::vector in) : tokensToParse(in) {} - - std::optional Parser::peek(int ahead) { - if (current + ahead < size) { - return tokensToParse[current + ahead]; - } else { - return {}; - } - } - - std::optional Parser::consume() { - if (current < size) { - return tokensToParse[current++]; - } else { - return {}; - } - } - - // Helper functions - bool Parser::isInt(std::string in) { - for (const char& c : in) { - if (!std::isdigit(c)) { - return false; - } - } - return true; - } - bool Parser::isDouble(std::string in) { - if (in.empty()) return false; - - bool foundDot = false; - bool foundDigit = false; - - for (const char& c : in) { - if (std::isdigit(c)) { - foundDigit = true; - } else if (c == '.' && !foundDot) { - foundDot = true; - } else { - return false; - } - } - - return foundDot && foundDigit; - } - bool Parser::isString(std::string in) { - if (in.size() > 1 && in[0] == '"' && in.back() == '"') { - return true; - } - return false; - } - bool Parser::isChar(std::string in) { - if (in.size() == 3 && in[0] == '\'' && in.back() == '\'') { - return true; - } - return false; - } - bool Parser::isBool(std::string in) { - if (in == "true" || in == "false") { - return true; - } - return false; - } - - SolDataType Parser::getDataType(std::string in) { - if (isInt(in)) { - return SolDataType::Int; - } - if (isDouble(in)) { - return SolDataType::Double; - } - if (isString(in)) { - return SolDataType::String; - } - if (isChar(in)) { - return SolDataType::Char; - } - if (isBool(in)) { - return SolDataType::Bool; - } - return SolDataType::None; - } - - SolNodeType Parser::getNodeType(std::string in) { - if (getDataType(in) != SolDataType::None) { - return SolNodeType::Value; - } - if (in == "+") { - return SolNodeType::Add; - } - if (in == "-") { - return SolNodeType::Subtract; - } - if (in == "*") { - return SolNodeType::Multiply; - } - if (in == "/") { - return SolNodeType::Divide; - } - if (in == "=" || in == ":") { - return SolNodeType::Set; - } - if (in == "==") { - return SolNodeType::Equal; - } - if (in == "!=") { - return SolNodeType::Inequal; - } - if (in == ">=") { - return SolNodeType::EqGreater; - } - if (in == "<=") { - return SolNodeType::EqLesser; - } - if (in == ">") { - return SolNodeType::Greater; - } - if (in == "<") { - return SolNodeType::Lesser; - } - if (in == "puts") { - return SolNodeType::Puts; - } - if (in == "if") { - return SolNodeType::If; - } - if (in == "while") { - return SolNodeType::While; - } - if (in == "def") { - return SolNodeType::FunctionDef; - } - if (in == "return") { - return SolNodeType::Return; - } - if (in == "ground") { - return SolNodeType::InlineGround; - } - if (in == "struct") { - return SolNodeType::Struct; - } - if (in == "new") { - return SolNodeType::New; - } - if (in == "use") { - return SolNodeType::Use; - } - if (in == "{") { - return SolNodeType::CodeBlockStart; - } - if (in == "}") { - return SolNodeType::CodeBlockEnd; - } - if (in == "(") { - return SolNodeType::BracketStart; - } - if (in == ")") { - return SolNodeType::BracketEnd; - } - if (in == ".") { - return SolNodeType::In; - } - return SolNodeType::Identifier; - } - - int Parser::getPrecedence(std::string token) { - if (token == "*" || token == "/") return 2; - if (token == "+" || token == "-") return 1; - return 0; - } - - SolNode Parser::parsePrimary() { - auto tokenopt = consume(); - if (!tokenopt) { - Error::syntaxError("Unexpected end of input"); - } - Token tokenObj = tokenopt.value(); - std::string token = tokenObj.value; - SolNodeType type = getNodeType(token); - - if (type == SolNodeType::Value) { - SolNode node(SolNodeType::Value); - node.line = tokenObj.line; - node.lineContent = tokenObj.lineContent; - switch (getDataType(token)) { - case SolDataType::Int: node.setValue((int64_t) std::stoll(token)); break; - case SolDataType::Double: node.setValue(std::stod(token)); break; - case SolDataType::String: node.setValue(token.substr(1, token.size() - 2)); break; - case SolDataType::Char: node.setValue(token[1]); break; - case SolDataType::Bool: node.setValue(token == "true"); break; - default: break; - } - return node; - } - - if (type == SolNodeType::Identifier) { - SolNode idNode(SolNodeType::Identifier); - idNode.outputId = token; - idNode.line = tokenObj.line; - idNode.lineContent = tokenObj.lineContent; - - auto peekOpt = peek(); - if (peekOpt.has_value() && peekOpt.value().value == "(") { - consume(); // ( - SolNode fnCallNode(SolNodeType::FunctionCall); - fnCallNode.line = tokenObj.line; - fnCallNode.lineContent = tokenObj.lineContent; - fnCallNode.addNode(idNode); - - std::vector tokens; - size_t brackets = 1; - while (auto t = consume()) { - if (t.value().value == "(") brackets++; - if (t.value().value == ")") brackets--; - if (brackets == 0) break; - tokens.push_back(t.value()); - } - if (brackets != 0) { - Error::syntaxError("Unclosed function call bracket", tokenObj.line, tokenObj.lineContent); - } - auto args = Parser(tokens).parse(); - for(auto& child : args.children) fnCallNode.addNode(child); - return fnCallNode; - } - return idNode; - } - - if (type == SolNodeType::BracketStart) { - std::vector tokens; - size_t brackets = 1; - while (auto t = consume()) { - if (t.value().value == "(") brackets++; - if (t.value().value == ")") brackets--; - if (brackets == 0) break; - tokens.push_back(t.value()); - } - if (brackets != 0) { - Error::syntaxError("Unclosed bracket", tokenObj.line, tokenObj.lineContent); - } - auto inner = Parser(tokens).parse(); - if (inner.children.size() > 0) return inner.children[0]; - return SolNode(SolNodeType::None); - } - - Error::syntaxError("Unexpected token in expression: " + token, tokenObj.line, tokenObj.lineContent); - return SolNode(SolNodeType::None); - } - - SolNode Parser::parseExpression(int minPrec) { - SolNode lhs = parsePrimary(); - while(true) { - auto opOpt = peek(); - if (!opOpt) break; - Token opToken = opOpt.value(); - std::string op = opToken.value; - int prec = getPrecedence(op); - if (prec < minPrec) break; - - consume(); - SolNode rhs = parseExpression(prec + 1); - SolNode newNode(getNodeType(op)); - newNode.line = opToken.line; - newNode.lineContent = opToken.lineContent; - newNode.addNode(lhs); - newNode.addNode(rhs); - lhs = newNode; - } - return lhs; - } - - SolNode Parser::parse() { - current = 0; - size = tokensToParse.size(); - SolNode rootNode(SolNodeType::Root); - while (auto tokenopt = consume()) { - Token tokenObj = tokenopt.value(); - std::string token = tokenObj.value; - switch (getNodeType(token)) { - case SolNodeType::Value: { - switch (getDataType(token)) { - case SolDataType::Int: { - SolNode intNode(SolNodeType::Value); - intNode.setValue((int64_t) std::stoll(token)); - intNode.line = tokenObj.line; - intNode.lineContent = tokenObj.lineContent; - rootNode.addNode(intNode); - break; - } - case SolDataType::Double: { - SolNode doubleNode(SolNodeType::Value); - doubleNode.setValue(std::stod(token)); - doubleNode.line = tokenObj.line; - doubleNode.lineContent = tokenObj.lineContent; - rootNode.addNode(doubleNode); - break; - } - case SolDataType::String: { - SolNode stringNode(SolNodeType::Value); - stringNode.setValue(token.substr(1, token.size() - 2)); - stringNode.line = tokenObj.line; - stringNode.lineContent = tokenObj.lineContent; - rootNode.addNode(stringNode); - break; - } - case SolDataType::Char: { - SolNode charNode(SolNodeType::Value); - charNode.setValue(token[1]); - charNode.line = tokenObj.line; - charNode.lineContent = tokenObj.lineContent; - rootNode.addNode(charNode); - break; - } - case SolDataType::Bool: { - SolNode boolNode(SolNodeType::Value); - boolNode.setValue(token == "true"); - boolNode.line = tokenObj.line; - boolNode.lineContent = tokenObj.lineContent; - rootNode.addNode(boolNode); - break; - } - } - break; - } - case SolNodeType::Add: - case SolNodeType::Subtract: - case SolNodeType::Multiply: - case SolNodeType::Divide: - { - SolNode opNode(getNodeType(token)); - opNode.line = tokenObj.line; - opNode.lineContent = tokenObj.lineContent; - - if (rootNode.children.empty()) { - Error::syntaxError("Expected operand before operator", tokenObj.line, tokenObj.lineContent); - } - opNode.addNode(rootNode.children.back()); - rootNode.children.pop_back(); - - // Parse RHS with higher precedence - SolNode rhs = parseExpression(getPrecedence(token) + 1); - opNode.addNode(rhs); - - rootNode.addNode(opNode); - break; - } - case SolNodeType::Equal: - case SolNodeType::Inequal: - case SolNodeType::Lesser: - case SolNodeType::Greater: - case SolNodeType::EqLesser: - case SolNodeType::EqGreater: - { - SolNode equalNode(getNodeType(token)); - equalNode.line = tokenObj.line; - equalNode.lineContent = tokenObj.lineContent; - - if (rootNode.children.empty()) { - Error::syntaxError("Expected operand before comparison", tokenObj.line, tokenObj.lineContent); - } - equalNode.addNode(rootNode.children.back()); - rootNode.children.pop_back(); - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "\n") { - break; - } - tokens.push_back(tokenopt.value()); - } - auto children = Parser(tokens).parse(); - equalNode.addNode(children.children[0]); - rootNode.addNode(equalNode); - break; - } - case SolNodeType::Puts: { - SolNode putsNode(SolNodeType::Puts); - putsNode.line = tokenObj.line; - putsNode.lineContent = tokenObj.lineContent; - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "\n") { - break; - } - tokens.push_back(tokenopt.value()); - } - auto children = Parser(tokens).parse(); - for (auto& child : children.children) { - putsNode.addNode(child); - } - rootNode.addNode(putsNode); - break; - } - case SolNodeType::If: { - SolNode ifNode(SolNodeType::If); - ifNode.line = tokenObj.line; - ifNode.lineContent = tokenObj.lineContent; - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "\n") { - break; - } - tokens.push_back(tokenopt.value()); - } - auto children = Parser(tokens).parse(); - ifNode.addNode(children.children[0]); - tokens.clear(); - size_t brackets = 1; - auto tokenopt = consume(); - if (tokenopt) { - if (tokenopt.value().value == "{") { - tokens.push_back(tokenopt.value()); - while (auto tokenopt = consume()) { - tokens.push_back(tokenopt.value()); - if (tokenopt.value().value == "{") { - brackets++; - } - if (tokenopt.value().value == "}") { - brackets--; - } - if (brackets == 0) { - break; - } - } - if (brackets != 0) { - Error::syntaxError("Unclosed code block", tokenopt.value().line, tokenopt.value().lineContent); - } - } else { - Error::syntaxError("I want a code block instead of a " + tokenopt.value().value, tokenopt.value().line, tokenopt.value().lineContent); - } - } else { - Error::syntaxError("Expected code block after if condition", ifNode.line, ifNode.lineContent); - } - auto childCodeBlock = Parser(tokens).parse(); - ifNode.addNode(childCodeBlock.children[0]); - rootNode.addNode(ifNode); - break; - } - case SolNodeType::While: { - SolNode whileNode(SolNodeType::While); - whileNode.line = tokenObj.line; - whileNode.lineContent = tokenObj.lineContent; - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "\n") { - break; - } - tokens.push_back(tokenopt.value()); - } - auto children = Parser(tokens).parse(); - whileNode.addNode(children.children[0]); - tokens.clear(); - size_t brackets = 1; - auto tokenopt = consume(); - if (tokenopt) { - if (tokenopt.value().value == "{") { - tokens.push_back(tokenopt.value()); - while (auto tokenopt = consume()) { - tokens.push_back(tokenopt.value()); - if (tokenopt.value().value == "{") { - brackets++; - } - if (tokenopt.value().value == "}") { - brackets--; - } - if (brackets == 0) { - break; - } - } - if (brackets != 0) { - Error::syntaxError("Unclosed code block", tokenopt.value().line, tokenopt.value().lineContent); - } - } else { - Error::syntaxError("I want a code block instead of a " + tokenopt.value().value, tokenopt.value().line, tokenopt.value().lineContent); - } - } else { - Error::syntaxError("Expected code block after while condition", whileNode.line, whileNode.lineContent); - } - auto childCodeBlock = Parser(tokens).parse(); - whileNode.addNode(childCodeBlock.children[0]); - rootNode.addNode(whileNode); - break; - } - case SolNodeType::CodeBlockStart: { - SolNode codeBlockNode(SolNodeType::CodeBlock); - codeBlockNode.line = tokenObj.line; - codeBlockNode.lineContent = tokenObj.lineContent; - size_t brackets = 1; - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "{") { - brackets++; - } - if (tokenopt.value().value == "}") { - brackets--; - } - if (brackets == 0) { - break; - } - tokens.push_back(tokenopt.value()); - } - if (brackets != 0) { - Error::syntaxError("Unclosed code block", tokenObj.line, tokenObj.lineContent); - } - codeBlockNode.children = Parser(tokens).parse().children; - rootNode.addNode(codeBlockNode); - break; - } - case SolNodeType::Identifier: { - SolNode idNode(SolNodeType::Identifier); - idNode.outputId = token; - idNode.line = tokenObj.line; - idNode.lineContent = tokenObj.lineContent; - rootNode.addNode(idNode); - break; - } - case SolNodeType::Set: { - SolNode setNode(SolNodeType::Set); - setNode.line = tokenObj.line; - setNode.lineContent = tokenObj.lineContent; - - setNode.addNode(rootNode.children.back()); - rootNode.children.pop_back(); - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "\n") { - break; - } - tokens.push_back(tokenopt.value()); - } - auto children = Parser(tokens).parse(); - setNode.addNode(children.children[0]); - rootNode.addNode(setNode); - break; - } - case SolNodeType::BracketStart: { - // function call - if (!rootNode.children.empty() && rootNode.children.back().nodeType == SolNodeType::Identifier) { - SolNode fnCallNode(SolNodeType::FunctionCall); - fnCallNode.line = tokenObj.line; - fnCallNode.lineContent = tokenObj.lineContent; - fnCallNode.addNode(rootNode.children.back()); - rootNode.children.pop_back(); - std::vector tokens; - size_t brackets = 1; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "(") { - brackets++; - } - if (tokenopt.value().value == ")") { - brackets--; - } - // comma, eval statement here - if (tokenopt.value().value == "," && brackets == 1) { - auto node = Parser(tokens).parse(); - if (!node.children.empty()) fnCallNode.children.push_back(node.children[0]); - else { - Error::syntaxError("dingus"); - } - tokens.clear(); - continue; - } - if (brackets < 1) { - break; - } - tokens.push_back(tokenopt.value()); - } - auto node = Parser(tokens).parse(); - fnCallNode.children.insert(fnCallNode.children.end(), node.children.begin(), node.children.end()); - rootNode.addNode(fnCallNode); - } else { - std::vector tokens; - size_t brackets = 1; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "(") { - brackets++; - } - if (tokenopt.value().value == ")") { - brackets--; - } - if (brackets < 1) { - break; - } - tokens.push_back(tokenopt.value()); - } - auto node = Parser(tokens).parse(); - if (!node.children.empty()) { - rootNode.addNode(node.children.back()); - } - } - break; - } - case SolNodeType::FunctionDef: { - SolFunction fn; - auto nameTokenOpt = consume(); - if (!nameTokenOpt || getNodeType(nameTokenOpt.value().value) != SolNodeType::Identifier) { - Error::syntaxError("Expected function name", tokenObj.line, tokenObj.lineContent); - } - fn.name = nameTokenOpt.value().value; - - auto openParen = consume(); - if (!openParen || openParen.value().value != "(") { - Error::syntaxError("Expected '(' after function name", tokenObj.line, tokenObj.lineContent); - } - - std::string signature = "fun("; - - bool first = true; - while (auto next = peek()) { - if (next.value().value == ")") { - consume(); - break; - } - if (!first) { - auto comma = consume(); - if (comma.value().value != ",") { - Error::syntaxError("Expected ',' between arguments", tokenObj.line, tokenObj.lineContent); - } - } - - auto typeToken = consume(); - if (!typeToken) Error::syntaxError("Expected type", tokenObj.line, tokenObj.lineContent); - std::string argType = typeToken.value().value; - - auto argNameToken = consume(); - if (!argNameToken) Error::syntaxError("Expected argument name", tokenObj.line, tokenObj.lineContent); - std::string argName = argNameToken.value().value; - - fn.parameters.push_back({argName, argType}); - - if (!first) signature += ", "; - signature += argType; - first = false; - } - signature += ") "; - - while (peek() && peek().value().value == "\n") consume(); - - auto next = peek(); - if (next && next.value().value != "{") { - auto retTypeToken = consume(); - fn.returnType = retTypeToken.value().value; - } else { - fn.returnType = "void"; - } - signature += fn.returnType; - fn.signature = signature; - - while (peek() && peek().value().value == "\n") consume(); - - auto brace = peek(); - if (!brace || brace.value().value != "{") { - Error::syntaxError("Expected '{' starting function body", tokenObj.line, tokenObj.lineContent); - } - - consume(); - std::vector bodyTokens; - size_t brackets = 1; - while(auto t = consume()) { - if (t.value().value == "{") brackets++; - if (t.value().value == "}") brackets--; - if (brackets == 0) break; - bodyTokens.push_back(t.value()); - } - if (brackets != 0) Error::syntaxError("Unclosed function body", tokenObj.line, tokenObj.lineContent); - - auto parsedBody = Parser(bodyTokens).parse(); - auto bodyNode = std::make_shared(SolNodeType::CodeBlock); - bodyNode->children = parsedBody.children; - - fn.content = bodyNode; - - SolNode fnNode(SolNodeType::FunctionDef); - fnNode.data = SolData(fn); - variables[fn.name] = signature; - - rootNode.addNode(fnNode); - break; - } - case SolNodeType::Return: { - SolNode returnNode(SolNodeType::Return); - returnNode.line = tokenObj.line; - returnNode.lineContent = tokenObj.lineContent; - std::vector tokens; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "\n") { - break; - } - tokens.push_back(tokenopt.value()); - } - auto children = Parser(tokens).parse(); - for (auto& child : children.children) { - returnNode.addNode(child); - } - rootNode.addNode(returnNode); - break; - } - case SolNodeType::InlineGround: { - SolNode groundNode(SolNodeType::InlineGround); - consume(); // some funny token - consume(); // { - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "}") { - break; - } - groundNode.ground += tokenopt.value().value + " "; - } - rootNode.addNode(groundNode); - break; - } - case SolNodeType::Struct: { - SolNode structNode(SolNodeType::Struct); - structNode.line = tokenObj.line; - structNode.lineContent = tokenObj.lineContent; - - auto nameopt = consume(); - if (nameopt) { - structNode.outputId = nameopt.value().value; - } - - while (peek() && peek().value().value == "\n") { - consume(); - } - - auto openBrace = consume(); - if (!openBrace || openBrace.value().value != "{") { - Error::syntaxError("Expected '{' after struct name", tokenObj.line, tokenObj.lineContent); - } - - std::vector tokens; - int count = 1; - while (auto tokenopt = consume()) { - if (tokenopt.value().value == "{") { - count++; - } - if (tokenopt.value().value == "}") { - count--; - } - if (count < 1) { - break; - } - tokens.push_back(tokenopt.value()); - } - - if (count != 0) { - Error::syntaxError("Unclosed struct body", tokenObj.line, tokenObj.lineContent); - } - - for (size_t i = 0; i < tokens.size(); i++) { - Token token = tokens[i]; - - if (token.value == "\n") { - continue; - } - - if (getNodeType(token.value) == SolNodeType::Identifier) { - if (i + 2 >= tokens.size()) { - Error::syntaxError("Expecting a value after the identifier", token.line, token.lineContent); - continue; - } - if (getNodeType(tokens[i + 1].value) != SolNodeType::Set) { - Error::syntaxError("Expected a set keyword ('=' or ':')", token.line, token.lineContent); - continue; - } - if (getNodeType(tokens[i + 2].value) != SolNodeType::Identifier && - getNodeType(tokens[i + 2].value) != SolNodeType::Value) { - Error::syntaxError("Expected a value or identifier for default value", token.line, token.lineContent); - continue; - } - - SolNode idNode(SolNodeType::Identifier); - idNode.outputId = token.value; - idNode.line = token.line; - idNode.lineContent = token.lineContent; - SolNode valueNode = Parser({tokens[i + 2]}).parse().children[0]; - idNode.addNode(valueNode); - structNode.addNode(idNode); - i += 2; - continue; - } - - else if (getNodeType(token.value) == SolNodeType::FunctionDef) { - std::vector functionTokens; - functionTokens.push_back(token); - int funcCount = 0; - i++; - - while (i < tokens.size()) { - functionTokens.push_back(tokens[i]); - - if (tokens[i].value == "{") { - funcCount++; - } - if (tokens[i].value == "}") { - funcCount--; - if (funcCount == 0) { - break; - } - } - i++; - } - - if (funcCount != 0) { - Error::syntaxError("Unclosed function in struct", token.line, token.lineContent); - } - - SolNode functionNode = Parser(functionTokens).parse().children[0]; - structNode.addNode(functionNode); - } else { - Error::syntaxError("Structs only support set statements and function definition", token.line, token.lineContent); - } - } - - rootNode.addNode(structNode); - break; - } - - case SolNodeType::New: { - SolNode newNode(SolNodeType::New); - newNode.line = tokenObj.line; - newNode.lineContent = tokenObj.lineContent; - auto typeopt = consume(); - if (typeopt) { - SolNode typeNode(SolNodeType::Identifier); - typeNode.outputId = typeopt.value().value; - typeNode.line = typeopt.value().line; - typeNode.lineContent = typeopt.value().lineContent; - newNode.addNode(typeNode); - } else { - Error::syntaxError("Expected type name after 'new'", tokenObj.line, tokenObj.lineContent); - } - rootNode.addNode(newNode); - break; - } - case SolNodeType::In: { - SolNode inNode(SolNodeType::In); - inNode.line = tokenObj.line; - inNode.lineContent = tokenObj.lineContent; - inNode.outputId = rootNode.children.back().outputId; - rootNode.children.pop_back(); - auto accessopt = consume(); - if (accessopt) { - SolNode accessNode(SolNodeType::Identifier); - accessNode.outputId = accessopt.value().value; - accessNode.line = accessopt.value().line; - accessNode.lineContent = accessopt.value().lineContent; - inNode.addNode(accessNode); - } else { - Error::syntaxError("Expected identifier after '.'", tokenObj.line, tokenObj.lineContent); - } - rootNode.addNode(inNode); - break; - } - case SolNodeType::Use: { - SolNode useNode(SolNodeType::Use); - useNode.line = tokenObj.line; - useNode.lineContent = tokenObj.lineContent; - auto tokenopt = consume(); - if (tokenopt) { - Token token = tokenopt.value(); - if (getNodeType(token.value) != SolNodeType::Identifier) { - Error::syntaxError("Expected identifier after 'use'", tokenObj.line, tokenObj.lineContent); - } - useNode.outputId = token.value; - rootNode.addNode(useNode); - } else { - Error::syntaxError("Expected identifier after 'use'", tokenObj.line, tokenObj.lineContent); - } - break; - } - } - } - return rootNode; - } - - GroundProgram assembleProgram(SolNode& rootNode) { - GroundProgram gp = groundCreateProgram(); - auto code = rootNode.generateCode(); - for (int i = 0; i < code.size(); i++) { - for (const auto& inst : code[i].code) { - groundAddInstructionToProgram(&gp, inst); - } - for (auto& tmpVar : code[i].toBeDropped) { - GroundInstruction gi = groundCreateInstruction(DROP); - char* droppedVar = (char*) malloc(tmpVar.size() + 1); - strcpy(droppedVar, tmpVar.c_str()); - groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, droppedVar)); - groundAddInstructionToProgram(&gp, gi); - } - } - return gp; - } - - } // namespace Parser - -} diff --git a/src/parser.h b/src/parser.h deleted file mode 100644 index 0d59846..0000000 --- a/src/parser.h +++ /dev/null @@ -1,167 +0,0 @@ -#ifndef PARSER_H -#define PARSER_H - -#include -#include -#include -#include -#include -#include -#include -#include "lexer.h" -#include "error.h" - -#define ensure(node, datatype, op) \ - if (checkNodeReturnType(node) != datatype) { \ - Error::typingError("Expected " + std::string(datatype) + " for " + op, node.line, node.lineContent); \ - } - -#define ensure2(node, datatype1, datatype2, op) { \ - std::string dataType = checkNodeReturnType(node); \ - if (!(dataType == datatype1 || dataType == datatype2)) { \ - Error::typingError("Expected either " + std::string(datatype1) + " or " + std::string(datatype2) + " for " + op, node.line, node.lineContent); \ - } \ - } - -#define ensure3(node, datatype1, datatype2, datatype3, op) { \ - std::string dataType = checkNodeReturnType(node); \ - if (!(dataType == datatype1 || dataType == datatype2 || dataType == datatype3)) { \ - Error::typingError("Expected either " + std::string(datatype1) + ", " + std::string(datatype2) + ", or " + std::string(datatype3) + " for " + op, node.line, node.lineContent); \ - } \ - } - -#define ensuresame(node1, node2, op) { \ - std::string n1t = checkNodeReturnType(node1); \ - std::string n2t = checkNodeReturnType(node2); \ - if (n1t != n2t) { \ - if (!(n1t == "int" && n2t == "double" || n1t == "double" && n2t == "int")) { \ - Error::typingError("Expected types to be the same for " + std::string(op) + " (got types '" + n1t + "' and '" + n2t + "')", node1.line, node1.lineContent); \ - } \ - } \ - } - -#define exists(node) \ - if (node.nodeType == SolNodeType::Identifier) { \ - if (variables.find(node.outputId) == variables.end()) { \ - Error::syntaxError("Variable does not exist", node.line, node.lineContent); \ - } \ - } - -namespace Solstice { - - // External variables referenced in parser logic - extern int tmpIdIterator; - extern int labelIterator; - - namespace Parser { - - - enum class SolNodeType { - Add, Subtract, Multiply, Divide, Equal, Inequal, Greater, Lesser, EqGreater, EqLesser, Set, While, If, Value, Identifier, None, Root, CodeBlock, CodeBlockStart, CodeBlockEnd, FunctionDef, FunctionCall, Expression, BracketStart, BracketEnd, Puts, Return, InlineGround, Struct, New, In, Use - }; - - enum class SolDataType { - Int, String, Double, Bool, Char, Function, None - }; - - extern std::map variables; - - class SolNode; - - class SolGroundCodeBlock { - public: - std::vector code; - std::vector toBeDropped; - SolGroundCodeBlock() = default; - }; - - class SolFunction { - public: - std::string signature; - std::vector> parameters; // name, type - std::string returnType; - std::shared_ptr content; - std::string name; - SolFunction() = default; - }; - - class SolData { - typedef std::variant varData; - varData data; - public: - SolDataType type = SolDataType::Int; - SolData() = default; - SolData(int64_t in); - SolData(double in); - SolData(std::string in); - SolData(char in); - SolData(bool in); - SolData(SolFunction in); - std::optional getInt(); - std::optional getDouble(); - std::optional getString(); - std::optional getChar(); - std::optional getBool(); - std::optional getFunction(); - std::string getTypeString(); - }; - - class SolNode { - public: - SolData data; - SolNodeType nodeType = SolNodeType::None; - std::vector children; - std::string outputId; - int line = 0; - std::string lineContent = ""; - std::string ground = ""; - - SolNode(SolNodeType nodeType); - SolNode(SolNodeType nodeType, SolData data); - SolNode() = default; - void addNode(SolNode in); - void setValue(SolData in); - const std::vector generateCode(); - }; - - class SolStruct { - std::map fields; - public: - std::string signature; // template(...) - std::string genSignature; // object(...) - SolStruct() = default; - friend bool operator==(const SolStruct& left, const SolStruct& right); - SolStruct(SolNode in); - }; - - class Parser { - std::vector tokensToParse; - size_t current; - size_t size; - - std::optional peek(int ahead = 0); - std::optional consume(); - bool isInt(std::string in); - bool isDouble(std::string in); - bool isString(std::string in); - bool isChar(std::string in); - bool isBool(std::string in); - SolDataType getDataType(std::string in); - SolNodeType getNodeType(std::string in); - - SolNode parsePrimary(); - SolNode parseExpression(int minPrec); - int getPrecedence(std::string token); - - public: - Parser(std::vector in); - SolNode parse(); - }; - - GroundProgram assembleProgram(SolNode& rootNode); - std::string checkNodeReturnType(SolNode in); - - } // namespace Parser -} - -#endif diff --git a/src/parser/SolsNode.c b/src/parser/SolsNode.c new file mode 100644 index 0000000..76b7a11 --- /dev/null +++ b/src/parser/SolsNode.c @@ -0,0 +1,61 @@ +#include "SolsNode.h" + +#include +#include + +#include "../include/error.h" +#include "../lexer/SolsLiteral.h" +#include "../lexer/SolsType.h" + +ResultType(SolsNode, charptr) createSolsNode(SolsNodeType type, ...) { + va_list args; + va_start(args, type); + + SolsNode node = { + .type = type, + .children.capacity = 32, + .children.count = 0, + .children.at = malloc(sizeof(SolsNode) * 32) + }; + + if (node.children.at == NULL) { + return Error(SolsNode, charptr, "Failed to allocate memory for children (in createSolsNode() function)"); + } + + switch (type) { + case SNT_LITERAL: { + node.as.literal = va_arg(args, SolsLiteral); + break; + } + case SNT_TYPE: { + node.as.type = va_arg(args, SolsType); + break; + } + case SNT_IDENTIFIER: { + node.as.idName = va_arg(args, char*); + break; + } + case SNT_GROUND: { + node.as.inlineGround = va_arg(args, char*); + break; + } + default: break; + } + + va_end(args); + return Success(SolsNode, charptr, node); +} + +ResultType(Nothing, charptr) addChildToSolsNode(SolsNode* parent, SolsNode child) { + if (parent->children.count + 1 >= parent->children.capacity) { + parent->children.capacity *= 2; + SolsNode* tmp = realloc(parent->children.at, sizeof(SolsNode) * parent->children.capacity); + if (tmp == NULL) { + return Error(Nothing, charptr, "Failed to increase memory for children (in addChildToSolsNode() function)"); + } + parent->children.at = tmp; + } + parent->children.at[parent->children.count] = child; + parent->children.count++; + return Success(Nothing, charptr, {}); +} diff --git a/src/parser/SolsNode.h b/src/parser/SolsNode.h new file mode 100644 index 0000000..b0f8961 --- /dev/null +++ b/src/parser/SolsNode.h @@ -0,0 +1,62 @@ +#ifndef SOLSNODE_H +#define SOLSNODE_H + +#include +#include + +#include "../include/error.h" + +#include "../lexer/SolsType.h" +#include "../lexer/SolsLiteral.h" +#include "../lexer/SolsToken.h" + +typedef enum SolsNodeType { + SNT_IDENTIFIER, SNT_LITERAL, SNT_TYPE, SNT_CODE_BLOCK, SNT_OP_ADD, SNT_OP_SUB, SNT_OP_MUL, SNT_OP_DIV, SNT_OP_ADDTO, SNT_OP_SUBTO, SNT_OP_MULTO, SNT_OP_DIVTO, SNT_OP_INCREMENT, SNT_OP_DECREMENT, SNT_OP_SET, SNT_OP_GREATER, SNT_OP_LESSER, SNT_OP_EQUAL, SNT_OP_INEQUAL, SNT_OP_EQGREATER, SNT_OP_EQLESSER, SNT_DEF, SNT_LAMBDA, SNT_FUNCTION_CALL, SNT_RETURN, SNT_USE, SNT_STRUCT, SNT_PUTS, SNT_IF, SNT_WHILE, SNT_NEW, SNT_GROUND, SNT_ROOT +} SolsNodeType; + +struct SolsNode; + +// Represents an AST node. +// Most node types use the .type and .children fields, however some nodes require storing +// more data, inside the .as union. +// Those tokens are: +// SNT_LITERAL: A literal value. Uses field .as.literal +// SNT_TYPE: A type descriptor. Uses field .as.type +// SNT_IDENTIFIER: An identifier. Uses field .as.idName +// SNT_GROUND: Ground code embedded inside Solstice. Uses field .as.inlineGround +typedef struct SolsNode { + SolsNodeType type; + union { + SolsLiteral literal; + SolsType type; + char* idName; + char* inlineGround; + } as; + struct { + size_t count; + size_t capacity; + struct SolsNode* at; + } children; + LineInfo line; + + // Used by the code generator, unless value is a literal do not use in parser + GroundArg accessArg; +} SolsNode; + +Result(SolsNode, charptr); + +// Creates a SolsNode. If the type passed in is SNT_LITERAL, SNT_TYPE, SNT_IDENTIFIER or +// SNT_KW_GROUND, the function expects another argument, corresponding to the data type +// the token holds. See the SolsNode struct for more information. +// Returns: +// Success: The created SolsNode +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsNode, charptr) createSolsNode(SolsNodeType type, ...); + +// Adds a child to a SolsNode. +// Returns: +// Success: Nothing +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(Nothing, charptr) addChildToSolsNode(SolsNode* parent, SolsNode child); + +#endif diff --git a/src/parser/parser.c b/src/parser/parser.c new file mode 100644 index 0000000..f58210a --- /dev/null +++ b/src/parser/parser.c @@ -0,0 +1,1677 @@ +#include "parser.h" +#include "SolsNode.h" + +#include "../include/estr.h" +#include "../include/ansii.h" +#include + +SolsTokenPrecedence getPrecedence(SolsToken *token) { + switch (token->type) { + case STT_LINE_END: return STP_NEWLINE; + case STT_KW_PUTS: return STP_PUTS; + case STT_KW_IF: return STP_IF; + case STT_KW_WHILE: return STP_WHILE; + case STT_OP_ADDTO: + case STT_OP_SUBTO: + case STT_OP_MULTO: + case STT_OP_DIVTO: + case STT_OP_INCREMENT: + case STT_OP_DECREMENT: + case STT_OP_SET: return STP_SET; + // FIXME figure out how to do functions + case STT_OP_MUL: case STT_OP_DIV: return STP_MUL; + case STT_OP_ADD: case STT_OP_SUB: return STP_ADD; + case STT_OP_EQUAL: + case STT_OP_EQGREATER: + case STT_OP_EQLESSER: + case STT_OP_INEQUAL: + case STT_OP_GREATER: + case STT_OP_LESSER: return STP_COMPARE; + default: return STP_OTHER; + } +} + +ResultType(SolsParser, charptr) createSolsParser(SolsTokens* input) { + ResultType(SolsNode, charptr) node = createSolsNode(SNT_ROOT); + if (node.error) { + Estr str = CREATE_ESTR(node.as.error); + APPEND_ESTR(str, " (in createSolsParser() function)"); + return Error(SolsParser, charptr, str.str); + } + SolsParser parser = { + .input = input, + .current = 0, + .output = node.as.success, + .errors.capacity = 32, + .errors.count = 0, + .errors.at = malloc(sizeof(char*) * 32) + }; + if (parser.errors.at == NULL) { + return Error(SolsParser, charptr, "Couldn't allocate memory to store errors (in createSolsParser() function)"); + } + return Success(SolsParser, charptr, parser); +} + +void addToParserErrors(SolsParser* parser, char* errors) { + if (parser->errors.count + 1 >= parser->errors.capacity) { + parser->errors.capacity *= 2; + char** tmp = realloc(parser->errors.at, sizeof(char*) * parser->errors.capacity); + if (tmp == NULL) { + parser->errors.at[parser->errors.count - 1] = "Failed to allocate more memory for addToParserErrors function"; + return; + } + parser->errors.at = tmp; + } + parser->errors.at[parser->errors.count] = errors; + parser->errors.count++; +} + +void createParserError(SolsParser* parser, char* what) { + + ResultType(SolsToken, Nothing) prevToken = Error(SolsToken, Nothing, {}); + SolsToken token = parserPeek(parser, 0).as.success; + ResultType(SolsToken, Nothing) nextToken = Error(SolsToken, Nothing, {}); + + // Find tokens for previous line + if (parser->current > 1) { + for (size_t i = parser->current - 1; i > 0; i--) { + ResultType(SolsToken, Nothing) check = parserLookAt(parser, i - 1); + if (check.error) break; + if (check.as.success.line.num != token.line.num) { + prevToken = check; + break; + } + } + } + + // Find tokens for next line + for (size_t i = parser->current; i < parser->input->count; i++) { + ResultType(SolsToken, Nothing) check = parserLookAt(parser, i); + if (check.error) break; + if (check.as.success.line.num != token.line.num) { + nextToken = check; + break; + } + } + + if (parser->errors.count + 1 >= parser->errors.capacity) { + parser->errors.capacity *= 2; + char** tmp = realloc(parser->errors.at, sizeof(char*) * parser->errors.capacity); + if (tmp == NULL) { + parser->errors.at[parser->errors.count - 1] = "Failed to allocate more memory for createParserError function"; + return; + } + parser->errors.at = tmp; + } + Estr err = CREATE_ESTR(ESC_BOLD ESC_RED_FG "error: " ESC_RESET ESC_BOLD); + + // Append the main error message and a newline + APPEND_ESTR(err, what); + APPEND_ESTR(err, "\n" ESC_RESET); + + APPEND_ESTR(err, ESC_CYAN_FG "-> " ESC_RESET); + APPEND_ESTR(err, "on line "); + + // Format the line number + char line_buf[16]; + snprintf(line_buf, sizeof(line_buf), "%zu", token.line.num); + APPEND_ESTR(err, line_buf); + + APPEND_ESTR(err, "\n\n"); + // Append line before + if (!prevToken.error) { + APPEND_ESTR(err, prevToken.as.success.line.content); + APPEND_ESTR(err, "\n"); + } + // Append the actual content of the line that failed + APPEND_ESTR(err, token.line.content); + APPEND_ESTR(err, "\n"); + if (!nextToken.error) { + APPEND_ESTR(err, nextToken.as.success.line.content); + APPEND_ESTR(err, "\n"); + } + + // Output the final constructed error + parser->errors.at[parser->errors.count] = err.str; + parser->errors.count++; +} + +static inline ResultType(Nothing, charptr) parseIdentifier(SolsParser* parser) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) { + return Error(Nothing, charptr, "ruh roh"); + } + ResultType(SolsNode, charptr) node = createSolsNode(SNT_IDENTIFIER, peek.as.success.as.idName); + if (node.error) { + Estr err = CREATE_ESTR(node.as.error); + APPEND_ESTR(err, " (in parseLiteral() function)"); + return Error(Nothing, charptr, err.str); + } + node.as.success.line = peek.as.success.line; + node.as.success.accessArg = (GroundArg) { + .type = VALREF, + .value.refName = peek.as.success.as.idName + }; + addChildToSolsNode(parser->currentParent, node.as.success); + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseSet(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting identifier before '='"); + } + if (parser->currentParent->children.at[parser->output.children.count - 1].type != SNT_IDENTIFIER) { + return Error(Nothing, charptr, "Expecting identifier before '='"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the identifier (previous node) + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + size_t bracketCount = 0; + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + + if (peek.as.success.type == STT_OPEN_CURLY) bracketCount++; + else if (peek.as.success.type == STT_CLOSE_CURLY && bracketCount > 0) bracketCount--; + + if (bracketCount == 0 && getPrecedence(&peek.as.success) <= STP_SET) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_SET); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '='"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the set node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseAdd(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '+'"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_ADD) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_ADD); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '+'"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the set node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseSub(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '-'"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_ADD) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_SUB); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '-'"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the set node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseMul(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '*'"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_MUL) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_MUL); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '*'"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the set node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseDiv(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '/'"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_MUL) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_SUB); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '/'"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the set node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseEqual(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '=='"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_COMPARE) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_EQUAL); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '=='"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the equal node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseInequal(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '!='"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_COMPARE) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_INEQUAL); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '!='"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the equal node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseGreater(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '>'"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_COMPARE) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_GREATER); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '>'"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the equal node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseLesser(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '<'"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_COMPARE) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_LESSER); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '<'"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the equal node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseEqGreater(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '>='"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_COMPARE) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_EQGREATER); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '>='"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the equal node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseEqLesser(SolsParser* parser) { + if (parser->currentParent->children.count < 1) { + return Error(Nothing, charptr, "Expecting something before '<='"); + } + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + // Get the previous node + SolsNode idNode = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_COMPARE) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_OP_EQLESSER); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (putsParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting token after '<='"); + } + + // Copy idnode into set node + addChildToSolsNode(&node.as.success, idNode); + + // Copy nodes into the set node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + // Put the equal node where the idNode was + parser->currentParent->children.count--; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseLiteral(SolsParser* parser) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) { + return Error(Nothing, charptr, "ruh roh"); + } + ResultType(SolsNode, charptr) node = createSolsNode(SNT_LITERAL, peek.as.success.as.literal); + if (node.error) { + Estr err = CREATE_ESTR(node.as.error); + APPEND_ESTR(err, " (in parseLiteral() function)"); + return Error(Nothing, charptr, err.str); + } + node.as.success.line = peek.as.success.line; + GroundValue value; + switch (peek.as.success.as.literal.type) { + case SLT_INT: { + value = groundCreateValue(INT, peek.as.success.as.literal.as.intv); + break; + } + case SLT_DOUBLE: { + value = groundCreateValue(DOUBLE, peek.as.success.as.literal.as.doublev); + break; + } + case SLT_STRING: { + value = groundCreateValue(STRING, peek.as.success.as.literal.as.stringv); + break; + } + case SLT_BOOL: { + value = groundCreateValue(BOOL, peek.as.success.as.literal.as.boolv); + break; + } + case SLT_CHAR: { + value = groundCreateValue(CHAR, peek.as.success.as.literal.as.charv); + break; + } + } + node.as.success.accessArg = (GroundArg) { + .type = VALUE, + .value.value = value + }; + addChildToSolsNode(parser->currentParent, node.as.success); + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parsePuts(SolsParser* parser) { + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (getPrecedence(&peek.as.success) <= STP_PUTS) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_PUTS); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) putsParser = createSolsParser(&tokens.as.success); + if (putsParser.error) return Error(Nothing, charptr, putsParser.as.error); + putsParser.as.success.currentParent = &putsParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&putsParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + // Copy nodes into the sols node + for (size_t i = 0; i < putsParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, putsParser.as.success.output.children.at[i]); + } + + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseCodeBlock(SolsParser* parser) { + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + size_t bracketCount = 1; + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) return Error(Nothing, charptr, "Expecting closing curly brace (})"); + if (peek.as.success.type == STT_OPEN_CURLY) { + bracketCount++; + } + if (peek.as.success.type == STT_CLOSE_CURLY) { + bracketCount--; + if (bracketCount < 1) { + parserConsume(parser); + break; + } + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_CODE_BLOCK); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) codeBlockParser = createSolsParser(&tokens.as.success); + if (codeBlockParser.error) { + return Error(Nothing, charptr, codeBlockParser.as.error); + } + codeBlockParser.as.success.currentParent = &codeBlockParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&codeBlockParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + // Copy nodes into the sols node + for (size_t i = 0; i < codeBlockParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, codeBlockParser.as.success.output.children.at[i]); + } + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseWhile(SolsParser* parser) { + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (peek.as.success.type == STT_OPEN_CURLY) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_WHILE); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) whileParser = createSolsParser(&tokens.as.success); + if (whileParser.error) return Error(Nothing, charptr, whileParser.as.error); + whileParser.as.success.currentParent = &whileParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&whileParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + // Copy nodes into the sols node + for (size_t i = 0; i < whileParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, whileParser.as.success.output.children.at[i]); + } + + addChildToSolsNode(parser->currentParent, node.as.success); + + if(parserPeek(parser, 1).as.success.type != STT_OPEN_CURLY) { + return Error(Nothing, charptr, "Expecting opening curly brace for while loop body"); + } + + parserConsume(parser); + + ResultType(Nothing, charptr) res = parseCodeBlock(parser); + if(res.error) return res; + + // Last child of parent is code block, we need to move it + SolsNode codeBlock = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + parser->currentParent->children.count--; + addChildToSolsNode(&node.as.success, codeBlock); + + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseIf(SolsParser* parser) { + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + if (peek.as.success.type == STT_OPEN_CURLY) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_IF); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) ifParser = createSolsParser(&tokens.as.success); + if (ifParser.error) return Error(Nothing, charptr, ifParser.as.error); + ifParser.as.success.currentParent = &ifParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&ifParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + // Copy nodes into the sols node + for (size_t i = 0; i < ifParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, ifParser.as.success.output.children.at[i]); + } + + addChildToSolsNode(parser->currentParent, node.as.success); + + if(parserPeek(parser, 1).as.success.type != STT_OPEN_CURLY) { + return Error(Nothing, charptr, "Expecting opening curly brace for if statement body"); + } + + parserConsume(parser); + + ResultType(Nothing, charptr) res = parseCodeBlock(parser); + if(res.error) return res; + + // Last child of parent is code block, we need to move it + SolsNode codeBlock = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + parser->currentParent->children.count--; + addChildToSolsNode(&node.as.success, codeBlock); + + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseCloseCurly(SolsParser* parser) { + (void)parser; + return Error(Nothing, charptr, "Extra closing curly brace"); +} + +static inline ResultType(Nothing, charptr) parseLambda(SolsParser* parser) { + ResultType(SolsToken, Nothing) openBracket = parserConsume(parser); + if (openBracket.error || openBracket.as.success.type != STT_OPEN_PAREN) { + return Error(Nothing, charptr, "Expecting '(' after 'lambda'"); + } + + ResultType(SolsType, charptr) type = createSolsType(STT_FUN); + if (type.error) { + return Error(Nothing, charptr, type.as.error); + } + + ResultType(SolsNode, charptr) node = createSolsNode(SNT_LAMBDA); + if (node.error) { + return Error(Nothing, charptr, node.as.error); + } + + // Parse type signature + for (;;) { + ResultType(SolsToken, Nothing) next = parserPeek(parser, 1); + if (next.error) { + return Error(Nothing, charptr, "Expecting ')' at end of lambda argument list"); + } + if (next.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); + break; + } + + if (type.error) { + return Error(Nothing, charptr, type.as.error); + } + + // Pattern of type, name, comma + + SolsType tmpType; + + if (next.as.success.type == STT_TYPE) { + tmpType = next.as.success.as.type; + } else if (next.as.success.type == STT_IDENTIFIER) { + tmpType.identifierType = next.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting a type or identifier of type in lambda argument list"); + } + + parserConsume(parser); + + char* argName; + next = parserPeek(parser, 1); + + if (next.as.success.type == STT_IDENTIFIER) { + argName = next.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting identifier after type in lambda argument list"); + } + + // Add type to constructed SolsType + addChildToSolsType(&type.as.success, tmpType, argName); + parserConsume(parser); + + next = parserPeek(parser, 1); + if (next.error) { + return Error(Nothing, charptr, "Expecting a comma or closing bracket"); + } + if (next.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); + break; + } + if (next.as.success.type != STT_COMMA) { + return Error(Nothing, charptr, "Expecting a comma or closing bracket"); + } + parserConsume(parser); + } + + // Parse type at the end + ResultType(SolsToken, Nothing) retType = parserPeek(parser, 1); + if (retType.error) { + return Error(Nothing, charptr, "Expecting return type or identifier of type after lambda argument list"); + } + + if (retType.as.success.type == STT_TYPE) { + type.as.success.returnType = malloc(sizeof(SolsType)); + if (type.as.success.returnType == NULL) { + return Error(Nothing, charptr, "Failed to allocate memory for type"); + } + *type.as.success.returnType = retType.as.success.as.type; + } else if (retType.as.success.type == STT_IDENTIFIER) { + type.as.success.returnType = malloc(sizeof(SolsType)); + if (type.as.success.returnType == NULL) { + return Error(Nothing, charptr, "Failed to allocate memory for type"); + } + type.as.success.returnType->identifierType = retType.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting return type or identifier of type after lambda argument list"); + } + + // Add type to node + node.as.success.as.type = type.as.success; + + parserConsume(parser); // Consumes return type + + // Skip newlines before the opening curly brace + while (parserPeek(parser, 1).as.success.type == STT_LINE_END) { + parserConsume(parser); + } + + ResultType(SolsToken, Nothing) openCurly = parserConsume(parser); + if (openCurly.error || openCurly.as.success.type != STT_OPEN_CURLY) { + return Error(Nothing, charptr, "Expecting opening curly brace ({) for lambda body"); + } + + // Add node to parent + addChildToSolsNode(parser->currentParent, node.as.success); + + // Parse code block + ResultType(Nothing, charptr) res = parseCodeBlock(parser); + if(res.error) return res; + + // Last child of parent is code block, we need to move it + SolsNode codeBlock = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + parser->currentParent->children.count--; + + // We need to get the actual node from the parent to modify it + SolsNode* lambdaNode = &parser->currentParent->children.at[parser->currentParent->children.count - 1]; + addChildToSolsNode(lambdaNode, codeBlock); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseDef(SolsParser* parser) { + // Consume and validate the function name identifier + ResultType(SolsToken, Nothing) nameTok = parserConsume(parser); + if (nameTok.error || nameTok.as.success.type != STT_IDENTIFIER) { + return Error(Nothing, charptr, "Expecting function name after 'def'"); + } + + ResultType(SolsToken, Nothing) openBracket = parserConsume(parser); + if (openBracket.error || openBracket.as.success.type != STT_OPEN_PAREN) { + return Error(Nothing, charptr, "Expecting '(' after function name in 'def'"); + } + + ResultType(SolsType, charptr) type = createSolsType(STT_FUN); + if (type.error) { + return Error(Nothing, charptr, type.as.error); + } + + ResultType(SolsNode, charptr) node = createSolsNode(SNT_DEF); + if (node.error) { + return Error(Nothing, charptr, node.as.error); + } + + // Add function name as the first child node + ResultType(SolsNode, charptr) nameNode = createSolsNode(SNT_IDENTIFIER, nameTok.as.success.as.idName); + if (nameNode.error) { + return Error(Nothing, charptr, nameNode.as.error); + } + nameNode.as.success.line = nameTok.as.success.line; + nameNode.as.success.accessArg = (GroundArg) { + .type = VALREF, + .value.refName = nameTok.as.success.as.idName + }; + addChildToSolsNode(&node.as.success, nameNode.as.success); + + // Parse type signature + for (;;) { + ResultType(SolsToken, Nothing) next = parserPeek(parser, 1); + if (next.error) { + return Error(Nothing, charptr, "Expecting ')' at end of def argument list"); + } + if (next.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); + break; + } + + if (type.error) { + return Error(Nothing, charptr, type.as.error); + } + + // Pattern of type, name, comma + + SolsType tmpType; + + if (next.as.success.type == STT_TYPE) { + tmpType = next.as.success.as.type; + } else if (next.as.success.type == STT_IDENTIFIER) { + tmpType.identifierType = next.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting a type or identifier of type in def argument list"); + } + + parserConsume(parser); + + char* argName; + next = parserPeek(parser, 1); + + if (next.as.success.type == STT_IDENTIFIER) { + argName = next.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting identifier after type in def argument list"); + } + + // Add type to constructed SolsType + addChildToSolsType(&type.as.success, tmpType, argName); + parserConsume(parser); + + next = parserPeek(parser, 1); + if (next.error) { + return Error(Nothing, charptr, "Expecting a comma or closing bracket"); + } + if (next.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); + break; + } + if (next.as.success.type != STT_COMMA) { + return Error(Nothing, charptr, "Expecting a comma or closing bracket"); + } + parserConsume(parser); + } + + // Parse return type after argument list + ResultType(SolsToken, Nothing) retType = parserPeek(parser, 1); + if (retType.error) { + return Error(Nothing, charptr, "Expecting return type or identifier of type after def argument list"); + } + + if (retType.as.success.type == STT_TYPE) { + type.as.success.returnType = malloc(sizeof(SolsType)); + if (type.as.success.returnType == NULL) { + return Error(Nothing, charptr, "Failed to allocate memory for type"); + } + *type.as.success.returnType = retType.as.success.as.type; + } else if (retType.as.success.type == STT_IDENTIFIER) { + type.as.success.returnType = malloc(sizeof(SolsType)); + if (type.as.success.returnType == NULL) { + return Error(Nothing, charptr, "Failed to allocate memory for type"); + } + type.as.success.returnType->identifierType = retType.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting return type or identifier of type after def argument list"); + } + + // Add type to node + node.as.success.as.type = type.as.success; + + parserConsume(parser); // Consumes return type + + // Skip newlines before the opening curly brace + while (parserPeek(parser, 1).as.success.type == STT_LINE_END) { + parserConsume(parser); + } + + ResultType(SolsToken, Nothing) openCurly = parserConsume(parser); + if (openCurly.error || openCurly.as.success.type != STT_OPEN_CURLY) { + return Error(Nothing, charptr, "Expecting opening curly brace ({) for def body"); + } + + // Add node to parent + addChildToSolsNode(parser->currentParent, node.as.success); + + // Parse code block + ResultType(Nothing, charptr) res = parseCodeBlock(parser); + if (res.error) return res; + + // Last child of parent is code block, we need to move it + SolsNode codeBlock = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + parser->currentParent->children.count--; + + // We need to get the actual node from the parent to modify it + SolsNode* defNode = &parser->currentParent->children.at[parser->currentParent->children.count - 1]; + addChildToSolsNode(defNode, codeBlock); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseFunctionCall(SolsParser* parser) { + + ResultType(SolsNode, charptr) node = createSolsNode(SNT_FUNCTION_CALL); + if (node.error) { + return Error(Nothing, charptr, node.as.error); + } + + // The identifier (function name) was already parsed as a node right before '(' + if (parser->currentParent->children.count < 1 || + parser->currentParent->children.at[parser->currentParent->children.count - 1].type != SNT_IDENTIFIER) { + return Error(Nothing, charptr, "Expecting identifier before '(' for function call"); + } + + SolsNode callee = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + parser->currentParent->children.count--; // remove callee identifier node; we'll replace it with the call node + + node.as.success.as.idName = callee.as.idName; + node.as.success.line = callee.line; + + // Empty argument list: foo() + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) { + return Error(Nothing, charptr, "Expecting ')' or a value"); + } + if (peek.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); // consume ')' + addChildToSolsNode(parser->currentParent, node.as.success); + return Success(Nothing, charptr, {}); + } + + // Parse each argument expression separated by commas until ')' + for (;;) { + ResultType(SolsTokens, charptr) resultTokens = createSolsTokens(); + if (resultTokens.error) { + return Error(Nothing, charptr, resultTokens.as.error); + } + SolsTokens tokens = resultTokens.as.success; + + size_t curlys = 0; + size_t parens = 0; + + for (;;) { + peek = parserPeek(parser, 1); + if (peek.error) { + return Error(Nothing, charptr, "Expecting ')'"); + } + + if (peek.as.success.type == STT_OPEN_PAREN) parens++; + if (peek.as.success.type == STT_CLOSE_PAREN) { + if (parens == 0) break; + parens--; + } + + if (peek.as.success.type == STT_OPEN_CURLY) curlys++; + if (peek.as.success.type == STT_CLOSE_CURLY) curlys--; + + if (curlys == 0 && parens == 0 && peek.as.success.type == STT_COMMA) { + break; + } + + addTokenToSolsTokens(&tokens, peek.as.success); + parserConsume(parser); + } + + ResultType(SolsParser, charptr) newParser = createSolsParser(&tokens); + if (newParser.error) { + return Error(Nothing, charptr, newParser.as.error); + } + newParser.as.success.currentParent = &newParser.as.success.output; + + ResultType(Nothing, charptr) parsed = parse(&newParser.as.success); + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + if (newParser.as.success.output.children.count < 1) { + return Error(Nothing, charptr, "Expecting a value before ',' or ')'"); + } + + // One argument expression -> one AST node + addChildToSolsNode(&node.as.success, newParser.as.success.output.children.at[0]); + + // Consume ',' or ')' + peek = parserConsume(parser); + if (peek.error) { + return Error(Nothing, charptr, "Expecting ')' or ','"); + } + if (peek.as.success.type == STT_CLOSE_PAREN) { + break; + } + if (peek.as.success.type != STT_COMMA) { + return Error(Nothing, charptr, "Expecting ',' or ')'"); + } + } + + // Add the fully-built call node once + addChildToSolsNode(parser->currentParent, node.as.success); + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseReturn(SolsParser* parser) { + + // Collect tokens for node + ResultType(SolsTokens, charptr) tokens = createSolsTokens(); + if (tokens.error) { + return Error(Nothing, charptr, tokens.as.error); + } + + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0); + if (peek.error) return Error(Nothing, charptr, "ruh roh"); + + for (;;) { + ResultType(SolsToken, Nothing) peek = parserPeek(parser, 1); + if (peek.error) break; + // Return and puts share the same high precedence + if (getPrecedence(&peek.as.success) <= STP_PUTS) { + break; + } + parserConsume(parser); + addTokenToSolsTokens(&tokens.as.success, peek.as.success); + } + + // Create node + ResultType(SolsNode, charptr) node = createSolsNode(SNT_RETURN); + if (node.error) return Error(Nothing, charptr, node.as.error); + node.as.success.line = peek.as.success.line; + + // Parse selected tokens + ResultType(SolsParser, charptr) returnParser = createSolsParser(&tokens.as.success); + if (returnParser.error) return Error(Nothing, charptr, returnParser.as.error); + returnParser.as.success.currentParent = &returnParser.as.success.output; + ResultType(Nothing, charptr) parsed = parse(&returnParser.as.success); + + // Add any error messages from parsing + if (parsed.error) { + addToParserErrors(parser, parsed.as.error); + return Success(Nothing, charptr, {}); + } + + // Copy nodes into the sols node + for (size_t i = 0; i < returnParser.as.success.output.children.count; i++) { + addChildToSolsNode(&node.as.success, returnParser.as.success.output.children.at[i]); + } + + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseUse(SolsParser* parser) { + ResultType(SolsToken, Nothing) name = parserConsume(parser); + if (name.error || name.as.success.type != STT_IDENTIFIER) { + return Error(Nothing, charptr, "Expecting identifier after 'use'"); + } + ResultType(SolsNode, charptr) node = createSolsNode(SNT_USE); + if (node.error) { + return Error(Nothing, charptr, node.as.error); + } + node.as.success.as.idName = name.as.success.as.idName; + addChildToSolsNode(parser->currentParent, node.as.success); + + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseInlineGround(SolsParser* parser) { + ResultType(SolsToken, Nothing) token = parserPeek(parser, 0); + if (token.error) { + return Error(Nothing, charptr, "ruh roh"); + } + ResultType(SolsNode, charptr) newNode = createSolsNode(SNT_GROUND, token.as.success.as.inlineGround); + if (newNode.error) { + return Error(Nothing, charptr, newNode.as.error); + } + + addChildToSolsNode(parser->currentParent, newNode.as.success); + return Success(Nothing, charptr, {}); +} + +static inline ResultType(Nothing, charptr) parseOpenParen(SolsParser* parser) { + return Error(Nothing, charptr, "WIP"); +} + +static inline ResultType(Nothing, charptr) parseCloseParen(SolsParser* parser) { + (void)parser; + return Error(Nothing, charptr, "Extra closing parenthases"); +} + +ResultType(Nothing, charptr) parse(SolsParser* parser) { + parser->currentParent = &parser->output; + for (;;) { + ResultType(SolsToken, Nothing) token = parserConsume(parser); + if (token.error) { + break; + } + switch (token.as.success.type) { + case STT_IDENTIFIER: PARSER_HANDLE(Identifier); + case STT_LITERAL: PARSER_HANDLE(Literal); + case STT_KW_PUTS: PARSER_HANDLE(Puts); + case STT_KW_IF: PARSER_HANDLE(If); + case STT_KW_WHILE: PARSER_HANDLE(While); + case STT_KW_GROUND: PARSER_HANDLE(InlineGround); + case STT_KW_LAMBDA: PARSER_HANDLE(Lambda); + case STT_KW_RETURN: PARSER_HANDLE(Return); + case STT_KW_USE: PARSER_HANDLE(Use); + case STT_KW_DEF: PARSER_HANDLE(Def); + case STT_OP_SET: PARSER_HANDLE(Set); + case STT_OP_ADD: PARSER_HANDLE(Add); + case STT_OP_SUB: PARSER_HANDLE(Sub); + case STT_OP_MUL: PARSER_HANDLE(Mul); + case STT_OP_DIV: PARSER_HANDLE(Div); + case STT_OP_EQUAL: PARSER_HANDLE(Equal); + case STT_OP_INEQUAL: PARSER_HANDLE(Inequal); + case STT_OP_GREATER: PARSER_HANDLE(Greater); + case STT_OP_LESSER: PARSER_HANDLE(Lesser); + case STT_OP_EQGREATER: PARSER_HANDLE(EqGreater); + case STT_OP_EQLESSER: PARSER_HANDLE(EqLesser); + case STT_OPEN_CURLY: PARSER_HANDLE(CodeBlock); + case STT_CLOSE_CURLY: PARSER_HANDLE(CloseCurly); + case STT_OPEN_PAREN: { + if (parser->output.children.count > 0 && parser->output.children.at[parser->output.children.count - 1].type == SNT_IDENTIFIER) { + PARSER_HANDLE(FunctionCall); + } else { + PARSER_HANDLE(OpenParen); + } + } + case STT_CLOSE_PAREN: PARSER_HANDLE(CloseParen); + } + } + if (parser->errors.count > 0) { + Estr estr = CREATE_ESTR(""); + for (size_t i = 0; i < parser->errors.count; i++) { + APPEND_ESTR(estr, parser->errors.at[i]); + APPEND_ESTR(estr, "\n"); + } + return Error(Nothing, charptr, estr.str); + } + return Success(Nothing, charptr, {}); +} + +ResultType(SolsToken, Nothing) parserLookAt(SolsParser* parser, size_t where) { + if (parser->input == NULL) { + return Error(SolsToken, Nothing, {}); + } + if (where >= parser->input->count) { + return Error(SolsToken, Nothing, {}); + } + return Success(SolsToken, Nothing, parser->input->at[where]); +} + +ResultType(SolsToken, Nothing) parserPeek(SolsParser* parser, int64_t ahead) { + if (parser->input == NULL) { + return Error(SolsToken, Nothing, {}); + } + if (parser->current + ahead - 1 >= parser->input->count) { + return Error(SolsToken, Nothing, {}); + } + return Success(SolsToken, Nothing, parser->input->at[parser->current + ahead - 1]); +} + +ResultType(SolsToken, Nothing) parserConsume(SolsParser* parser) { + if (parser->input == NULL) { + return Error(SolsToken, Nothing, {}); + } + if (parser->current >= parser->input->count) { + return Error(SolsToken, Nothing, {}); + } + return Success(SolsToken, Nothing, parser->input->at[parser->current ++]); +} diff --git a/src/parser/parser.h b/src/parser/parser.h new file mode 100644 index 0000000..4968e4e --- /dev/null +++ b/src/parser/parser.h @@ -0,0 +1,99 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "SolsNode.h" +#include "../lexer/SolsToken.h" +#include "../include/error.h" +#include "../include/nothing.h" + +// Defines precedence for different tokens. +// Lower number means higher priority. +// Any operation involving same or higher precedence than +// the current token's precedence should be processed first +// (i.e. placed further down the tree) +typedef enum SolsTokenPrecedence { + STP_NEWLINE = 0, + STP_PUTS = 1, + STP_IF = 1, + STP_WHILE = 1, + STP_COMPARE = 2, + STP_SET = 3, + STP_FUNCTION = 4, + STP_ADD = 5, + STP_MUL = 6, + STP_OTHER = 7, +} SolsTokenPrecedence; + +// Gets the precedence of the provided token. +SolsTokenPrecedence getPrecedence(SolsToken* token); + +// Holds information about the parser. +// .input is lexed tokens, produced by a lexer. +// .current is the token currently being parsed. +// .output is the final product of the parser. +// .currentParent points to the current node being processed +// .errors holds any errors generated during parsing +typedef struct SolsParser { + SolsTokens* input; + size_t current; + SolsNode output; + SolsNode* currentParent; + struct { + size_t count; + size_t capacity; + char** at; + } errors; +} SolsParser; + +Result(SolsParser, charptr); + +// Creates a SolsParser. +// Returns: +// Success: Constructed SolsParser +// Failure: char* detailing what went wrong (usually memory failure) +ResultType(SolsParser, charptr) createSolsParser(SolsTokens* input); + +// Parses the tokens in the SolsParser into an AST. +// Returns: +// Success: Nothing (output is stored in the passed SolsLexer) +// Failure: char* detailing what went wrong (usually user error) +ResultType(Nothing, charptr) parse(SolsParser* parser); + +Result(SolsNode, Nothing); + +// Parses one singular node and returns it. +// Returns: +// Success: The parsed SolsNode +// Failure: Nothing (out of bounds, or an error) + +Result(SolsToken, Nothing); + +// Peeks at a token at a specific index in the lexer, 0 being the first token. +// Returns: +// Success: The requested token +// Failure: Nothing (token is out of bounds) +ResultType(SolsToken, Nothing) parserLookAt(SolsParser* parser, size_t where); + +// Peeks at future tokens in the parser, 0 meaning current token, 1 the next. +// Returns: +// Success: The requested token +// Failure: Nothing (token is out of bounds) +ResultType(SolsToken, Nothing) parserPeek(SolsParser* parser, int64_t ahead); + +// Consumes the next token in the parser. +// Returns: +// Success: The requested token +// Failure: Nothing (we have reached the end of the tokens passed) +ResultType(SolsToken, Nothing) parserConsume(SolsParser* parser); + +// Macro for cleaner handling of each token type in the parser. +// Calls functions and returns errors for you! Such amazing +#define PARSER_HANDLE(tokentype) {\ + ResultType(Nothing, charptr) __result = parse##tokentype(parser);\ + if (__result.error) {\ + createParserError(parser, __result.as.error);\ + }\ + break;\ +} + +#endif