Friendship ended with C++, C is my new best friend

This commit is contained in:
2026-03-01 16:00:03 +11:00
parent 139be30e2d
commit 38473f0e01
32 changed files with 5996 additions and 2518 deletions

View File

@@ -1,5 +1,5 @@
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Isrc
CXX = gcc
CXXFLAGS = -Wall -Wextra -pedantic -O3 -ggdb
LDFLAGS = -lgroundvm
BUILD_DIR = build
@@ -9,8 +9,8 @@ PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
LIBDIR = /usr/lib
SRCS = $(SRC_DIR)/main.cpp $(SRC_DIR)/argparser.cpp $(SRC_DIR)/lexer.cpp $(SRC_DIR)/parser.cpp $(SRC_DIR)/error.cpp
OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(BUILD_DIR)/%.o, $(SRCS))
SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/codegen/SolsScope.c $(SRC_DIR)/codegen/codegen.c $(SRC_DIR)/lexer/SolsLiteral.c $(SRC_DIR)/lexer/SolsToken.c $(SRC_DIR)/lexer/SolsType.c $(SRC_DIR)/lexer/lexer.c $(SRC_DIR)/parser/SolsNode.c $(SRC_DIR)/parser/parser.c
OBJS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
TARGET = solstice
all: $(TARGET)
@@ -18,7 +18,7 @@ all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
install: $(TARGET)
@@ -29,7 +29,7 @@ install: $(TARGET)
cp -r libs/* $(LIBDIR)/solstice
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
mkdir -p $(BUILD_DIR) $(BUILD_DIR)/codegen $(BUILD_DIR)/lexer $(BUILD_DIR)/parser
clean:
rm -rf $(BUILD_DIR) $(TARGET)

View File

@@ -1,79 +0,0 @@
#include <string>
#include <optional>
#include <iostream>
#include <cstring>
#include "argparser.h"
namespace ArgParser {
bool nostdlib = false;
void printHelp() {
std::cout << "Solstice compiler\n";
std::cout << "This program takes a Solstice source file (.sols) and compiles it to Ground\n";
std::cout << "Usage:\n";
std::cout << " solstice inputFile [-h] [--help] [-o file] [--output file] [-t type] [--type type]\n";
std::cout << "Options:\n";
std::cout << " -h or --help\n";
std::cout << " Shows this help message\n";
std::cout << " -o file or --output file\n";
std::cout << " Specifies where to output the compiled Ground program.\n";
std::cout << " If this option is omitted, the program is automatically run.\n";
std::cout << " -t type or --type type\n";
std::cout << " Specifies the type of output.\n";
std::cout << " Currently supported options:\n";
std::cout << " ground, native (BETA)\n";
std::cout << " Choosing the 'ground' option outputs the compiled program as a .grnd textual representation file.\n";
std::cout << " Choosing the 'native' option uses Ground's ground->asm compiler to create a native executable.\n";
std::cout << " This feature is currently in beta, as Ground's ground->asm compiler is not fully complete.\n";
std::cout << " See https://sols.dev/docs#nativecompiler for more details\n";
}
Args parseArgs(int argc, char** argv) {
if (argc < 2) {
printHelp();
exit(1);
}
Args args;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
printHelp();
exit(1);
}
else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
i++;
if (i < argc) {
args.output = Argument(ArgType::OUTPUT, std::string(argv[i]));
continue;
} else {
std::cout << "(error) Please provide a filename to output to\n";
exit(1);
}
}
else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--type") == 0) {
i++;
if (i < argc) {
args.outputtype = Argument(ArgType::OUTPUTTYPE, std::string(argv[i]));
continue;
} else {
std::cout << "(error) Please provide an output type\n";
exit(1);
}
}
else {
if (args.file.has_value()) {
std::cout << "(error) Multiple input files provided\n";
exit(1);
}
args.file = Argument(ArgType::FILE, std::string(argv[i]));
}
}
if (!args.file.has_value()) {
std::cout << "(error) No input file provided\n";
exit(1);
}
return args;
}
} // namespace ArgParser

View File

@@ -1,29 +0,0 @@
#include <string>
#include <optional>
#include <iostream>
#include <cstring>
namespace ArgParser {
enum class ArgType {
FILE, OUTPUT, OUTPUTTYPE
};
class Argument {
public:
ArgType type;
std::string value;
Argument() = default;
Argument(ArgType type, std::string value) : type(type), value(value) {}
};
class Args {
public:
std::optional<Argument> output = {};
std::optional<Argument> outputtype = {};
std::optional<Argument> file = {};
};
void printHelp();
Args parseArgs(int argc, char** argv);
} // namespace ArgParser

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

@@ -0,0 +1,49 @@
#include "SolsScope.h"
#include "../include/uthash.h"
#include "../lexer/SolsType.h"
void addVariableToScope(SolsScope* scope, const char* name, SolsType type) {
SolsVariable* s = malloc(sizeof(SolsVariable));
strncpy(s->id, name, sizeof(s->id) - 1);
s->id[sizeof(s->id) - 1] = '\0';
s->typeinfo = type;
HASH_ADD_STR(scope->variables, id, s);
}
SolsVariable* findSolsVariable(SolsScope* scope, const char* name) {
if (scope == NULL || scope->variables == NULL || name == NULL) {
return NULL;
}
SolsVariable* s;
HASH_FIND_STR(scope->variables, name, s);
return s;
}
SolsScope copySolsScope(SolsScope* scope) {
SolsScope newScope = {
.variables = NULL,
.tmpCounter = scope->tmpCounter,
.returnType = scope->returnType
};
SolsVariable *var, *tmp;
HASH_ITER(hh, scope->variables, var, tmp) {
addVariableToScope(&newScope, var->id, var->typeinfo);
}
return newScope;
}
void destroySolsScope(SolsScope* scope) {
SolsVariable *var, *tmp;
HASH_ITER(hh, scope->variables, var, tmp) {
HASH_DEL(scope->variables, var);
free(var);
}
}

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

1036
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

View File

@@ -1,49 +0,0 @@
#include "error.h"
#include <string>
#include <iostream>
namespace Solstice {
namespace Error {
// ANSI Color Codes
const std::string RED = "\033[31m";
const std::string YELLOW = "\033[33m";
const std::string CYAN = "\033[36m";
const std::string RESET = "\033[0m";
const std::string BOLD = "\033[1m";
int amountOfErrors = 0;
void syntaxError(std::string what, int line, std::string lineContent) {
std::cout << BOLD << RED << "Syntax Error" << RESET;
if (line > 0) std::cout << " on line " << BOLD << line << RESET;
std::cout << ":\n";
if (!lineContent.empty()) {
std::cout << "\n " << CYAN << lineContent << RESET << "\n\n";
} else {
std::cout << "\n";
}
std::cout << YELLOW << "-> " << what << RESET << "\n";
amountOfErrors++;
}
void typingError(std::string what, int line, std::string lineContent) {
std::cout << BOLD << RED << "Typing Error" << RESET;
if (line > 0) std::cout << " on line " << BOLD << line << RESET;
std::cout << ":\n";
if (!lineContent.empty()) {
std::cout << "\n " << CYAN << lineContent << RESET << "\n\n";
} else {
std::cout << "\n";
}
std::cout << YELLOW << "-> " << what << RESET << "\n";
amountOfErrors++;
}
void summariseErrors() {
if (amountOfErrors > 0) {
std::cout << amountOfErrors << " errors generated.\n";
exit(1);
}
}
}
}

View File

@@ -1,15 +0,0 @@
#ifndef ERROR_H
#define ERROR_H
#include <string>
namespace Solstice {
namespace Error {
void syntaxError(std::string what, int line = 0, std::string lineContent = "");
void typingError(std::string what, int line = 0, std::string lineContent = "");
void summariseErrors();
extern int amountOfErrors;
}
}
#endif

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

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

@@ -1,204 +0,0 @@
#include <string>
#include <optional>
#include <vector>
#include <sstream>
#include "lexer.h"
namespace Solstice {
std::optional<char> Lexer::peek(int ahead) {
if (current + ahead < size) {
return input[current + ahead];
} else {
return {};
}
}
std::optional<char> Lexer::consume() {
if (current < size) {
return input[current++];
} else {
return {};
}
}
Lexer::Lexer(std::string in) : input(in), size(in.size()) {
std::stringstream ss(in);
std::string line;
while (std::getline(ss, line)) {
lines.push_back(line);
}
if (lines.empty()) lines.push_back("");
}
std::vector<Token> Lexer::lex() {
current = 0;
std::vector<Token> tokens;
std::string buf;
bool inString = false;
size_t currentLine = 1;
auto addToken = [&](std::string val) {
std::string content = "";
if (currentLine <= lines.size()) {
content = lines[currentLine - 1];
}
tokens.push_back({val, currentLine, content});
};
while (auto copt = consume()) {
char c = copt.value();
if (inString) {
buf.push_back(c);
if (c == '"') {
addToken(buf);
buf.clear();
inString = false;
}
if (c == '\n') currentLine++;
continue;
}
switch (c) {
// double quotes are special
case '"': {
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
buf.push_back(c);
inString = true;
break;
}
// slash is special
case '/': {
if (peek(0).has_value() && peek(0).value() == '/') {
while (auto copt = consume()) {
char c = copt.value();
if (c == '\n') {
break;
}
}
continue;
}
std::string newToken(1, c);
auto tokenopt = peek(0);
if (tokenopt) {
char token = tokenopt.value();
if (token == '=') {
newToken += token;
consume();
}
}
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
addToken(newToken);
break;
}
// tokens which are not followed by anything
case '\n':
{
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
addToken(std::string(1, c));
currentLine++;
break;
}
case '(':
case ')':
case '}':
case ',':
case ':':
case '.':
{
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
addToken(std::string(1, c));
break;
}
// tokens which may be followed by either themselves
// or an equals sign
case '+':
case '-':
{
std::string newToken(1, c);
auto tokenopt = peek(0);
if (tokenopt) {
char token = tokenopt.value();
if (token == c || token == '=') {
newToken += token;
consume();
}
}
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
addToken(newToken);
break;
}
// tokens which may be followed by an equals sign
case '!':
case '*':
case '=':
case '>':
case '<':
{
std::string newToken(1, c);
auto tokenopt = peek(0);
if (tokenopt) {
char token = tokenopt.value();
if (token == '=') {
newToken += token;
consume();
}
}
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
addToken(newToken);
break;
}
// tokens which need a newline inserted for them
case '{':
{
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
addToken("\n");
addToken(std::string(1, c));
break;
}
// tokens which do not need to be included
case ' ':
case '\r':
{
if (!buf.empty()) {
addToken(buf);
buf.clear();
}
break;
}
default:
{
buf += c;
}
}
}
if (!buf.empty()) {
addToken(buf);
}
return tokens;
}
}

View File

@@ -1,29 +0,0 @@
#ifndef LEXER_H
#define LEXER_H
#include <string>
#include <optional>
#include <vector>
namespace Solstice {
struct Token {
std::string value;
size_t line;
std::string lineContent;
};
class Lexer {
std::string input;
std::vector<std::string> lines;
size_t size;
size_t current;
std::optional<char> peek(int ahead = 1);
std::optional<char> consume();
public:
Lexer(std::string in);
std::vector<Token> lex();
};
}
#endif

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

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

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

@@ -0,0 +1,46 @@
#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);
#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, {});
}

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

@@ -0,0 +1,83 @@
#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_PUTS, STT_KW_IF, STT_KW_WHILE, STT_KW_NEW, STT_KW_GROUND, STT_LINE_END, STT_COMMA
} SolsTokenType;
typedef char* charptr;
// Stores information about the line that the token/node is on, for printing if an error
// occurs.
// .num is the line number, .content is the line's contents.
typedef struct LineInfo {
size_t num;
char* content;
} LineInfo;
// Represents a token lexed by the lex() function.
// Most token types exclusively use the .type field, however some tokens require storing
// more data, inside the .as union.
// Those tokens are:
// STT_LITERAL: A literal value. Uses field .as.literal
// STT_TYPE: A type descriptor. Uses field .as.type
// STT_IDENTIFIER: An identifier. Uses field .as.idName
// STT_KW_GROUND: Ground code embedded inside Solstice. Uses field .as.inlineGround
typedef struct SolsToken {
SolsTokenType type;
union {
SolsLiteral literal;
SolsType type;
char* idName;
char* inlineGround;
} as;
LineInfo line;
} SolsToken;
Result(SolsToken, charptr);
// Creates a SolsToken. If the type passed in is STT_LITERAL, STT_TYPE, STT_IDENTIFIER or
// STT_KW_GROUND, the function expects another argument, corresponding to the data type
// the token holds. See the SolsToken struct for more information.
// Returns:
// Success: The created SolsToken
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(SolsToken, charptr) createSolsToken(SolsTokenType type, ...);
// Frees a SolsToken, specifically the .as field elements.
void freeSolsToken(SolsToken* token);
// Represents a Solstice program, seperated into tokens.
// .at is a pointer to the tokens
// .count is how many tokens are currently being stored
// .capacity is how many tokens worth of memory is allocated
typedef struct SolsTokens {
SolsToken* at;
size_t count;
size_t capacity;
} SolsTokens;
Result(SolsTokens, charptr);
// Creates a SolsTokens holder.
// Returns:
// Success: Constructed SolsTokens
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(SolsTokens, charptr) createSolsTokens();
// Adds a token to SolsTokens. Used by the lex() function.
// Returns:
// Success: Nothing
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(Nothing, charptr) addTokenToSolsTokens(SolsTokens* tokens, SolsToken token);
#endif

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

@@ -0,0 +1,176 @@
#include "SolsType.h"
#include "lexer.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, .children.capacity = 32, .children.at = ptr };
return Success(SolsType, charptr, type);
}
ResultType(SolsType, charptr) copySolsType(SolsType* type) {
SolsType ret = { .type = type->type, .children.count = type->children.count, .children.capacity = type->children.capacity};
// Allocate memory
SolsTypeField* ptr = malloc(sizeof(SolsTypeField) * type->children.capacity);
if (ptr == NULL) {
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
}
ret.children.at = ptr;
// Deep copy values
for (size_t i = 0; i < type->children.count; i++) {
// Copy the SolsType value
ResultType(SolsType, charptr) copied = copySolsType(&type->children.at[i].type);
if (copied.error) {
Estr err = CREATE_ESTR(copied.as.error);
APPEND_ESTR(err, " (in addChildToSolsType() function)");
return Error(SolsType, charptr, err.str);
}
ret.children.at[i].type = copied.as.success;
// Copy the name
if (type->children.at[i].name == NULL) {
ret.children.at[i].name = NULL;
} else {
ret.children.at[i].name = malloc(strlen(type->children.at[i].name) + 1);
if (ret.children.at[i].name == NULL) {
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
}
strcpy(ret.children.at[i].name, type->children.at[i].name);
}
}
return Success(SolsType, charptr, ret);
}
ResultType(Nothing, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name) {
if (type->children.capacity < type->children.count + 1) {
type->children.capacity *= 2;
SolsTypeField* ptr = realloc(type->children.at, sizeof(SolsTypeField) * type->children.capacity);
if (ptr == NULL) {
return Error(Nothing, charptr, "Couldn't allocate memory (in addChildToSolsType() function)");
}
type->children.at = ptr;
}
ResultType(SolsType, charptr) copied = copySolsType(&child);
if (copied.error) {
Estr err = CREATE_ESTR(copied.as.error);
APPEND_ESTR(err, " (in addChildToSolsType() function)");
return Error(Nothing, charptr, err.str);
}
type->children.at[type->children.count].type = copied.as.success;
if (name == NULL) {
type->children.at[type->children.count].name = NULL;
} else {
type->children.at[type->children.count].name = malloc(strlen(name) + 1);
strcpy(type->children.at[type->children.count].name, name);
}
type->children.count++;
return Success(Nothing, charptr, {});
}
void freeSolsType(SolsType* type) {
for (size_t i = 0; i < type->children.count; i++) {
// Free the name
if (type->children.at[i].name != NULL) {
free(type->children.at[i].name);
}
// Free the child SolsTypes
freeSolsType(&type->children.at[i].type);
}
// Free the field itself
free(type->children.at);
type->children.at = NULL;
// Set count and capacity to zero
type->children.count = 0;
type->children.capacity = 0;
}
bool compareTypes(SolsType* left, SolsType* right) {
if (left->type != right->type) {
return false;
}
switch (left->type) {
case STT_OBJECT: {
if (left->children.count != right->children.count) {
return false;
}
for (size_t i = 0; i < left->children.count; i++) {
if (strcmp(left->children.at[i].name, right->children.at[i].name) != 0) {
return false;
}
if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) {
return false;
}
}
return true;
}
case STT_TEMPLATE: {
if (left->children.count != right->children.count) {
return false;
}
for (size_t i = 0; i < left->children.count; i++) {
if (strcmp(left->children.at[i].name, right->children.at[i].name) != 0) {
return false;
}
if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) {
return false;
}
}
return true;
}
case STT_FUN: {
if (left->children.count != right->children.count) {
return false;
}
for (size_t i = 0; i < left->children.count; i++) {
if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) {
return false;
}
}
return true;
}
default: return true;
}
}
ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type) {
switch (type->type) {
case STT_INT: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "int"));
}
case STT_DOUBLE: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "double"));
}
case STT_STRING: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "string"));
}
case STT_BOOL: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "bool"));
}
case STT_CHAR: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "char"));
}
case STT_FUN: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "function"));
}
case STT_TEMPLATE: {
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "struct"));
}
case STT_OBJECT: {
// FIXME Do this later
return Error(GroundArg, charptr, "FIXME");
}
}
return Error(GroundArg, charptr, "How did we get here?");
}

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

@@ -0,0 +1,105 @@
#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
} SolsTypeType;
// Definition of charptr for Result() and ResultType() macros
typedef char* charptr;
struct SolsTypeField;
// Holds type information for a struct, object or function.
// Say, for example, your type signature looks like this:
// object(string x, fun(int) y)
// This is stored like this:
// SolsType {
// type: STT_OBJECT
// children: [
// {
// type: {
// type: STT_STRING
// }
// name: "x"
// }
// {
// type: {
// type: STT_FUN
// children: [
// {
// type: {
// type: STT_INT
// }
// }
// ]
// }
// name: "y"
// }
// ]
// }
//
// (Sorry for the long explaination, but it's worth it so you know how the type system works.)
//
typedef struct SolsType {
SolsTypeType type;
// For use when type is identified with a name
char* identifierType;
// For use in functions
struct SolsType* returnType;
// For use by fun, template, object
struct {
struct SolsTypeField* at;
size_t count;
size_t capacity;
} children;
} SolsType;
// Assists with holding child types in the SolsType struct.
typedef struct SolsTypeField {
SolsType type;
char* name;
} SolsTypeField;
Result(SolsType, charptr);
// Creates a SolsType, with the provided type type.
// Use the "addChildToSolsType()" function to add children, in case this type has children.
// Returns:
// Success: The constructed SolsType
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(SolsType, charptr) createSolsType(SolsTypeType in);
Result(Nothing, charptr);
// Adds a child SolsType to a given SolsType.
// Returns:
// Success: Nothing
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(Nothing, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name);
// Makes a deep copy of a SolsType.
ResultType(SolsType, charptr) copySolsType(SolsType* type);
Result(GroundArg, charptr);
// Represents a SolsType as a GroundArg (in typeref form)
ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type);
// Frees a SolsType
void freeSolsType(SolsType* type);
// Compares two SolsTypes
bool compareTypes(SolsType* left, SolsType* right);
#endif

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

@@ -0,0 +1,844 @@
#include "lexer.h"
#include "SolsLiteral.h"
#include "SolsToken.h"
#include "../include/error.h"
#include "../include/estr.h"
#include "../include/ansii.h"
#include <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},
{"ground", STT_KW_GROUND},
{"{", STT_OPEN_CURLY},
{"}", STT_CLOSE_CURLY},
{"(", STT_OPEN_PAREN},
{")", STT_CLOSE_PAREN},
{"+", STT_OP_ADD},
{"-", STT_OP_SUB},
{"*", STT_OP_MUL},
{"/", STT_OP_DIV},
{"=", STT_OP_SET},
{"+=", STT_OP_ADDTO},
{"-=", STT_OP_SUBTO},
{"*=", STT_OP_MULTO},
{"/=", STT_OP_DIVTO},
{"++", STT_OP_INCREMENT},
{"--", STT_OP_DECREMENT},
{"==", STT_OP_EQUAL},
{"!=", STT_OP_INEQUAL},
{">", STT_OP_GREATER},
{"<", STT_OP_LESSER},
{">=", STT_OP_EQGREATER},
{"<=", STT_OP_EQLESSER},
{"\n", STT_LINE_END},
{";", STT_LINE_END},
{",", STT_COMMA},
// Shh, this is our little secret
// Your reward for actually reading the source code
// Enable this by adding -DSUPER_SILLY_MODE to your
// compile flags (not recommended for production)
#ifdef SUPER_SILLY_MODE
{"plus", STT_OP_ADD},
{"minus", STT_OP_SUB},
{"times", STT_OP_MUL},
{"dividedby", STT_OP_DIV},
{"then", STT_OPEN_CURLY},
{"do", STT_OPEN_CURLY},
{"end", STT_CLOSE_CURLY},
{"is", STT_OP_SET},
{"equals", STT_OP_EQUAL},
{"greaterthan", STT_OP_GREATER},
{"lesserthan", STT_OP_LESSER},
{"increment", STT_OP_INCREMENT},
{"decrement", STT_OP_DECREMENT},
{"adds", STT_OP_ADDTO},
{"subtracts", STT_OP_SUBTO},
{"multiplies", STT_OP_MULTO},
{"divides", STT_OP_DIVTO},
#endif
};
ResultType(SolsTokenType, Nothing) getTokenType(const char* input) {
size_t mapsize = sizeof(SolsTokenTypeMap) / sizeof(struct _SolsTokenTypeMap);
for (size_t i = 0; i < mapsize; i++) {
if (strcmp(input, SolsTokenTypeMap[i].str) == 0) {
return Success(SolsTokenType, Nothing, SolsTokenTypeMap[i].type);
}
}
return Error(SolsTokenType, Nothing, {});
}
static ResultType(Nothing, charptr) handleGround(SolsLexer* lexer, SolsToken* token, size_t* lineNum, Estr* currentLine, char currentChr, bool* skipDelimiter) {
bool foundBrace = false;
if (currentChr == '{') {
foundBrace = true;
*skipDelimiter = true;
} else {
while (true) {
ResultType(char, Nothing) peek = lexerPeek(lexer, 1);
if (peek.error) break;
if (isspace(peek.as.success)) {
char c = lexerConsume(lexer).as.success;
if (c == '\n') {
(*lineNum)++;
DESTROY_ESTR((*currentLine));
*currentLine = CREATE_ESTR("");
size_t lineStart = lexer->current;
for (size_t i = lineStart; i < lexer->inputsize; i++) {
if (lexer->input[i] == '\n') break;
char buf_tmp[] = {lexer->input[i], '\0'};
APPEND_ESTR((*currentLine), buf_tmp);
}
}
} else if (peek.as.success == '{') {
lexerConsume(lexer);
foundBrace = true;
break;
} else {
break;
}
}
}
if (!foundBrace) {
return Error(Nothing, charptr, "Expected '{' after 'ground'");
}
Estr groundBuf = CREATE_ESTR("");
int depth = 1;
while (depth > 0) {
ResultType(char, Nothing) next = lexerConsume(lexer);
if (next.error) {
DESTROY_ESTR(groundBuf);
return Error(Nothing, charptr, "Unterminated 'ground' block");
}
if (next.as.success == '{') depth++;
if (next.as.success == '}') {
depth--;
if (depth == 0) break;
}
char tmp[] = {next.as.success, '\0'};
APPEND_ESTR(groundBuf, tmp);
if (next.as.success == '\n') {
(*lineNum)++;
DESTROY_ESTR((*currentLine));
*currentLine = CREATE_ESTR("");
size_t lineStart = lexer->current;
for (size_t i = lineStart; i < lexer->inputsize; i++) {
if (lexer->input[i] == '\n') break;
char buf_tmp[] = {lexer->input[i], '\0'};
APPEND_ESTR((*currentLine), buf_tmp);
}
}
}
token->as.inlineGround = malloc(strlen(groundBuf.str) + 1);
if (token->as.inlineGround == NULL) {
DESTROY_ESTR(groundBuf);
return Error(Nothing, charptr, "Memory allocation failed (in handleGround() function)");
}
strcpy(token->as.inlineGround, groundBuf.str);
DESTROY_ESTR(groundBuf);
return Success(Nothing, charptr, {});
}
static ResultType(Nothing, charptr) identifyAndAdd(SolsLexer* lexer, Estr* buf, size_t* lineNum, Estr* currentLine, char currentChr, bool* skipDelimiter) {
if (strcmp(buf->str, "") == 0) return Success(Nothing, charptr, {});
ResultType(SolsToken, charptr) result = identifyToken(buf->str);
if (result.error) {
return Error(Nothing, charptr, result.as.error);
}
result.as.success.line.num = *lineNum;
result.as.success.line.content = malloc(strlen(currentLine->str) + 1);
if (result.as.success.line.content == NULL) {
return Error(Nothing, charptr, "Couldn't allocate memory to store line information in token (in identifyAndAdd() function)");
}
strcpy(result.as.success.line.content, currentLine->str);
if (result.as.success.type == STT_KW_GROUND) {
ResultType(Nothing, charptr) res = handleGround(lexer, &result.as.success, lineNum, currentLine, currentChr, skipDelimiter);
if (res.error) return res;
}
addTokenToSolsTokens(&lexer->output, result.as.success);
DESTROY_ESTR((*buf));
*buf = CREATE_ESTR("");
return Success(Nothing, charptr, {});
}
ResultType(SolsLexer, charptr) createLexer(char* input) {
// Copy input into the new lexer struct
char* inputcopy = malloc(strlen(input) + 1);
if (inputcopy == NULL) {
return Error(SolsLexer, charptr, "Couldn't copy string into lexer (in createLexer() function)");
}
strcpy(inputcopy, input);
// Create SolsTokens
ResultType(SolsTokens, charptr) tokens = createSolsTokens();
if (tokens.error) {
Estr e = CREATE_ESTR(tokens.as.error);
APPEND_ESTR(e, " (in createLexer() function)");
return Error(SolsLexer, charptr, e.str);
}
// Construct and return lexer
SolsLexer lexer = {
.input = inputcopy,
.inputsize = strlen(inputcopy),
.output = tokens.as.success,
.current = 0,
};
return Success(SolsLexer, charptr, lexer);
}
ResultType(char, Nothing) lexerPeek(SolsLexer* lexer, size_t ahead) {
// Reduce by 1 so peeking at the next token with 1 works
ahead--;
// Bounds and null checking
if (lexer->input == NULL) {
return Error(char, Nothing, {});
}
if (lexer->current + ahead >= lexer->inputsize) {
return Error(char, Nothing, {});
}
// Char is within bounds, return it
return Success(char, Nothing, lexer->input[lexer->current + ahead]);
}
ResultType(char, Nothing) lexerConsume(SolsLexer* lexer) {
// Bounds and null checking
if (lexer->input == NULL) {
return Error(char, Nothing, {});
}
if (lexer->current + 1 > lexer->inputsize) {
return Error(char, Nothing, {});
}
// Char is within bounds, return and increment
return Success(char, Nothing, lexer->input[lexer->current++]);
}
ResultType(SolsToken, charptr) identifyToken(const char* token) {
// Process strings
if (token[0] == '"') {
if (token[strlen(token) - 1] == '"') {
// Cut out the quotes
char* tokencopy = malloc(strlen(token) + 1);
strncpy(tokencopy, token + 1, strlen(token) - 2);
tokencopy[strlen(token) - 2] = '\0';
// Create a literal
ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_STRING, tokencopy);
// Free our copy of the string, createSolsLiteral creates a copy
free(tokencopy);
if (literal.error) {
Estr str = CREATE_ESTR(literal.as.error);
APPEND_ESTR(str, " (in identifyToken() function)");
return Error(SolsToken, charptr, str.str);
}
// Construct and return the token
SolsToken tok = {
.type = STT_LITERAL,
.as.literal = literal.as.success
};
return Success(SolsToken, charptr, tok);
}
return Error(SolsToken, charptr, "Unterminated string (in identifyToken() function)");
}
// Process characters
if (token[0] == '\'') {
if (strlen(token) != 3) {
return Error(SolsToken, charptr, "Characters can only hold one character at a time (try using \"this\" for strings?)");
}
if (token[2] == '\'') {
ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_CHAR, token[1]);
if (literal.error) {
Estr str = CREATE_ESTR(literal.as.error);
APPEND_ESTR(str, " (in identifyToken() function)");
return Error(SolsToken, charptr, str.str);
}
SolsToken tok = {
.type = STT_LITERAL,
.as.literal = literal.as.success
};
return Success(SolsToken, charptr, tok);
} else {
return Error(SolsToken, charptr, "Unterminated character (in identifyToken() function)");
}
}
// Process integers and floats
if (isdigit(token[0]) || (token[0] == '-' && strlen(token) > 1 && (isdigit(token[1]) || token[1] == '.'))) {
size_t len = strlen(token);
bool isInt = true;
bool isDouble = false;
for (size_t i = 1; i < len; i++) {
if (isInt && token[i] == '.') {
isInt = false;
isDouble = true;
continue;
}
if (!isdigit(token[i])) {
isInt = false;
isDouble = false;
}
}
if (isInt) {
int64_t newInt = atoll(token);
ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_INT, newInt);
if (literal.error) {
Estr str = CREATE_ESTR(literal.as.error);
APPEND_ESTR(str, " (in identifyToken() function)");
return Error(SolsToken, charptr, str.str);
}
SolsToken tok = {
.type = STT_LITERAL,
.as.literal = literal.as.success
};
return Success(SolsToken, charptr, tok);
}
if (isDouble) {
double newDouble = atof(token);
ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_DOUBLE, newDouble);
if (literal.error) {
Estr str = CREATE_ESTR(literal.as.error);
APPEND_ESTR(str, " (in identifyToken() function)");
return Error(SolsToken, charptr, str.str);
}
SolsToken tok = {
.type = STT_LITERAL,
.as.literal = literal.as.success
};
return Success(SolsToken, charptr, tok);
}
}
// Handle boolean (true/false)
if (strcmp(token, "true") == 0) {
ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_BOOL, true);
if (literal.error) {
Estr str = CREATE_ESTR(literal.as.error);
APPEND_ESTR(str, " (in identifyToken() function)");
return Error(SolsToken, charptr, str.str);
}
SolsToken tok = {
.type = STT_LITERAL,
.as.literal = literal.as.success
};
return Success(SolsToken, charptr, tok);
}
if (strcmp(token, "false") == 0) {
ResultType(SolsLiteral, charptr) literal = createSolsLiteral(SLT_BOOL, false);
if (literal.error) {
Estr str = CREATE_ESTR(literal.as.error);
APPEND_ESTR(str, " (in identifyToken() function)");
return Error(SolsToken, charptr, str.str);
}
SolsToken tok = {
.type = STT_LITERAL,
.as.literal = literal.as.success
};
return Success(SolsToken, charptr, tok);
}
// Process base types
if (strcmp(token, "int") == 0) {
ResultType(SolsType, charptr) type = createSolsType(STT_INT);
if (type.error) {
Estr e = CREATE_ESTR(type.as.error);
APPEND_ESTR(e, " (in identifyToken() function)");
return Error(SolsToken, charptr, e.str);
}
SolsToken tok = {
.type = STT_TYPE,
.as.type = type.as.success
};
return Success(SolsToken, charptr, tok);
}
if (strcmp(token, "double") == 0) {
ResultType(SolsType, charptr) type = createSolsType(STT_DOUBLE);
if (type.error) {
Estr e = CREATE_ESTR(type.as.error);
APPEND_ESTR(e, " (in identifyToken() function)");
return Error(SolsToken, charptr, e.str);
}
SolsToken tok = {
.type = STT_TYPE,
.as.type = type.as.success
};
return Success(SolsToken, charptr, tok);
}
if (strcmp(token, "string") == 0) {
ResultType(SolsType, charptr) type = createSolsType(STT_STRING);
if (type.error) {
Estr e = CREATE_ESTR(type.as.error);
APPEND_ESTR(e, " (in identifyToken() function)");
return Error(SolsToken, charptr, e.str);
}
SolsToken tok = {
.type = STT_TYPE,
.as.type = type.as.success
};
return Success(SolsToken, charptr, tok);
}
if (strcmp(token, "char") == 0) {
ResultType(SolsType, charptr) type = createSolsType(STT_CHAR);
if (type.error) {
Estr e = CREATE_ESTR(type.as.error);
APPEND_ESTR(e, " (in identifyToken() function)");
return Error(SolsToken, charptr, e.str);
}
SolsToken tok = {
.type = STT_TYPE,
.as.type = type.as.success
};
return Success(SolsToken, charptr, tok);
}
if (strcmp(token, "bool") == 0) {
ResultType(SolsType, charptr) type = createSolsType(STT_BOOL);
if (type.error) {
Estr e = CREATE_ESTR(type.as.error);
APPEND_ESTR(e, " (in identifyToken() function)");
return Error(SolsToken, charptr, e.str);
}
SolsToken tok = {
.type = STT_TYPE,
.as.type = type.as.success
};
return Success(SolsToken, charptr, tok);
}
// Find if it's a reserved keyword/operator
ResultType(SolsTokenType, Nothing) result = getTokenType(token);
if (!result.error) {
return Success(SolsToken, charptr, {result.as.success});
}
// No appropriate token found, it's an identifier (I hope)
SolsToken id = {
.type = STT_IDENTIFIER,
.as.idName = malloc(strlen(token) + 1)
};
if (id.as.idName == NULL) {
return Error(SolsToken, charptr, "Couldn't allocate memory to copy string (in identifyToken() function)");
}
strcpy(id.as.idName, token);
return Success(SolsToken, charptr, id);
}
char* createLexingError(size_t lineNum, char* line, char* why) {
Estr error = CREATE_ESTR(ESC_RESET ESC_BOLD ESC_RED_FG "Lexing Error " ESC_RESET ESC_YELLOW_FG "on line ");
char buf[256];
snprintf(buf, sizeof(buf), "%zu", lineNum);
APPEND_ESTR(error, buf);
APPEND_ESTR(error, ":\n\n" ESC_RESET ESC_BLUE_FG " ");
APPEND_ESTR(error, line);
APPEND_ESTR(error, "\n\n");
APPEND_ESTR(error, ESC_RESET ESC_MAGENTA_FG "-> ");
APPEND_ESTR(error, why);
APPEND_ESTR(error, "\n");
return error.str;
}
ResultType(Nothing, charptr) lex(SolsLexer* lexer) {
if (lexer->input == NULL) {
return Error(Nothing, charptr, "Lexer is not initialised");
}
Estr buf = CREATE_ESTR("");
bool inString = false;
size_t lineNum = 1;
size_t lineStart = 0;
Estr currentLine = CREATE_ESTR("");
for (; lineStart < lexer->inputsize; lineStart++) {
if (lexer->input[lineStart] == '\n') {
break;
}
char tmp[] = {lexer->input[lineStart], '\0'};
APPEND_ESTR(currentLine, tmp);
}
bool skipDelimiter = false;
for (;;) {
ResultType(char, Nothing) chr = lexerConsume(lexer);
if (chr.error) {
break;
}
skipDelimiter = false;
if (chr.as.success == '/' && !inString) {
ResultType(char, Nothing) peek = lexerPeek(lexer, 1);
if (!peek.error && peek.as.success == '/') {
// Consume characters until \n or EOF
while (true) {
ResultType(char, Nothing) next = lexerPeek(lexer, 1);
if (next.error || next.as.success == '\n') break;
lexerConsume(lexer);
}
continue;
} else if (!peek.error && peek.as.success == '*') {
// Skip the *
lexerConsume(lexer);
// Consume characters until */ or EOF
while (true) {
ResultType(char, Nothing) next = lexerConsume(lexer);
if (next.error) break;
if (next.as.success == '\n') {
lineNum++;
DESTROY_ESTR(currentLine);
currentLine = CREATE_ESTR("");
lineStart = lexer->current;
for (size_t i = lineStart; i < lexer->inputsize; i++) {
if (lexer->input[i] == '\n') break;
char tmp[] = {lexer->input[i], '\0'};
APPEND_ESTR(currentLine, tmp);
}
}
if (next.as.success == '*') {
ResultType(char, Nothing) peek2 = lexerPeek(lexer, 1);
if (!peek2.error && peek2.as.success == '/') {
lexerConsume(lexer); // skip /
break;
}
}
}
continue;
}
}
if (chr.as.success == '#' && !inString) {
while (true) {
ResultType(char, Nothing) next = lexerPeek(lexer, 1);
if (next.error || next.as.success == '\n') break;
lexerConsume(lexer);
}
continue;
}
if (chr.as.success == '\n') {
lineNum++;
DESTROY_ESTR(currentLine);
currentLine = CREATE_ESTR("");
lineStart = lexer->current;
for (size_t i = lineStart; i < lexer->inputsize; i++) {
if (lexer->input[i] == '\n') {
break;
}
char buf_tmp[] = {lexer->input[i], '\0'};
APPEND_ESTR(currentLine, buf_tmp);
}
}
if (inString) {
char str[2] = { chr.as.success, '\0' };
APPEND_ESTR(buf, str);
if (chr.as.success == '"') {
inString = false;
}
continue;
}
switch (chr.as.success) {
case '"': {
inString = true;
APPEND_ESTR(buf, "\"");
break;
}
// These characters require themselves added seperately from the previous token.
case '{':
case '}':
case '(':
case ')':
case ',':
case ':':
case ';':
case '\n':
{
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, &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

117
src/main.c Normal file
View File

@@ -0,0 +1,117 @@
#include "lexer/SolsType.h"
#include "lexer/lexer.h"
#include "parser/parser.h"
#include "codegen/codegen.h"
#include <stdio.h>
#include <groundvm.h>
char* getFileContents(const char* filename) {
// https://stackoverflow.com/questions/3747086/reading-the-whole-text-file-into-a-char-array-in-c
FILE* fp;
long lSize;
char* file;
fp = fopen(filename, "rb");
if (!fp) {
perror(filename);
exit(1);
}
fseek(fp, 0L, SEEK_END);
lSize = ftell(fp);
rewind(fp);
file = calloc(1, lSize + 1);
if (!file) {
fclose(fp);
return NULL;
}
if (1!=fread(file, lSize, 1, fp)) {
fclose(fp);
free(file);
return NULL;
}
// we done
fclose(fp);
return file;
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s [file]\n", argv[0]);
exit(1);
}
char* fileContents = NULL;
bool printProgram = false;
if (strcmp(argv[1], "-p") == 0) {
if (argc < 3) {
printf("Usage: %s -p [file]\n", argv[0]);
exit(1);
}
printProgram = true;
fileContents = getFileContents(argv[2]);
} else {
fileContents = getFileContents(argv[1]);
}
if (fileContents == NULL) {
printf("Couldn't read that file :(\n");
exit(1);
}
// Lex file
ResultType(SolsLexer, charptr) lexer = createLexer(fileContents);
if (lexer.error) {
printf("Error while creating lexer: %s", lexer.as.error);
exit(1);
}
ResultType(Nothing, charptr) lexed = lex(&lexer.as.success);
if (lexed.error) {
printf("%s\n", lexed.as.error);
exit(1);
}
// Parse file
ResultType(SolsParser, charptr) parser = createSolsParser(&lexer.as.success.output);
if (parser.error) {
printf("Error while creating parser: %s\n", parser.as.error);
exit(1);
}
ResultType(Nothing, charptr) parsed = parse(&parser.as.success);
if (parsed.error) {
printf("%s\n", parsed.as.error);
exit(1);
}
SolsScope scope = {
.variables = NULL,
.tmpCounter = 0,
.returnType = createSolsType(STT_INT).as.success
};
// Do codegen on root node
ResultType(GroundProgram, charptr) codegen = generateCode(&parser.as.success.output, &scope);
if (codegen.error) {
printf("%s\n", codegen.as.error);
exit(1);
}
if (printProgram) {
groundPrintProgram(&codegen.as.success);
exit(0);
}
// Run program on GroundVM
GroundValue retval = groundRunProgram(&codegen.as.success);
if (retval.type == INT) {
return retval.data.intVal;
} else {
return 0;
}
}

View File

@@ -1,90 +0,0 @@
#include <cstdio>
#include <cstdlib>
#include <groundvm.h>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "lexer.h"
#include "parser.h"
#include "argparser.h"
#include "error.h"
#define parseOneToken(token) Parser({token.value()}).parse().children[0]
namespace Solstice {
int tmpIdIterator = 0;
int labelIterator = 0;
} // namespace Solstice
int main(int argc, char** argv) {
auto args = ArgParser::parseArgs(argc, argv);
std::ifstream file(args.file->value);
std::ostringstream ss;
ss << file.rdbuf();
auto lexed = Solstice::Lexer(ss.str()).lex();
auto parsed = Solstice::Parser::Parser(lexed).parse();
GroundProgram program = Solstice::Parser::assembleProgram(parsed);
Solstice::Error::summariseErrors();
if (args.output.has_value()) {
if (!args.outputtype.has_value() || args.outputtype.value().value == "ground") {
std::FILE* originalStdout = stdout;
std::FILE* tmp = std::tmpfile();
if (!tmp) {
std::cout << "Failed to create tmp file\n";
exit(1);
}
stdout = tmp;
groundPrintProgram(&program);
std::fflush(tmp);
std::fseek(tmp, 0, SEEK_END);
long size = std::ftell(tmp);
std::fseek(tmp, 0, SEEK_SET);
std::string result(size, '\0');
std::fread(&result[0], 1, size, tmp);
stdout = originalStdout;
std::fclose(tmp);
std::ofstream outputFile(args.output.value().value);
if (!outputFile) {
std::cout << "Failed to write to " << args.output.value().value << "\n";
}
outputFile << "#!/usr/bin/env ground\n" << result;
outputFile.close();
system(std::string("chmod +x " + args.output.value().value).c_str());
exit(0);
} else if (args.outputtype.value().value == "native") {
std::string assembly(groundCompileProgram(&program));
std::string folder = "." + args.output.value().value + "_solsbuild";
std::filesystem::create_directory(folder);
std::ofstream asmfile(folder + "/program.s");
if (asmfile) {
asmfile << assembly;
asmfile.close();
} else {
std::cout << "Failed to create temporary file for assembly\n";
exit(1);
}
std::string command = "nasm -f elf64 -o " + folder + "/program.o " + folder + "/program.s";
std::system(command.c_str());
command = "ld -o " + args.output.value().value + " " + folder + "/program.o";
std::system(command.c_str());
exit(0);
}
} else {
GroundValue gv = groundRunProgram(&program);
if (gv.type == INT) {
return gv.data.intVal;
} else {
return 0;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,167 +0,0 @@
#ifndef PARSER_H
#define PARSER_H
#include <groundvm.h>
#include <map>
#include <vector>
#include <optional>
#include <string>
#include <variant>
#include <memory>
#include "lexer.h"
#include "error.h"
#define ensure(node, datatype, op) \
if (checkNodeReturnType(node) != datatype) { \
Error::typingError("Expected " + std::string(datatype) + " for " + op, node.line, node.lineContent); \
}
#define ensure2(node, datatype1, datatype2, op) { \
std::string dataType = checkNodeReturnType(node); \
if (!(dataType == datatype1 || dataType == datatype2)) { \
Error::typingError("Expected either " + std::string(datatype1) + " or " + std::string(datatype2) + " for " + op, node.line, node.lineContent); \
} \
}
#define ensure3(node, datatype1, datatype2, datatype3, op) { \
std::string dataType = checkNodeReturnType(node); \
if (!(dataType == datatype1 || dataType == datatype2 || dataType == datatype3)) { \
Error::typingError("Expected either " + std::string(datatype1) + ", " + std::string(datatype2) + ", or " + std::string(datatype3) + " for " + op, node.line, node.lineContent); \
} \
}
#define ensuresame(node1, node2, op) { \
std::string n1t = checkNodeReturnType(node1); \
std::string n2t = checkNodeReturnType(node2); \
if (n1t != n2t) { \
if (!(n1t == "int" && n2t == "double" || n1t == "double" && n2t == "int")) { \
Error::typingError("Expected types to be the same for " + std::string(op) + " (got types '" + n1t + "' and '" + n2t + "')", node1.line, node1.lineContent); \
} \
} \
}
#define exists(node) \
if (node.nodeType == SolNodeType::Identifier) { \
if (variables.find(node.outputId) == variables.end()) { \
Error::syntaxError("Variable does not exist", node.line, node.lineContent); \
} \
}
namespace Solstice {
// External variables referenced in parser logic
extern int tmpIdIterator;
extern int labelIterator;
namespace Parser {
enum class SolNodeType {
Add, Subtract, Multiply, Divide, Equal, Inequal, Greater, Lesser, EqGreater, EqLesser, Set, While, If, Value, Identifier, None, Root, CodeBlock, CodeBlockStart, CodeBlockEnd, FunctionDef, FunctionCall, Expression, BracketStart, BracketEnd, Puts, Return, InlineGround, Struct, New, In, Use
};
enum class SolDataType {
Int, String, Double, Bool, Char, Function, None
};
extern std::map<std::string, std::string> variables;
class SolNode;
class SolGroundCodeBlock {
public:
std::vector<GroundInstruction> code;
std::vector<std::string> toBeDropped;
SolGroundCodeBlock() = default;
};
class SolFunction {
public:
std::string signature;
std::vector<std::pair<std::string, std::string>> parameters; // name, type
std::string returnType;
std::shared_ptr<SolNode> content;
std::string name;
SolFunction() = default;
};
class SolData {
typedef std::variant<int64_t, std::string, double, bool, char, SolFunction> varData;
varData data;
public:
SolDataType type = SolDataType::Int;
SolData() = default;
SolData(int64_t in);
SolData(double in);
SolData(std::string in);
SolData(char in);
SolData(bool in);
SolData(SolFunction in);
std::optional<int64_t> getInt();
std::optional<double> getDouble();
std::optional<std::string> getString();
std::optional<char> getChar();
std::optional<bool> getBool();
std::optional<SolFunction> getFunction();
std::string getTypeString();
};
class SolNode {
public:
SolData data;
SolNodeType nodeType = SolNodeType::None;
std::vector<SolNode> children;
std::string outputId;
int line = 0;
std::string lineContent = "";
std::string ground = "";
SolNode(SolNodeType nodeType);
SolNode(SolNodeType nodeType, SolData data);
SolNode() = default;
void addNode(SolNode in);
void setValue(SolData in);
const std::vector<SolGroundCodeBlock> generateCode();
};
class SolStruct {
std::map<std::string, std::string> fields;
public:
std::string signature; // template(...)
std::string genSignature; // object(...)
SolStruct() = default;
friend bool operator==(const SolStruct& left, const SolStruct& right);
SolStruct(SolNode in);
};
class Parser {
std::vector<Token> tokensToParse;
size_t current;
size_t size;
std::optional<Token> peek(int ahead = 0);
std::optional<Token> consume();
bool isInt(std::string in);
bool isDouble(std::string in);
bool isString(std::string in);
bool isChar(std::string in);
bool isBool(std::string in);
SolDataType getDataType(std::string in);
SolNodeType getNodeType(std::string in);
SolNode parsePrimary();
SolNode parseExpression(int minPrec);
int getPrecedence(std::string token);
public:
Parser(std::vector<Token> in);
SolNode parse();
};
GroundProgram assembleProgram(SolNode& rootNode);
std::string checkNodeReturnType(SolNode in);
} // namespace Parser
}
#endif

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

@@ -0,0 +1,61 @@
#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] = child;
parent->children.count++;
return Success(Nothing, charptr, {});
}

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

