Compare commits

...

58 Commits

Author SHA1 Message Date
631b587d07 Fix a load of stuff 2026-03-07 16:26:54 +11:00
445ba032f5 Add unistd.sols to libraries 2026-03-07 16:26:20 +11:00
41a2fa53c6 Start work on WASM compatibility, lambda fix 2026-03-06 09:24:16 +11:00
00ef8a7d56 Type parser 2026-03-05 19:32:31 +11:00
f1eee4f6a8 Command line argument parsing 2026-03-04 12:25:59 +11:00
cfca3c1d7a Lambdas now capture state 2026-03-03 08:00:00 +11:00
40e0aec0d4 Package builder 2026-03-01 16:33:00 +11:00
38473f0e01 Friendship ended with C++, C is my new best friend 2026-03-01 16:00:03 +11:00
139be30e2d Fix spelling mistake 2026-01-29 09:07:43 +11:00
d576c7cdfc Wrap websockets in request library 2026-01-28 21:11:54 +11:00
2e4dbce100 Fix Makefile 2026-01-28 20:24:15 +11:00
ea6bf5925b Add file and request libs 2026-01-28 20:13:28 +11:00
142268c016 Output ground files as executable 2026-01-28 18:58:39 +11:00
1c6b145d35 Update Makefile 2026-01-28 17:44:38 +11:00
6002bd922b (BREAKING) "use" keyword, stdlib seperate 2026-01-28 17:03:40 +11:00
4b86fee7b5 Object field assignment 2026-01-28 14:56:36 +11:00
d12036fe70 Fix type issues with struct access 2026-01-27 21:30:50 +11:00
59e273b26e Member access (no writing yet) 2026-01-27 21:12:09 +11:00
e285b2c59f Use new to create an object from a struct 2026-01-27 14:10:57 +11:00
e3abe07f4b Add intToString() and stringToInt() functions 2026-01-27 12:19:26 +11:00
010d155f5d Start work on structs 2026-01-25 17:40:07 +11:00
44785185f7 Update syntax highlighting 2026-01-25 13:37:03 +11:00
c82f81f668 Compilation with ground->asm 2026-01-21 16:26:17 +11:00
9397f410da Use Chookspace Pages to redirect auto to sols.dev 2026-01-20 12:14:15 +11:00
6d5d29f05b Use inbuilt stdlib instead of hard coded functions 2026-01-12 16:18:59 +11:00
48c130351a Fix indentation 2026-01-12 15:43:18 +11:00
6bc483b1db Inline ground 2026-01-12 15:24:37 +11:00
c5b67bff72 Function calling fix 2026-01-12 14:45:07 +11:00
ca8db171d9 Type checking for functions 2025-12-29 10:30:07 +11:00
c266728ff0 make function calling better 2025-12-28 13:49:05 +11:00
b46a66cea7 Partially working function calling 2025-12-28 13:41:05 +11:00
337b88c148 Fix function calling bug 2025-12-26 13:50:14 +11:00
d8812fa14e Refactor type system, print all errors in file 2025-12-26 13:28:47 +11:00
24ea348858 Fix clang builds 2025-12-25 22:42:25 +11:00
9a311c3cb8 Better errors, comments 2025-12-25 22:37:40 +11:00
aa5ef0e664 Operator precedence I think 2025-12-24 22:01:16 +11:00
7ff306b9e8 Some math (no order of operations yet) 2025-12-24 16:22:51 +11:00
ac7f22e1bc Continue type checker work 2025-12-24 14:31:43 +11:00
5ec2f86b70 Start work on static type checking 2025-12-24 13:16:11 +11:00
7dd2b10603 Direct users to solstice website for docs 2025-12-23 23:47:01 +11:00
7351604571 Vim syntax highlighting 2025-12-22 20:38:02 +11:00
43310c70bf Clean up temporary variables properly 2025-12-22 19:38:06 +11:00
957e0fd95a Start work on dropping tmp vars after use 2025-12-22 14:26:12 +11:00
525b2bc733 Add >, >=, <, <= 2025-12-22 13:49:44 +11:00
d2f295f46a refactor 2025-12-21 12:06:55 +11:00
869f71466e Add argument parser, output files 2025-12-21 11:42:18 +11:00
a3a9553189 Function calling and input() builtin 2025-12-20 20:25:48 +11:00
3c66df5be0 Add fib test 2025-12-20 16:27:54 +11:00
ce058c8f9c Update README.md 2025-12-20 15:29:18 +11:00
2aff15a317 Add logo 2025-12-20 15:28:39 +11:00
72ec9c1fb6 Fixes, rebrand 2025-12-20 15:09:09 +11:00
c04e631180 Half working stuff 2025-12-20 14:47:24 +11:00
16a406b52f Variables 2025-12-20 14:28:39 +11:00
0488067ef2 While loop 2025-12-20 07:37:15 +11:00
a8e5f6a0f1 Proper string parsing 2025-12-20 06:58:41 +11:00
e8bf7b70f7 Fix lexer 2025-12-19 17:01:57 +11:00
b5c8b1b7ec WIP conditionals work 2025-12-19 16:17:57 +11:00
99bc0dbdc2 If statements 2025-12-15 11:37:27 +11:00
57 changed files with 6949 additions and 604 deletions

