34 Commits

Author SHA1 Message Date
02ecef4ce2 Add updated Makefile 2026-05-10 14:56:18 +10:00
e0869d526e Start work on inbuilt compiler debugger 2026-05-09 14:07:35 +10:00
cba5ec1fa2 Calling functions in nested objects 2026-05-05 09:12:58 +10:00
908af13d8f I forgot to remove the fixme lmao 2026-05-04 19:29:09 +10:00
10046703ed Nested object setting 2026-05-04 19:28:03 +10:00
ebfd1d5045 Add sizeof test 2026-05-04 11:51:31 +10:00
1e0abcc0b4 sizeof operator 2026-05-04 11:51:07 +10:00
c7bd6e7766 Working generics 2026-04-20 18:43:20 +10:00
68f5868538 use files rel. to current file's directory 2026-04-20 15:16:36 +10:00
692dd6b0db Fix static builds 2026-04-17 13:28:02 +10:00
8087a8150e Static build 2026-04-17 13:25:21 +10:00
53908d29e2 woops, lemme delete that 2026-04-17 12:43:23 +10:00
effad2920f removed conversions lib, use the 'as' from now on 2026-04-17 12:42:24 +10:00
ac13b7a0ae Update highlighting 2026-04-17 12:37:11 +10:00
8b40dbd563 Fix generic constructor issue 2026-04-17 12:27:59 +10:00
0cf63b034f Fix some things, may break others 2026-04-16 17:13:55 +10:00
ba6ec79a10 x as y 2026-04-16 12:59:49 +10:00
f261a1cd0e More generics stuff 2026-04-15 16:57:41 +10:00
00bf654882 IT SORTA WORKS 2026-04-15 11:25:52 +10:00
591adf79a4 More work on generics, still no worky 2026-04-15 11:13:38 +10:00
c7d4a7700e Further work on generics 2026-04-14 17:08:44 +10:00
d0d1dc7465 Parse generic initializations 2026-04-14 12:00:20 +10:00
f8afa2f564 Start work on codegen for generics 2026-04-14 11:32:05 +10:00
7a81a47986 finish parsing generics 2026-04-14 09:35:23 +10:00
f127c2f5ab Add generics example 2026-04-14 09:21:30 +10:00
23041c041a merge 2026-04-14 08:52:30 +10:00
0bb3741c66 "as" in struct 2026-04-14 08:49:57 +10:00
af97f1b712 added strings solstice wrapper 2026-04-13 20:02:14 +10:00
dfe37de5c0 Fix duplicators 2026-04-13 18:27:27 +10:00
5d9cb02e7e Parse and codegen duplicator 2026-04-13 14:02:47 +10:00
547488964a Fix an issue with constructors 2026-04-13 13:12:58 +10:00
f0692eb940 added solstice wrappers for fileio and collections 2026-04-13 12:15:47 +10:00
67ea6cc5fc Enums as return types 2026-04-13 12:06:56 +10:00
36030f01a2 Basic enum 2026-04-13 11:53:20 +10:00
42 changed files with 9692 additions and 116 deletions

56
Makefile Normal file
View File