@@ -0,0 +1,62 @@
#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_USE, SNT_STRUCT, SNT_PUTS, SNT_IF, SNT_WHILE, SNT_NEW, SNT_GROUND, SNT_ROOT
} SolsNodeType;
struct SolsNode;
// Represents an AST node.
// Most node types use the .type and .children fields, however some nodes require storing
// more data, inside the .as union.
// Those tokens are:
// SNT_LITERAL: A literal value. Uses field .as.literal
// SNT_TYPE: A type descriptor. Uses field .as.type
// SNT_IDENTIFIER: An identifier. Uses field .as.idName
// SNT_GROUND: Ground code embedded inside Solstice. Uses field .as.inlineGround
typedef struct SolsNode {
SolsNodeType type;
union {
SolsLiteral literal;
SolsType type;
char* idName;
char* inlineGround;
} as;
struct {
size_t count;
size_t capacity;
struct SolsNode* at;
} children;
LineInfo line;
// Used by the code generator, unless value is a literal do not use in parser
GroundArg accessArg;
} SolsNode;
Result(SolsNode, charptr);
// Creates a SolsNode. If the type passed in is SNT_LITERAL, SNT_TYPE, SNT_IDENTIFIER or
// SNT_KW_GROUND, the function expects another argument, corresponding to the data type
// the token holds. See the SolsNode struct for more information.
// Returns:
// Success: The created SolsNode
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(SolsNode, charptr) createSolsNode(SolsNodeType type, ...);
// Adds a child to a SolsNode.
// Returns:
// Success: Nothing
// Failure: char* detailing what went wrong (usually memory failure)
ResultType(Nothing, charptr) addChildToSolsNode(SolsNode* parent, SolsNode child);
#endif

1677
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