4
.gitignore vendored
View File

@@ -1 +1,3 @@
hg
solstice
build
.*_solsbuild

5
.project.fish Normal file
View File

@@ -0,0 +1,5 @@
# source .project.fish
alias run "make && ./solstice"
alias clean "make clean"
alias cleanrun "make clean && make && ./solstice"

46
Makefile Normal file
View File

@@ -0,0 +1,46 @@
CXX = gcc
CXXFLAGS = -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
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
$(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
clean:
rm -rf $(BUILD_DIR) $(TARGET)
.PHONY: all clean

View File

@@ -1,49 +1,21 @@
# High Ground
![](https://chookspace.com/max/solstice/raw/branch/master/logo/solstice.png)
High Ground is a programming language based on Ground.
# Solstice
It is the reference language designed to teach you how to build your own Ground-based language.
Solstice is a programming language based on Ground.
## Compiling
First, ensure CGround is installed on your system with `sudo make install`. Then, compile with
```
g++ src/main.cpp -o hg -lgroundvm
make
```
## Usage
High Ground files use the `.hg` extension. Run files as you would with any other interpreted language.
Solstice files use the `.sols` extension. Run files as you would with any other interpreted language.
## Using High Ground
## Docs
### Types
* `int`: Ground int (64 bit integer) eg 3, 7, 31432
* `double`: Ground double (Double precision floating point) eg 3.141, 2.7
* `string`: Ground string (char*) eg "Hello, World"
* `char`: Ground char (char) eg 'a'
* `bool`: Ground bool (either true or false)
### Printing
Prefix an expression with `puts` to print it to the console.
```
puts "Hello, world!"
puts 3.141
puts 7
puts 'a'
puts true
```
### Math
Add numbers with `+` (more operations coming soon)
```
puts 1 + 1
puts 3.14 + 2.7
puts 532 + 314 + 89432
```
Docs are avaliable at https://sols.dev/docs/

14
chookspace/index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirecting to sols.dev</title>
</head>
<body>
<p>Redirecting to sols.dev, if that doesn't work <a href="https://sols.dev">click here</a>.</p>
<script>
window.location.href = "https://sols.dev";
</script>
</body>
</html>

15
libs/conversions.sols Normal file
View File

@@ -0,0 +1,15 @@
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
}

6
libs/file.sols Normal file
View File

@@ -0,0 +1,6 @@
def file_Read(string file) string {}
def file_Write(string file, string content) {}
ground {
extern "fileio"
}

23
libs/io.sols Normal file
View File

@@ -0,0 +1,23 @@
def input(string msg) string {
retval = ""
ground {
print $msg
input &retval
}
return retval
}
def print(string msg) string {
ground {
print $msg
}
return msg
}
def println(string msg) string {
ground {
println $msg
}
return msg
}

12
libs/request.sols Normal file
View File

@@ -0,0 +1,12 @@
def request_Get(string url) string {}
def request_Post(string url, string data) string {}
def ws_Connect(string url) int {}
def ws_Send(int connId, string data) int {}
def ws_SendBinary(int connId, string data) int {}
def ws_Receive(int connId) string {}
def ws_ReceiveTimeout(int connId, int timeout) string {}
def ws_Close(int connId) bool {}
ground {
extern "request"
}

43
libs/unistd.sols Normal file
View File

@@ -0,0 +1,43 @@
def unistd_Crypt(string key, string value) string {}
def unistd_GetHostId() string {}
def unistd_SetHotId(int hostid) int {}
def unistd_GetHostname() string {}
def unistd_SetHostname(string name) int {}
def unistd_Alarm(int seconds) int {}
def unistd_Pause() int {}
unistd_F_OK = 0
unistd_R_OK = 0
unistd_W_OK = 0
unistd_X_OK = 0
def unistd_Access(string path, int mode) int {}
def unistd_Chdir(string path) int {}
def unistd_Chown(string path, int owner, int group) int {}
def unistd_Link(string oldpath, string newpath) int {}
def unistd_Rmdir(string path) int {}
def unistd_Symlink(string target, string linkpath) int {}
def unistd_Exit(int status) int {}
def unistd_Fork() int {}
def unistd_GetPid() int {}
def unistd_GetPPid() int {}
def unistd_GetSid(int pid) int {}
def unistd_Nice(int inc) int {}
def unistd_SetSid() int {}
def unistd_Sleep(int seconds) int {}
def unistd_GetGid() int {}
def unistd_GetEGid() int {}
def unistd_GetUid() int {}
def unistd_GetEUid() int {}
def unistd_GetLogin() string {}
def unistd_SetEUid(int euid) int {}
def unistd_SetEGid(int egid) int {}
def unistd_SetREUid(int ruid, int euid) int {}
def unistd_SetREGid(int rgid, int egid) int {}
ground {
extern "unistd"
}

BIN
logo/solstice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

78
logo/solstice.svg Normal file
View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="solstice.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.8101934"
inkscape:cx="341.39999"
inkscape:cy="633.35776"
inkscape:window-width="1908"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<linearGradient
id="linearGradient8"
inkscape:collect="always">
<stop
style="stop-color:#64ffe1;stop-opacity:1;"
offset="0"
id="stop8" />
<stop
style="stop-color:#a3b9f5;stop-opacity:1;"
offset="0.5"
id="stop10" />
<stop
style="stop-color:#d1b8fb;stop-opacity:1;"
offset="0.75"
id="stop11" />
<stop
style="stop-color:#f3cdff;stop-opacity:1;"
offset="1"
id="stop9" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8"
id="linearGradient9"
x1="-42.253399"
y1="135.74071"
x2="-41.225731"
y2="239.96782"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.73239966,-0.77274564,0.76516532,0.73965537,-4.0932457,-8.6853091)" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path1"
style="fill:url(#linearGradient9);fill-opacity:1;stroke:#ffffff;stroke-width:0.281629;stroke-opacity:0"
d="m 100.38267,124.7767 a 31.775753,32.090549 0 0 0 -22.257004,9.893 31.775753,32.090549 0 0 0 0.979215,45.36846 l -0.0038,0.004 45.917939,44.38672 43.95132,-46.37229 -45.91795,-44.38673 -0.004,0.004 a 31.775753,32.090549 0 0 0 -22.66587,-8.8969 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

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