@@ -0,0 +1,56 @@
CXX = gcc
GROUND_STATIC = /usr/local/lib/libgroundvm.a
GROUND_INCLUDE = /usr/local/include/
CXXFLAGS = -I$(GROUND_INCLUDE) -Wall -Wextra -pedantic -O3 -ggdb
LDFLAGS = -lgroundvm
BUILD_DIR = build
SRC_DIR = src
LIBS_DIR = libs
PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
LIBDIR = /usr/lib
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 $(SRC_DIR)/typeparser/typeparser.c $(SRC_DIR)/interactive/interactive.c
OBJS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
TARGET = solstice
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
install: $(TARGET)
mkdir -p /usr/lib/solstice
install -d $(BINDIR)
install -m 755 $(TARGET) $(BINDIR)/$(TARGET)
install -d $(LIBDIR)
cp -r libs/* $(LIBDIR)/solstice
static: $(OBJS)
$(CXX) $(OBJS) $(GROUND_STATIC) -o $(TARGET)
$(BUILD_DIR)/solstice.tar.gz: $(TARGET) $(LIBS_DIR)
mkdir -p $(BUILD_DIR)/pkg/bin $(BUILD_DIR)/pkg/lib/
cp $(TARGET) $(BUILD_DIR)/pkg/bin/solstice
cp -r $(LIBS_DIR) $(BUILD_DIR)/pkg/lib/solstice
tar -czvf $(BUILD_DIR)/solstice.tar.gz $(BUILD_DIR)/pkg
package: $(BUILD_DIR)/solstice.tar.gz
$(BUILD_DIR):
mkdir -p $(BUILD_DIR) $(BUILD_DIR)/codegen $(BUILD_DIR)/lexer $(BUILD_DIR)/parser $(BUILD_DIR)/typeparser $(BUILD_DIR)/interactive
clean:
rm -rf $(BUILD_DIR) $(TARGET)
run: $(TARGET)
./$(TARGET)
.PHONY: all clean run

37
libs/collections.sols Normal file
View File

@@ -0,0 +1,37 @@
struct List {
protected size = 0
protected capacity = 0
protected memSize = 0
private ptr = 0
constructor(int length) {}
def append(int value) int {}
def insert(int value, int index) int {}
def remove(int index) int {}
def at(int index) int {}
def clear() int {}
def set(int value, int index) int {}
def isEmpty() bool {}
def contains(int value) bool {}
def reverse() int {}
def find(int value) int {}
def reserve(int n) int {}
}
struct Hashmap {
private ptr = 0
constructor() {}
def set(string key, int value) int {}
def get(string key) int {}
def getOr(string key, int fallback) int {}
def remove(string key) int {}
def removeIfPresent(string key) bool {}
}
ground {
extern "collections"
}

View File

@@ -1,15 +0,0 @@
def stringToInt(string in) int {
result = 0
ground {
stoi $in &result
}
return result
}
def intToString(int in) string {
result = ""
ground {
tostring $in &result
}
return result
}

View File

@@ -1,5 +1,18 @@
def file_Read(string file) string {}
def file_Write(string file, string content) bool {}
struct File {
private fileHandle = 0
protected filePath = ""
protected size = 0
protected tell = 0
constructor (string filePath) {}
def read() string {}
def write(string buffer) int {}
def append(string buffer) int {}
def flush() int {}
def seek(int offset) int {}
def seekEnd(int offset) int {}
}
ground {
extern "fileio"

28
libs/strings.sols Normal file
View File

@@ -0,0 +1,28 @@
def string_CharAt(string str, int index) string {}
def string_Upper(string str) string {}
def string_Lower(string str) string {}
def string_Trim(string str) string {}
def string_TrimLeft(string str) string {}
def string_TrimRight(string str) string {}
def string_Substring(string str, int start, int end) string {}
def string_Repeat(string str, int times) string {}
def string_Replace(string haystack, string needle) string {}
def string_Reverse(string str) string {}
def string_StartsWith(string str, string prefix) bool {}
def string_EndsWith(string str, string suffix) bool {}
def string_Contains(string haystack, string needle) bool {}
def string_Find(string haystack, string needle) int {}
def string_FindLast(string haystack, string needle) int {}
def string_Count(string haystack, string needle) int {}
def string_isAlpha(string str) bool {}
def string_isAlnum(string str) bool {}
def string_isDigit(string str) bool {}
def string_isSpace(string str) bool {}
ground {
extern "string"
}

55
src/codegen/SolsScope.c Normal file
View File

@@ -0,0 +1,55 @@
#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 = ({
ResultType(SolsType, charptr) _result = copySolsType(&type);
if (_result.error) {
return;
}
_result.as.success;
});
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);
}
}

32
src/codegen/SolsScope.h Normal file
View File

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

2368
src/codegen/codegen.c Normal file

File diff suppressed because it is too large Load Diff

34
src/codegen/codegen.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef CODEGEN_H
#define CODEGEN_H
#include <groundvm.h>
#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

67
src/include/ansii.h Normal file
View File

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

83
src/include/error.h Normal file
View File

@@ -0,0 +1,83 @@
#include <stdlib.h>
#include <stdbool.h>
#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 <stdio.h>
*
* // 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

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

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

71
src/include/list.h Normal file
View File

@@ -0,0 +1,71 @@
// list.h - create a list of any kind in C
// append, pop, safely retrieve
// licenced to you under the MIT license
// example:
//
// #include "list.h"
//
// UseList(int);
//
// int main() {
// List(int) myList = newList(int);
// append(myList, 32);
// printf("%d\n", *get(myList, 0));
// pop(myList);
// destroy(myList);
// }
//
#ifndef LIST_H
#define LIST_H
#include <stdlib.h>
#include <stdio.h>
#define LIST_H_INIT_CAPACITY 32
#define UseList(type) struct __List_##type { type* pointer; size_t count; size_t capacity; size_t itemsize; }
#define List(type) struct __List_##type
#define newList(type) ({ \
List(type) __list_tmp = { .pointer = malloc(sizeof(type) * LIST_H_INIT_CAPACITY), .count = 0, .capacity = LIST_H_INIT_CAPACITY, .itemsize = sizeof(type) };\
if (__list_tmp.pointer == NULL) { \
printf("list.h:32 (newList(type)) - failed to allocate memory"); exit(1);\
}\
__list_tmp;\
})
#define append(list, item) {\
if (list.pointer == NULL) {\
printf("list.h:39 (append(list, item)) - list pointer is null (perhaps you accidently destroyed the list?)"); exit(1);\
}\
if (list.capacity < list.count + 1) {\
list.capacity *= 2;\
void* __tmp_ptr = realloc(list.pointer, list.capacity * list.itemsize);\
if (__tmp_ptr == NULL) {\
printf("list.h:45 (append(list, item)) - failed to allocate memory"); exit(1);\
}\
list.pointer = __tmp_ptr;\
}\
list.pointer[list.count] = item;\
list.count++;\
}
#define get(list, idx) ({\
if (list.pointer == NULL) {\
printf("list.h:55 (get(list, idx)) - list pointer is null (perhaps you accidently destroyed the list?)"); exit(1);\
}\
(idx >= list.count) ? NULL : &list.pointer[idx];\
})
#define pop(list) {\
if (list.pointer == NULL) {\
printf("list.h:65 (pop(list)) - list pointer is null (perhaps you accidently destroyed the list?)"); exit(1);\
}\
if (list.count > 0) list.count--;\
}
#define destroy(list) if (list.pointer != NULL) {\
free(list.pointer); list.pointer = NULL; \
}
#endif

10
src/include/nothing.h Normal file
View File

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

1137
src/include/uthash.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
#include "interactive.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
Interactive createInteractive() {
return (Interactive) {
.callbacks.at = malloc(sizeof(InteractiveCallback) * 16),
.callbacks.capacity = 16,
.callbacks.size = 0
};
}
void addCallback(Interactive* interactive, int (*function)(size_t, char**, void*), char* name) {
if (interactive->callbacks.size + 1 >= interactive->callbacks.capacity) {
interactive->callbacks.at = realloc(interactive->callbacks.at, (interactive->callbacks.capacity * 2) * sizeof(InteractiveCallback));
interactive->callbacks.capacity *= 2;
}
interactive->callbacks.at[interactive->callbacks.size].function = function;
interactive->callbacks.at[interactive->callbacks.size].name = name;
interactive->callbacks.size++;
}
typedef struct InteractiveCommand {
char* command;
size_t argc;
size_t capacity;
char** argv;
} InteractiveCommand;
static inline InteractiveCommand parseInteractiveCommand(char* input) {
InteractiveCommand command = {
.command = malloc(sizeof(char*) * 16),
.argc = 0,
.capacity = 16,
.argv = malloc(sizeof(char*) * 16)
};
for (size_t i = 0; i < command.capacity; i++) {
command.argv[i] = malloc(sizeof(char) * 64);
}
size_t len = strlen(input);
char** currentBuf = &command.command;
size_t currentLen = 0;
for (size_t i = 0; i < len; i++) {
if (input[i] == '\n') break;
if (input[i] == ' ' || currentLen == 63) {
if (command.argc + 1 >= command.capacity) {
command.capacity *= 2;
command.argv = realloc(command.argv, command.capacity * sizeof(char**));
}
currentBuf = &command.argv[command.argc];
command.argc++;
} else {
currentBuf[currentLen][i] = input[i];
}
}
return command;
}
void runInteractive(Interactive* interactive, void* ctx) {
for (;;) {
char buf[512];
printf("> ");
fgets(buf, sizeof(buf), stdin);
InteractiveCommand command = parseInteractiveCommand(buf);
if (strcmp(command.command, "exit") == 0) {
break;
}
for (size_t i = 0; i < interactive->callbacks.size; i++) {
if (strcmp(command.command, interactive->callbacks.at[i].name) == 0) {
interactive->callbacks.at[i].function(command.argc, command.argv, ctx);
}
}
}
}

View File

@@ -0,0 +1,26 @@
#ifndef INTERACTIVE_H
#define INTERACTIVE_H
#include <stdlib.h>
// Callback functions are of the form
// int callback(int argc, char** argv, void* ctx)
typedef struct InteractiveCallback {
int (*function)(size_t, char**, void*);
char* name;
} InteractiveCallback;
typedef struct Interactive {
struct {
InteractiveCallback* at;
size_t size;
size_t capacity;
} callbacks;
} Interactive;
Interactive createInteractive();
void addCallback(Interactive* interactive, int (*function)(size_t, char**, void*), char* name);
void runInteractive(Interactive* interactive, void* ctx);
#endif

BIN
src/lexer/.DS_Store vendored

Binary file not shown.

78
src/lexer/SolsLiteral.c Normal file
View File

@@ -0,0 +1,78 @@
#include "SolsLiteral.h"
#include <stdarg.h>
#include <string.h>
#include <inttypes.h>
#include <stdio.h>
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);
}
}
void printSolsLiteral(SolsLiteral* lit) {
switch (lit->type) {
case SLT_INT: {
printf("%" PRId64, lit->as.intv);
break;
}
case SLT_DOUBLE: {
printf("%f", lit->as.doublev);
break;
}
case SLT_STRING: {
printf("%s", lit->as.stringv);
break;
}
case SLT_BOOL: {
printf(lit->as.boolv ? "true" : "false");
break;
}
case SLT_CHAR: {
printf("%c", lit->as.charv);
break;
}
}
}

49
src/lexer/SolsLiteral.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef SOLSLITERAL_H
#define SOLSLITERAL_H
#include <inttypes.h>
#include <stdarg.h>
#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);
// Prints a SolsLiteral
void printSolsLiteral(SolsLiteral* lit);
#endif

93
src/lexer/SolsToken.c Normal file
View File

@@ -0,0 +1,93 @@
#include "SolsToken.h"
#include "SolsLiteral.h"
#include "../include/error.h"
#include <stdarg.h>
#include <string.h>
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, {});
}

96
src/lexer/SolsToken.h Normal file
View File

@@ -0,0 +1,96 @@
#ifndef SOLSTOKEN_H
#define SOLSTOKEN_H
#include <stdarg.h>
#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_ENUM, STT_KW_CONSTRUCTOR, STT_KW_DESTRUCTOR, STT_KW_DUPLICATOR,
STT_KW_AS, STT_KW_SIZEOF,
STT_KW_PRIVATE, STT_KW_PROTECTED,
STT_KW_PUTS, STT_KW_IF, STT_KW_WHILE,
STT_KW_NEW, STT_KW_GROUND, STT_LINE_END, STT_COMMA,
STT_OPEN_SQUARE, STT_CLOSE_SQUARE,
STT_KW_PRAGMA,
} 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

362
src/lexer/SolsType.c Normal file
View File

@@ -0,0 +1,362 @@
#include "SolsType.h"
#include "../include/error.h"
#include "../include/estr.h"
#include <groundvm.h>
#include <string.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 == STT_GENERIC ? STT_TEMPLATE : in,
.identifierType = NULL,
.returnType = NULL,
.children.capacity = 32,
.children.count = 0,
.children.at = ptr,
.typeIsKnown = true,
.needsGroundStruct = false,
.metadata.isPrivate = false,
.metadata.isProtected = false,
.metadata.isEnum = false,
.metadata.isGenericField = in == STT_GENERIC,
.metadata.isGenericStruct = false,
.metadata.genericStructNode = NULL
};
return Success(SolsType, charptr, type);
}
ResultType(SolsType, charptr) createIdentifiedSolsType(char* in) {
char* copy = malloc(strlen(in) + 1);
if (copy == NULL) {
return Error(SolsType, charptr, "Couldn't allocate memory (in createIdentifiedSolsType() function)");
}
strcpy(copy, in);
return Success(SolsType, charptr, ((SolsType) {
.type = STT_UNKNOWN,
.identifierType = copy,
.returnType = NULL,
.children.capacity = 0,
.children.count = 0,
.children.at = NULL,
.typeIsKnown = false,
.needsGroundStruct = false,
.metadata.isPrivate = false,
.metadata.isProtected = false,
.metadata.isEnum = false,
.metadata.isGenericField = false,
.metadata.isGenericStruct = false,
.metadata.genericStructNode = NULL
}));
}
ResultType(SolsType, charptr) copySolsType(SolsType* type) {
SolsType ret = {
.type = type->type,
.identifierType = NULL,
.typeIsKnown = type->typeIsKnown,
.needsGroundStruct = type->needsGroundStruct,
.returnType = NULL,
.children.count = type->children.count,
.children.capacity = type->children.capacity,
.children.at = NULL,
.metadata = type->metadata
};
if (type->identifierType != NULL) {
ret.identifierType = malloc(strlen(type->identifierType) + 1);
if (ret.identifierType == NULL) {
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
}
strcpy(ret.identifierType, type->identifierType);
}
if (type->returnType != NULL && type->returnType->type != STT_NONE) {
ResultType(SolsType, charptr) copiedReturn = copySolsType(type->returnType);
if (copiedReturn.error) {
Estr err = CREATE_ESTR(copiedReturn.as.error);
APPEND_ESTR(err, " (in copySolsType() function)");
return Error(SolsType, charptr, err.str);
}
ret.returnType = malloc(sizeof(SolsType));
if (ret.returnType == NULL) {
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
}
*ret.returnType = copiedReturn.as.success;
}
if (type->children.capacity > 0) {
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;
}
for (size_t i = 0; i < type->children.count; i++) {
ResultType(SolsType, charptr) copied = copySolsType(&type->children.at[i].type);
if (copied.error) {
Estr err = CREATE_ESTR(copied.as.error);
APPEND_ESTR(err, " (in copySolsType() function)");
return Error(SolsType, charptr, err.str);
}
ret.children.at[i].type = copied.as.success;
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(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, struct SolsScope* scope) {
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: {
if (!type->needsGroundStruct) {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, type->identifierType));
} else {
// FIXME do this later
return Error(GroundArg, charptr, "Anonymous structs are not supported yet");
}
}
case STT_UNKNOWN: {
if (!type->needsGroundStruct) {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, type->identifierType));
} else {
// FIXME do this later
return Error(GroundArg, charptr, "Anonymous structs are not supported yet");
}
}
case STT_NONE: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "none"));
}
}
return Error(GroundArg, charptr, "How did we get here?");
}
ResultType(SolsType, charptr) findStructMemberType(SolsType* type, char* member) {
for (size_t i = 0; i < type->children.count; i++) {
if (strcmp(type->children.at[i].name, member) == 0) {
return Success(SolsType, charptr, type->children.at[i].type);
}
}
return Error(SolsType, charptr, "Could not find member");
}
void printSolsType(SolsType* type) {
printf("Type: ");
switch (type->type) {
case STT_INT:
printf("int");
break;
case STT_DOUBLE:
printf("double");
break;
case STT_STRING:
printf("string");
break;
case STT_BOOL:
printf("bool");
break;
case STT_CHAR:
printf("char");
break;
case STT_FUN:
printf("fun");
break;
case STT_TEMPLATE:
printf("template");
break;
case STT_OBJECT:
printf("object");
break;
case STT_UNKNOWN:
printf("(unknown)");
break;
case STT_NONE:
printf("(none)");
break;
case STT_GENERIC:
printf("(generic");
break;
}
if (type->returnType != NULL) {
printf("\nReturn type: ");
printSolsType(type->returnType);
}
if (type->children.count > 0) {
printf("\nChildren {");
for (size_t i = 0; i < type->children.count; i++) {
if (type->children.at[i].name != NULL) {
printf("\nName: %s", type->children.at[i].name);
}
printf("\nType: ");
printSolsType(&type->children.at[i].type);
}
printf("}");
}
}

135
src/lexer/SolsType.h Normal file
View File

@@ -0,0 +1,135 @@
#ifndef SOLSTYPE_H
#define SOLSTYPE_H
#include <stdlib.h>
#include <groundvm.h>
#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, STT_UNKNOWN, STT_GENERIC, STT_NONE
} SolsTypeType;
// Definition of charptr for Result() and ResultType() macros
typedef char* charptr;
struct SolsTypeField;
struct SolsNode; // for generic struct
// 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;
// If type is identified with a name, record whether we have found the actual type
bool typeIsKnown;
// If using anonymous struct, record whether we need to generate a Ground struct
bool needsGroundStruct;
// For use in functions
struct SolsType* returnType;
// For use by fun, template, object
struct {
struct SolsTypeField* at;
size_t count;
size_t capacity;
} children;
struct {
bool isPrivate;
bool isProtected;
bool isEnum;
bool isGenericField;
bool isGenericStruct;
// For use by generic struct
// We will codegen this where required
struct SolsNode* genericStructNode;
} metadata;
} 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);
// Creates a SolsType which is identified by a name.
// The type details are not known yet, so the type is marked as unknown.
ResultType(SolsType, charptr) createIdentifiedSolsType(char* 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);
struct SolsScope;
// Represents a SolsType as a GroundArg (in typeref form)
ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type, struct SolsScope* scope);
// Frees a SolsType
void freeSolsType(SolsType* type);
// Compares two SolsTypes
bool compareTypes(SolsType* left, SolsType* right);
// Finds the type of a struct member. Errors if the member is not found.
ResultType(SolsType, charptr) findStructMemberType(SolsType* type, char* member);
void printSolsType(SolsType* type);
#endif

860
src/lexer/lexer.c Normal file
View File

@@ -0,0 +1,860 @@
#include "lexer.h"
#include "SolsLiteral.h"
#include "SolsToken.h"
#include "../include/error.h"
#include "../include/estr.h"
#include "../include/ansii.h"
#include <ctype.h>
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},
{"enum", STT_KW_ENUM},
{"constructor", STT_KW_CONSTRUCTOR},
{"destructor", STT_KW_DESTRUCTOR},
{"duplicator", STT_KW_DUPLICATOR},
{"private", STT_KW_PRIVATE},
{"protected", STT_KW_PROTECTED},
{"ground", STT_KW_GROUND},
{"new", STT_KW_NEW},
{"as", STT_KW_AS},
{"sizeof", STT_KW_SIZEOF},
{"pragma", STT_KW_PRAGMA},
{"{", STT_OPEN_CURLY},
{"}", STT_CLOSE_CURLY},
{"(", STT_OPEN_PAREN},
{")", STT_CLOSE_PAREN},
{"[", STT_OPEN_SQUARE},
{"]", STT_CLOSE_SQUARE},
{"+", 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},
{"class", STT_KW_STRUCT},
{"compilerpleasedothisforme", STT_KW_PRAGMA}
#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 '[':
case ']':
case '\n':
{
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, &currentLine, 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, &currentLine, 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, &currentLine, 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, &currentLine, 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, &currentLine, 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, &currentLine, '\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)");
}

76
src/lexer/lexer.h Normal file
View File

@@ -0,0 +1,76 @@
#ifndef LEXER_H
#define LEXER_H
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#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

View File

@@ -1,97 +0,0 @@
use collections
use string
//
// Token type enum.
//
// Each token can be one of these types.
//
enum SolsToken {
Identifier, Literal, Type,
Dot, OpenCurly, CloseCurly, OpenParen, CloseParen, Comma,
OpAdd, OpSub, OpMul, OpDiv, OpAddTo, OpSubTo, OpMulTo, OpDivTo, OpIncrement, OpDecrement, OpSet,
OpGreater, OpLesser, OpEqual, OpInequal, OpEqGreater, OpEqLesser,
KwDef, KwLambda, KwReturn, KwUse, KwStruct, KwEnum, KwConstructor,
KwDestructor, KwDuplicator, KwPrivate, KwProtected, KwPuts, KwIf,
KwWhile, KwNew, KwGround,
LineEnd,
}
//
// Token type map.
//
// If a token matches one of the strings in this hashmap,
// its type gets set to the token type for that string.
//
KEYWORDS = Hashmap()
KEYWORDS.set("puts", SolsToken.KwPuts)
KEYWORDS.set("if", SolsToken.KwIf)
KEYWORDS.set("while", SolsToken.KwWhile)
KEYWORDS.set("def", SolsToken.KwDef)
KEYWORDS.set("lambda", SolsToken.KwLambda)
KEYWORDS.set("return", SolsToken.KwReturn)
KEYWORDS.set("use", SolsToken.KwUse)
KEYWORDS.set("struct", SolsToken.KwStruct)
KEYWORDS.set("enum", SolsToken.KwEnum)
KEYWORDS.set("constructor", SolsToken.KwConstructor)
KEYWORDS.set("destructor", SolsToken.KwDestructor)
KEYWORDS.set("duplicator", SolsToken.KwDuplicator)
KEYWORDS.set("private", SolsToken.KwPrivate)
KEYWORDS.set("protected", SolsToken.KwProtected)
KEYWORDS.set("ground", SolsToken.KwGround)
KEYWORDS.set("new", SolsToken.KwNew)
KEYWORDS.set("{", SolsToken.OpenCurly)
KEYWORDS.set("}", SolsToken.CloseCurly)
KEYWORDS.set("(", SolsToken.OpenParen)
KEYWORDS.set(")", SolsToken.CloseParen)
KEYWORDS.set("+", SolsToken.OpAdd)
KEYWORDS.set("-", SolsToken.OpSub)
KEYWORDS.set("*", SolsToken.OpMul)
KEYWORDS.set("/", SolsToken.OpDiv)
KEYWORDS.set("=", SolsToken.OpSet)
KEYWORDS.set("+=", SolsToken.OpAddTo)
KEYWORDS.set("-=", SolsToken.OpSubTo)
KEYWORDS.set("*=", SolsToken.OpMulTo)
KEYWORDS.set("/=", SolsToken.OpDivTo)
KEYWORDS.set("++", SolsToken.OpIncrement)
KEYWORDS.set("--", SolsToken.OpDecrement)
KEYWORDS.set("==", SolsToken.OpEqual)
KEYWORDS.set("!=", SolsToken.OpInequal)
KEYWORDS.set(">", SolsToken.OpGreater)
KEYWORDS.set("<", SolsToken.OpLesser)
KEYWORDS.set(">=", SolsToken.OpEqGreater)
KEYWORDS.set("<=", SolsToken.OpEqLesser)
KEYWORDS.set("\n", SolsToken.LineEnd)
KEYWORDS.set(";", SolsToken.LineEnd)
KEYWORDS.set(",", SolsToken.Comma)
struct SolsLexer {
sourceCode = "x = 123"
current = 0
private def getTokenType(string input) SolsToken {
return KEYWORDS.get(input)
}
def lex() {
inString = false
lineNum = 1
lineStart = 0
currentLine = ""
while lineStart < string_Length() {
}
}
}
lexer = new SolsLexer
puts lexer.getTokenType("if")

254
src/main.c Normal file
View File

@@ -0,0 +1,254 @@
#include "lexer/SolsType.h"
#include "lexer/lexer.h"
#include "typeparser/typeparser.h"
#include "parser/parser.h"
#include "codegen/codegen.h"
#include "include/error.h"
#include "include/estr.h"
#include <stdio.h>
#include <unistd.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#include <libgen.h>
#endif
#include <groundvm.h>
extern bool groundDisableTypeChecking;
char* fileDir = NULL;
typedef enum SolsAction {
SA_PRINT, SA_EXEC, SA_BYTECODE, SA_COMPILE, SA_EXIT, SA_EXITOK
} SolsAction;
typedef struct Args {
SolsAction action;
char* inputFile;
char* outputFile;
} Args;
Args parseArgs(int argc, char** argv) {
Args args = {
.action = SA_EXEC,
.inputFile = NULL,
.outputFile = NULL
};
for (int i = 0; i < argc; i++) {
if (strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) {
printf("Solstice programming language\n");
printf("Usage: %s <file> [-h] [--help] [-p] [--print] [-b <file>] [--bytecode <file>] [-c <file>] [--compile <file>]\n", argv[0]);
printf("Args:\n");
printf(" <file>: Solstice source file\n");
printf(" -h or --help: Prints this help message and exits\n");
printf(" -p or --print: Prints textual version of Ground bytecode to console\n");
printf(" -b <file> or --bytecode <file>: Generates Ground bytecode (.grbc) and saves it to the provided filename\n");
printf(" -c <file> or --compile <file>: Compiles Ground to Linux x86_64 assembly, outputs a binary to the provided filename (experimental)\n");
printf(" If no extra arguments are provided, the generated Ground bytecode will be executed.\n");
args.action = SA_EXIT;
return args;
}
else if (strcmp("-p", argv[i]) == 0 || strcmp("--print", argv[i]) == 0) {
args.action = SA_PRINT;
}
else if (strcmp("-b", argv[i]) == 0 || strcmp("--bytecode", argv[i]) == 0) {
if (args.action != SA_EXEC) {
printf("Expecting only one action\n");
args.action = SA_EXIT;
return args;
}
args.action = SA_BYTECODE;
if (i + 1 >= argc) {
printf("Expecting file name after %s\n", argv[i]);
args.action = SA_EXIT;
return args;
}
i++;
args.outputFile = argv[i];
}
else if (strcmp("-c", argv[i]) == 0 || strcmp("--compile", argv[i]) == 0) {
if (args.action != SA_EXEC) {
printf("Expecting only one action\n");
args.action = SA_EXIT;
return args;
}
args.action = SA_COMPILE;
if (i + 1 >= argc) {
printf("Expecting file name after %s\n", argv[i]);
args.action = SA_EXIT;
return args;
}
i++;
args.outputFile = argv[i];
}
else {
args.inputFile = argv[i];
}
}
if (args.inputFile == NULL) {
printf("Usage: %s <file> [-h] [--help] [-p] [--print] [-b <file>] [--bytecode <file>] [-c <file>] [--compile <file>]\n", argv[0]);
args.action = SA_EXIT;
return args;
}
return args;
}
// Use ground's getFileContents
char* getFileContents(const char* filename);
int main(int argc, char** argv) {
groundDisableTypeChecking = true;
Args args = parseArgs(argc, argv);
if (args.action == SA_EXIT) {
return 1;
}
if (args.action == SA_EXITOK) {
return 0;
}
#ifdef _WIN32
fileDir = NULL;
#else
{
char tmp[FILENAME_MAX];
char pwd[FILENAME_MAX];
getcwd(pwd, sizeof(pwd));
snprintf(tmp, FILENAME_MAX, "%s/%s", pwd, args.inputFile);
fileDir = dirname(tmp);
}
#endif
char* fileContents = getFileContents(args.inputFile);
if (fileContents == NULL) {
printf("Couldn't read that file :(\n");
return 1;
}
// Lex file
ResultType(SolsLexer, charptr) lexer = createLexer(fileContents);
if (lexer.error) {
printf("Error while creating lexer: %s", lexer.as.error);
return 1;
}
ResultType(Nothing, charptr) lexed = lex(&lexer.as.success);
if (lexed.error) {
printf("%s\n", lexed.as.error);
return 1;
}
// Detect and parse types
ResultType(SolsTokens, charptr) typed = addTypeInfo(&lexer.as.success.output);
if (typed.error) {
printf("%s\n", typed.as.error);
return 1;
}
// Parse file
ResultType(SolsParser, charptr) parser = createSolsParser(&typed.as.success);
if (parser.error) {
printf("Error while creating parser: %s\n", parser.as.error);
return 1;
}
ResultType(Nothing, charptr) parsed = parse(&parser.as.success);
if (parsed.error) {
printf("%s\n", parsed.as.error);
return 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);
return 1;
}
switch (args.action) {
case SA_PRINT: {
groundPrintProgram(&codegen.as.success);
break;
}
case SA_EXEC: {
GroundValue retval = groundRunProgram(&codegen.as.success);
if (retval.type == INT) {
return retval.data.intVal;
} else {
return 0;
}
break;
}
case SA_BYTECODE: {
serializeProgramToFile(args.outputFile, &codegen.as.success);
break;
}
case SA_COMPILE: {
char* compiled = groundCompileProgram(&codegen.as.success);
// Make work directory
Estr dir = CREATE_ESTR(".solsbuild_");
APPEND_ESTR(dir, args.outputFile);
#ifdef _WIN32
int dirstatus = _mkdir(estr.str);
#else
int dirstatus = mkdir(dir.str, 0755);
#endif
if (dirstatus != 0) {
printf("Couldn't create temporary work directory\n");
return 1;
}
// Write generated asm to .s
Estr tmpasm = CREATE_ESTR(dir.str);
APPEND_ESTR(tmpasm, "/generated.s");
FILE* file = fopen(tmpasm.str, "w");
if (file == NULL) {
printf("Couldn't write to temporary assembly file");
}
fprintf(file, "%s", compiled);
fclose(file);
// Assemble with nasm
Estr nasmcmd = CREATE_ESTR("nasm -f elf64 -o ");
APPEND_ESTR(nasmcmd, dir.str);
APPEND_ESTR(nasmcmd, "/generated.o ");
APPEND_ESTR(nasmcmd, tmpasm.str);
int nasmstatus = system(nasmcmd.str);
if (nasmstatus != 0) {
printf("command \"%s\" exited with code %d\n", nasmcmd.str, nasmstatus);
return 1;
}
// Link with ld
Estr ldcmd = CREATE_ESTR("ld -o ");
APPEND_ESTR(ldcmd, args.outputFile);
APPEND_ESTR(ldcmd, " ");
APPEND_ESTR(ldcmd, dir.str);
APPEND_ESTR(ldcmd, "/generated.o");
int ldstatus = system(ldcmd.str);
if (ldstatus != 0) {
printf("command \"%s\" exited with code %d\n", ldcmd.str, ldstatus);
return 1;
}
// Yay we compiled it
break;
}
}
return 0;
}

View File

265
src/parser/SolsNode.c Normal file
View File

@@ -0,0 +1,265 @@
#include "SolsNode.h"
#include <stdarg.h>
#include <stdio.h>
#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] = deepCopySolsNode(child);
parent->children.count++;
return Success(Nothing, charptr, {});
}
SolsNode deepCopySolsNode(SolsNode node) {
SolsNode copy = node;
copy.children.at = malloc(sizeof(SolsNode) * node.children.capacity);
for (size_t i = 0; i < node.children.count; i++) {
copy.children.at[i] = deepCopySolsNode(node.children.at[i]);
}
return copy;
}
void printSolsNodeType(SolsNodeType type) {
switch (type) {
case SNT_IDENTIFIER:
printf("Identifier");
break;
case SNT_LITERAL:
printf("Literal");
break;
case SNT_TYPE:
printf("Type");
break;
case SNT_CODE_BLOCK:
printf("Code Block");
break;
case SNT_OP_ADD:
printf("+");
break;
case SNT_OP_SUB:
printf("-");
break;
case SNT_OP_MUL:
printf("*");
break;
case SNT_OP_DIV:
printf("/");
break;
case SNT_OP_ADDTO:
printf("+=");
break;
case SNT_OP_SUBTO:
printf("-=");
break;
case SNT_OP_MULTO:
printf("*=");
break;
case SNT_OP_DIVTO:
printf("/=");
break;
case SNT_OP_INCREMENT:
printf("++");
break;
case SNT_OP_DECREMENT:
printf("--");
break;
case SNT_OP_SET:
printf("=");
break;
case SNT_OP_GREATER:
printf(">");
break;
case SNT_OP_LESSER:
printf("<");
break;
case SNT_OP_EQUAL:
printf("==");
break;
case SNT_OP_INEQUAL:
printf("!=");
break;
case SNT_OP_EQGREATER:
printf(">=");
break;
case SNT_OP_EQLESSER:
printf("<=");
break;
case SNT_DEF:
printf("def");
break;
case SNT_LAMBDA:
printf("lambda");
break;
case SNT_FUNCTION_CALL:
printf("Function call");
break;
case SNT_RETURN:
printf("return");
break;
case SNT_SET_PRIVATE:
printf("Private =");
break;
case SNT_SET_PROTECTED:
printf("Protected =");
break;
case SNT_DEF_PRIVATE:
printf("Private def");
break;
case SNT_DEF_PROTECTED:
printf("Protected def");
break;
case SNT_USE:
printf("use");
break;
case SNT_LOCAL_USE:
printf("Local use");
break;
case SNT_STRUCT:
printf("struct");
break;
case SNT_ENUM:
printf("enum");
break;
case SNT_CONSTRUCTOR:
printf("constructor");
break;
case SNT_DESTRUCTOR:
printf("destructor");
break;
case SNT_DUPLICATOR:
printf("duplicator");
break;
case SNT_STRUCT_AS:
printf("struct as");
break;
case SNT_AS:
printf("as");
break;
case SNT_SIZE_OF:
printf("sizeof");
break;
case SNT_PUTS:
printf("puts");
break;
case SNT_IF:
printf("if");
break;
case SNT_WHILE:
printf("while");
break;
case SNT_NEW:
printf("new");
break;
case SNT_GROUND:
printf("ground");
break;
case SNT_ROOT:
printf("Root node");
break;
case SNT_EXPR_IN_PAREN:
printf("Parenthases");
break;
case SNT_DOT:
printf(".");
break;
case SNT_GENERIC:
printf("Generic");
break;
case SNT_GENERIC_INIT:
printf("Generic init");
break;
case SNT_PRAGMA:
printf("pragma");
break;
}
}
void printSolsNode(SolsNode* node) {
printf("Node (\nNodeType: ");
printSolsNodeType(node->type);
printf("\n");
printf("Line: num: %zu, content: %s",node->line.num, node->line.content);
switch (node->type) {
case SNT_IDENTIFIER: {
printf("\nIdentifier name: '%s'", node->as.idName);
break;
}
case SNT_GROUND: {
printf("\nInline Ground: '%s'", node->as.inlineGround);
break;
}
case SNT_LITERAL: {
printf("\nLiteral: ");
printSolsLiteral(&node->as.literal);
break;
}
case SNT_TYPE: {
printf("\nType: ");
printSolsType(&node->as.type);
break;
}
}
printf("\nChildren count: %zu", node->children.count);
if (node->children.count > 0) {
printf("\nChildren: (\n");
for (size_t i = 0; i < node->children.count; i++) {
printSolsNode(&node->children.at[i]);
}
printf("\n)");
}
printf("\n)\n");
}

81
src/parser/SolsNode.h Normal file
View File

@@ -0,0 +1,81 @@
#ifndef SOLSNODE_H
#define SOLSNODE_H
#include <stdarg.h>
#include <groundvm.h>
#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_SET_PRIVATE, SNT_SET_PROTECTED, SNT_DEF_PRIVATE, SNT_DEF_PROTECTED,
SNT_USE, SNT_LOCAL_USE, SNT_STRUCT, SNT_ENUM, SNT_CONSTRUCTOR, SNT_DESTRUCTOR, SNT_DUPLICATOR,
SNT_STRUCT_AS, SNT_AS, SNT_SIZE_OF,
SNT_PUTS, SNT_IF, SNT_WHILE, SNT_NEW,
SNT_GROUND, SNT_ROOT, SNT_EXPR_IN_PAREN, SNT_DOT,
SNT_GENERIC, SNT_GENERIC_INIT,
SNT_PRAGMA
} 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);
void printSolsNode(SolsNode* node);
// Deep copies a SolsNode
SolsNode deepCopySolsNode(SolsNode node);
#endif

2675
src/parser/parser.c Normal file

File diff suppressed because it is too large Load Diff

99
src/parser/parser.h Normal file
View File

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

230
src/typeparser/typeparser.c Normal file
View File

@@ -0,0 +1,230 @@
#include "typeparser.h"
#include "../lexer/SolsToken.h"
#include "../include/error.h"
#include <string.h>
ResultType(SolsType, charptr) parseType(SolsTokens* in, size_t* index) {
if (*index >= in->count) {
return Error(SolsType, charptr, "Unexpected end of tokens while parsing type");
}
SolsToken* token = &in->at[*index];
if (token->type == STT_TYPE) {
// It's already a basic type token
ResultType(SolsType, charptr) res = copySolsType(&token->as.type);
(*index)++;
return res;
}
if (token->type == STT_IDENTIFIER) {
if (strcmp(token->as.idName, "fun") == 0) {
(*index)++; // skip fun
if (*index >= in->count || in->at[*index].type != STT_OPEN_PAREN) {
return Error(SolsType, charptr, "Expected '(' after 'fun' in type signature");
}
(*index)++; // skip (
ResultType(SolsType, charptr) funTypeRes = createSolsType(STT_FUN);
if (funTypeRes.error) return funTypeRes;
SolsType funType = funTypeRes.as.success;
if (*index < in->count && in->at[*index].type != STT_CLOSE_PAREN) {
for (;;) {
ResultType(SolsType, charptr) argType = parseType(in, index);
if (argType.error) {
freeSolsType(&funType);
return argType;
}
addChildToSolsType(&funType, argType.as.success, NULL);
freeSolsType(&argType.as.success); // addChildToSolsType copies it
if (*index < in->count && in->at[*index].type == STT_COMMA) {
(*index)++; // skip comma
} else {
break;
}
}
}
if (*index >= in->count || in->at[*index].type != STT_CLOSE_PAREN) {
freeSolsType(&funType);
return Error(SolsType, charptr, "Expected ')' after function arguments in type signature");
}
(*index)++; // skip )
// Return type
ResultType(SolsType, charptr) retType = parseType(in, index);
if (retType.error) {
freeSolsType(&funType);
return retType;
}
// Allocate memory for returnType pointer
funType.returnType = malloc(sizeof(SolsType));
if (funType.returnType == NULL) {
freeSolsType(&funType);
freeSolsType(&retType.as.success);
return Error(SolsType, charptr, "Couldn't allocate memory (in parseType() function)");
}
*funType.returnType = retType.as.success;
return Success(SolsType, charptr, funType);
}
if (strcmp(token->as.idName, "object") == 0 || strcmp(token->as.idName, "template") == 0) {
bool isTemplate = strcmp(token->as.idName, "template") == 0;
(*index)++; // skip object/template
if (*index >= in->count || in->at[*index].type != STT_OPEN_PAREN) {
return Error(SolsType, charptr, "Expected '(' after object/template in type signature");
}
(*index)++; // skip (
ResultType(SolsType, charptr) complexTypeRes = createSolsType(isTemplate ? STT_TEMPLATE : STT_OBJECT);
if (complexTypeRes.error) return complexTypeRes;
SolsType complexType = complexTypeRes.as.success;
if (*index < in->count && in->at[*index].type != STT_CLOSE_PAREN) {
for (;;) {
ResultType(SolsType, charptr) fieldType = parseType(in, index);
if (fieldType.error) {
freeSolsType(&complexType);
return fieldType;
}
char* fieldName = NULL;
if (*index < in->count && in->at[*index].type == STT_IDENTIFIER) {
fieldName = in->at[*index].as.idName;
(*index)++;
}
addChildToSolsType(&complexType, fieldType.as.success, fieldName);
freeSolsType(&fieldType.as.success);
if (*index < in->count && in->at[*index].type == STT_COMMA) {
(*index)++; // skip comma
} else {
break;
}
}
}
if (*index >= in->count || in->at[*index].type != STT_CLOSE_PAREN) {
freeSolsType(&complexType);
return Error(SolsType, charptr, "Expected ')' in type signature");
}
(*index)++; // skip )
return Success(SolsType, charptr, complexType);
}
// Handle user-defined types (as identifiers)
// For now, let's just create an OBJECT type with identifierType set
ResultType(SolsType, charptr) userTypeRes = createSolsType(STT_OBJECT);
if (userTypeRes.error) return userTypeRes;
SolsType userType = userTypeRes.as.success;
userType.identifierType = malloc(strlen(token->as.idName) + 1);
if (userType.identifierType == NULL) {
freeSolsType(&userType);
return Error(SolsType, charptr, "Couldn't allocate memory (in parseType() function)");
}
strcpy(userType.identifierType, token->as.idName);
(*index)++;
return Success(SolsType, charptr, userType);
}
return Error(SolsType, charptr, "Unexpected token while parsing type");
}
ResultType(SolsTokens, charptr) addTypeInfo(SolsTokens* in) {
ResultType(SolsTokens, charptr) tokensres = createSolsTokens();
if (tokensres.error) {
return tokensres;
}
SolsTokens tokens = tokensres.as.success;
for (size_t i = 0; i < in->count; ) {
if (in->at[i].type == STT_IDENTIFIER &&
(strcmp(in->at[i].as.idName, "fun") == 0 ||
strcmp(in->at[i].as.idName, "object") == 0 ||
strcmp(in->at[i].as.idName, "template") == 0)) {
size_t startIndex = i;
ResultType(SolsType, charptr) typeRes = parseType(in, &i);
if (typeRes.error) {
// For now, return error
return Error(SolsTokens, charptr, typeRes.as.error);
}
ResultType(SolsToken, charptr) tokenRes = createSolsToken(STT_TYPE, typeRes.as.success);
if (tokenRes.error) {
freeSolsType(&typeRes.as.success);
return Error(SolsTokens, charptr, tokenRes.as.error);
}
// Add line info from original token
tokenRes.as.success.line.num = in->at[startIndex].line.num;
if (in->at[startIndex].line.content) {
tokenRes.as.success.line.content = malloc(strlen(in->at[startIndex].line.content) + 1);
if (tokenRes.as.success.line.content) {
strcpy(tokenRes.as.success.line.content, in->at[startIndex].line.content);
}
} else {
tokenRes.as.success.line.content = NULL;
}
addTokenToSolsTokens(&tokens, tokenRes.as.success);
// SolsToken now owns typeRes.as.success and its buffers.
} else {
// Need to deep copy the token because addTokenToSolsTokens just copies the struct
// and freeSolsToken will free buffers.
// Wait, if we use the same tokens, it's fine.
// But 'in' tokens might be freed later.
// Actually, we are creating a *new* SolsTokens.
// So we should probably copy the token properly.
SolsToken original = in->at[i];
SolsToken copy = {0};
copy.type = original.type;
copy.line.num = original.line.num;
if (original.line.content) {
copy.line.content = malloc(strlen(original.line.content) + 1);
if (copy.line.content) strcpy(copy.line.content, original.line.content);
}
switch (original.type) {
case STT_IDENTIFIER:
copy.as.idName = malloc(strlen(original.as.idName) + 1);
if (copy.as.idName) strcpy(copy.as.idName, original.as.idName);
break;
case STT_KW_GROUND:
copy.as.inlineGround = malloc(strlen(original.as.inlineGround) + 1);
if (copy.as.inlineGround) strcpy(copy.as.inlineGround, original.as.inlineGround);
break;
case STT_LITERAL:
// Literals also need deep copy?
// SolsLiteral has stringv.
if (original.as.literal.type == SLT_STRING) {
copy.as.literal.type = SLT_STRING;
copy.as.literal.as.stringv = malloc(strlen(original.as.literal.as.stringv) + 1);
if (copy.as.literal.as.stringv) strcpy(copy.as.literal.as.stringv, original.as.literal.as.stringv);
} else {
copy.as.literal = original.as.literal;
}
break;
case STT_TYPE: {
ResultType(SolsType, charptr) copiedType = copySolsType(&original.as.type);
if (!copiedType.error) {
copy.as.type = copiedType.as.success;
}
break;
}
default:
copy.as = original.as;
break;
}
addTokenToSolsTokens(&tokens, copy);
i++;
}
}
return Success(SolsTokens, charptr, tokens);
}

View File

@@ -0,0 +1,11 @@
#ifndef TYPEPARSER_H
#define TYPEPARSER_H
#include "../lexer/SolsToken.h"
#include "../include/error.h"
// Scans a SolsTokens and identifies type signatures, like fun(int) string.
// These are then converted into a single STT_TYPE token.
ResultType(SolsTokens, charptr) addTypeInfo(SolsTokens* in);
#endif

81
src/wasm_main.c Normal file
View File

@@ -0,0 +1,81 @@
#include <emscripten.h>
#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include "lexer/lexer.h"
#include "typeparser/typeparser.h"
#include "parser/parser.h"
#include "codegen/codegen.h"
#include <groundvm.h>
static char out_buf[65536];
static int out_pos = 0;
// Defined by Ground
void wasm_print(const char* str);
EMSCRIPTEN_KEEPALIVE
const char* solstice_run(char* source) {
out_pos = 0;
out_buf[0] = '\0';
// 1. Lex
ResultType(SolsLexer, charptr) lexer = createLexer(source);
if (lexer.error) {
snprintf(out_buf, sizeof(out_buf),
"[lex error] %s", lexer.as.error);
return out_buf;
}
ResultType(Nothing, charptr) lexed = lex(&lexer.as.success);
if (lexed.error) {
snprintf(out_buf, sizeof(out_buf), "%s", lexed.as.error);
return out_buf;
}
// 2. Type parse
ResultType(SolsTokens, charptr) typed = addTypeInfo(&lexer.as.success.output);
if (typed.error) {
snprintf(out_buf, sizeof(out_buf), "%s", typed.as.error);
return out_buf;
}
// 3. Parse
ResultType(SolsParser, charptr) parser = createSolsParser(&typed.as.success);
if (parser.error) {
snprintf(out_buf, sizeof(out_buf),
"[parse error] %s", parser.as.error);
return out_buf;
}
ResultType(Nothing, charptr) parsed = parse(&parser.as.success);
if (parsed.error) {
snprintf(out_buf, sizeof(out_buf), "%s", parsed.as.error);
return out_buf;
}
// 4. Codegen
SolsScope scope = {
.variables = NULL,
.tmpCounter = 0,
.returnType = createSolsType(STT_INT).as.success
};
ResultType(GroundProgram, charptr) codegen =
generateCode(&parser.as.success.output, &scope);
if (codegen.error) {
snprintf(out_buf, sizeof(out_buf), "%s", codegen.as.error);
return out_buf;
}
// 5. Run
GroundValue retval = groundRunProgram(&codegen.as.success);
if (out_pos == 0) {
// Program produced no output — report exit code
snprintf(out_buf, sizeof(out_buf),
"[exited with code %d]",
retval.type == INT ? retval.data.intVal : 0);
}
return out_buf;
}

11
tests/enum.sols Normal file
View File

@@ -0,0 +1,11 @@
enum Food {
Apple, Banana, Orange,
}
def function(Food food) Food {
return food
}
puts Food
puts function(Food.Banana)

46
tests/generics.sols Normal file
View File

@@ -0,0 +1,46 @@
struct Hash {
protected hashstr = ""
}
struct Person {
age = 0
name = ""
constructor(int age, string name) {
self.age = age
self.name = name
}
as Hash {
// hash the Person here
}
}
struct HashTable<Key can Hash, Value> {
private ptr = 0
protected size = 0
private capacity = 0
def find(Key key) {
hash = key as Hash
// find in table using hash
}
def set(Key key, Value value) {
hash = key as Hash
// store Value in the table
}
constructor() {
// malloc the pointer and set up hash information
}
duplicator {
// copy all contents
}
destructor {
// free the ptr
}
}

23
tests/nested-object.sols Normal file
View File

@@ -0,0 +1,23 @@
struct x {
a = 5
def modifyA(int a) int {
self.a = a
return a
}
}
struct y {
b = new x
}
z = new y
puts z
puts z.b
puts z.b.a
z.b.a = 10
puts z
z.b.modifyA(15)
puts z

7
tests/sizeof.sols Normal file
View File

@@ -0,0 +1,7 @@
puts sizeof "dingleing"
struct MyStruct {
size = 32
}
puts sizeof new MyStruct

View File

@@ -19,6 +19,15 @@ struct Person {
// We don't need to do anything here.
}
duplicator {
puts "Duplicator called!"
// Also nothing here
}
as string {
return "dingus"
}
}
max = Person("Max", 16)
@@ -26,6 +35,7 @@ max = Person("Max", 16)
puts max
puts max.greet()
puts max.name
puts max as string
// puts max.age (causes compile time error, age is private)
// max.name = "Maximilian" (causes compile time error, name is protected)

View File

@@ -5,7 +5,7 @@ if exists("b:current_syntax")
endif
" Keywords
syn keyword solsKeyword puts if while def lambda return use struct ground
syn keyword solsKeyword puts if while def lambda return use struct new private protected constructor destructor duplicator as ground
syn keyword solsBool true false
" Types
@@ -23,7 +23,7 @@ syn match solsInt /\<[0-9]\+\>/
syn match solsOperator /+\|-\|\*\|\/\|=\|!\|>\|<\|+=\|-=\|\*=\|\/=\|++\|--\|==\|!=\|>=\|<=/
" Delimiters
syn match solsDelimiter /[{}(),;]/
syn match solsDelimiter /[{}(),;\[\]]/
" Comments
syn match solsComment /\/\/.*$/