commit 29abae0a960803dce269085153a607bacf9cce8e Author: Maxwell Jeffress Date: Thu Feb 5 08:34:16 2026 +1100 Initial commit, save progress so far diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f5ba35 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +solstice diff --git a/build.c b/build.c new file mode 100644 index 0000000..764a61b --- /dev/null +++ b/build.c @@ -0,0 +1,12 @@ +// -- LEXER -- +#include "src/lexer/SolsType.c" +#include "src/lexer/lexer.c" + +// -- PARSER -- + + +// -- CODEGEN -- + + +// -- MAIN -- +#include "src/main.c" 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/lexer/SolsType.c b/src/lexer/SolsType.c new file mode 100644 index 0000000..a3025e7 --- /dev/null +++ b/src/lexer/SolsType.c @@ -0,0 +1,93 @@ +#include "lexer.h" +#include "../include/error.h" +#include "../include/estr.h" + +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(voidptr, 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(voidptr, 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(voidptr, 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(voidptr, charptr, NULL); +} + +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; +} diff --git a/src/lexer/SolsType.h b/src/lexer/SolsType.h new file mode 100644 index 0000000..fba3602 --- /dev/null +++ b/src/lexer/SolsType.h @@ -0,0 +1,80 @@ +#ifndef SOLSTYPE_H +#define SOLSTYPE_H + +#include +#include "../include/error.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; +// Definition of voidptr for Result() and ResultType() macros +typedef void* voidptr; + +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; + 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; + + +// Creates a SolsType, with the provided type type. +// Use the "addChildToSolsType()" function to add children, in case this type has children. +Result(SolsType, charptr); +ResultType(SolsType, charptr) createSolsType(SolsTypeType in); + +// Adds a child SolsType to a given SolsType. +Result(voidptr, charptr); +ResultType(voidptr, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name); + +// Makes a deep copy of a SolsType. +ResultType(SolsType, charptr) copySolsType(SolsType* type); + +// Frees a SolsType +void freeSolsType(SolsType* type); + +#endif diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c new file mode 100644 index 0000000..e69de29 diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h new file mode 100644 index 0000000..013f0d4 --- /dev/null +++ b/src/lexer/lexer.h @@ -0,0 +1,77 @@ +#ifndef LEXER_H +#define LEXER_H + +#include +#include +#include + +#include "../include/error.h" + +#include "SolsType.h" + +typedef enum SolsTokenType { + STT_IDENTIFIER, STT_LITERAL, STT_TYPE, 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_GREATER, STT_OP_LESSER, STT_OP_EQUAL, STT_OP_INEQUAL, STT_OP_EQGREATER, STT_OP_EQLESSER, STT_KW_DEF, STT_KW_STRUCT, STT_KW_PUTS, STT_KW_GROUND +} SolsTokenType; + +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; + +// 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; +} SolsToken; + +// 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; + +// 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; + SolsTokens output; + size_t current; +} SolsLexer; + +// Creates a lexer for use by the lex() function. +SolsLexer createLexer(char* input); + +// Uses the provided lexer to scan the code, and create tokens. +void lex(SolsLexer* lexer); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..7e22b22 --- /dev/null +++ b/src/main.c @@ -0,0 +1,5 @@ +#include "lexer/lexer.h" + +int main() { + return 0; +}