1033
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

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

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

266
src/main.c Normal file
View File

@@ -0,0 +1,266 @@
#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>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif
#include <groundvm.h>
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;
}
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);
return NULL;
}
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) {
Args args = parseArgs(argc, argv);
if (args.action == SA_EXIT) {
return 1;
}
if (args.action == SA_EXITOK) {
return 0;
}
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

@@ -1,563 +0,0 @@
#include <cctype>
#include <cstdint>
#include <groundvm.h>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <optional>
#include <variant>
#define parseOneToken(token) Parser({token.value()}).parse().children[0]
namespace HighGround {
int tmpIdIterator = 0;
namespace Parser {
enum class HGNodeType {
Add, Subtract, Equal, Set, While, If, Value, Identifier, None, Root, CodeBlock, CodeBlockStart, CodeBlockEnd, Puts
};
enum class HGDataType {
Int, String, Double, Bool, Char, None
};
class HGNode;
class HGGroundCodeBlock {
public:
std::vector<GroundInstruction> code;
HGGroundCodeBlock() = default;
};
class HGData {
typedef std::variant<int64_t, std::string, double, bool, char> varData;
varData data;
public:
HGDataType type = HGDataType::Int;
HGData() = default;
HGData(int64_t in) : data(in), type(HGDataType::Int) {}
HGData(double in) : data(in), type(HGDataType::Double) {}
HGData(std::string in) : data(in), type(HGDataType::String) {}
HGData(char in) : data(in), type(HGDataType::Char) {}
HGData(bool in) : data(in), type(HGDataType::Bool) {}
std::optional<int64_t> getInt() {
if (type == HGDataType::Int) {
return std::get<int64_t>(data);
} else {
return {};
}
}
std::optional<double> getDouble() {
if (type == HGDataType::Double) {
return std::get<double>(data);
} else {
return {};
}
}
std::optional<std::string> getString() {
if (type == HGDataType::String) {
return std::get<std::string>(data);
} else {
return {};
}
}
std::optional<char> getChar() {
if (type == HGDataType::Char) {
return std::get<char>(data);
} else {
return {};
}
}
std::optional<bool> getBool() {
if (type == HGDataType::Bool) {
return std::get<bool>(data);
} else {
return {};
}
}
};
class HGNode {
HGNodeType nodeType = HGNodeType::None;
HGData data;
public:
std::vector<HGNode> children;
std::string outputId;
HGNode(HGNodeType nodeType) : nodeType(nodeType) {}
HGNode(HGNodeType nodeType, HGData data) : nodeType(nodeType), data(data) {}
HGNode() = default;
void addNode(HGNode in) {
children.push_back(in);
}
void setValue(HGData in) {
data = in;
}
const std::vector<HGGroundCodeBlock> generateCode() {
std::vector<HGGroundCodeBlock> code;
for (auto& child : children) {
auto childCode = child.generateCode();
code.insert(code.end(), childCode.begin(), childCode.end());
}
switch (nodeType) {
case HGNodeType::Value: {
outputId = "tmp_" + std::to_string(tmpIdIterator++);
HGGroundCodeBlock codeBlock;
GroundInstruction gi = groundCreateInstruction(SET);
groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, outputId.data()));
switch (data.type) {
case HGDataType::Int: {
auto dataopt = data.getInt();
if (dataopt) {
groundAddValueToInstruction(&gi, groundCreateValue(INT, dataopt.value()));
}
break;
}
case HGDataType::Double: {
auto dataopt = data.getDouble();
if (dataopt) {
groundAddValueToInstruction(&gi, groundCreateValue(DOUBLE, dataopt.value()));
}
break;
}
case HGDataType::String: {
auto dataopt = data.getString();
if (dataopt) {
groundAddValueToInstruction(&gi, groundCreateValue(STRING, dataopt.value().c_str()));
}
break;
}
case HGDataType::Char: {
auto dataopt = data.getChar();
if (dataopt) {
groundAddValueToInstruction(&gi, groundCreateValue(CHAR, dataopt.value()));
}
break;
}
case HGDataType::Bool: {
auto dataopt = data.getBool();
if (dataopt) {
groundAddValueToInstruction(&gi, groundCreateValue(BOOL, dataopt.value()));
}
break;
}
}
codeBlock.code.push_back(gi);
code.push_back(codeBlock);
break;
}
case HGNodeType::Add: {
HGGroundCodeBlock codeBlock;
outputId = "tmp_" + std::to_string(tmpIdIterator++);
GroundInstruction gi = groundCreateInstruction(ADD);
if (children.size() < 2) {
std::cout << "Need more stuff to add\n";
}
groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, children[0].outputId.data()));
groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, children[1].outputId.data()));
groundAddReferenceToInstruction(&gi, groundCreateReference(DIRREF, outputId.data()));
codeBlock.code.push_back(gi);
code.push_back(codeBlock);
break;
}
case HGNodeType::Puts: {
HGGroundCodeBlock codeBlock;
GroundInstruction gi = groundCreateInstruction(PRINTLN);
if (children.size() < 1) {
std::cout << "Need more stuff to puts\n";
}
groundAddReferenceToInstruction(&gi, groundCreateReference(VALREF, children[0].outputId.data()));
codeBlock.code.push_back(gi);
code.push_back(codeBlock);
break;
}
default: {}
}
return code;
}
};
class Parser {
std::vector<std::string> tokensToParse;
size_t current;
size_t size;
std::optional<std::string> peek(int ahead = 1) {
if (current + ahead < size) {
return tokensToParse[current + ahead];
} else {
return {};
}
}
std::optional<std::string> consume() {
if (current < size) {
return tokensToParse[current++];
} else {
return {};
}
}
bool isInt(std::string in) {
for (const char& c : in) {
if (!std::isdigit(c)) {
return false;
}
}
return true;
}
bool isDouble(std::string in) {
bool foundDot = false;
for (const char& c : in) {
if (!std::isdigit(c)) {
if (!foundDot && c == '.') {
foundDot = true;
continue;
}
return false;
}
}
return true;
}
bool isString(std::string in) {
if (in.size() > 1 && in[0] == '"' && in.back() == '"') {
return true;
}
return false;
}
bool isChar(std::string in) {
if (in.size() == 3 && in[0] == '\'' && in.back() == '\'') {
return true;
}
return false;
}
bool isBool(std::string in) {
if (in == "true" || in == "false") {
return true;
}
return false;
}
HGDataType getDataType(std::string in) {
if (isInt(in)) {
return HGDataType::Int;
}
if (isDouble(in)) {
return HGDataType::Double;
}
if (isString(in)) {
return HGDataType::String;
}
if (isChar(in)) {
return HGDataType::Char;
}
if (isBool(in)) {
return HGDataType::Bool;
}
return HGDataType::None;
}
HGNodeType getNodeType(std::string in) {
if (getDataType(in) != HGDataType::None) {
return HGNodeType::Value;
}
if (in == "+") {
return HGNodeType::Add;
}
if (in == "puts") {
return HGNodeType::Puts;
}
if (in == "if") {
return HGNodeType::If;
}
if (in == "{") {
return HGNodeType::CodeBlockStart;
}
if (in == "}") {
return HGNodeType::CodeBlockEnd;
}
return HGNodeType::None;
}
public:
Parser(std::vector<std::string> in) : tokensToParse(in) {}
HGNode parse() {
current = 0;
size = tokensToParse.size();
HGNode rootNode(HGNodeType::Root);
while (auto tokenopt = consume()) {
std::string token = tokenopt.value();
switch (getNodeType(token)) {
case HGNodeType::Value: {
switch (getDataType(token)) {
case HGDataType::Int: {
HGNode intNode(HGNodeType::Value);
intNode.setValue((int64_t) std::stoll(token));
rootNode.addNode(intNode);
break;
}
case HGDataType::Double: {
HGNode doubleNode(HGNodeType::Value);
doubleNode.setValue(std::stod(token));
rootNode.addNode(doubleNode);
break;
}
case HGDataType::String: {
HGNode stringNode(HGNodeType::Value);
stringNode.setValue(token.substr(1, token.size() - 2));
rootNode.addNode(stringNode);
break;
}
case HGDataType::Char: {
HGNode charNode(HGNodeType::Value);
charNode.setValue(token[1]);
rootNode.addNode(charNode);
break;
}
case HGDataType::Bool: {
HGNode boolNode(HGNodeType::Value);
boolNode.setValue(token == "true");
rootNode.addNode(boolNode);
break;
}
}
break;
}
case HGNodeType::Add: {
HGNode addNode(HGNodeType::Add);
addNode.addNode(rootNode.children.back());
rootNode.children.pop_back();
auto tokenopt = consume();
if (tokenopt) {
addNode.addNode(parseOneToken(tokenopt));
} else {
std::cout << "FEED ME MORE TOKENS\n";
exit(1);
}
rootNode.addNode(addNode);
break;
}
case HGNodeType::Puts: {
HGNode putsNode(HGNodeType::Puts);
std::vector<std::string> tokens;
while (auto tokenopt = consume()) {
if (tokenopt.value() == "\n") {
break;
}
tokens.push_back(tokenopt.value());
}
auto children = Parser(tokens).parse();
for (auto& child : children.children) {
putsNode.addNode(child);
}
rootNode.addNode(putsNode);
break;
}
case HGNodeType::If: {
HGNode ifNode(HGNodeType::If);
std::vector<std::string> tokens;
while (auto tokenopt = consume()) {
if (tokenopt.value() == "\n") {
break;
}
tokens.push_back(tokenopt.value());
}
auto children = Parser(tokens).parse();
if (children.children.size() != 1) {
std::cout << "Too many or too little conditions for if\n";
exit(1);
}
ifNode.addNode(children.children[0]);
tokens.clear();
size_t brackets = 1;
auto tokenopt = consume();
if (tokenopt) {
if (tokenopt.value() == "{") {
tokens.push_back(tokenopt.value());
while (auto tokenopt = consume()) {
tokens.push_back(tokenopt.value());
if (tokenopt.value() == "{") {
brackets++;
}
if (tokenopt.value() == "}") {
brackets--;
}
if (brackets == 0) {
break;
}
}
} else {
std::cout << "I want a code block\n";
exit(1);
}
} else {
std::cout << "FEED ME MORE TOKENSSSSS\n";
exit(1);
}
break;
}
case HGNodeType::CodeBlockStart: {
HGNode codeBlockNode(HGNodeType::CodeBlock);
// WIP
break;
}
}
}
return rootNode;
}
};
GroundProgram assembleProgram(HGNode& rootNode) {
GroundProgram gp = groundCreateProgram();
auto code = rootNode.generateCode();
for (int i = 0; i < code.size(); i++) {
for (const auto& inst : code[i].code) {
groundAddInstructionToProgram(&gp, inst);
}
}
return gp;
}
} // namespace Parser
class Lexer {
std::string input;
size_t size;
size_t current;
std::optional<char> peek(int ahead = 1) {
if (current + ahead < size) {
return input[current + ahead];
} else {
return {};
}
}
std::optional<char> consume() {
if (current < size) {
return input[current++];
} else {
return {};
}
}
public:
Lexer(std::string in) : input(in), size(in.size()) {};
std::vector<std::string> lex() {
current = 0;
std::vector<std::string> tokens;
std::string buf;
while (auto copt = consume()) {
char c = copt.value();
switch (c) {
// tokens which are not followed by anything
case '\n':
case '(':
case ')':
case '}':
{
if (!buf.empty()) {
tokens.push_back(buf);
buf.clear();
}
tokens.push_back(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();
if (tokenopt) {
char token = tokenopt.value();
if (token == c || token == '=') {
newToken += token;
consume();
}
}
if (!buf.empty()) {
tokens.push_back(buf);
buf.clear();
}
tokens.push_back(newToken);
break;
}
// tokens which may be followed by an equals sign
case '*':
case '/':
case '=':
{
std::string newToken(1, c);
auto tokenopt = peek();
if (tokenopt) {
char token = tokenopt.value();
if (token == '=') {
newToken += token;
consume();
}
}
if (!buf.empty()) {
tokens.push_back(buf);
buf.clear();
}
tokens.push_back(newToken);
break;
}
// tokens which need a newline inserted for them
case '{':
{
if (!buf.empty()) {
tokens.push_back(buf);
buf.clear();
}
tokens.push_back(std::string(1, c));
tokens.push_back("\n");
}
// tokens which do not need to be included
case ' ':
{
if (!buf.empty()) {
tokens.push_back(buf);
buf.clear();
}
break;
}
default:
{
buf += c;
}
}
}
if (!buf.empty()) {
tokens.push_back(buf);
}
return tokens;
}
};
} // namespace HighGround
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " (file)\n";
exit(1);
}
std::ifstream file(argv[1]);
std::ostringstream ss;
ss << file.rdbuf();
auto lexed = HighGround::Lexer(ss.str()).lex();
auto parsed = HighGround::Parser::Parser(lexed).parse();
GroundProgram program = HighGround::Parser::assembleProgram(parsed);
groundRunProgram(&program);
}

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

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

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

