forked from solstice/solstice
Compare commits
139 Commits
5196f73e16
...
newground
| Author | SHA1 | Date | |
|---|---|---|---|
| 119a408a92 | |||
| d2f4d93e3d | |||
| 023506fc77 | |||
| 76af86135b | |||
| a798159f8d | |||
| b93e196ab0 | |||
| e684c32f0d | |||
| be0f633286 | |||
| ab6580b591 | |||
| b427787f73 | |||
| 4e3686bfaf | |||
| 1c0c575494 | |||
| e2c15b2f49 | |||
| dce308eb3f | |||
| 85d2a0d300 | |||
| 4ea28537e4 | |||
| dd1ac9ae71 | |||
| 1da7f91313 | |||
| 6a936bf282 | |||
| 8a8e4381bf | |||
| b78a5ee7bb | |||
| cbc92b5b1d | |||
| 45c9d9ee37 | |||
| 3003605031 | |||
| c68bf1b662 | |||
| 3c36e92261 | |||
| 246a212cb9 | |||
| 02ecef4ce2 | |||
| e0869d526e | |||
| cba5ec1fa2 | |||
| 908af13d8f | |||
| 10046703ed | |||
| ebfd1d5045 | |||
| 1e0abcc0b4 | |||
| c7bd6e7766 | |||
| 68f5868538 | |||
| 692dd6b0db | |||
| 8087a8150e | |||
| 53908d29e2 | |||
| effad2920f | |||
| ac13b7a0ae | |||
| 8b40dbd563 | |||
| 0cf63b034f | |||
| ba6ec79a10 | |||
| f261a1cd0e | |||
| 00bf654882 | |||
| 591adf79a4 | |||
| c7d4a7700e | |||
| d0d1dc7465 | |||
| f8afa2f564 | |||
| 7a81a47986 | |||
| f127c2f5ab | |||
| 23041c041a | |||
| 0bb3741c66 | |||
| af97f1b712 | |||
| dfe37de5c0 | |||
| 5d9cb02e7e | |||
| 547488964a | |||
| f0692eb940 | |||
| 67ea6cc5fc | |||
| 36030f01a2 | |||
| f384e19c06 | |||
| d24462f844 | |||
| 4351821d30 | |||
| 78f974e189 | |||
| 1dedb30a87 | |||
| 2e7b5b7480 | |||
| 1cf995f7ac | |||
| 605d0a87b1 | |||
| 16569d7355 | |||
| fd08b7cdb7 | |||
| 5841a7a999 | |||
| a2fc138ba1 | |||
| 9b55b509f5 | |||
| f694f50d70 | |||
| 5b61a11f00 | |||
| 00d6ed83fb | |||
| 6988f314b0 | |||
| 70dc5eb5a0 | |||
| 1e3bd6c601 | |||
| f66464a7cc | |||
| 631b587d07 | |||
| 445ba032f5 | |||
| 41a2fa53c6 | |||
| 00ef8a7d56 | |||
| f1eee4f6a8 | |||
| cfca3c1d7a | |||
| 40e0aec0d4 | |||
| 38473f0e01 | |||
| 139be30e2d | |||
| d576c7cdfc | |||
| 2e4dbce100 | |||
| ea6bf5925b | |||
| 142268c016 | |||
| 1c6b145d35 | |||
| 6002bd922b | |||
| 4b86fee7b5 | |||
| d12036fe70 | |||
| 59e273b26e | |||
| e285b2c59f | |||
| e3abe07f4b | |||
| 010d155f5d | |||
| 44785185f7 | |||
| c82f81f668 | |||
| 9397f410da | |||
| 6d5d29f05b | |||
| 48c130351a | |||
| 6bc483b1db | |||
| c5b67bff72 | |||
| ca8db171d9 | |||
| c266728ff0 | |||
| b46a66cea7 | |||
| 337b88c148 | |||
| d8812fa14e | |||
| 24ea348858 | |||
| 9a311c3cb8 | |||
| aa5ef0e664 | |||
| 7ff306b9e8 | |||
| ac7f22e1bc | |||
| 5ec2f86b70 | |||
| 7dd2b10603 | |||
| 7351604571 | |||
| 43310c70bf | |||
| 957e0fd95a | |||
| 525b2bc733 | |||
| d2f295f46a | |||
| 869f71466e | |||
| a3a9553189 | |||
| 3c66df5be0 | |||
| ce058c8f9c | |||
| 2aff15a317 | |||
| 72ec9c1fb6 | |||
| c04e631180 | |||
| 16a406b52f | |||
| 0488067ef2 | |||
| a8e5f6a0f1 | |||
| e8bf7b70f7 | |||
| b5c8b1b7ec | |||
| 99bc0dbdc2 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1 +1,6 @@
|
||||
hg
|
||||
solstice
|
||||
build
|
||||
.*_solsbuild
|
||||
builddir
|
||||
windows-builddir/
|
||||
.meson-cross/
|
||||
|
||||
5
.project.fish
Normal file
5
.project.fish
Normal file
@@ -0,0 +1,5 @@
|
||||
# source .project.fish
|
||||
|
||||
alias run "make && ./solstice"
|
||||
alias clean "make clean"
|
||||
alias cleanrun "make clean && make && ./solstice"
|
||||
56
Makefile
Normal file
56
Makefile
Normal file
@@ -0,0 +1,56 @@
|
||||
CXX = gcc
|
||||
|
||||
GROUND_STATIC = /usr/local/lib/libgroundvm.a
|
||||
GROUND_INCLUDE = /usr/local/include/
|
||||
|
||||
CXXFLAGS = -I$(GROUND_INCLUDE) -Wall -Wextra -pedantic -ggdb
|
||||
LDFLAGS = -lgroundvm
|
||||
|
||||
BUILD_DIR = build
|
||||
SRC_DIR = src
|
||||
LIBS_DIR = libs
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = /usr/lib
|
||||
|
||||
SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/codegen/SolsScope.c $(SRC_DIR)/codegen/codegen.c $(SRC_DIR)/lexer/SolsLiteral.c $(SRC_DIR)/lexer/SolsToken.c $(SRC_DIR)/lexer/SolsType.c $(SRC_DIR)/lexer/lexer.c $(SRC_DIR)/parser/SolsNode.c $(SRC_DIR)/parser/parser.c $(SRC_DIR)/typeparser/typeparser.c $(SRC_DIR)/interactive/interactive.c $(SRC_DIR)/repl/repl.c $(SRC_DIR)/linenoise/linenoise.c
|
||||
OBJS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
|
||||
TARGET = solstice
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS)
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
install: $(TARGET)
|
||||
mkdir -p /usr/lib/solstice
|
||||
install -d $(BINDIR)
|
||||
install -m 755 $(TARGET) $(BINDIR)/$(TARGET)
|
||||
install -d $(LIBDIR)
|
||||
cp -r libs/* $(LIBDIR)/solstice
|
||||
|
||||
static: $(OBJS)
|
||||
$(CXX) $(OBJS) $(GROUND_STATIC) -o $(TARGET)
|
||||
|
||||
$(BUILD_DIR)/solstice.tar.gz: $(TARGET) $(LIBS_DIR)
|
||||
mkdir -p $(BUILD_DIR)/pkg/bin $(BUILD_DIR)/pkg/lib/
|
||||
cp $(TARGET) $(BUILD_DIR)/pkg/bin/solstice
|
||||
cp -r $(LIBS_DIR) $(BUILD_DIR)/pkg/lib/solstice
|
||||
tar -czvf $(BUILD_DIR)/solstice.tar.gz $(BUILD_DIR)/pkg
|
||||
|
||||
package: $(BUILD_DIR)/solstice.tar.gz
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR) $(BUILD_DIR)/codegen $(BUILD_DIR)/lexer $(BUILD_DIR)/parser $(BUILD_DIR)/typeparser $(BUILD_DIR)/interactive $(BUILD_DIR)/repl $(BUILD_DIR)/linenoise
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(TARGET)
|
||||
|
||||
run: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
.PHONY: all clean run
|
||||
58
README.md
58
README.md
@@ -1,49 +1,35 @@
|
||||
# High Ground
|
||||

|
||||
|
||||
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
|
||||
First, ensure the new Ground rewrite is installed on your system with Meson. Then, compile with
|
||||
|
||||
```
|
||||
g++ src/main.cpp -o hg -lgroundvm
|
||||
meson setup builddir
|
||||
meson compile -C builddir
|
||||
```
|
||||
|
||||
## Cross-compiling for Windows
|
||||
|
||||
Requires the MinGW-w64 toolchain and a Windows build of Ground (including a static `libground.a` and `libffi.a`).
|
||||
|
||||
```
|
||||
PKG_CONFIG_PATH=.meson-cross:/usr/x86_64-w64-mingw32/lib/pkgconfig \
|
||||
meson setup windows-builddir --cross-file x86_64-w64-mingw32.txt --wipe
|
||||
PKG_CONFIG_PATH=.meson-cross:/usr/x86_64-w64-mingw32/lib/pkgconfig \
|
||||
meson compile -C windows-builddir
|
||||
```
|
||||
|
||||
The resulting `solstice.exe` will be statically linked with no external DLL dependencies beyond the Windows system runtime.
|
||||
|
||||
## 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
14
chookspace/index.html
Normal 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>
|
||||
37
libs/box.sols
Normal file
37
libs/box.sols
Normal file
@@ -0,0 +1,37 @@
|
||||
struct _box {
|
||||
private ptr = 0
|
||||
def get() int { return 0 }
|
||||
def set() int { return 0 }
|
||||
def free() int { return 0 }
|
||||
}
|
||||
|
||||
ground {
|
||||
extern "_box"
|
||||
}
|
||||
|
||||
/*
|
||||
Shared mutable object. Use with caution - may cause impurities!
|
||||
*/
|
||||
struct Box[T] {
|
||||
|
||||
private box = new _box
|
||||
|
||||
def get() T {
|
||||
retval = new T
|
||||
ground { callmethod &self &box !get &retval }
|
||||
return retval
|
||||
}
|
||||
|
||||
def set(T in) int {
|
||||
ground { callmethod &self &box !set $in &retval }
|
||||
return 0
|
||||
}
|
||||
|
||||
def free() int {
|
||||
ground { callmethod &self &box !free &retval }
|
||||
return 0
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
}
|
||||
37
libs/collections.sols
Normal file
37
libs/collections.sols
Normal file
@@ -0,0 +1,37 @@
|
||||
struct List {
|
||||
protected size = 0
|
||||
protected capacity = 0
|
||||
protected memSize = 0
|
||||
|
||||
private ptr = 0
|
||||
|
||||
constructor(int length) {}
|
||||
|
||||
def append(int value) int {}
|
||||
def insert(int value, int index) int {}
|
||||
def remove(int index) int {}
|
||||
def at(int index) int {}
|
||||
def clear() int {}
|
||||
def set(int value, int index) int {}
|
||||
def isEmpty() bool {}
|
||||
def contains(int value) bool {}
|
||||
def reverse() int {}
|
||||
def find(int value) int {}
|
||||
def reserve(int n) int {}
|
||||
}
|
||||
|
||||
struct Hashmap {
|
||||
private ptr = 0
|
||||
|
||||
constructor() {}
|
||||
|
||||
def set(string key, int value) int {}
|
||||
def get(string key) int {}
|
||||
def getOr(string key, int fallback) int {}
|
||||
def remove(string key) int {}
|
||||
def removeIfPresent(string key) bool {}
|
||||
}
|
||||
|
||||
ground {
|
||||
extern "collections"
|
||||
}
|
||||
19
libs/file.sols
Normal file
19
libs/file.sols
Normal file
@@ -0,0 +1,19 @@
|
||||
struct File {
|
||||
private fileHandle = 0
|
||||
protected filePath = ""
|
||||
protected size = 0
|
||||
protected tell = 0
|
||||
|
||||
constructor (string filePath) {}
|
||||
|
||||
def read() string {}
|
||||
def write(string buffer) int {}
|
||||
def append(string buffer) int {}
|
||||
def flush() int {}
|
||||
def seek(int offset) int {}
|
||||
def seekEnd(int offset) int {}
|
||||
}
|
||||
|
||||
ground {
|
||||
extern "fileio"
|
||||
}
|
||||
23
libs/io.sols
Normal file
23
libs/io.sols
Normal 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
12
libs/request.sols
Normal 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"
|
||||
}
|
||||
9
libs/socket.sols
Normal file
9
libs/socket.sols
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
Function that blocks the main thread which listens to TCP connections.
|
||||
All connection data will be pass
|
||||
*/
|
||||
def socket_Listen(fun(string) string handler, int port) int {}
|
||||
|
||||
ground {
|
||||
extern "socket"
|
||||
}
|
||||
28
libs/strings.sols
Normal file
28
libs/strings.sols
Normal file
@@ -0,0 +1,28 @@
|
||||
def string_CharAt(string str, int index) string {}
|
||||
|
||||
def string_Upper(string str) string {}
|
||||
def string_Lower(string str) string {}
|
||||
def string_Trim(string str) string {}
|
||||
def string_TrimLeft(string str) string {}
|
||||
def string_TrimRight(string str) string {}
|
||||
def string_Substring(string str, int start, int end) string {}
|
||||
def string_Repeat(string str, int times) string {}
|
||||
def string_Replace(string haystack, string needle) string {}
|
||||
def string_Reverse(string str) string {}
|
||||
|
||||
def string_StartsWith(string str, string prefix) bool {}
|
||||
def string_EndsWith(string str, string suffix) bool {}
|
||||
def string_Contains(string haystack, string needle) bool {}
|
||||
|
||||
def string_Find(string haystack, string needle) int {}
|
||||
def string_FindLast(string haystack, string needle) int {}
|
||||
def string_Count(string haystack, string needle) int {}
|
||||
|
||||
def string_isAlpha(string str) bool {}
|
||||
def string_isAlnum(string str) bool {}
|
||||
def string_isDigit(string str) bool {}
|
||||
def string_isSpace(string str) bool {}
|
||||
|
||||
ground {
|
||||
extern "string"
|
||||
}
|
||||
43
libs/unistd.sols
Normal file
43
libs/unistd.sols
Normal 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
BIN
logo/solstice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
78
logo/solstice.svg
Normal file
78
logo/solstice.svg
Normal 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 |
24
meson.build
Normal file
24
meson.build
Normal file
@@ -0,0 +1,24 @@
|
||||
project('solstice', 'c', version : '0.1.1')
|
||||
|
||||
sources = files(
|
||||
'src/codegen/codegen.c',
|
||||
'src/codegen/SolsScope.c',
|
||||
'src/interactive/interactive.c',
|
||||
'src/lexer/lexer.c',
|
||||
'src/lexer/SolsLiteral.c',
|
||||
'src/lexer/SolsToken.c',
|
||||
'src/lexer/SolsType.c',
|
||||
'src/main.c',
|
||||
'src/parser/parser.c',
|
||||
'src/parser/SolsNode.c',
|
||||
'src/typeparser/typeparser.c'
|
||||
)
|
||||
|
||||
ground = dependency('ground', version : '>=0.1.0')
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
executable('solstice', sources, dependencies : ground, install : true, c_args : ['-static'], link_args : ['-static'])
|
||||
else
|
||||
sources += ['src/linenoise/linenoise.c', 'src/repl/repl.c']
|
||||
executable('solstice', sources, dependencies : ground, install : true)
|
||||
endif
|
||||
55
src/codegen/SolsScope.c
Normal file
55
src/codegen/SolsScope.c
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "SolsScope.h"
|
||||
|
||||
#include "../include/uthash.h"
|
||||
#include "../lexer/SolsType.h"
|
||||
|
||||
void addVariableToScope(SolsScope* scope, const char* name, SolsType type) {
|
||||
SolsVariable* s = malloc(sizeof(SolsVariable));
|
||||
|
||||
strncpy(s->id, name, sizeof(s->id) - 1);
|
||||
s->id[sizeof(s->id) - 1] = '\0';
|
||||
|
||||
s->typeinfo = ({
|
||||
ResultType(SolsType, charptr) _result = copySolsType(&type);
|
||||
if (_result.error) {
|
||||
return;
|
||||
}
|
||||
_result.as.success;
|
||||
});
|
||||
|
||||
HASH_ADD_STR(scope->variables, id, s);
|
||||
}
|
||||
|
||||
SolsVariable* findSolsVariable(SolsScope* scope, const char* name) {
|
||||
if (scope == NULL || scope->variables == NULL || name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
SolsVariable* s;
|
||||
HASH_FIND_STR(scope->variables, name, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
SolsScope copySolsScope(SolsScope* scope) {
|
||||
SolsScope newScope = {
|
||||
.variables = NULL,
|
||||
.tmpCounter = scope->tmpCounter,
|
||||
.returnType = scope->returnType
|
||||
};
|
||||
|
||||
SolsVariable *var, *tmp;
|
||||
|
||||
HASH_ITER(hh, scope->variables, var, tmp) {
|
||||
addVariableToScope(&newScope, var->id, var->typeinfo);
|
||||
}
|
||||
|
||||
return newScope;
|
||||
}
|
||||
|
||||
void destroySolsScope(SolsScope* scope) {
|
||||
SolsVariable *var, *tmp;
|
||||
|
||||
HASH_ITER(hh, scope->variables, var, tmp) {
|
||||
HASH_DEL(scope->variables, var);
|
||||
free(var);
|
||||
}
|
||||
}
|
||||
32
src/codegen/SolsScope.h
Normal file
32
src/codegen/SolsScope.h
Normal 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
|
||||
2853
src/codegen/codegen.c
Normal file
2853
src/codegen/codegen.c
Normal file
File diff suppressed because it is too large
Load Diff
34
src/codegen/codegen.h
Normal file
34
src/codegen/codegen.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef CODEGEN_H
|
||||
#define CODEGEN_H
|
||||
|
||||
#include <ground.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.len; i++) {\
|
||||
Ground.Program.append(&program, __result.as.success.at[i]);\
|
||||
}\
|
||||
break;\
|
||||
}
|
||||
|
||||
#endif
|
||||
67
src/include/ansii.h
Normal file
67
src/include/ansii.h
Normal 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
83
src/include/error.h
Normal 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
52
src/include/estr.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef ESTR_H
|
||||
#define ESTR_H
|
||||
|
||||
/*
|
||||
|
||||
estr.h - Easy string manipulation
|
||||
This library has macros to allow easier manipulation of strings. No longer shall
|
||||
you have to malloc and realloc away to keep adding to your strings.
|
||||
|
||||
Usage:
|
||||
|
||||
Estr myString = CREATE_ESTR("my awesome string");
|
||||
APPEND_ESTR(myString, " is so cool");
|
||||
printf("%s\n", myString.str);
|
||||
|
||||
*/
|
||||
|
||||
#define CREATE_ESTR(instr) \
|
||||
(Estr) { \
|
||||
.str = instr,\
|
||||
.size = strlen(instr),\
|
||||
.shouldBeFreed = 0, \
|
||||
.destroyed = 0 \
|
||||
}
|
||||
|
||||
#define APPEND_ESTR(estr, instr) { \
|
||||
estr.size = estr.size + strlen(instr); \
|
||||
char* tmp_ptr = malloc(estr.size + 1); \
|
||||
if (tmp_ptr == NULL) printf("WARNING: Could not realloc estr " #estr "\n"); \
|
||||
else { \
|
||||
snprintf(tmp_ptr, estr.size + 1, "%s%s", estr.str, instr); \
|
||||
if (estr.shouldBeFreed > 0) free(estr.str); \
|
||||
estr.shouldBeFreed = 1; \
|
||||
estr.str = tmp_ptr; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DESTROY_ESTR(estr) if (estr.shouldBeFreed > 0 && estr.destroyed < 1) free(estr.str);
|
||||
|
||||
typedef struct Estr {
|
||||
char* str;
|
||||
size_t size;
|
||||
int8_t shouldBeFreed;
|
||||
int8_t destroyed;
|
||||
} Estr;
|
||||
|
||||
#endif // ESTR_H
|
||||
71
src/include/list.h
Normal file
71
src/include/list.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// list.h - create a list of any kind in C
|
||||
// append, pop, safely retrieve
|
||||
// licenced to you under the MIT license
|
||||
// example:
|
||||
//
|
||||
// #include "list.h"
|
||||
//
|
||||
// UseList(int);
|
||||
//
|
||||
// int main() {
|
||||
// List(int) myList = newList(int);
|
||||
// append(myList, 32);
|
||||
// printf("%d\n", *get(myList, 0));
|
||||
// pop(myList);
|
||||
// destroy(myList);
|
||||
// }
|
||||
//
|
||||
#ifndef LIST_H
|
||||
#define LIST_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LIST_H_INIT_CAPACITY 32
|
||||
|
||||
#define UseList(type) struct __List_##type { type* pointer; size_t count; size_t capacity; size_t itemsize; }
|
||||
#define List(type) struct __List_##type
|
||||
|
||||
#define newList(type) ({ \
|
||||
List(type) __list_tmp = { .pointer = malloc(sizeof(type) * LIST_H_INIT_CAPACITY), .count = 0, .capacity = LIST_H_INIT_CAPACITY, .itemsize = sizeof(type) };\
|
||||
if (__list_tmp.pointer == NULL) { \
|
||||
printf("list.h:32 (newList(type)) - failed to allocate memory"); exit(1);\
|
||||
}\
|
||||
__list_tmp;\
|
||||
})
|
||||
|
||||
#define appendList(list, item) {\
|
||||
if (list.pointer == NULL) {\
|
||||
printf("list.h:39 (append(list, item)) - list pointer is null (perhaps you accidently destroyed the list?)"); exit(1);\
|
||||
}\
|
||||
if (list.capacity < list.count + 1) {\
|
||||
list.capacity *= 2;\
|
||||
void* __tmp_ptr = realloc(list.pointer, list.capacity * list.itemsize);\
|
||||
if (__tmp_ptr == NULL) {\
|
||||
printf("list.h:45 (append(list, item)) - failed to allocate memory"); exit(1);\
|
||||
}\
|
||||
list.pointer = __tmp_ptr;\
|
||||
}\
|
||||
list.pointer[list.count] = item;\
|
||||
list.count++;\
|
||||
}
|
||||
|
||||
#define get(list, idx) ({\
|
||||
if (list.pointer == NULL) {\
|
||||
printf("list.h:55 (get(list, idx)) - list pointer is null (perhaps you accidently destroyed the list?)"); exit(1);\
|
||||
}\
|
||||
(idx >= list.count) ? NULL : &list.pointer[idx];\
|
||||
})
|
||||
|
||||
#define pop(list) {\
|
||||
if (list.pointer == NULL) {\
|
||||
printf("list.h:65 (pop(list)) - list pointer is null (perhaps you accidently destroyed the list?)"); exit(1);\
|
||||
}\
|
||||
if (list.count > 0) list.count--;\
|
||||
}
|
||||
|
||||
#define destroy(list) if (list.pointer != NULL) {\
|
||||
free(list.pointer); list.pointer = NULL; \
|
||||
}
|
||||
|
||||
#endif
|
||||
10
src/include/nothing.h
Normal file
10
src/include/nothing.h
Normal 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
1137
src/include/uthash.h
Normal file
File diff suppressed because it is too large
Load Diff
77
src/interactive/interactive.c
Normal file
77
src/interactive/interactive.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "interactive.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
Interactive createInteractive() {
|
||||
return (Interactive) {
|
||||
.callbacks.at = malloc(sizeof(InteractiveCallback) * 16),
|
||||
.callbacks.capacity = 16,
|
||||
.callbacks.size = 0
|
||||
};
|
||||
}
|
||||
|
||||
void addCallback(Interactive* interactive, int (*function)(size_t, char**, void*), char* name) {
|
||||
if (interactive->callbacks.size + 1 >= interactive->callbacks.capacity) {
|
||||
interactive->callbacks.at = realloc(interactive->callbacks.at, (interactive->callbacks.capacity * 2) * sizeof(InteractiveCallback));
|
||||
interactive->callbacks.capacity *= 2;
|
||||
}
|
||||
|
||||
interactive->callbacks.at[interactive->callbacks.size].function = function;
|
||||
interactive->callbacks.at[interactive->callbacks.size].name = name;
|
||||
interactive->callbacks.size++;
|
||||
|
||||
}
|
||||
|
||||
typedef struct InteractiveCommand {
|
||||
char* command;
|
||||
size_t argc;
|
||||
size_t capacity;
|
||||
char** argv;
|
||||
} InteractiveCommand;
|
||||
|
||||
static inline InteractiveCommand parseInteractiveCommand(char* input) {
|
||||
InteractiveCommand command = {
|
||||
.command = malloc(sizeof(char*) * 16),
|
||||
.argc = 0,
|
||||
.capacity = 16,
|
||||
.argv = malloc(sizeof(char*) * 16)
|
||||
};
|
||||
for (size_t i = 0; i < command.capacity; i++) {
|
||||
command.argv[i] = malloc(sizeof(char) * 64);
|
||||
}
|
||||
size_t len = strlen(input);
|
||||
char** currentBuf = &command.command;
|
||||
size_t currentLen = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (input[i] == '\n') break;
|
||||
if (input[i] == ' ' || currentLen == 63) {
|
||||
if (command.argc + 1 >= command.capacity) {
|
||||
command.capacity *= 2;
|
||||
command.argv = realloc(command.argv, command.capacity * sizeof(char**));
|
||||
}
|
||||
currentBuf = &command.argv[command.argc];
|
||||
command.argc++;
|
||||
} else {
|
||||
currentBuf[currentLen][i] = input[i];
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
void runInteractive(Interactive* interactive, void* ctx) {
|
||||
for (;;) {
|
||||
char buf[512];
|
||||
printf("> ");
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
InteractiveCommand command = parseInteractiveCommand(buf);
|
||||
if (strcmp(command.command, "exit") == 0) {
|
||||
break;
|
||||
}
|
||||
for (size_t i = 0; i < interactive->callbacks.size; i++) {
|
||||
if (strcmp(command.command, interactive->callbacks.at[i].name) == 0) {
|
||||
interactive->callbacks.at[i].function(command.argc, command.argv, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/interactive/interactive.h
Normal file
26
src/interactive/interactive.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef INTERACTIVE_H
|
||||
#define INTERACTIVE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// Callback functions are of the form
|
||||
// int callback(int argc, char** argv, void* ctx)
|
||||
|
||||
typedef struct InteractiveCallback {
|
||||
int (*function)(size_t, char**, void*);
|
||||
char* name;
|
||||
} InteractiveCallback;
|
||||
|
||||
typedef struct Interactive {
|
||||
struct {
|
||||
InteractiveCallback* at;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} callbacks;
|
||||
} Interactive;
|
||||
|
||||
Interactive createInteractive();
|
||||
void addCallback(Interactive* interactive, int (*function)(size_t, char**, void*), char* name);
|
||||
void runInteractive(Interactive* interactive, void* ctx);
|
||||
|
||||
#endif
|
||||
78
src/lexer/SolsLiteral.c
Normal file
78
src/lexer/SolsLiteral.c
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "SolsLiteral.h"
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
ResultType(SolsLiteral, charptr) createSolsLiteral(SolsLiteralType type, ...) {
|
||||
va_list args;
|
||||
va_start(args, type);
|
||||
SolsLiteral literal = {
|
||||
.type = type
|
||||
};
|
||||
switch (type) {
|
||||
case SLT_INT: {
|
||||
literal.as.intv = va_arg(args, int64_t);
|
||||
break;
|
||||
}
|
||||
case SLT_DOUBLE: {
|
||||
literal.as.doublev = va_arg(args, double);
|
||||
break;
|
||||
}
|
||||
case SLT_BOOL: {
|
||||
literal.as.boolv = (bool) va_arg(args, int);
|
||||
break;
|
||||
}
|
||||
case SLT_CHAR: {
|
||||
literal.as.charv = (char) va_arg(args, int);
|
||||
break;
|
||||
}
|
||||
case SLT_STRING: {
|
||||
char* input = va_arg(args, char*);
|
||||
if (input == NULL) {
|
||||
va_end(args);
|
||||
return Error(SolsLiteral, charptr, "Unexpected NULL value (in createSolsLiteral() function)");
|
||||
}
|
||||
literal.as.stringv = malloc(strlen(input) + 1);
|
||||
if (literal.as.stringv == NULL) {
|
||||
va_end(args);
|
||||
return Error(SolsLiteral, charptr, "Couldn't allocate memory (in createSolsLiteral() function)");
|
||||
}
|
||||
strcpy(literal.as.stringv, input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
return Success(SolsLiteral, charptr, literal);
|
||||
}
|
||||
|
||||
void freeSolsLiteral(SolsLiteral* lit) {
|
||||
if (lit->type == SLT_STRING && lit->as.stringv != NULL) {
|
||||
free(lit->as.stringv);
|
||||
}
|
||||
}
|
||||
|
||||
void printSolsLiteral(SolsLiteral* lit) {
|
||||
switch (lit->type) {
|
||||
case SLT_INT: {
|
||||
printf("%" PRId64, lit->as.intv);
|
||||
break;
|
||||
}
|
||||
case SLT_DOUBLE: {
|
||||
printf("%f", lit->as.doublev);
|
||||
break;
|
||||
}
|
||||
case SLT_STRING: {
|
||||
printf("%s", lit->as.stringv);
|
||||
break;
|
||||
}
|
||||
case SLT_BOOL: {
|
||||
printf(lit->as.boolv ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
case SLT_CHAR: {
|
||||
printf("%c", lit->as.charv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/lexer/SolsLiteral.h
Normal file
49
src/lexer/SolsLiteral.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef SOLSLITERAL_H
|
||||
#define SOLSLITERAL_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "../include/error.h"
|
||||
#include "../include/nothing.h"
|
||||
|
||||
typedef char* charptr;
|
||||
|
||||
typedef enum SolsLiteralType {
|
||||
SLT_INT, SLT_STRING, SLT_DOUBLE, SLT_BOOL, SLT_CHAR
|
||||
} SolsLiteralType;
|
||||
|
||||
// Stores literal values which will be added to the Ground code.
|
||||
// Not much explaining needed here.
|
||||
typedef struct SolsLiteral {
|
||||
SolsLiteralType type;
|
||||
union {
|
||||
int64_t intv;
|
||||
char* stringv;
|
||||
double doublev;
|
||||
bool boolv;
|
||||
char charv;
|
||||
} as;
|
||||
} SolsLiteral;
|
||||
|
||||
Result(SolsLiteral, charptr);
|
||||
|
||||
// Creates a SolsLiteral, based on the type provided.
|
||||
// SolsLiteralType -> C type:
|
||||
// SLT_INT -> int64_t
|
||||
// SLT_STRING -> char*
|
||||
// SLT_DOUBLE -> double
|
||||
// SLT_BOOL -> bool
|
||||
// SL_CHAR -> char
|
||||
// An error will only be returned if there is an issue copying a provided char*.
|
||||
// There is no way to detect incorrectly provided types, so ensure that the right type
|
||||
// is provided!!!!
|
||||
ResultType(SolsLiteral, charptr) createSolsLiteral(SolsLiteralType type, ...);
|
||||
|
||||
// Frees a SolsLiteral. Primarily concerned with freeing .as.stringv
|
||||
void freeSolsLiteral(SolsLiteral* lit);
|
||||
|
||||
// Prints a SolsLiteral
|
||||
void printSolsLiteral(SolsLiteral* lit);
|
||||
|
||||
#endif
|
||||
93
src/lexer/SolsToken.c
Normal file
93
src/lexer/SolsToken.c
Normal 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, {});
|
||||
}
|
||||
97
src/lexer/SolsToken.h
Normal file
97
src/lexer/SolsToken.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#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_FROM,
|
||||
STT_KW_STRUCT, STT_KW_ENUM, STT_KW_CONSTRUCTOR, STT_KW_DESTRUCTOR, STT_KW_DUPLICATOR,
|
||||
STT_KW_AS, STT_KW_SIZEOF,
|
||||
STT_KW_PRIVATE, STT_KW_PROTECTED,
|
||||
STT_KW_PUTS, STT_KW_IF, STT_KW_ELSE, STT_KW_WHILE, STT_KW_BREAK, STT_KW_CONTINUE,
|
||||
STT_KW_NEW, STT_KW_GROUND, STT_LINE_END, STT_COMMA,
|
||||
STT_OPEN_SQUARE, STT_CLOSE_SQUARE,
|
||||
STT_KW_PRAGMA,
|
||||
} SolsTokenType;
|
||||
|
||||
typedef char* charptr;
|
||||
|
||||
// Stores information about the line that the token/node is on, for printing if an error
|
||||
// occurs.
|
||||
// .num is the line number, .content is the line's contents.
|
||||
typedef struct LineInfo {
|
||||
size_t num;
|
||||
char* content;
|
||||
} LineInfo;
|
||||
|
||||
// Represents a token lexed by the lex() function.
|
||||
// Most token types exclusively use the .type field, however some tokens require storing
|
||||
// more data, inside the .as union.
|
||||
// Those tokens are:
|
||||
// STT_LITERAL: A literal value. Uses field .as.literal
|
||||
// STT_TYPE: A type descriptor. Uses field .as.type
|
||||
// STT_IDENTIFIER: An identifier. Uses field .as.idName
|
||||
// STT_KW_GROUND: Ground code embedded inside Solstice. Uses field .as.inlineGround
|
||||
typedef struct SolsToken {
|
||||
SolsTokenType type;
|
||||
union {
|
||||
SolsLiteral literal;
|
||||
SolsType type;
|
||||
char* idName;
|
||||
char* inlineGround;
|
||||
} as;
|
||||
LineInfo line;
|
||||
} SolsToken;
|
||||
|
||||
Result(SolsToken, charptr);
|
||||
|
||||
// Creates a SolsToken. If the type passed in is STT_LITERAL, STT_TYPE, STT_IDENTIFIER or
|
||||
// STT_KW_GROUND, the function expects another argument, corresponding to the data type
|
||||
// the token holds. See the SolsToken struct for more information.
|
||||
// Returns:
|
||||
// Success: The created SolsToken
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(SolsToken, charptr) createSolsToken(SolsTokenType type, ...);
|
||||
|
||||
// Frees a SolsToken, specifically the .as field elements.
|
||||
void freeSolsToken(SolsToken* token);
|
||||
|
||||
// Represents a Solstice program, seperated into tokens.
|
||||
// .at is a pointer to the tokens
|
||||
// .count is how many tokens are currently being stored
|
||||
// .capacity is how many tokens worth of memory is allocated
|
||||
typedef struct SolsTokens {
|
||||
SolsToken* at;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} SolsTokens;
|
||||
|
||||
Result(SolsTokens, charptr);
|
||||
|
||||
// Creates a SolsTokens holder.
|
||||
// Returns:
|
||||
// Success: Constructed SolsTokens
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(SolsTokens, charptr) createSolsTokens();
|
||||
|
||||
// Adds a token to SolsTokens. Used by the lex() function.
|
||||
// Returns:
|
||||
// Success: Nothing
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(Nothing, charptr) addTokenToSolsTokens(SolsTokens* tokens, SolsToken token);
|
||||
|
||||
|
||||
#endif
|
||||
435
src/lexer/SolsType.c
Normal file
435
src/lexer/SolsType.c
Normal file
@@ -0,0 +1,435 @@
|
||||
#include "SolsType.h"
|
||||
#include "../include/error.h"
|
||||
#include "../include/estr.h"
|
||||
#include <ground.h>
|
||||
#include <string.h>
|
||||
|
||||
ResultType(SolsType, charptr) createSolsType(SolsTypeType in) {
|
||||
SolsTypeField* ptr = malloc(sizeof(SolsTypeField) * 32);
|
||||
if (ptr == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in createSolsType() function)");
|
||||
}
|
||||
SolsType type = {
|
||||
.type = in == STT_GENERIC ? STT_TEMPLATE : in,
|
||||
.identifierType = NULL,
|
||||
.returnType = NULL,
|
||||
.children.capacity = 32,
|
||||
.children.count = 0,
|
||||
.children.at = ptr,
|
||||
.genericChildren.at = NULL,
|
||||
.genericChildren.capacity = 0,
|
||||
.genericChildren.count = 0,
|
||||
.typeIsKnown = true,
|
||||
.needsGroundStruct = false,
|
||||
.metadata.isPrivate = false,
|
||||
.metadata.isProtected = false,
|
||||
.metadata.isEnum = false,
|
||||
.metadata.isGenericField = in == STT_GENERIC,
|
||||
.metadata.isGenericStruct = false,
|
||||
.metadata.genericStructNode = NULL
|
||||
};
|
||||
return Success(SolsType, charptr, type);
|
||||
}
|
||||
|
||||
ResultType(SolsType, charptr) createIdentifiedSolsType(char* in) {
|
||||
char* copy = malloc(strlen(in) + 1);
|
||||
if (copy == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in createIdentifiedSolsType() function)");
|
||||
}
|
||||
strcpy(copy, in);
|
||||
return Success(SolsType, charptr, ((SolsType) {
|
||||
.type = STT_UNKNOWN,
|
||||
.identifierType = copy,
|
||||
.returnType = NULL,
|
||||
.children.capacity = 0,
|
||||
.children.count = 0,
|
||||
.children.at = NULL,
|
||||
.genericChildren.at = NULL,
|
||||
.genericChildren.capacity = 0,
|
||||
.genericChildren.count = 0,
|
||||
.typeIsKnown = false,
|
||||
.needsGroundStruct = false,
|
||||
.metadata.isPrivate = false,
|
||||
.metadata.isProtected = false,
|
||||
.metadata.isEnum = false,
|
||||
.metadata.isGenericField = false,
|
||||
.metadata.isGenericStruct = false,
|
||||
.metadata.genericStructNode = NULL
|
||||
}));
|
||||
}
|
||||
|
||||
ResultType(SolsType, charptr) copySolsType(SolsType* type) {
|
||||
SolsType ret = {
|
||||
.type = type->type,
|
||||
.identifierType = NULL,
|
||||
.typeIsKnown = type->typeIsKnown,
|
||||
.needsGroundStruct = type->needsGroundStruct,
|
||||
.returnType = NULL,
|
||||
.children.count = type->children.count,
|
||||
.children.capacity = type->children.capacity,
|
||||
.children.at = NULL,
|
||||
.genericChildren.count = type->genericChildren.count,
|
||||
.genericChildren.capacity = type->genericChildren.capacity,
|
||||
.genericChildren.at = NULL,
|
||||
.metadata = type->metadata
|
||||
};
|
||||
|
||||
if (type->identifierType != NULL) {
|
||||
ret.identifierType = malloc(strlen(type->identifierType) + 1);
|
||||
if (ret.identifierType == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
strcpy(ret.identifierType, type->identifierType);
|
||||
}
|
||||
|
||||
if (type->returnType != NULL && type->returnType->type != STT_NONE) {
|
||||
ResultType(SolsType, charptr) copiedReturn = copySolsType(type->returnType);
|
||||
if (copiedReturn.error) {
|
||||
Estr err = CREATE_ESTR(copiedReturn.as.error);
|
||||
APPEND_ESTR(err, " (in copySolsType() function)");
|
||||
return Error(SolsType, charptr, err.str);
|
||||
}
|
||||
|
||||
ret.returnType = malloc(sizeof(SolsType));
|
||||
if (ret.returnType == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
*ret.returnType = copiedReturn.as.success;
|
||||
}
|
||||
|
||||
if (type->children.capacity > 0) {
|
||||
SolsTypeField* ptr = malloc(sizeof(SolsTypeField) * type->children.capacity);
|
||||
if (ptr == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
ret.children.at = ptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < type->children.count; i++) {
|
||||
ResultType(SolsType, charptr) copied = copySolsType(&type->children.at[i].type);
|
||||
if (copied.error) {
|
||||
Estr err = CREATE_ESTR(copied.as.error);
|
||||
APPEND_ESTR(err, " (in copySolsType() function)");
|
||||
return Error(SolsType, charptr, err.str);
|
||||
}
|
||||
ret.children.at[i].type = copied.as.success;
|
||||
|
||||
if (type->children.at[i].name == NULL) {
|
||||
ret.children.at[i].name = NULL;
|
||||
} else {
|
||||
ret.children.at[i].name = malloc(strlen(type->children.at[i].name) + 1);
|
||||
if (ret.children.at[i].name == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
strcpy(ret.children.at[i].name, type->children.at[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
if (type->genericChildren.capacity > 0) {
|
||||
SolsType* ptr = malloc(sizeof(SolsType) * type->genericChildren.capacity);
|
||||
if (ptr == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
ret.genericChildren.at = ptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < type->genericChildren.count; i++) {
|
||||
ResultType(SolsType, charptr) copied = copySolsType(&type->genericChildren.at[i]);
|
||||
if (copied.error) {
|
||||
Estr err = CREATE_ESTR(copied.as.error);
|
||||
APPEND_ESTR(err, " (in copySolsType() function)");
|
||||
return Error(SolsType, charptr, err.str);
|
||||
}
|
||||
ret.genericChildren.at[i] = copied.as.success;
|
||||
}
|
||||
|
||||
return Success(SolsType, charptr, ret);
|
||||
}
|
||||
/*
|
||||
ResultType(SolsType, charptr) copySolsType(SolsType* type) {
|
||||
SolsType ret = { .type = type->type, .children.count = type->children.count, .children.capacity = type->children.capacity};
|
||||
|
||||
// Allocate memory
|
||||
SolsTypeField* ptr = malloc(sizeof(SolsTypeField) * type->children.capacity);
|
||||
if (ptr == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
ret.children.at = ptr;
|
||||
|
||||
// Deep copy values
|
||||
for (size_t i = 0; i < type->children.count; i++) {
|
||||
// Copy the SolsType value
|
||||
ResultType(SolsType, charptr) copied = copySolsType(&type->children.at[i].type);
|
||||
if (copied.error) {
|
||||
Estr err = CREATE_ESTR(copied.as.error);
|
||||
APPEND_ESTR(err, " (in addChildToSolsType() function)");
|
||||
return Error(SolsType, charptr, err.str);
|
||||
}
|
||||
ret.children.at[i].type = copied.as.success;
|
||||
|
||||
// Copy the name
|
||||
if (type->children.at[i].name == NULL) {
|
||||
ret.children.at[i].name = NULL;
|
||||
} else {
|
||||
ret.children.at[i].name = malloc(strlen(type->children.at[i].name) + 1);
|
||||
if (ret.children.at[i].name == NULL) {
|
||||
return Error(SolsType, charptr, "Couldn't allocate memory (in copySolsType() function)");
|
||||
}
|
||||
strcpy(ret.children.at[i].name, type->children.at[i].name);
|
||||
}
|
||||
}
|
||||
return Success(SolsType, charptr, ret);
|
||||
}
|
||||
*/
|
||||
|
||||
ResultType(Nothing, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name) {
|
||||
if (type->children.capacity < type->children.count + 1) {
|
||||
type->children.capacity *= 2;
|
||||
SolsTypeField* ptr = realloc(type->children.at, sizeof(SolsTypeField) * type->children.capacity);
|
||||
if (ptr == NULL) {
|
||||
return Error(Nothing, charptr, "Couldn't allocate memory (in addChildToSolsType() function)");
|
||||
}
|
||||
type->children.at = ptr;
|
||||
}
|
||||
ResultType(SolsType, charptr) copied = copySolsType(&child);
|
||||
if (copied.error) {
|
||||
Estr err = CREATE_ESTR(copied.as.error);
|
||||
APPEND_ESTR(err, " (in addChildToSolsType() function)");
|
||||
return Error(Nothing, charptr, err.str);
|
||||
}
|
||||
type->children.at[type->children.count].type = copied.as.success;
|
||||
if (name == NULL) {
|
||||
type->children.at[type->children.count].name = NULL;
|
||||
} else {
|
||||
type->children.at[type->children.count].name = malloc(strlen(name) + 1);
|
||||
strcpy(type->children.at[type->children.count].name, name);
|
||||
}
|
||||
type->children.count++;
|
||||
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
|
||||
void freeSolsType(SolsType* type) {
|
||||
for (size_t i = 0; i < type->children.count; i++) {
|
||||
// Free the name
|
||||
if (type->children.at[i].name != NULL) {
|
||||
free(type->children.at[i].name);
|
||||
}
|
||||
|
||||
// Free the child SolsTypes
|
||||
freeSolsType(&type->children.at[i].type);
|
||||
}
|
||||
// Free the field itself
|
||||
free(type->children.at);
|
||||
type->children.at = NULL;
|
||||
|
||||
// Set count and capacity to zero
|
||||
type->children.count = 0;
|
||||
type->children.capacity = 0;
|
||||
}
|
||||
|
||||
bool compareTypes(SolsType* left, SolsType* right) {
|
||||
if (left->type != right->type) {
|
||||
return false;
|
||||
}
|
||||
switch (left->type) {
|
||||
case STT_OBJECT: {
|
||||
if (left->children.count != right->children.count) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < left->children.count; i++) {
|
||||
if (strcmp(left->children.at[i].name, right->children.at[i].name) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case STT_TEMPLATE: {
|
||||
if (left->children.count != right->children.count) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < left->children.count; i++) {
|
||||
if (strcmp(left->children.at[i].name, right->children.at[i].name) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
case STT_FUN: {
|
||||
if (left->children.count != right->children.count) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < left->children.count; i++) {
|
||||
if (compareTypes(&left->children.at[i].type, &right->children.at[i].type) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type, struct SolsScope* scope) {
|
||||
switch (type->type) {
|
||||
case STT_INT: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("int")));
|
||||
}
|
||||
case STT_DOUBLE: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("double")));
|
||||
}
|
||||
case STT_STRING: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("string")));
|
||||
}
|
||||
case STT_BOOL: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("bool")));
|
||||
}
|
||||
case STT_CHAR: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("char")));
|
||||
}
|
||||
case STT_FUN: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("function")));
|
||||
}
|
||||
case STT_TEMPLATE: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("struct")));
|
||||
}
|
||||
case STT_OBJECT: {
|
||||
if (!type->needsGroundStruct) {
|
||||
char* name = type->identifierType ? type->identifierType : "object";
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier(name)));
|
||||
} else {
|
||||
// FIXME do this later
|
||||
return Error(GroundArg, charptr, "Anonymous structs are not supported yet");
|
||||
}
|
||||
}
|
||||
case STT_UNKNOWN: {
|
||||
if (!type->needsGroundStruct) {
|
||||
char* name = type->identifierType ? type->identifierType : "unknown";
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier(name)));
|
||||
} else {
|
||||
// FIXME do this later
|
||||
return Error(GroundArg, charptr, "Anonymous structs are not supported yet");
|
||||
}
|
||||
}
|
||||
case STT_NONE: {
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("none")));
|
||||
}
|
||||
case STT_GENERIC: {
|
||||
Estr typeName = CREATE_ESTR(type->identifierType);
|
||||
APPEND_ESTR(typeName, "_SOLS_GENERIC_");
|
||||
for (size_t i = 0; i < type->genericChildren.count; i++) {
|
||||
ResultType(GroundArg, charptr) arg = createGroundArgFromSolsType(&type->genericChildren.at[i], scope);
|
||||
if (arg.error) {
|
||||
return Error(GroundArg, charptr, arg.as.error);
|
||||
}
|
||||
APPEND_ESTR(typeName, arg.as.success.as.ref->string);
|
||||
APPEND_ESTR(typeName, "_");
|
||||
}
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier(typeName.str)));
|
||||
}
|
||||
}
|
||||
return Error(GroundArg, charptr, "How did we get here?");
|
||||
}
|
||||
|
||||
ResultType(SolsType, charptr) findStructMemberType(SolsType* type, char* member) {
|
||||
for (size_t i = 0; i < type->children.count; i++) {
|
||||
if (strcmp(type->children.at[i].name, member) == 0) {
|
||||
return Success(SolsType, charptr, type->children.at[i].type);
|
||||
}
|
||||
}
|
||||
return Error(SolsType, charptr, "Could not find member");
|
||||
}
|
||||
|
||||
void printSolsType(SolsType* type) {
|
||||
printf("Type: ");
|
||||
switch (type->type) {
|
||||
case STT_INT:
|
||||
printf("int");
|
||||
break;
|
||||
case STT_DOUBLE:
|
||||
printf("double");
|
||||
break;
|
||||
case STT_STRING:
|
||||
printf("string");
|
||||
break;
|
||||
case STT_BOOL:
|
||||
printf("bool");
|
||||
break;
|
||||
case STT_CHAR:
|
||||
printf("char");
|
||||
break;
|
||||
case STT_FUN:
|
||||
printf("fun");
|
||||
break;
|
||||
case STT_TEMPLATE:
|
||||
printf("template");
|
||||
break;
|
||||
case STT_OBJECT:
|
||||
printf("object");
|
||||
break;
|
||||
case STT_UNKNOWN:
|
||||
printf("(unknown)");
|
||||
break;
|
||||
case STT_NONE:
|
||||
printf("(none)");
|
||||
break;
|
||||
case STT_GENERIC:
|
||||
printf("(generic");
|
||||
break;
|
||||
}
|
||||
|
||||
if (type->returnType != NULL) {
|
||||
printf("\nReturn type: ");
|
||||
printSolsType(type->returnType);
|
||||
}
|
||||
|
||||
if (type->children.count > 0) {
|
||||
printf("\nChildren {");
|
||||
for (size_t i = 0; i < type->children.count; i++) {
|
||||
if (type->children.at[i].name != NULL) {
|
||||
printf("\nName: %s", type->children.at[i].name);
|
||||
}
|
||||
printf("\nType: ");
|
||||
printSolsType(&type->children.at[i].type);
|
||||
}
|
||||
printf("}");
|
||||
}
|
||||
}
|
||||
|
||||
ResultType(Nothing, charptr) addGenericFieldToType(SolsType* type, SolsType field) {
|
||||
|
||||
if (type->genericChildren.at == NULL) {
|
||||
type->genericChildren.at = malloc(sizeof(SolsType) * 4);
|
||||
if (type->genericChildren.at == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to allocate memory");
|
||||
}
|
||||
type->genericChildren.capacity = 4;
|
||||
type->genericChildren.count = 0;
|
||||
}
|
||||
|
||||
if (type->genericChildren.count >= type->genericChildren.capacity) {
|
||||
SolsType* tmp = realloc(type->genericChildren.at, type->genericChildren.capacity * 2 * sizeof(SolsType));
|
||||
if (tmp == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to allocate memory");
|
||||
}
|
||||
type->genericChildren.capacity *= 2;
|
||||
}
|
||||
|
||||
type->genericChildren.at[type->genericChildren.count] = ({
|
||||
ResultType(SolsType, charptr) _res = copySolsType(&field);
|
||||
if (_res.error) {
|
||||
return Error(Nothing, charptr, _res.as.error);
|
||||
}
|
||||
_res.as.success;
|
||||
});
|
||||
type->genericChildren.count++;
|
||||
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
149
src/lexer/SolsType.h
Normal file
149
src/lexer/SolsType.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#ifndef SOLSTYPE_H
|
||||
#define SOLSTYPE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ground.h>
|
||||
|
||||
#include "../include/error.h"
|
||||
#include "../include/nothing.h"
|
||||
|
||||
typedef enum SolsTypeType {
|
||||
STT_INT, STT_STRING, STT_DOUBLE, STT_BOOL, STT_CHAR, STT_FUN, STT_TEMPLATE, STT_OBJECT, STT_UNKNOWN, STT_GENERIC, STT_NONE
|
||||
} SolsTypeType;
|
||||
|
||||
// Definition of charptr for Result() and ResultType() macros
|
||||
typedef char* charptr;
|
||||
|
||||
struct SolsTypeField;
|
||||
struct SolsNode; // for generic struct
|
||||
|
||||
// Holds type information for a struct, object or function.
|
||||
// Say, for example, your type signature looks like this:
|
||||
// object(string x, fun(int) y)
|
||||
// This is stored like this:
|
||||
// SolsType {
|
||||
// type: STT_OBJECT
|
||||
// children: [
|
||||
// {
|
||||
// type: {
|
||||
// type: STT_STRING
|
||||
// }
|
||||
// name: "x"
|
||||
// }
|
||||
// {
|
||||
// type: {
|
||||
// type: STT_FUN
|
||||
// children: [
|
||||
// {
|
||||
// type: {
|
||||
// type: STT_INT
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// name: "y"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// (Sorry for the long explaination, but it's worth it so you know how the type system works.)
|
||||
//
|
||||
typedef struct SolsType {
|
||||
SolsTypeType type;
|
||||
|
||||
// For use when type is identified with a name
|
||||
char* identifierType;
|
||||
|
||||
// If type is identified with a name, record whether we have found the actual type
|
||||
bool typeIsKnown;
|
||||
|
||||
// If using anonymous struct, record whether we need to generate a Ground struct
|
||||
bool needsGroundStruct;
|
||||
|
||||
// For use in functions
|
||||
struct SolsType* returnType;
|
||||
|
||||
// For use by fun, template, object
|
||||
struct {
|
||||
struct SolsTypeField* at;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} children;
|
||||
|
||||
// For use in generics
|
||||
struct {
|
||||
struct SolsType* at;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} genericChildren;
|
||||
|
||||
struct {
|
||||
bool isPrivate;
|
||||
bool isProtected;
|
||||
bool isEnum;
|
||||
bool isGenericField;
|
||||
bool isGenericStruct;
|
||||
|
||||
// For use by generic struct
|
||||
// We will codegen this where required
|
||||
struct SolsNode* genericStructNode;
|
||||
} metadata;
|
||||
} SolsType;
|
||||
|
||||
// Assists with holding child types in the SolsType struct.
|
||||
typedef struct SolsTypeField {
|
||||
SolsType type;
|
||||
char* name;
|
||||
} SolsTypeField;
|
||||
|
||||
|
||||
Result(SolsType, charptr);
|
||||
|
||||
// Creates a SolsType, with the provided type type.
|
||||
// Use the "addChildToSolsType()" function to add children, in case this type has children.
|
||||
// Returns:
|
||||
// Success: The constructed SolsType
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(SolsType, charptr) createSolsType(SolsTypeType in);
|
||||
|
||||
// Creates a SolsType which is identified by a name.
|
||||
// The type details are not known yet, so the type is marked as unknown.
|
||||
ResultType(SolsType, charptr) createIdentifiedSolsType(char* in);
|
||||
|
||||
Result(Nothing, charptr);
|
||||
|
||||
// Adds a child SolsType to a given SolsType.
|
||||
// Returns:
|
||||
// Success: Nothing
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(Nothing, charptr) addChildToSolsType(SolsType* type, SolsType child, const char* name);
|
||||
|
||||
// Makes a deep copy of a SolsType.
|
||||
ResultType(SolsType, charptr) copySolsType(SolsType* type);
|
||||
|
||||
Result(GroundArg, charptr);
|
||||
|
||||
struct SolsScope;
|
||||
|
||||
// Represents a SolsType as a GroundArg (in typeref form)
|
||||
ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type, struct SolsScope* scope);
|
||||
|
||||
// Frees a SolsType
|
||||
void freeSolsType(SolsType* type);
|
||||
|
||||
// Compares two SolsTypes
|
||||
bool compareTypes(SolsType* left, SolsType* right);
|
||||
|
||||
// Finds the type of a struct member. Errors if the member is not found.
|
||||
ResultType(SolsType, charptr) findStructMemberType(SolsType* type, char* member);
|
||||
|
||||
// Adds a generic argument to a SolsType.
|
||||
// If you have
|
||||
// MyStruct[int, string]
|
||||
// you would add SolsType(STT_INT) and SolsType(STT_STRING) to the
|
||||
// SolsType for `MyStruct` using this function.
|
||||
ResultType(Nothing, charptr) addGenericFieldToType(SolsType* type, SolsType field);
|
||||
|
||||
void printSolsType(SolsType* type);
|
||||
|
||||
#endif
|
||||
898
src/lexer/lexer.c
Normal file
898
src/lexer/lexer.c
Normal file
@@ -0,0 +1,898 @@
|
||||
#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},
|
||||
{"else", STT_KW_ELSE},
|
||||
{"while", STT_KW_WHILE},
|
||||
{"break", STT_KW_BREAK},
|
||||
{"continue", STT_KW_CONTINUE},
|
||||
{"def", STT_KW_DEF},
|
||||
{"lambda", STT_KW_LAMBDA},
|
||||
{"return", STT_KW_RETURN},
|
||||
{"use", STT_KW_USE},
|
||||
{"from", STT_KW_FROM},
|
||||
{"struct", STT_KW_STRUCT},
|
||||
{"enum", STT_KW_ENUM},
|
||||
{"constructor", STT_KW_CONSTRUCTOR},
|
||||
{"destructor", STT_KW_DESTRUCTOR},
|
||||
{"duplicator", STT_KW_DUPLICATOR},
|
||||
{"private", STT_KW_PRIVATE},
|
||||
{"protected", STT_KW_PROTECTED},
|
||||
{"ground", STT_KW_GROUND},
|
||||
{"new", STT_KW_NEW},
|
||||
{"as", STT_KW_AS},
|
||||
{"sizeof", STT_KW_SIZEOF},
|
||||
{"pragma", STT_KW_PRAGMA},
|
||||
{"{", STT_OPEN_CURLY},
|
||||
{"}", STT_CLOSE_CURLY},
|
||||
{"(", STT_OPEN_PAREN},
|
||||
{")", STT_CLOSE_PAREN},
|
||||
{"[", STT_OPEN_SQUARE},
|
||||
{"]", STT_CLOSE_SQUARE},
|
||||
{"+", STT_OP_ADD},
|
||||
{"-", STT_OP_SUB},
|
||||
{"*", STT_OP_MUL},
|
||||
{"/", STT_OP_DIV},
|
||||
{"=", STT_OP_SET},
|
||||
{"+=", STT_OP_ADDTO},
|
||||
{"-=", STT_OP_SUBTO},
|
||||
{"*=", STT_OP_MULTO},
|
||||
{"/=", STT_OP_DIVTO},
|
||||
{"++", STT_OP_INCREMENT},
|
||||
{"--", STT_OP_DECREMENT},
|
||||
{"==", STT_OP_EQUAL},
|
||||
{"!=", STT_OP_INEQUAL},
|
||||
{">", STT_OP_GREATER},
|
||||
{"<", STT_OP_LESSER},
|
||||
{">=", STT_OP_EQGREATER},
|
||||
{"<=", STT_OP_EQLESSER},
|
||||
{"\n", STT_LINE_END},
|
||||
{";", STT_LINE_END},
|
||||
{",", STT_COMMA},
|
||||
// Shh, this is our little secret
|
||||
// Your reward for actually reading the source code
|
||||
// Enable this by adding -DSUPER_SILLY_MODE to your
|
||||
// compile flags (not recommended for production)
|
||||
#ifdef SUPER_SILLY_MODE
|
||||
{"plus", STT_OP_ADD},
|
||||
{"minus", STT_OP_SUB},
|
||||
{"times", STT_OP_MUL},
|
||||
{"dividedby", STT_OP_DIV},
|
||||
{"then", STT_OPEN_CURLY},
|
||||
{"do", STT_OPEN_CURLY},
|
||||
{"end", STT_CLOSE_CURLY},
|
||||
{"is", STT_OP_SET},
|
||||
{"equals", STT_OP_EQUAL},
|
||||
{"greaterthan", STT_OP_GREATER},
|
||||
{"lesserthan", STT_OP_LESSER},
|
||||
{"increment", STT_OP_INCREMENT},
|
||||
{"decrement", STT_OP_DECREMENT},
|
||||
{"adds", STT_OP_ADDTO},
|
||||
{"subtracts", STT_OP_SUBTO},
|
||||
{"multiplies", STT_OP_MULTO},
|
||||
{"divides", STT_OP_DIVTO},
|
||||
{"function", STT_KW_DEF},
|
||||
{"func", STT_KW_DEF},
|
||||
{"fn", STT_KW_DEF},
|
||||
{"ion", STT_KW_DEF},
|
||||
{"class", STT_KW_STRUCT},
|
||||
{"compilerpleasedothisforme", STT_KW_PRAGMA}
|
||||
#endif
|
||||
};
|
||||
|
||||
ResultType(SolsTokenType, Nothing) getTokenType(const char* input) {
|
||||
size_t mapsize = sizeof(SolsTokenTypeMap) / sizeof(struct _SolsTokenTypeMap);
|
||||
for (size_t i = 0; i < mapsize; i++) {
|
||||
if (strcmp(input, SolsTokenTypeMap[i].str) == 0) {
|
||||
return Success(SolsTokenType, Nothing, SolsTokenTypeMap[i].type);
|
||||
}
|
||||
}
|
||||
return Error(SolsTokenType, Nothing, {});
|
||||
}
|
||||
|
||||
static ResultType(Nothing, charptr) handleGround(SolsLexer* lexer, SolsToken* token, size_t* lineNum, Estr* currentLine, char currentChr, bool* skipDelimiter) {
|
||||
bool foundBrace = false;
|
||||
if (currentChr == '{') {
|
||||
foundBrace = true;
|
||||
*skipDelimiter = true;
|
||||
} else {
|
||||
while (true) {
|
||||
ResultType(char, Nothing) peek = lexerPeek(lexer, 1);
|
||||
if (peek.error) break;
|
||||
if (isspace(peek.as.success)) {
|
||||
char c = lexerConsume(lexer).as.success;
|
||||
if (c == '\n') {
|
||||
(*lineNum)++;
|
||||
DESTROY_ESTR((*currentLine));
|
||||
*currentLine = CREATE_ESTR("");
|
||||
size_t lineStart = lexer->current;
|
||||
for (size_t i = lineStart; i < lexer->inputsize; i++) {
|
||||
if (lexer->input[i] == '\n' || lexer->input[i] == '\r') 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;
|
||||
bool isHex = false;
|
||||
if (token[0] == '0' && (token[1] == 'x' || token[1] == 'X')) {
|
||||
isHex = true;
|
||||
isInt = false;
|
||||
}
|
||||
for (size_t i = 1; i < len; i++) {
|
||||
if (isHex) {
|
||||
if (i == 1) continue;
|
||||
if (!isdigit(token[i]) && !('A' <= token[i] && token[i] <= 'F') && !('a' <= token[i] && token[i] <= 'f')) {
|
||||
isHex = false;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isInt && token[i] == '.') {
|
||||
isInt = false;
|
||||
isDouble = true;
|
||||
continue;
|
||||
}
|
||||
if (!isdigit(token[i])) {
|
||||
isInt = false;
|
||||
isDouble = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (isHex) {
|
||||
int64_t newInt = strtoll(token, NULL, 0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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' || lexer->input[lineStart] == '\r') {
|
||||
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' || lexer->input[i] == '\r') 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' || lexer->input[i] == '\r') {
|
||||
break;
|
||||
}
|
||||
char buf_tmp[] = {lexer->input[i], '\0'};
|
||||
APPEND_ESTR(currentLine, buf_tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if (inString) {
|
||||
char str[2] = { chr.as.success, '\0' };
|
||||
APPEND_ESTR(buf, str);
|
||||
if (chr.as.success == '"') {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (chr.as.success) {
|
||||
case '"': {
|
||||
inString = true;
|
||||
APPEND_ESTR(buf, "\"");
|
||||
break;
|
||||
}
|
||||
|
||||
// These characters require themselves added seperately from the previous token.
|
||||
case '{':
|
||||
case '}':
|
||||
case '(':
|
||||
case ')':
|
||||
case ',':
|
||||
case ':':
|
||||
case ';':
|
||||
case '[':
|
||||
case ']':
|
||||
case '\n':
|
||||
{
|
||||
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter);
|
||||
if (res.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, res.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
|
||||
if (skipDelimiter) break;
|
||||
|
||||
char tmp[] = {chr.as.success, '\0'};
|
||||
ResultType(SolsToken, charptr) result = identifyToken(tmp);
|
||||
if (result.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, result.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
result.as.success.line.num = lineNum;
|
||||
result.as.success.line.content = malloc(strlen(currentLine.str) + 1);
|
||||
if (result.as.success.line.content == NULL) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
strcpy(result.as.success.line.content, currentLine.str);
|
||||
|
||||
addTokenToSolsTokens(&lexer->output, result.as.success);
|
||||
break;
|
||||
}
|
||||
|
||||
// These characters may be repeated, or followed by an equals sign.
|
||||
case '+':
|
||||
case '-': {
|
||||
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter);
|
||||
if (res.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, res.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
// skipDelimiter is unlikely here but handled just in case
|
||||
if (skipDelimiter) break;
|
||||
|
||||
ResultType(char, Nothing) next = lexerPeek(lexer, 1);
|
||||
if (next.error || (next.as.success != chr.as.success && next.as.success != '=')) {
|
||||
char tmp[] = {chr.as.success, '\0'};
|
||||
ResultType(SolsToken, charptr) result = identifyToken(tmp);
|
||||
if (result.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, result.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
result.as.success.line.num = lineNum;
|
||||
result.as.success.line.content = malloc(strlen(currentLine.str) + 1);
|
||||
if (result.as.success.line.content == NULL) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
strcpy(result.as.success.line.content, currentLine.str);
|
||||
addTokenToSolsTokens(&lexer->output, result.as.success);
|
||||
}
|
||||
if (next.as.success == '=') {
|
||||
char tmp[] = {chr.as.success, '=', '\0'};
|
||||
ResultType(SolsToken, charptr) result = identifyToken(tmp);
|
||||
if (result.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, result.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
result.as.success.line.num = lineNum;
|
||||
result.as.success.line.content = malloc(strlen(currentLine.str) + 1);
|
||||
if (result.as.success.line.content == NULL) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
strcpy(result.as.success.line.content, currentLine.str);
|
||||
addTokenToSolsTokens(&lexer->output, result.as.success);
|
||||
lexerConsume(lexer);
|
||||
}
|
||||
if (next.as.success == chr.as.success) {
|
||||
char tmp[] = {chr.as.success, chr.as.success, '\0'};
|
||||
ResultType(SolsToken, charptr) result = identifyToken(tmp);
|
||||
if (result.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, result.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
result.as.success.line.num = lineNum;
|
||||
result.as.success.line.content = malloc(strlen(currentLine.str) + 1);
|
||||
if (result.as.success.line.content == NULL) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
strcpy(result.as.success.line.content, currentLine.str);
|
||||
addTokenToSolsTokens(&lexer->output, result.as.success);
|
||||
lexerConsume(lexer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// These characters may be followed by an equals sign, or nothing else.
|
||||
case '=':
|
||||
case '!':
|
||||
case '>':
|
||||
case '<':
|
||||
case '*':
|
||||
case '/': {
|
||||
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter);
|
||||
if (res.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, res.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
if (skipDelimiter) break;
|
||||
|
||||
ResultType(char, Nothing) next = lexerPeek(lexer, 1);
|
||||
if (next.error || next.as.success != '=') {
|
||||
char tmp[] = {chr.as.success, '\0'};
|
||||
ResultType(SolsToken, charptr) result = identifyToken(tmp);
|
||||
if (result.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, result.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
result.as.success.line.num = lineNum;
|
||||
result.as.success.line.content = malloc(strlen(currentLine.str) + 1);
|
||||
if (result.as.success.line.content == NULL) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
strcpy(result.as.success.line.content, currentLine.str);
|
||||
addTokenToSolsTokens(&lexer->output, result.as.success);
|
||||
}
|
||||
if (next.as.success == '=') {
|
||||
char tmp[] = {chr.as.success, '=', '\0'};
|
||||
ResultType(SolsToken, charptr) result = identifyToken(tmp);
|
||||
if (result.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, result.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
result.as.success.line.num = lineNum;
|
||||
result.as.success.line.content = malloc(strlen(currentLine.str) + 1);
|
||||
if (result.as.success.line.content == NULL) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Couldn't allocate memory to store line information in token (in lex() function)");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
strcpy(result.as.success.line.content, currentLine.str);
|
||||
addTokenToSolsTokens(&lexer->output, result.as.success);
|
||||
lexerConsume(lexer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// '.' requires checking whether it's a number or an identifier after
|
||||
case '.': {
|
||||
ResultType(char, Nothing) peek = lexerPeek(lexer, 1);
|
||||
// If the next character is a digit, then this is a literal, not a member access dot.
|
||||
if (!peek.error && isdigit(peek.as.success)) {
|
||||
APPEND_ESTR(buf, ".");
|
||||
} else {
|
||||
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter);
|
||||
if (res.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, res.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
if (!skipDelimiter) {
|
||||
addTokenToSolsTokens(&lexer->output, (SolsToken) {.type = STT_DOT});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// This whitespace splits the program and does not get appended as it's own token.
|
||||
case '\t':
|
||||
case ' ':
|
||||
case '\r': {
|
||||
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, chr.as.success, &skipDelimiter);
|
||||
if (res.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, res.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
char newchar[] = {chr.as.success, '\0'};
|
||||
APPEND_ESTR(buf, newchar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether we need to parse types
|
||||
if (strcmp(buf.str, "fun") == 0) {
|
||||
if (!lexerPeek(lexer, 1).error && lexerPeek(lexer, 1).as.success == '(') {
|
||||
// do stuff
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(buf.str, "template") == 0 ) {
|
||||
if (!lexerPeek(lexer, 1).error && lexerPeek(lexer, 1).as.success == '(') {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(buf.str, "object") == 0 ) {
|
||||
if (!lexerPeek(lexer, 1).error && lexerPeek(lexer, 1).as.success == '(') {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResultType(Nothing, charptr) res = identifyAndAdd(lexer, &buf, &lineNum, ¤tLine, '\0', &skipDelimiter);
|
||||
if (res.error) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, res.as.error);
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
|
||||
if (inString) {
|
||||
char* err = createLexingError(lineNum, currentLine.str, "Unterminated string");
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Error(Nothing, charptr, err);
|
||||
}
|
||||
|
||||
DESTROY_ESTR(buf);
|
||||
DESTROY_ESTR(currentLine);
|
||||
return Success(Nothing, charptr, (Nothing){});
|
||||
}
|
||||
|
||||
ResultType(Nothing, charptr) processTypeSignature(SolsLexer* lexer) {
|
||||
return Error(Nothing, charptr, "WIP (in processTypeSignature() function)");
|
||||
}
|
||||
76
src/lexer/lexer.h
Normal file
76
src/lexer/lexer.h
Normal 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
|
||||
4
src/linenoise/.gitignore
vendored
Normal file
4
src/linenoise/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
linenoise-example
|
||||
linenoise-test
|
||||
*.dSYM
|
||||
history.txt
|
||||
25
src/linenoise/LICENSE
Normal file
25
src/linenoise/LICENSE
Normal file
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
13
src/linenoise/Makefile
Normal file
13
src/linenoise/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
all: linenoise-example linenoise-test
|
||||
|
||||
linenoise-example: linenoise.h linenoise.c example.c
|
||||
$(CC) -Wall -W -Os -g -o linenoise-example linenoise.c example.c
|
||||
|
||||
linenoise-test: linenoise-test.c linenoise-example
|
||||
$(CC) -Wall -W -Os -g -o linenoise-test linenoise-test.c
|
||||
|
||||
test: linenoise-test linenoise-example
|
||||
./linenoise-test
|
||||
|
||||
clean:
|
||||
rm -f linenoise-example linenoise-test
|
||||
380
src/linenoise/README.markdown
Normal file
380
src/linenoise/README.markdown
Normal file
@@ -0,0 +1,380 @@
|
||||
# Linenoise
|
||||
|
||||
A minimal, zero-config, BSD licensed, readline replacement used in Redis,
|
||||
MongoDB, Android and many other projects.
|
||||
|
||||
* Single and multi line editing mode with the usual key bindings implemented.
|
||||
* History handling.
|
||||
* Completion.
|
||||
* Hints (suggestions at the right of the prompt as you type).
|
||||
* Multiplexing mode, with prompt hiding/restoring for asynchronous output.
|
||||
* UTF-8 support for multi-byte characters and emoji.
|
||||
* About ~1100 lines (comments and spaces excluded) of BSD license source code.
|
||||
* Only uses a subset of VT100 escapes (ANSI.SYS compatible).
|
||||
|
||||
## Can a line editing library be 20k lines of code?
|
||||
|
||||
Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?
|
||||
|
||||
So what usually happens is either:
|
||||
|
||||
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (real world example of this problem: Tclsh).
|
||||
* Smaller programs not using a configure script not supporting line editing at all (A problem we had with `redis-cli`, for instance).
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no
|
||||
VT220 specific sequences are used anymore.
|
||||
|
||||
The library is currently about 850 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is pretty straightforward. The library supports both a blocking mode and a multiplexing mode, see the API documentation later in this file for more information.
|
||||
|
||||
Linenoise is BSD-licensed code, so you can use both in free software and commercial software.
|
||||
|
||||
## Tested with...
|
||||
|
||||
* Linux text only console ($TERM = linux)
|
||||
* Linux KDE terminal application ($TERM = xterm)
|
||||
* Linux xterm ($TERM = xterm)
|
||||
* Linux Buildroot ($TERM = vt100)
|
||||
* Mac OS X iTerm ($TERM = xterm)
|
||||
* Mac OS X default Terminal.app ($TERM = xterm)
|
||||
* OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
|
||||
* IBM AIX 6.1
|
||||
* FreeBSD xterm ($TERM = xterm)
|
||||
* ANSI.SYS
|
||||
* Emacs comint mode ($TERM = dumb)
|
||||
|
||||
Please test it everywhere you can and report back!
|
||||
|
||||
## Let's push this forward!
|
||||
|
||||
Patches should be provided in the respect of Linenoise sensibility for small
|
||||
easy to understand code.
|
||||
|
||||
Send feedbacks to antirez at gmail
|
||||
|
||||
# The API
|
||||
|
||||
Linenoise is very easy to use, and reading the example shipped with the
|
||||
library should get you up to speed ASAP. Here is a list of API calls
|
||||
and how to use them. Let's start with the simple blocking mode:
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
|
||||
This is the main Linenoise call: it shows the user a prompt with line editing
|
||||
and history capabilities. The prompt you specify is used as a prompt, that is,
|
||||
it will be printed to the left of the cursor. The library returns a buffer
|
||||
with the line composed by the user, or NULL on end of file or when there
|
||||
is an out of memory condition.
|
||||
|
||||
When a tty is detected (the user is actually typing into a terminal session)
|
||||
the maximum editable line length is `LINENOISE_MAX_LINE`. When instead the
|
||||
standard input is not a tty, which happens every time you redirect a file
|
||||
to a program, or use it in an Unix pipeline, there are no limits to the
|
||||
length of the line that can be returned.
|
||||
|
||||
The returned line should be freed with the `free()` standard system call.
|
||||
However sometimes it could happen that your program uses a different dynamic
|
||||
allocation library, so you may also used `linenoiseFree` to make sure the
|
||||
line is freed with the same allocator it was created.
|
||||
|
||||
The canonical loop used by a program using Linenoise will be something like
|
||||
this:
|
||||
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
printf("You wrote: %s\n", line);
|
||||
linenoiseFree(line); /* Or just free(line) if you use libc malloc. */
|
||||
}
|
||||
|
||||
## Single line VS multi line editing
|
||||
|
||||
By default, Linenoise uses single line editing, that is, a single row on the
|
||||
screen will be used, and as the user types more, the text will scroll towards
|
||||
left to make room. This works if your program is one where the user is
|
||||
unlikely to write a lot of text, otherwise multi line editing, where multiple
|
||||
screens rows are used, can be a lot more comfortable.
|
||||
|
||||
In order to enable multi line editing use the following API call:
|
||||
|
||||
linenoiseSetMultiLine(1);
|
||||
|
||||
You can disable it using `0` as argument.
|
||||
|
||||
## History
|
||||
|
||||
Linenoise supporst history, so that the user does not have to retype
|
||||
again and again the same things, but can use the down and up arrows in order
|
||||
to search and re-edit already inserted lines of text.
|
||||
|
||||
The followings are the history API calls:
|
||||
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
|
||||
Use `linenoiseHistoryAdd` every time you want to add a new element
|
||||
to the top of the history (it will be the first the user will see when
|
||||
using the up arrow).
|
||||
|
||||
Note that for history to work, you have to set a length for the history
|
||||
(which is zero by default, so history will be disabled if you don't set
|
||||
a proper one). This is accomplished using the `linenoiseHistorySetMaxLen`
|
||||
function.
|
||||
|
||||
Linenoise has direct support for persisting the history into an history
|
||||
file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
|
||||
just that. Both functions return -1 on error and 0 on success.
|
||||
|
||||
## Mask mode
|
||||
|
||||
Sometimes it is useful to allow the user to type passwords or other
|
||||
secrets that should not be displayed. For such situations linenoise supports
|
||||
a "mask mode" that will just replace the characters the user is typing
|
||||
with `*` characters, like in the following example:
|
||||
|
||||
$ ./linenoise_example
|
||||
hello> get mykey
|
||||
echo: 'get mykey'
|
||||
hello> /mask
|
||||
hello> *********
|
||||
|
||||
You can enable and disable mask mode using the following two functions:
|
||||
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
## Completion
|
||||
|
||||
Linenoise supports completion, which is the ability to complete the user
|
||||
input when she or he presses the `<TAB>` key.
|
||||
|
||||
In order to use completion, you need to register a completion callback, which
|
||||
is called every time the user presses `<TAB>`. Your callback will return a
|
||||
list of items that are completions for the current string.
|
||||
|
||||
The following is an example of registering a completion callback:
|
||||
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
|
||||
The completion must be a function returning `void` and getting as input
|
||||
a `const char` pointer, which is the line the user has typed so far, and
|
||||
a `linenoiseCompletions` object pointer, which is used as argument of
|
||||
`linenoiseAddCompletion` in order to add completions inside the callback.
|
||||
An example will make it more clear:
|
||||
|
||||
void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
if (buf[0] == 'h') {
|
||||
linenoiseAddCompletion(lc,"hello");
|
||||
linenoiseAddCompletion(lc,"hello there");
|
||||
}
|
||||
}
|
||||
|
||||
Basically in your completion callback, you inspect the input, and return
|
||||
a list of items that are good completions by using `linenoiseAddCompletion`.
|
||||
|
||||
If you want to test the completion feature, compile the example program
|
||||
with `make`, run it, type `h` and press `<TAB>`.
|
||||
|
||||
## Hints
|
||||
|
||||
Linenoise has a feature called *hints* which is very useful when you
|
||||
use Linenoise in order to implement a REPL (Read Eval Print Loop) for
|
||||
a program that accepts commands and arguments, but may also be useful in
|
||||
other conditions.
|
||||
|
||||
The feature shows, on the right of the cursor, as the user types, hints that
|
||||
may be useful. The hints can be displayed using a different color compared
|
||||
to the color the user is typing, and can also be bold.
|
||||
|
||||
For example as the user starts to type `"git remote add"`, with hints it's
|
||||
possible to show on the right of the prompt a string `<name> <url>`.
|
||||
|
||||
The feature works similarly to the history feature, using a callback.
|
||||
To register the callback we use:
|
||||
|
||||
linenoiseSetHintsCallback(hints);
|
||||
|
||||
The callback itself is implemented like this:
|
||||
|
||||
char *hints(const char *buf, int *color, int *bold) {
|
||||
if (!strcasecmp(buf,"git remote add")) {
|
||||
*color = 35;
|
||||
*bold = 0;
|
||||
return " <name> <url>";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
The callback function returns the string that should be displayed or NULL
|
||||
if no hint is available for the text the user currently typed. The returned
|
||||
string will be trimmed as needed depending on the number of columns available
|
||||
on the screen.
|
||||
|
||||
It is possible to return a string allocated in dynamic way, by also registering
|
||||
a function to deallocate the hint string once used:
|
||||
|
||||
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
|
||||
|
||||
The free hint callback will just receive the pointer and free the string
|
||||
as needed (depending on how the hits callback allocated it).
|
||||
|
||||
As you can see in the example above, a `color` (in xterm color terminal codes)
|
||||
can be provided together with a `bold` attribute. If no color is set, the
|
||||
current terminal foreground color is used. If no bold attribute is set,
|
||||
non-bold text is printed.
|
||||
|
||||
Color codes are:
|
||||
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
magenta = 35
|
||||
cyan = 36
|
||||
white = 37;
|
||||
|
||||
## Screen handling
|
||||
|
||||
Sometimes you may want to clear the screen as a result of something the
|
||||
user typed. You can do this by calling the following function:
|
||||
|
||||
void linenoiseClearScreen(void);
|
||||
|
||||
## Asyncrhronous API
|
||||
|
||||
Sometimes you want to read from the keyboard but also from sockets or other
|
||||
external events, and at the same time there could be input to display to the
|
||||
user *while* the user is typing something. Let's call this the "IRC problem",
|
||||
since if you want to write an IRC client with linenoise, without using
|
||||
some fully featured libcurses approach, you will surely end having such an
|
||||
issue.
|
||||
|
||||
Fortunately now a multiplexing friendly API exists, and it is just what the
|
||||
blocking calls internally use. To start, we need to initialize a linenoise
|
||||
context like this:
|
||||
|
||||
struct linenoiseState ls;
|
||||
char buf[1024];
|
||||
linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"some prompt> ");
|
||||
|
||||
The two -1 and -1 arguments are the stdin/out descriptors. If they are
|
||||
set to -1, linenoise will just use the default stdin/out file descriptors.
|
||||
Now as soon as we have data from stdin (and we know it via select(2) or
|
||||
some other way), we can ask linenoise to read the next character with:
|
||||
|
||||
linenoiseEditFeed(&ls);
|
||||
|
||||
The function returns a `char` pointer: if the user didn't yet press enter
|
||||
to provide a line to the program, it will return `linenoiseEditMore`, that
|
||||
means we need to call `linenoiseEditFeed()` again when more data is
|
||||
available. If the function returns non NULL, then this is a heap allocated
|
||||
data (to be freed with `linenoiseFree()`) representing the user input.
|
||||
When the function returns NULL, than the user pressed CTRL-C or CTRL-D
|
||||
with an empty line, to quit the program, or there was some I/O error.
|
||||
|
||||
After each line is received (or if you want to quit the program, and exit raw mode), the following function needs to be called:
|
||||
|
||||
linenoiseEditStop(&ls);
|
||||
|
||||
To start reading the next line, a new linenoiseEditStart() must
|
||||
be called, in order to reset the state, and so forth, so a typical event
|
||||
handler called when the standard input is readable, will work similarly
|
||||
to the example below:
|
||||
|
||||
``` c
|
||||
void stdinHasSomeData(void) {
|
||||
char *line = linenoiseEditFeed(&LineNoiseState);
|
||||
if (line == linenoiseEditMore) return;
|
||||
linenoiseEditStop(&LineNoiseState);
|
||||
if (line == NULL) exit(0);
|
||||
|
||||
printf("line: %s\n", line);
|
||||
linenoiseFree(line);
|
||||
linenoiseEditStart(&LineNoiseState,-1,-1,LineNoiseBuffer,sizeof(LineNoiseBuffer),"serial> ");
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have a way to avoid blocking in the user input, we can use
|
||||
two calls to hide/show the edited line, so that it is possible to also
|
||||
show some input that we received (from socekts, bluetooth, whatever) on
|
||||
screen:
|
||||
|
||||
linenoiseHide(&ls);
|
||||
printf("some data...\n");
|
||||
linenoiseShow(&ls);
|
||||
|
||||
To the API calls, the linenoise example C file implements a multiplexing
|
||||
example using select(2) and the asynchronous API:
|
||||
|
||||
```c
|
||||
struct linenoiseState ls;
|
||||
char buf[1024];
|
||||
linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"hello> ");
|
||||
|
||||
while(1) {
|
||||
// Select(2) setup code removed...
|
||||
retval = select(ls.ifd+1, &readfds, NULL, NULL, &tv);
|
||||
if (retval == -1) {
|
||||
perror("select()");
|
||||
exit(1);
|
||||
} else if (retval) {
|
||||
line = linenoiseEditFeed(&ls);
|
||||
/* A NULL return means: line editing is continuing.
|
||||
* Otherwise the user hit enter or stopped editing
|
||||
* (CTRL+C/D). */
|
||||
if (line != linenoiseEditMore) break;
|
||||
} else {
|
||||
// Timeout occurred
|
||||
static int counter = 0;
|
||||
linenoiseHide(&ls);
|
||||
printf("Async output %d.\n", counter++);
|
||||
linenoiseShow(&ls);
|
||||
}
|
||||
}
|
||||
linenoiseEditStop(&ls);
|
||||
if (line == NULL) exit(0); /* Ctrl+D/C. */
|
||||
```
|
||||
|
||||
You can test the example by running the example program with the `--async` option.
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the test suite:
|
||||
|
||||
make test
|
||||
|
||||
The tests will display a virtual terminal showing linenoise output in real-time, making it easy to see what's being tested and debug any failures.
|
||||
|
||||
### What the tests cover
|
||||
|
||||
The test suite verifies:
|
||||
|
||||
* Basic typing and cursor movement (left, right, home, end)
|
||||
* Backspace and delete operations
|
||||
* UTF-8 multi-byte characters (accented letters, CJK)
|
||||
* Emoji and grapheme clusters (skin tones, ZWJ sequences like flags)
|
||||
* Horizontal scrolling for long lines
|
||||
* Multiline mode editing and navigation
|
||||
* History navigation in multiline mode
|
||||
* Word and line deletion (Ctrl-W, Ctrl-U)
|
||||
|
||||
### How the test harness works
|
||||
|
||||
The test program (`linenoise-test.c`) implements a VT100 terminal emulator that captures and verifies linenoise output:
|
||||
|
||||
1. **Fork and pipes**: The test harness forks `linenoise-example`, connecting to it via pipes. The child process sees `LINENOISE_ASSUME_TTY=1` to enable terminal mode despite not having a real TTY.
|
||||
2. **VT100 emulator**: A minimal VT100 emulator parses escape sequences (cursor movement, screen clearing, etc.) and maintains a virtual screen buffer. Each cell stores a complete UTF-8 grapheme cluster and its display width.
|
||||
3. **Visual rendering**: After each operation, the virtual screen is rendered to your real terminal with a border, so you can watch the test execute and see exactly what linenoise is displaying.
|
||||
4. **Assertions**: Tests verify screen contents and cursor position against expected values.
|
||||
|
||||
This approach tests linenoise as users actually experience it, catching rendering bugs that unit tests would miss.
|
||||
|
||||
## Related projects
|
||||
|
||||
* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like Windows support and other features. Uses C++ instead of C as development language.
|
||||
* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.
|
||||
124
src/linenoise/example.c
Normal file
124
src/linenoise/example.c
Normal file
@@ -0,0 +1,124 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
if (buf[0] == 'h') {
|
||||
linenoiseAddCompletion(lc,"hello");
|
||||
linenoiseAddCompletion(lc,"hello there");
|
||||
}
|
||||
}
|
||||
|
||||
char *hints(const char *buf, int *color, int *bold) {
|
||||
if (!strcasecmp(buf,"hello")) {
|
||||
*color = 35;
|
||||
*bold = 0;
|
||||
return " World";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char *line;
|
||||
char *prgname = argv[0];
|
||||
int async = 0;
|
||||
|
||||
/* Parse options, with --multiline we enable multi line editing. */
|
||||
while(argc > 1) {
|
||||
argc--;
|
||||
argv++;
|
||||
if (!strcmp(*argv,"--multiline")) {
|
||||
linenoiseSetMultiLine(1);
|
||||
printf("Multi-line mode enabled.\n");
|
||||
} else if (!strcmp(*argv,"--keycodes")) {
|
||||
linenoisePrintKeyCodes();
|
||||
exit(0);
|
||||
} else if (!strcmp(*argv,"--async")) {
|
||||
async = 1;
|
||||
} else {
|
||||
fprintf(stderr, "Usage: %s [--multiline] [--keycodes] [--async]\n", prgname);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the completion callback. This will be called every time the
|
||||
* user uses the <tab> key. */
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
linenoiseSetHintsCallback(hints);
|
||||
|
||||
/* Load history from file. The history file is just a plain text file
|
||||
* where entries are separated by newlines. */
|
||||
linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
|
||||
|
||||
/* Now this is the main loop of the typical linenoise-based application.
|
||||
* The call to linenoise() will block as long as the user types something
|
||||
* and presses enter.
|
||||
*
|
||||
* The typed string is returned as a malloc() allocated string by
|
||||
* linenoise, so the user needs to free() it. */
|
||||
|
||||
while(1) {
|
||||
if (!async) {
|
||||
line = linenoise("hello> ");
|
||||
if (line == NULL) break;
|
||||
} else {
|
||||
/* Asynchronous mode using the multiplexing API: wait for
|
||||
* data on stdin, and simulate async data coming from some source
|
||||
* using the select(2) timeout. */
|
||||
struct linenoiseState ls;
|
||||
char buf[1024];
|
||||
linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"hello> ");
|
||||
while(1) {
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
int retval;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(ls.ifd, &readfds);
|
||||
tv.tv_sec = 1; // 1 sec timeout
|
||||
tv.tv_usec = 0;
|
||||
|
||||
retval = select(ls.ifd+1, &readfds, NULL, NULL, &tv);
|
||||
if (retval == -1) {
|
||||
perror("select()");
|
||||
exit(1);
|
||||
} else if (retval) {
|
||||
line = linenoiseEditFeed(&ls);
|
||||
/* A NULL return means: line editing is continuing.
|
||||
* Otherwise the user hit enter or stopped editing
|
||||
* (CTRL+C/D). */
|
||||
if (line != linenoiseEditMore) break;
|
||||
} else {
|
||||
// Timeout occurred
|
||||
static int counter = 0;
|
||||
linenoiseHide(&ls);
|
||||
printf("Async output %d.\n", counter++);
|
||||
linenoiseShow(&ls);
|
||||
}
|
||||
}
|
||||
linenoiseEditStop(&ls);
|
||||
if (line == NULL) exit(0); /* Ctrl+D/C. */
|
||||
}
|
||||
|
||||
/* Do something with the string. */
|
||||
if (line[0] != '\0' && line[0] != '/') {
|
||||
printf("echo: '%s'\n", line);
|
||||
linenoiseHistoryAdd(line); /* Add to the history. */
|
||||
linenoiseHistorySave("history.txt"); /* Save the history on disk. */
|
||||
} else if (!strncmp(line,"/historylen",11)) {
|
||||
/* The "/historylen" command will change the history len. */
|
||||
int len = atoi(line+11);
|
||||
linenoiseHistorySetMaxLen(len);
|
||||
} else if (!strncmp(line, "/mask", 5)) {
|
||||
linenoiseMaskModeEnable();
|
||||
} else if (!strncmp(line, "/unmask", 7)) {
|
||||
linenoiseMaskModeDisable();
|
||||
} else if (line[0] == '/') {
|
||||
printf("Unreconized command: %s\n", line);
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
1297
src/linenoise/linenoise-test.c
Normal file
1297
src/linenoise/linenoise-test.c
Normal file
File diff suppressed because it is too large
Load Diff
1762
src/linenoise/linenoise.c
Normal file
1762
src/linenoise/linenoise.c
Normal file
File diff suppressed because it is too large
Load Diff
114
src/linenoise/linenoise.h
Normal file
114
src/linenoise/linenoise.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/* linenoise.h -- VERSION 1.0
|
||||
*
|
||||
* Guerrilla line editing library against the idea that a line editing lib
|
||||
* needs to be 20,000 lines of C code.
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h> /* For size_t. */
|
||||
|
||||
extern char *linenoiseEditMore;
|
||||
|
||||
/* The linenoiseState structure represents the state during line editing.
|
||||
* We pass this state to functions implementing specific editing
|
||||
* functionalities. */
|
||||
struct linenoiseState {
|
||||
int in_completion; /* The user pressed TAB and we are now in completion
|
||||
* mode, so input is handled by completeLine(). */
|
||||
size_t completion_idx; /* Index of next completion to propose. */
|
||||
int ifd; /* Terminal stdin file descriptor. */
|
||||
int ofd; /* Terminal stdout file descriptor. */
|
||||
char *buf; /* Edited line buffer. */
|
||||
size_t buflen; /* Edited line buffer size. */
|
||||
const char *prompt; /* Prompt to display. */
|
||||
size_t plen; /* Prompt length. */
|
||||
size_t pos; /* Current cursor position. */
|
||||
size_t oldpos; /* Previous refresh cursor position. */
|
||||
size_t len; /* Current edited line length. */
|
||||
size_t cols; /* Number of columns in terminal. */
|
||||
size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */
|
||||
int oldrpos; /* Cursor row from last refresh (for multiline clearing). */
|
||||
int history_index; /* The history index we are currently editing. */
|
||||
};
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
} linenoiseCompletions;
|
||||
|
||||
/* Non blocking API. */
|
||||
int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt);
|
||||
char *linenoiseEditFeed(struct linenoiseState *l);
|
||||
void linenoiseEditStop(struct linenoiseState *l);
|
||||
void linenoiseHide(struct linenoiseState *l);
|
||||
void linenoiseShow(struct linenoiseState *l);
|
||||
|
||||
/* Blocking API. */
|
||||
char *linenoise(const char *prompt);
|
||||
void linenoiseFree(void *ptr);
|
||||
|
||||
/* Completion API. */
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
|
||||
typedef void(linenoiseFreeHintsCallback)(void *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
|
||||
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
||||
|
||||
/* History API. */
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
|
||||
/* Other utilities. */
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
328
src/main.c
Normal file
328
src/main.c
Normal file
@@ -0,0 +1,328 @@
|
||||
#include "lexer/SolsType.h"
|
||||
#include "lexer/lexer.h"
|
||||
#include "repl/repl.h"
|
||||
#include "typeparser/typeparser.h"
|
||||
#include "parser/parser.h"
|
||||
#include "codegen/codegen.h"
|
||||
#include "include/error.h"
|
||||
#include "include/estr.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
#include <ground.h>
|
||||
|
||||
char* getFileContents(const char* filename) {
|
||||
// https://stackoverflow.com/questions/3747086/reading-the-whole-text-file-into-a-char-array-in-c
|
||||
FILE* fp;
|
||||
long lSize;
|
||||
char* file;
|
||||
|
||||
fp = fopen(filename, "rb");
|
||||
if (!fp) {
|
||||
perror(filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
lSize = ftell(fp);
|
||||
rewind(fp);
|
||||
|
||||
file = calloc(1, lSize + 1);
|
||||
if (!file) {
|
||||
fclose(fp);
|
||||
fprintf(stderr, "memory allocation fail when reading file %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (1!=fread(file, lSize, 1, fp)) {
|
||||
fclose(fp);
|
||||
free(file);
|
||||
fputs("couldn't read entire file", stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// we done
|
||||
fclose(fp);
|
||||
|
||||
// Strip UTF-8 Byte Order Mark (BOM) if present
|
||||
if (lSize >= 3 && (unsigned char)file[0] == 0xEF && (unsigned char)file[1] == 0xBB && (unsigned char)file[2] == 0xBF) {
|
||||
memmove(file, file + 3, lSize - 2);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
char* fileDir = NULL;
|
||||
|
||||
typedef enum SolsAction {
|
||||
SA_PRINT, SA_EXEC, SA_BYTECODE, SA_COMPILE, SA_EXIT, SA_EXITOK
|
||||
} SolsAction;
|
||||
|
||||
typedef struct Args {
|
||||
SolsAction action;
|
||||
char* inputFile;
|
||||
char* outputFile;
|
||||
} Args;
|
||||
|
||||
Args parseArgs(int argc, char** argv) {
|
||||
Args args = {
|
||||
.action = SA_EXEC,
|
||||
.inputFile = NULL,
|
||||
.outputFile = NULL
|
||||
};
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) {
|
||||
printf("Solstice programming language\n");
|
||||
printf("Usage: %s <file> [-h] [--help] [-p] [--print] [-b <file>] [--bytecode <file>] [-c <file>] [--compile <file>]\n", argv[0]);
|
||||
printf("Args:\n");
|
||||
printf(" <file>: Solstice source file\n");
|
||||
printf(" -h or --help: Prints this help message and exits\n");
|
||||
printf(" -p or --print: Prints textual version of Ground bytecode to console\n");
|
||||
printf(" -b <file> or --bytecode <file>: Generates Ground bytecode (.grbc) and saves it to the provided filename\n");
|
||||
printf(" -c <file> or --compile <file>: Compiles Ground to Linux x86_64 assembly, outputs a binary to the provided filename (experimental)\n");
|
||||
printf(" If no extra arguments are provided, the generated Ground bytecode will be executed.\n");
|
||||
args.action = SA_EXIT;
|
||||
return args;
|
||||
}
|
||||
else if (strcmp("-p", argv[i]) == 0 || strcmp("--print", argv[i]) == 0) {
|
||||
args.action = SA_PRINT;
|
||||
}
|
||||
else if (strcmp("-b", argv[i]) == 0 || strcmp("--bytecode", argv[i]) == 0) {
|
||||
if (args.action != SA_EXEC) {
|
||||
printf("Expecting only one action\n");
|
||||
args.action = SA_EXIT;
|
||||
return args;
|
||||
}
|
||||
args.action = SA_BYTECODE;
|
||||
if (i + 1 >= argc) {
|
||||
printf("Expecting file name after %s\n", argv[i]);
|
||||
args.action = SA_EXIT;
|
||||
return args;
|
||||
}
|
||||
i++;
|
||||
args.outputFile = argv[i];
|
||||
}
|
||||
else if (strcmp("-c", argv[i]) == 0 || strcmp("--compile", argv[i]) == 0) {
|
||||
if (args.action != SA_EXEC) {
|
||||
printf("Expecting only one action\n");
|
||||
args.action = SA_EXIT;
|
||||
return args;
|
||||
}
|
||||
args.action = SA_COMPILE;
|
||||
if (i + 1 >= argc) {
|
||||
printf("Expecting file name after %s\n", argv[i]);
|
||||
args.action = SA_EXIT;
|
||||
return args;
|
||||
}
|
||||
i++;
|
||||
args.outputFile = argv[i];
|
||||
}
|
||||
else {
|
||||
args.inputFile = argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (args.inputFile == NULL) {
|
||||
printf("Usage: %s <file> [-h] [--help] [-p] [--print] [-b <file>] [--bytecode <file>] [-c <file>] [--compile <file>]\n", argv[0]);
|
||||
args.action = SA_EXIT;
|
||||
return args;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// Use ground's getFileContents
|
||||
char* getFileContents(const char* filename);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc == 1) {
|
||||
#ifdef _WIN32
|
||||
printf("Usage: %s <file> [-h] [--help] [-p] [--print] [-b <file>] [--bytecode <file>] [-c <file>] [--compile <file>]\n", argv[0]);
|
||||
#else
|
||||
solsticeRepl();
|
||||
exit(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
Args args = parseArgs(argc, argv);
|
||||
|
||||
if (args.action == SA_EXIT) {
|
||||
return 1;
|
||||
}
|
||||
if (args.action == SA_EXITOK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
fileDir = NULL;
|
||||
#else
|
||||
{
|
||||
char tmp[FILENAME_MAX];
|
||||
char pwd[FILENAME_MAX];
|
||||
getcwd(pwd, sizeof(pwd));
|
||||
snprintf(tmp, FILENAME_MAX, "%s/%s", pwd, args.inputFile);
|
||||
fileDir = dirname(tmp);
|
||||
}
|
||||
#endif
|
||||
char* fileContents = getFileContents(args.inputFile);
|
||||
|
||||
if (fileContents == NULL) {
|
||||
printf("Couldn't read that file :(\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Lex file
|
||||
ResultType(SolsLexer, charptr) lexer = createLexer(fileContents);
|
||||
if (lexer.error) {
|
||||
printf("Error while creating lexer: %s", lexer.as.error);
|
||||
return 1;
|
||||
}
|
||||
ResultType(Nothing, charptr) lexed = lex(&lexer.as.success);
|
||||
if (lexed.error) {
|
||||
printf("%s\n", lexed.as.error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Detect and parse types
|
||||
ResultType(SolsTokens, charptr) typed = addTypeInfo(&lexer.as.success.output);
|
||||
if (typed.error) {
|
||||
printf("%s\n", typed.as.error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse file
|
||||
ResultType(SolsParser, charptr) parser = createSolsParser(&typed.as.success);
|
||||
if (parser.error) {
|
||||
printf("Error while creating parser: %s\n", parser.as.error);
|
||||
return 1;
|
||||
}
|
||||
ResultType(Nothing, charptr) parsed = parse(&parser.as.success);
|
||||
if (parsed.error) {
|
||||
printf("%s\n", parsed.as.error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
SolsScope scope = {
|
||||
.variables = NULL,
|
||||
.tmpCounter = 0,
|
||||
.returnType = createSolsType(STT_INT).as.success
|
||||
};
|
||||
|
||||
// Do codegen on root node
|
||||
ResultType(GroundProgram, charptr) codegen = generateCode(&parser.as.success.output, &scope);
|
||||
if (codegen.error) {
|
||||
printf("%s\n", codegen.as.error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (args.action) {
|
||||
case SA_PRINT: {
|
||||
printf("%s", Ground.Stringify.Program(&codegen.as.success));
|
||||
break;
|
||||
}
|
||||
case SA_EXEC: {
|
||||
GroundState state = {
|
||||
.catches = NULL,
|
||||
.labels = NULL,
|
||||
.variables = NULL
|
||||
};
|
||||
Ground.Program.execute(&codegen.as.success, &state);
|
||||
if (Ground.Flags.error) {
|
||||
Ground.Log.printErrors();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SA_BYTECODE: {
|
||||
GroundState state = {
|
||||
.catches = NULL,
|
||||
.labels = NULL,
|
||||
.variables = NULL
|
||||
};
|
||||
// Ground requires we preprocess the program before converting to bytecode
|
||||
GroundProgram preprocessed = Ground.Program.preprocess(&codegen.as.success, &state);
|
||||
if (Ground.Flags.error) {
|
||||
Ground.Log.printErrors();
|
||||
return 1;
|
||||
}
|
||||
// Then generate and save bytecode
|
||||
GroundBytecode bc = Ground.New.Bytecode(&preprocessed, &state);
|
||||
if (Ground.Flags.error) {
|
||||
Ground.Log.printErrors();
|
||||
return 1;
|
||||
}
|
||||
Ground.Bytecode.save(&bc, args.outputFile);
|
||||
if (Ground.Flags.error) {
|
||||
Ground.Log.printErrors();
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SA_COMPILE: {
|
||||
// FIXME
|
||||
/*
|
||||
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;
|
||||
}
|
||||
563
src/main.cpp
563
src/main.cpp
@@ -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);
|
||||
}
|
||||
266
src/parser/SolsNode.c
Normal file
266
src/parser/SolsNode.c
Normal file
@@ -0,0 +1,266 @@
|
||||
#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),
|
||||
.accessArg = Ground.New.Arg.Value(Ground.New.Value.Int(0))
|
||||
};
|
||||
|
||||
if (node.children.at == NULL) {
|
||||
return Error(SolsNode, charptr, "Failed to allocate memory for children (in createSolsNode() function)");
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SNT_LITERAL: {
|
||||
node.as.literal = va_arg(args, SolsLiteral);
|
||||
break;
|
||||
}
|
||||
case SNT_TYPE: {
|
||||
node.as.type = va_arg(args, SolsType);
|
||||
break;
|
||||
}
|
||||
case SNT_IDENTIFIER: {
|
||||
node.as.idName = va_arg(args, char*);
|
||||
break;
|
||||
}
|
||||
case SNT_GROUND: {
|
||||
node.as.inlineGround = va_arg(args, char*);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
return Success(SolsNode, charptr, node);
|
||||
}
|
||||
|
||||
ResultType(Nothing, charptr) addChildToSolsNode(SolsNode* parent, SolsNode child) {
|
||||
if (parent->children.count + 1 >= parent->children.capacity) {
|
||||
parent->children.capacity *= 2;
|
||||
SolsNode* tmp = realloc(parent->children.at, sizeof(SolsNode) * parent->children.capacity);
|
||||
if (tmp == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to increase memory for children (in addChildToSolsNode() function)");
|
||||
}
|
||||
parent->children.at = tmp;
|
||||
}
|
||||
parent->children.at[parent->children.count] = deepCopySolsNode(child);
|
||||
parent->children.count++;
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
|
||||
SolsNode deepCopySolsNode(SolsNode node) {
|
||||
SolsNode copy = node;
|
||||
copy.children.at = malloc(sizeof(SolsNode) * node.children.capacity);
|
||||
for (size_t i = 0; i < node.children.count; i++) {
|
||||
copy.children.at[i] = deepCopySolsNode(node.children.at[i]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
void printSolsNodeType(SolsNodeType type) {
|
||||
switch (type) {
|
||||
case SNT_IDENTIFIER:
|
||||
printf("Identifier");
|
||||
break;
|
||||
case SNT_LITERAL:
|
||||
printf("Literal");
|
||||
break;
|
||||
case SNT_TYPE:
|
||||
printf("Type");
|
||||
break;
|
||||
case SNT_CODE_BLOCK:
|
||||
printf("Code Block");
|
||||
break;
|
||||
case SNT_OP_ADD:
|
||||
printf("+");
|
||||
break;
|
||||
case SNT_OP_SUB:
|
||||
printf("-");
|
||||
break;
|
||||
case SNT_OP_MUL:
|
||||
printf("*");
|
||||
break;
|
||||
case SNT_OP_DIV:
|
||||
printf("/");
|
||||
break;
|
||||
case SNT_OP_ADDTO:
|
||||
printf("+=");
|
||||
break;
|
||||
case SNT_OP_SUBTO:
|
||||
printf("-=");
|
||||
break;
|
||||
case SNT_OP_MULTO:
|
||||
printf("*=");
|
||||
break;
|
||||
case SNT_OP_DIVTO:
|
||||
printf("/=");
|
||||
break;
|
||||
case SNT_OP_INCREMENT:
|
||||
printf("++");
|
||||
break;
|
||||
case SNT_OP_DECREMENT:
|
||||
printf("--");
|
||||
break;
|
||||
case SNT_OP_SET:
|
||||
printf("=");
|
||||
break;
|
||||
case SNT_OP_GREATER:
|
||||
printf(">");
|
||||
break;
|
||||
case SNT_OP_LESSER:
|
||||
printf("<");
|
||||
break;
|
||||
case SNT_OP_EQUAL:
|
||||
printf("==");
|
||||
break;
|
||||
case SNT_OP_INEQUAL:
|
||||
printf("!=");
|
||||
break;
|
||||
case SNT_OP_EQGREATER:
|
||||
printf(">=");
|
||||
break;
|
||||
case SNT_OP_EQLESSER:
|
||||
printf("<=");
|
||||
break;
|
||||
case SNT_DEF:
|
||||
printf("def");
|
||||
break;
|
||||
case SNT_LAMBDA:
|
||||
printf("lambda");
|
||||
break;
|
||||
case SNT_FUNCTION_CALL:
|
||||
printf("Function call");
|
||||
break;
|
||||
case SNT_RETURN:
|
||||
printf("return");
|
||||
break;
|
||||
case SNT_SET_PRIVATE:
|
||||
printf("Private =");
|
||||
break;
|
||||
case SNT_SET_PROTECTED:
|
||||
printf("Protected =");
|
||||
break;
|
||||
case SNT_DEF_PRIVATE:
|
||||
printf("Private def");
|
||||
break;
|
||||
case SNT_DEF_PROTECTED:
|
||||
printf("Protected def");
|
||||
break;
|
||||
case SNT_USE:
|
||||
printf("use");
|
||||
break;
|
||||
case SNT_LOCAL_USE:
|
||||
printf("Local use");
|
||||
break;
|
||||
case SNT_STRUCT:
|
||||
printf("struct");
|
||||
break;
|
||||
case SNT_ENUM:
|
||||
printf("enum");
|
||||
break;
|
||||
case SNT_CONSTRUCTOR:
|
||||
printf("constructor");
|
||||
break;
|
||||
case SNT_DESTRUCTOR:
|
||||
printf("destructor");
|
||||
break;
|
||||
case SNT_DUPLICATOR:
|
||||
printf("duplicator");
|
||||
break;
|
||||
case SNT_STRUCT_AS:
|
||||
printf("struct as");
|
||||
break;
|
||||
case SNT_AS:
|
||||
printf("as");
|
||||
break;
|
||||
case SNT_SIZE_OF:
|
||||
printf("sizeof");
|
||||
break;
|
||||
case SNT_PUTS:
|
||||
printf("puts");
|
||||
break;
|
||||
case SNT_IF:
|
||||
printf("if");
|
||||
break;
|
||||
case SNT_WHILE:
|
||||
printf("while");
|
||||
break;
|
||||
case SNT_NEW:
|
||||
printf("new");
|
||||
break;
|
||||
case SNT_GROUND:
|
||||
printf("ground");
|
||||
break;
|
||||
case SNT_ROOT:
|
||||
printf("Root node");
|
||||
break;
|
||||
case SNT_EXPR_IN_PAREN:
|
||||
printf("Parenthases");
|
||||
break;
|
||||
case SNT_DOT:
|
||||
printf(".");
|
||||
break;
|
||||
case SNT_GENERIC:
|
||||
printf("Generic");
|
||||
break;
|
||||
case SNT_GENERIC_INIT:
|
||||
printf("Generic init");
|
||||
break;
|
||||
case SNT_PRAGMA:
|
||||
printf("pragma");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void printSolsNode(SolsNode* node) {
|
||||
|
||||
printf("Node (\nNodeType: ");
|
||||
printSolsNodeType(node->type);
|
||||
printf("\n");
|
||||
printf("Line: num: %zu, content: %s",node->line.num, node->line.content);
|
||||
|
||||
switch (node->type) {
|
||||
case SNT_IDENTIFIER: {
|
||||
printf("\nIdentifier name: '%s'", node->as.idName);
|
||||
break;
|
||||
}
|
||||
case SNT_GROUND: {
|
||||
printf("\nInline Ground: '%s'", node->as.inlineGround);
|
||||
break;
|
||||
}
|
||||
case SNT_LITERAL: {
|
||||
printf("\nLiteral: ");
|
||||
printSolsLiteral(&node->as.literal);
|
||||
break;
|
||||
}
|
||||
case SNT_TYPE: {
|
||||
printf("\nType: ");
|
||||
printSolsType(&node->as.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("\nChildren count: %zu", node->children.count);
|
||||
if (node->children.count > 0) {
|
||||
printf("\nChildren: (\n");
|
||||
for (size_t i = 0; i < node->children.count; i++) {
|
||||
printSolsNode(&node->children.at[i]);
|
||||
}
|
||||
printf("\n)");
|
||||
}
|
||||
|
||||
printf("\n)\n");
|
||||
|
||||
}
|
||||
82
src/parser/SolsNode.h
Normal file
82
src/parser/SolsNode.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef SOLSNODE_H
|
||||
#define SOLSNODE_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <ground.h>
|
||||
|
||||
#include "../include/error.h"
|
||||
|
||||
#include "../lexer/SolsType.h"
|
||||
#include "../lexer/SolsLiteral.h"
|
||||
#include "../lexer/SolsToken.h"
|
||||
|
||||
typedef enum SolsNodeType {
|
||||
SNT_IDENTIFIER, SNT_LITERAL, SNT_TYPE,
|
||||
SNT_CODE_BLOCK,
|
||||
SNT_OP_ADD, SNT_OP_SUB, SNT_OP_MUL, SNT_OP_DIV,
|
||||
SNT_OP_ADDTO, SNT_OP_SUBTO, SNT_OP_MULTO, SNT_OP_DIVTO,
|
||||
SNT_OP_INCREMENT, SNT_OP_DECREMENT,
|
||||
SNT_OP_SET,
|
||||
SNT_OP_GREATER, SNT_OP_LESSER, SNT_OP_EQUAL, SNT_OP_INEQUAL, SNT_OP_EQGREATER, SNT_OP_EQLESSER,
|
||||
SNT_DEF, SNT_LAMBDA, SNT_FUNCTION_CALL, SNT_RETURN,
|
||||
SNT_SET_PRIVATE, SNT_SET_PROTECTED, SNT_DEF_PRIVATE, SNT_DEF_PROTECTED,
|
||||
SNT_USE, SNT_LOCAL_USE, SNT_FROM_C, SNT_FROM_GROUND,
|
||||
SNT_STRUCT, SNT_ENUM, SNT_CONSTRUCTOR, SNT_DESTRUCTOR, SNT_DUPLICATOR,
|
||||
SNT_STRUCT_AS, SNT_AS, SNT_SIZE_OF,
|
||||
SNT_PUTS, SNT_IF, SNT_ELSE, SNT_WHILE, SNT_BREAK, SNT_CONTINUE, SNT_NEW,
|
||||
SNT_GROUND, SNT_ROOT, SNT_EXPR_IN_PAREN, SNT_DOT,
|
||||
SNT_GENERIC, SNT_GENERIC_INIT,
|
||||
SNT_PRAGMA, SNT__
|
||||
} SolsNodeType;
|
||||
|
||||
struct SolsNode;
|
||||
|
||||
// Represents an AST node.
|
||||
// Most node types use the .type and .children fields, however some nodes require storing
|
||||
// more data, inside the .as union.
|
||||
// Those tokens are:
|
||||
// SNT_LITERAL: A literal value. Uses field .as.literal
|
||||
// SNT_TYPE: A type descriptor. Uses field .as.type
|
||||
// SNT_IDENTIFIER: An identifier. Uses field .as.idName
|
||||
// SNT_GROUND: Ground code embedded inside Solstice. Uses field .as.inlineGround
|
||||
typedef struct SolsNode {
|
||||
SolsNodeType type;
|
||||
union {
|
||||
SolsLiteral literal;
|
||||
SolsType type;
|
||||
char* idName;
|
||||
char* inlineGround;
|
||||
} as;
|
||||
struct {
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
struct SolsNode* at;
|
||||
} children;
|
||||
LineInfo line;
|
||||
|
||||
// Used by the code generator, unless value is a literal do not use in parser
|
||||
GroundArg accessArg;
|
||||
} SolsNode;
|
||||
|
||||
Result(SolsNode, charptr);
|
||||
|
||||
// Creates a SolsNode. If the type passed in is SNT_LITERAL, SNT_TYPE, SNT_IDENTIFIER or
|
||||
// SNT_KW_GROUND, the function expects another argument, corresponding to the data type
|
||||
// the token holds. See the SolsNode struct for more information.
|
||||
// Returns:
|
||||
// Success: The created SolsNode
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(SolsNode, charptr) createSolsNode(SolsNodeType type, ...);
|
||||
|
||||
// Adds a child to a SolsNode.
|
||||
// Returns:
|
||||
// Success: Nothing
|
||||
// Failure: char* detailing what went wrong (usually memory failure)
|
||||
ResultType(Nothing, charptr) addChildToSolsNode(SolsNode* parent, SolsNode child);
|
||||
|
||||
void printSolsNode(SolsNode* node);
|
||||
|
||||
// Deep copies a SolsNode
|
||||
SolsNode deepCopySolsNode(SolsNode node);
|
||||
|
||||
#endif
|
||||
2993
src/parser/parser.c
Normal file
2993
src/parser/parser.c
Normal file
File diff suppressed because it is too large
Load Diff
99
src/parser/parser.h
Normal file
99
src/parser/parser.h
Normal 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
|
||||
141
src/repl/repl.c
Normal file
141
src/repl/repl.c
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "repl.h"
|
||||
#include "../lexer/lexer.h"
|
||||
#include "../typeparser/typeparser.h"
|
||||
#include "../parser/parser.h"
|
||||
#include "../codegen/codegen.h"
|
||||
#include "../linenoise/linenoise.h"
|
||||
#include "../include/estr.h"
|
||||
#include <ground.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void printGroundValue(GroundValue* value);
|
||||
|
||||
void solsticeRepl() {
|
||||
|
||||
SolsScope scope = {
|
||||
.variables = NULL,
|
||||
.tmpCounter = 0,
|
||||
.returnType = createSolsType(STT_INT).as.success
|
||||
};
|
||||
|
||||
GroundState state = {
|
||||
.variables = NULL,
|
||||
.labels = NULL,
|
||||
.catches = NULL
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
|
||||
Estr toRun = CREATE_ESTR("");
|
||||
|
||||
int curlyCount = 0;
|
||||
int bracketCount = 0;
|
||||
int squareCount = 0;
|
||||
|
||||
for (;;) {
|
||||
char* line = linenoise("sols > ");
|
||||
|
||||
if (line == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
APPEND_ESTR(toRun, line);
|
||||
|
||||
size_t lineLen = strlen(line);
|
||||
for (size_t i = 0; i < lineLen; i++) {
|
||||
switch (line[i]) {
|
||||
case '{': {
|
||||
curlyCount++;
|
||||
break;
|
||||
}
|
||||
case '}': {
|
||||
curlyCount--;
|
||||
break;
|
||||
}
|
||||
case '(': {
|
||||
bracketCount++;
|
||||
break;
|
||||
}
|
||||
case ')': {
|
||||
bracketCount--;
|
||||
break;
|
||||
}
|
||||
case '[': {
|
||||
squareCount++;
|
||||
break;
|
||||
}
|
||||
case ']': {
|
||||
squareCount--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linenoiseFree(line);
|
||||
|
||||
if (curlyCount == 0 && bracketCount == 0 && squareCount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (curlyCount < 0 || bracketCount < 0 || squareCount < 0) {
|
||||
DESTROY_ESTR(toRun);
|
||||
toRun = CREATE_ESTR("");
|
||||
printf("You've got some extra closing brackets :(\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
linenoiseHistoryAdd(toRun.str);
|
||||
|
||||
if (toRun.str[0] == '.') {
|
||||
// In-built command
|
||||
if (strcmp(toRun.str, ".exit") == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ResultType(SolsLexer, charptr) lexer = createLexer(toRun.str);
|
||||
if (lexer.error) {
|
||||
printf("Error while creating lexer: %s", lexer.as.error);
|
||||
continue;
|
||||
}
|
||||
ResultType(Nothing, charptr) lexed = lex(&lexer.as.success);
|
||||
if (lexed.error) {
|
||||
printf("%s\n", lexed.as.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect and parse types
|
||||
ResultType(SolsTokens, charptr) typed = addTypeInfo(&lexer.as.success.output);
|
||||
if (typed.error) {
|
||||
printf("%s\n", typed.as.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse file
|
||||
ResultType(SolsParser, charptr) parser = createSolsParser(&typed.as.success);
|
||||
if (parser.error) {
|
||||
printf("Error while creating parser: %s\n", parser.as.error);
|
||||
continue;
|
||||
}
|
||||
ResultType(Nothing, charptr) parsed = parse(&parser.as.success);
|
||||
if (parsed.error) {
|
||||
printf("%s\n", parsed.as.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do codegen on root node
|
||||
ResultType(GroundProgram, charptr) codegen = generateCode(&parser.as.success.output, &scope);
|
||||
if (codegen.error) {
|
||||
printf("%s\n", codegen.as.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
Ground.Program.execute(&codegen.as.success, &state);
|
||||
DESTROY_ESTR(toRun);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
6
src/repl/repl.h
Normal file
6
src/repl/repl.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef REPL_H
|
||||
#define REPL_H
|
||||
|
||||
void solsticeRepl();
|
||||
|
||||
#endif
|
||||
230
src/typeparser/typeparser.c
Normal file
230
src/typeparser/typeparser.c
Normal 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);
|
||||
}
|
||||
11
src/typeparser/typeparser.h
Normal file
11
src/typeparser/typeparser.h
Normal 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
81
src/wasm_main.c
Normal 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 <ground.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
9
tests/closure.sols
Normal 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
55
tests/compare.sols
Normal 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
11
tests/convert.sols
Normal 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
6
tests/count.sols
Normal file
@@ -0,0 +1,6 @@
|
||||
number = 0
|
||||
|
||||
while number != 10 {
|
||||
number = number + 1
|
||||
puts number
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
puts "dingus"
|
||||
puts 432
|
||||
puts 3.141
|
||||
puts 'c'
|
||||
puts true
|
||||
7
tests/data.sols
Normal file
7
tests/data.sols
Normal 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!@"
|
||||
11
tests/enum.sols
Normal file
11
tests/enum.sols
Normal file
@@ -0,0 +1,11 @@
|
||||
enum Food {
|
||||
Apple, Banana, Orange,
|
||||
}
|
||||
|
||||
def function(Food food) Food {
|
||||
return food
|
||||
}
|
||||
|
||||
puts Food
|
||||
|
||||
puts function(Food.Banana)
|
||||
15
tests/fib.sols
Normal file
15
tests/fib.sols
Normal 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
6
tests/function.sols
Normal file
@@ -0,0 +1,6 @@
|
||||
def add(int a, int b) int {
|
||||
puts "testing"
|
||||
return 3
|
||||
}
|
||||
|
||||
puts add(1, 2)
|
||||
46
tests/generics.sols
Normal file
46
tests/generics.sols
Normal file
@@ -0,0 +1,46 @@
|
||||
struct Hash {
|
||||
protected hashstr = ""
|
||||
}
|
||||
|
||||
struct Person {
|
||||
age = 0
|
||||
name = ""
|
||||
|
||||
constructor(int age, string name) {
|
||||
self.age = age
|
||||
self.name = name
|
||||
}
|
||||
|
||||
as Hash {
|
||||
// hash the Person here
|
||||
}
|
||||
}
|
||||
|
||||
struct HashTable<Key can Hash, Value> {
|
||||
|
||||
private ptr = 0
|
||||
protected size = 0
|
||||
private capacity = 0
|
||||
|
||||
def find(Key key) {
|
||||
hash = key as Hash
|
||||
// find in table using hash
|
||||
}
|
||||
|
||||
def set(Key key, Value value) {
|
||||
hash = key as Hash
|
||||
// store Value in the table
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// malloc the pointer and set up hash information
|
||||
}
|
||||
|
||||
duplicator {
|
||||
// copy all contents
|
||||
}
|
||||
|
||||
destructor {
|
||||
// free the ptr
|
||||
}
|
||||
}
|
||||
7
tests/if.sols
Normal file
7
tests/if.sols
Normal file
@@ -0,0 +1,7 @@
|
||||
if 1 == 1 {
|
||||
puts "huzzah"
|
||||
}
|
||||
|
||||
if 5 == 10 {
|
||||
puts "aww"
|
||||
}
|
||||
7
tests/inlineground.sols
Normal file
7
tests/inlineground.sols
Normal file
@@ -0,0 +1,7 @@
|
||||
result = 0
|
||||
ground {
|
||||
println "dingus"
|
||||
add 3 2 &result
|
||||
}
|
||||
|
||||
puts result
|
||||
15
tests/input.sols
Normal file
15
tests/input.sols
Normal 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!"
|
||||
23
tests/nested-object.sols
Normal file
23
tests/nested-object.sols
Normal file
@@ -0,0 +1,23 @@
|
||||
struct x {
|
||||
a = 5
|
||||
def modifyA(int a) int {
|
||||
self.a = a
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
struct y {
|
||||
b = new x
|
||||
}
|
||||
|
||||
z = new y
|
||||
puts z
|
||||
|
||||
puts z.b
|
||||
puts z.b.a
|
||||
|
||||
z.b.a = 10
|
||||
puts z
|
||||
|
||||
z.b.modifyA(15)
|
||||
puts z
|
||||
@@ -1 +1,3 @@
|
||||
use io
|
||||
|
||||
println("dingus")
|
||||
3
tests/set.sols
Normal file
3
tests/set.sols
Normal file
@@ -0,0 +1,3 @@
|
||||
x = 5
|
||||
y = 10
|
||||
puts x + y
|
||||
7
tests/sizeof.sols
Normal file
7
tests/sizeof.sols
Normal file
@@ -0,0 +1,7 @@
|
||||
puts sizeof "dingleing"
|
||||
|
||||
struct MyStruct {
|
||||
size = 32
|
||||
}
|
||||
|
||||
puts sizeof new MyStruct
|
||||
42
tests/struct.sols
Normal file
42
tests/struct.sols
Normal file
@@ -0,0 +1,42 @@
|
||||
struct Person {
|
||||
protected name = ""
|
||||
private age = 0
|
||||
|
||||
def greet() string {
|
||||
return "Hello, " + self.name + "!"
|
||||
}
|
||||
|
||||
def dance() string {
|
||||
return "Dancing..."
|
||||
}
|
||||
|
||||
constructor(string name, int age) {
|
||||
self.name = name
|
||||
self.age = age
|
||||
}
|
||||
|
||||
destructor {
|
||||
// We don't need to do anything here.
|
||||
}
|
||||
|
||||
duplicator {
|
||||
puts "Duplicator called!"
|
||||
// Also nothing here
|
||||
}
|
||||
|
||||
as string {
|
||||
return "dingus"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
max = Person("Max", 16)
|
||||
|
||||
puts max
|
||||
puts max.greet()
|
||||
puts max.name
|
||||
puts max as string
|
||||
// puts max.age (causes compile time error, age is private)
|
||||
// max.name = "Maximilian" (causes compile time error, name is protected)
|
||||
|
||||
max.dance()
|
||||
3
tests/while.sols
Normal file
3
tests/while.sols
Normal file
@@ -0,0 +1,3 @@
|
||||
while 1 == 1 {
|
||||
puts "yay infinite loop"
|
||||
}
|
||||
1
vim/ftdetect/solstice.vim
Normal file
1
vim/ftdetect/solstice.vim
Normal file
@@ -0,0 +1 @@
|
||||
au BufRead,BufNewFile *.sols setfiletype solstice
|
||||
45
vim/syntax/solstice.vim
Normal file
45
vim/syntax/solstice.vim
Normal file
@@ -0,0 +1,45 @@
|
||||
" Vim highlight file for Solstice (.sols)
|
||||
|
||||
if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
" Keywords
|
||||
syn keyword solsKeyword puts if else while break continue def lambda return use from struct enum constructor destructor duplicator private protected ground new as sizeof pragma
|
||||
syn keyword solsBool true false
|
||||
|
||||
" Types
|
||||
syn keyword solsType int double string char bool fun template object
|
||||
|
||||
" Strings and chars
|
||||
syn region solsString start=/"/ skip=/\\"/ end=/"/
|
||||
syn region solsChar start=/'/ skip=/\\'/ end=/'/
|
||||
|
||||
" Numbers
|
||||
syn match solsFloat /\<[0-9]\+\.[0-9]*\>/
|
||||
syn match solsInt /\<[0-9]\+\>/
|
||||
|
||||
" Operators
|
||||
syn match solsOperator /+\|-\|\*\|\/\|=\|!\|>\|<\|+=\|-=\|\*=\|\/=\|++\|--\|==\|!=\|>=\|<=/
|
||||
|
||||
" Delimiters
|
||||
syn match solsDelimiter /[{}(),;\[\]]/
|
||||
|
||||
" Comments
|
||||
syn match solsComment /\/\/.*$/
|
||||
syn match solsComment /#.*$/
|
||||
|
||||
" Highlight links
|
||||
hi def link solsKeyword Keyword
|
||||
hi def link solsBool Boolean
|
||||
hi def link solsType Type
|
||||
hi def link solsString String
|
||||
hi def link solsChar Character
|
||||
hi def link solsFloat Float
|
||||
hi def link solsInt Number
|
||||
hi def link solsOperator Operator
|
||||
hi def link solsDelimiter Delimiter
|
||||
hi def link solsComment Comment
|
||||
|
||||
let b:current_syntax = "solstice"
|
||||
|
||||
11
x86_64-w64-mingw32.txt
Normal file
11
x86_64-w64-mingw32.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
[binaries]
|
||||
c = 'x86_64-w64-mingw32-gcc'
|
||||
ar = 'x86_64-w64-mingw32-ar'
|
||||
exe_wrapper = 'wine'
|
||||
pkg-config = 'pkg-config'
|
||||
|
||||
[host_machine]
|
||||
system = 'windows'
|
||||
cpu_family = 'x86_64'
|
||||
cpu = 'x86_64'
|
||||
endian = 'little'
|
||||
Reference in New Issue
Block a user