@@ -0,0 +1,65 @@
#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);
// Deep copies a SolsNode
SolsNode deepCopySolsNode(SolsNode node);
#endif

1698
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;
}

9
tests/closure.sols Normal file
View File

@@ -0,0 +1,9 @@
def createClosure(int x) fun(int) int {
return lambda(int y) int {
return x + y
}
}
myVar = createClosure(5)
puts myVar(3)

55
tests/compare.sols Normal file
View File

@@ -0,0 +1,55 @@
if 1 == 1 {
puts "working"
}
if 1 == 2 {
puts "not working"
}
if 1 != 2 {
puts "working"
}
if 1 != 1 {
puts "not working"
}
if 2 > 1 {
puts "working"
}
if 1 > 2 {
puts "not working"
}
if 1 < 2 {
puts "working"
}
if 2 < 1 {
puts "not working"
}
if 2 >= 2 {
puts "working"
}
if 2 >= 1 {
puts "working"
}
if 2 >= 3 {
puts "not working"
}
if 2 <= 2 {
puts "working"
}
if 2 <= 3 {
puts "working"
}
if 3 <= 2 {
puts "not working"
}

11
tests/convert.sols Normal file
View File

@@ -0,0 +1,11 @@
use conversions
use io
myString = "312"
myInt = 435
myNewString = intToString(myInt)
myNewInt = stringToInt(myString)
println(myNewString)
puts myNewInt

6
tests/count.sols Normal file
View File

@@ -0,0 +1,6 @@
number = 0
while number != 10 {
number = number + 1
puts number
}

View File

@@ -1,5 +0,0 @@
puts "dingus"
puts 432
puts 3.141
puts 'c'
puts true

7
tests/data.sols Normal file
View File

@@ -0,0 +1,7 @@
puts "dingus"
puts 432
puts 3.141
puts 'c'
puts true
puts "now we have proper string lexing!!!!!11!!@!2!1@1@12!!!112!@"

15
tests/fib.sols Normal file
View File

@@ -0,0 +1,15 @@
a = 0
b = 1
n = 92
i = 0
while i != n {
temp = a + b
a = b
b = temp
i = i + 1
}
puts a

6
tests/function.sols Normal file
View File

@@ -0,0 +1,6 @@
def add(int a, int b) int {
puts "testing"
return 3
}
puts add(1, 2)

7
tests/if.sols Normal file
View File

@@ -0,0 +1,7 @@
if 1 == 1 {
puts "huzzah"
}
if 5 == 10 {
puts "aww"
}

7
tests/inlineground.sols Normal file
View File

@@ -0,0 +1,7 @@
result = 0
ground {
println "dingus"
add 3 2 &result
}
puts result

15
tests/input.sols Normal file
View File

@@ -0,0 +1,15 @@
use io
accessNotGranted = true
while accessNotGranted {
password = input("Password: ")
if password == "dingus" {
accessNotGranted = false
}
if password != "dingus" {
puts "Incorrect password!"
}
}
puts "Welcome!"

View File

@@ -1 +1,3 @@
use io
println("dingus")

3
tests/set.sols Normal file
View File

@@ -0,0 +1,3 @@
x = 5
y = 10
puts x + y

18
tests/struct.sols Normal file
View File

@@ -0,0 +1,18 @@
use io
struct dingus {
x = 5
y = "dingus"
}
e = new dingus
puts e
puts dingus
puts e.x
println(e.y)
e.x = 7
e.y = "heheheha"
puts e.x
println(e.y)

3
tests/while.sols Normal file
View File

@@ -0,0 +1,3 @@
while 1 == 1 {
puts "yay infinite loop"
}

View File

@@ -0,0 +1 @@
au BufRead,BufNewFile *.sols setfiletype solstice

41
vim/syntax/solstice.vim Normal file
View File

@@ -0,0 +1,41 @@
" Vim highlight file for Solstice (.sols)
if exists("b:current_syntax")
finish
endif
" Keywords
syn keyword solsticeConditional if
syn keyword solsticeRepeat while
syn keyword solsticeKeyword def struct return use
syn keyword solsticeType int string bool double char
syn keyword solsticeBoolean true false
" Built-in functions
syn keyword solsticeBuiltin puts print println input
" Data Types
syn match solsticeNumber "\d\+\(\.\d\+\)\="
syn region solsticeString start=/"/ end=/"/
syn match solsticeCharacter /'[^']'/
" Operators
syn match solsticeOperator "==\|!=\|>=\|<=\|++\|--\|+\=\|-\=\|\*=\|\/="
syn match solsticeOperator "[><=+\-*/]"
" Delimiters
syn match solsticeDelimiter "[{()}]"
hi def link solsticeConditional Conditional
hi def link solsticeRepeat Repeat
hi def link solsticeKeyword Keyword
hi def link solsticeType Type
hi def link solsticeBoolean Boolean
hi def link solsticeBuiltin Function
hi def link solsticeNumber Number
hi def link solsticeString String
hi def link solsticeCharacter Character
hi def link solsticeOperator Operator
hi def link solsticeDelimiter Delimiter
let b:current_syntax = "solstice"