Compare commits
27 Commits
| 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 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
solstice
|
||||
build
|
||||
.*_solsbuild
|
||||
builddir
|
||||
windows-builddir/
|
||||
.meson-cross/
|
||||
|
||||
6
Makefile
6
Makefile
@@ -3,7 +3,7 @@ CXX = gcc
|
||||
GROUND_STATIC = /usr/local/lib/libgroundvm.a
|
||||
GROUND_INCLUDE = /usr/local/include/
|
||||
|
||||
CXXFLAGS = -I$(GROUND_INCLUDE) -Wall -Wextra -pedantic -O3 -ggdb
|
||||
CXXFLAGS = -I$(GROUND_INCLUDE) -Wall -Wextra -pedantic -ggdb
|
||||
LDFLAGS = -lgroundvm
|
||||
|
||||
BUILD_DIR = build
|
||||
@@ -14,7 +14,7 @@ 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
|
||||
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
|
||||
|
||||
@@ -45,7 +45,7 @@ $(BUILD_DIR)/solstice.tar.gz: $(TARGET) $(LIBS_DIR)
|
||||
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
|
||||
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)
|
||||
|
||||
20
README.md
20
README.md
@@ -6,16 +6,30 @@ 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
|
||||
|
||||
```
|
||||
make
|
||||
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
|
||||
|
||||
Solstice files use the `.sols` extension. Run files as you would with any other interpreted language.
|
||||
|
||||
## Docs
|
||||
|
||||
Docs are avaliable at https://sols.dev/docs/
|
||||
Docs are avaliable at https://sols.dev/docs/
|
||||
|
||||
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() {}
|
||||
|
||||
}
|
||||
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"
|
||||
}
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
#ifndef CODEGEN_H
|
||||
#define CODEGEN_H
|
||||
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
|
||||
#include "SolsScope.h"
|
||||
|
||||
@@ -25,8 +25,8 @@ ResultType(SolsType, charptr) getNodeType(SolsNode* node, SolsScope* scope);
|
||||
if (__result.error) {\
|
||||
return Error(GroundProgram, charptr, __result.as.error);\
|
||||
}\
|
||||
for (size_t i = 0; i < __result.as.success.size; i++) {\
|
||||
groundAddInstructionToProgram(&program, __result.as.success.instructions[i]);\
|
||||
for (size_t i = 0; i < __result.as.success.len; i++) {\
|
||||
Ground.Program.append(&program, __result.as.success.at[i]);\
|
||||
}\
|
||||
break;\
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
__list_tmp;\
|
||||
})
|
||||
|
||||
#define append(list, item) {\
|
||||
#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);\
|
||||
}\
|
||||
|
||||
@@ -17,10 +17,11 @@ typedef enum SolsTokenType {
|
||||
STT_OP_INCREMENT, STT_OP_DECREMENT, STT_OP_SET,
|
||||
STT_OP_GREATER, STT_OP_LESSER, STT_OP_EQUAL, STT_OP_INEQUAL, STT_OP_EQGREATER, STT_OP_EQLESSER,
|
||||
STT_KW_DEF, STT_KW_LAMBDA, STT_KW_RETURN,
|
||||
STT_KW_USE, STT_KW_STRUCT, STT_KW_ENUM, STT_KW_CONSTRUCTOR, STT_KW_DESTRUCTOR, STT_KW_DUPLICATOR,
|
||||
STT_KW_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_WHILE,
|
||||
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,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "SolsType.h"
|
||||
#include "../include/error.h"
|
||||
#include "../include/estr.h"
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
#include <string.h>
|
||||
|
||||
ResultType(SolsType, charptr) createSolsType(SolsTypeType in) {
|
||||
@@ -16,6 +16,9 @@ ResultType(SolsType, charptr) createSolsType(SolsTypeType in) {
|
||||
.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,
|
||||
@@ -41,6 +44,9 @@ ResultType(SolsType, charptr) createIdentifiedSolsType(char* in) {
|
||||
.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,
|
||||
@@ -62,6 +68,9 @@ ResultType(SolsType, charptr) copySolsType(SolsType* type) {
|
||||
.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
|
||||
};
|
||||
|
||||
@@ -116,6 +125,24 @@ ResultType(SolsType, charptr) copySolsType(SolsType* type) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
/*
|
||||
@@ -253,29 +280,30 @@ bool compareTypes(SolsType* left, SolsType* right) {
|
||||
ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type, struct SolsScope* scope) {
|
||||
switch (type->type) {
|
||||
case STT_INT: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "int"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("int")));
|
||||
}
|
||||
case STT_DOUBLE: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "double"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("double")));
|
||||
}
|
||||
case STT_STRING: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "string"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("string")));
|
||||
}
|
||||
case STT_BOOL: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "bool"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("bool")));
|
||||
}
|
||||
case STT_CHAR: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "char"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("char")));
|
||||
}
|
||||
case STT_FUN: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "function"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("function")));
|
||||
}
|
||||
case STT_TEMPLATE: {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "struct"));
|
||||
return Success(GroundArg, charptr, Ground.New.Arg.TypeRef(Ground.New.Identifier("struct")));
|
||||
}
|
||||
case STT_OBJECT: {
|
||||
if (!type->needsGroundStruct) {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, type->identifierType));
|
||||
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");
|
||||
@@ -283,14 +311,28 @@ ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type, struc
|
||||
}
|
||||
case STT_UNKNOWN: {
|
||||
if (!type->needsGroundStruct) {
|
||||
return Success(GroundArg, charptr, groundCreateReference(TYPEREF, type->identifierType));
|
||||
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, groundCreateReference(TYPEREF, "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?");
|
||||
@@ -360,3 +402,34 @@ void printSolsType(SolsType* 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, {});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define SOLSTYPE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
|
||||
#include "../include/error.h"
|
||||
#include "../include/nothing.h"
|
||||
@@ -70,6 +70,13 @@ typedef struct SolsType {
|
||||
size_t capacity;
|
||||
} children;
|
||||
|
||||
// For use in generics
|
||||
struct {
|
||||
struct SolsType* at;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} genericChildren;
|
||||
|
||||
struct {
|
||||
bool isPrivate;
|
||||
bool isProtected;
|
||||
@@ -130,6 +137,13 @@ 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
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
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},
|
||||
@@ -74,6 +78,10 @@ struct _SolsTokenTypeMap SolsTokenTypeMap[] = {
|
||||
{"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
|
||||
@@ -106,7 +114,7 @@ static ResultType(Nothing, charptr) handleGround(SolsLexer* lexer, SolsToken* to
|
||||
*currentLine = CREATE_ESTR("");
|
||||
size_t lineStart = lexer->current;
|
||||
for (size_t i = lineStart; i < lexer->inputsize; i++) {
|
||||
if (lexer->input[i] == '\n') break;
|
||||
if (lexer->input[i] == '\n' || lexer->input[i] == '\r') break;
|
||||
char buf_tmp[] = {lexer->input[i], '\0'};
|
||||
APPEND_ESTR((*currentLine), buf_tmp);
|
||||
}
|
||||
@@ -306,7 +314,20 @@ ResultType(SolsToken, charptr) identifyToken(const char* token) {
|
||||
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;
|
||||
@@ -315,6 +336,7 @@ ResultType(SolsToken, charptr) identifyToken(const char* token) {
|
||||
if (!isdigit(token[i])) {
|
||||
isInt = false;
|
||||
isDouble = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isInt) {
|
||||
@@ -346,6 +368,21 @@ ResultType(SolsToken, charptr) identifyToken(const char* token) {
|
||||
};
|
||||
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)
|
||||
@@ -495,7 +532,7 @@ ResultType(Nothing, charptr) lex(SolsLexer* lexer) {
|
||||
Estr currentLine = CREATE_ESTR("");
|
||||
|
||||
for (; lineStart < lexer->inputsize; lineStart++) {
|
||||
if (lexer->input[lineStart] == '\n') {
|
||||
if (lexer->input[lineStart] == '\n' || lexer->input[lineStart] == '\r') {
|
||||
break;
|
||||
}
|
||||
char tmp[] = {lexer->input[lineStart], '\0'};
|
||||
@@ -535,7 +572,7 @@ ResultType(Nothing, charptr) lex(SolsLexer* lexer) {
|
||||
currentLine = CREATE_ESTR("");
|
||||
lineStart = lexer->current;
|
||||
for (size_t i = lineStart; i < lexer->inputsize; i++) {
|
||||
if (lexer->input[i] == '\n') break;
|
||||
if (lexer->input[i] == '\n' || lexer->input[i] == '\r') break;
|
||||
char tmp[] = {lexer->input[i], '\0'};
|
||||
APPEND_ESTR(currentLine, tmp);
|
||||
}
|
||||
@@ -566,7 +603,7 @@ ResultType(Nothing, charptr) lex(SolsLexer* lexer) {
|
||||
currentLine = CREATE_ESTR("");
|
||||
lineStart = lexer->current;
|
||||
for (size_t i = lineStart; i < lexer->inputsize; i++) {
|
||||
if (lexer->input[i] == '\n') {
|
||||
if (lexer->input[i] == '\n' || lexer->input[i] == '\r') {
|
||||
break;
|
||||
}
|
||||
char buf_tmp[] = {lexer->input[i], '\0'};
|
||||
@@ -798,7 +835,8 @@ ResultType(Nothing, charptr) lex(SolsLexer* lexer) {
|
||||
|
||||
// This whitespace splits the program and does not get appended as it's own token.
|
||||
case '\t':
|
||||
case ' ': {
|
||||
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);
|
||||
|
||||
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 */
|
||||
94
src/main.c
94
src/main.c
@@ -1,5 +1,6 @@
|
||||
#include "lexer/SolsType.h"
|
||||
#include "lexer/lexer.h"
|
||||
#include "repl/repl.h"
|
||||
#include "typeparser/typeparser.h"
|
||||
#include "parser/parser.h"
|
||||
#include "codegen/codegen.h"
|
||||
@@ -14,9 +15,48 @@
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
|
||||
extern bool groundDisableTypeChecking;
|
||||
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;
|
||||
|
||||
@@ -101,7 +141,14 @@ Args parseArgs(int argc, char** argv) {
|
||||
char* getFileContents(const char* filename);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
groundDisableTypeChecking = true;
|
||||
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);
|
||||
|
||||
@@ -176,23 +223,49 @@ int main(int argc, char** argv) {
|
||||
|
||||
switch (args.action) {
|
||||
case SA_PRINT: {
|
||||
groundPrintProgram(&codegen.as.success);
|
||||
printf("%s", Ground.Stringify.Program(&codegen.as.success));
|
||||
break;
|
||||
}
|
||||
case SA_EXEC: {
|
||||
GroundValue retval = groundRunProgram(&codegen.as.success);
|
||||
if (retval.type == INT) {
|
||||
return retval.data.intVal;
|
||||
} else {
|
||||
return 0;
|
||||
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: {
|
||||
serializeProgramToFile(args.outputFile, &codegen.as.success);
|
||||
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
|
||||
@@ -246,6 +319,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
// Yay we compiled it
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ ResultType(SolsNode, charptr) createSolsNode(SolsNodeType type, ...) {
|
||||
.type = type,
|
||||
.children.capacity = 32,
|
||||
.children.count = 0,
|
||||
.children.at = malloc(sizeof(SolsNode) * 32)
|
||||
.children.at = malloc(sizeof(SolsNode) * 32),
|
||||
.accessArg = Ground.New.Arg.Value(Ground.New.Value.Int(0))
|
||||
};
|
||||
|
||||
if (node.children.at == NULL) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define SOLSNODE_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
|
||||
#include "../include/error.h"
|
||||
|
||||
@@ -20,12 +20,13 @@ typedef enum SolsNodeType {
|
||||
SNT_OP_GREATER, SNT_OP_LESSER, SNT_OP_EQUAL, SNT_OP_INEQUAL, SNT_OP_EQGREATER, SNT_OP_EQLESSER,
|
||||
SNT_DEF, SNT_LAMBDA, SNT_FUNCTION_CALL, SNT_RETURN,
|
||||
SNT_SET_PRIVATE, SNT_SET_PROTECTED, SNT_DEF_PRIVATE, SNT_DEF_PROTECTED,
|
||||
SNT_USE, SNT_LOCAL_USE, SNT_STRUCT, SNT_ENUM, SNT_CONSTRUCTOR, SNT_DESTRUCTOR, SNT_DUPLICATOR,
|
||||
SNT_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_WHILE, SNT_NEW,
|
||||
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_PRAGMA, SNT__
|
||||
} SolsNodeType;
|
||||
|
||||
struct SolsNode;
|
||||
|
||||
@@ -4,9 +4,137 @@
|
||||
#include "../include/estr.h"
|
||||
#include "../include/ansii.h"
|
||||
#include "../interactive/interactive.h"
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
#include <string.h>
|
||||
|
||||
char* parseEscapeSequences(const char* in) {
|
||||
size_t len = strlen(in);
|
||||
size_t currentPos = 0;
|
||||
|
||||
char* ret = malloc(len + 1);
|
||||
if (ret == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (in[i] == '\\') {
|
||||
if (i + 1 >= len) {
|
||||
ret[currentPos++] = '\\';
|
||||
} else {
|
||||
switch (in[i+1]) {
|
||||
case '\\':
|
||||
ret[currentPos++] = '\\';
|
||||
i++;
|
||||
break;
|
||||
case 'n':
|
||||
ret[currentPos++] = '\n';
|
||||
i++;
|
||||
break;
|
||||
case 't':
|
||||
ret[currentPos++] = '\t';
|
||||
i++;
|
||||
break;
|
||||
case 'r':
|
||||
ret[currentPos++] = '\r';
|
||||
i++;
|
||||
break;
|
||||
case '"':
|
||||
ret[currentPos++] = '"';
|
||||
i++;
|
||||
break;
|
||||
case '\'':
|
||||
ret[currentPos++] = '\'';
|
||||
i++;
|
||||
break;
|
||||
case 'a':
|
||||
ret[currentPos++] = '\a';
|
||||
i++;
|
||||
break;
|
||||
case 'b':
|
||||
ret[currentPos++] = '\b';
|
||||
i++;
|
||||
break;
|
||||
case 'f':
|
||||
ret[currentPos++] = '\f';
|
||||
i++;
|
||||
break;
|
||||
case 'v':
|
||||
ret[currentPos++] = '\v';
|
||||
i++;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
ret[currentPos++] = '\x1b';
|
||||
i++;
|
||||
break;
|
||||
case 'x':
|
||||
case 'X': {
|
||||
i++; // consume 'x' or 'X'
|
||||
unsigned int val = 0;
|
||||
int count = 0;
|
||||
while (count < 2 && i + 1 < len) {
|
||||
char c = in[i+1];
|
||||
if (c >= '0' && c <= '9') {
|
||||
val = val * 16 + (c - '0');
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
val = val * 16 + (c - 'a' + 10);
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
val = val * 16 + (c - 'A' + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
count++;
|
||||
}
|
||||
if (count > 0) {
|
||||
ret[currentPos++] = (char)val;
|
||||
} else {
|
||||
// No hex digits, treat as literal '\' and 'x' or 'X'
|
||||
ret[currentPos++] = '\\';
|
||||
ret[currentPos++] = in[i]; // which is 'x' or 'X'
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7': {
|
||||
unsigned int val = in[i+1] - '0';
|
||||
int count = 1;
|
||||
i++; // consume first octal digit
|
||||
while (count < 3 && i + 1 < len) {
|
||||
char c = in[i+1];
|
||||
if (c >= '0' && c <= '7') {
|
||||
val = val * 8 + (c - '0');
|
||||
i++;
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret[currentPos++] = (char)val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret[currentPos++] = '\\';
|
||||
ret[currentPos++] = in[i+1];
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret[currentPos++] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
ret[currentPos] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
SolsTokenPrecedence getPrecedence(SolsToken *token) {
|
||||
static size_t braceCount = 0;
|
||||
static size_t squareBracketCount = 0;
|
||||
@@ -167,6 +295,80 @@ void createParserError(SolsParser* parser, char* what) {
|
||||
parser->errors.count++;
|
||||
}
|
||||
|
||||
// Parses a type, inclusive of generic arguments.
|
||||
// Will always return a SolsNode with type SNT_TYPE, with the type in .as.type
|
||||
static ResultType(SolsNode, charptr) parseType(SolsParser* parser);
|
||||
static ResultType(SolsNode, charptr) parseType(SolsParser* parser) {
|
||||
// get next token
|
||||
ResultType(SolsToken, Nothing) next = parserConsume(parser);
|
||||
if (next.error) {
|
||||
return Error(SolsNode, charptr, "Expecting token");
|
||||
}
|
||||
char* idType = NULL;
|
||||
bool bracketConsumed = false;
|
||||
switch (next.as.success.type) {
|
||||
case STT_TYPE: {
|
||||
return createSolsNode(SNT_TYPE, next.as.success.as.type);
|
||||
}
|
||||
case STT_IDENTIFIER: {
|
||||
idType = next.as.success.as.idName;
|
||||
break;
|
||||
}
|
||||
case STT_OPEN_SQUARE: {
|
||||
if (parser->current < 2) {
|
||||
return Error(SolsNode, charptr, "wowowowowow you got the legendary error wowowow max couldn't be bothered to fix it properly so this is why you're getting the error lmao");
|
||||
}
|
||||
SolsToken node = parser->input->at[parser->current - 2];
|
||||
idType = node.as.idName;
|
||||
bracketConsumed = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return Error(SolsNode, charptr, "Expecting type identifier");
|
||||
}
|
||||
}
|
||||
|
||||
SolsType type = ({
|
||||
ResultType(SolsType, charptr) _res = createIdentifiedSolsType(idType);
|
||||
if (_res.error) {
|
||||
return Error(SolsNode, charptr, _res.as.error);
|
||||
}
|
||||
_res.as.success;
|
||||
});
|
||||
|
||||
if (!bracketConsumed) {
|
||||
next = parserPeek(parser, 1);
|
||||
if (next.error || next.as.success.type != STT_OPEN_SQUARE) {
|
||||
return createSolsNode(SNT_TYPE, type);
|
||||
}
|
||||
parserConsume(parser); // opening square bracket
|
||||
}
|
||||
|
||||
type.type = STT_GENERIC;
|
||||
|
||||
for (;;) {
|
||||
ResultType(SolsNode, charptr) childType = parseType(parser);
|
||||
if (childType.error) return childType;
|
||||
|
||||
addGenericFieldToType(&type, childType.as.success.as.type);
|
||||
|
||||
next = parserConsume(parser);
|
||||
if (next.error) {
|
||||
return Error(SolsNode, charptr, "Expecting ']' to end generic argument list");
|
||||
}
|
||||
|
||||
if (next.as.success.type == STT_CLOSE_SQUARE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (next.as.success.type != STT_COMMA) {
|
||||
return Error(SolsNode, charptr, "Expecting ',' or ']' after generic argument");
|
||||
}
|
||||
}
|
||||
|
||||
return createSolsNode(SNT_TYPE, type);
|
||||
}
|
||||
|
||||
static inline ResultType(Nothing, charptr) parseIdentifier(SolsParser* parser) {
|
||||
ResultType(SolsToken, Nothing) peek = parserPeek(parser, 0);
|
||||
if (peek.error) {
|
||||
@@ -179,10 +381,7 @@ static inline ResultType(Nothing, charptr) parseIdentifier(SolsParser* parser) {
|
||||
return Error(Nothing, charptr, err.str);
|
||||
}
|
||||
node.as.success.line = peek.as.success.line;
|
||||
node.as.success.accessArg = (GroundArg) {
|
||||
.type = VALREF,
|
||||
.value.refName = peek.as.success.as.idName
|
||||
};
|
||||
node.as.success.accessArg = Ground.New.Arg.ValueRef(Ground.New.Identifier(peek.as.success.as.idName));
|
||||
addChildToSolsNode(parser->currentParent, node.as.success);
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
@@ -904,30 +1103,27 @@ static inline ResultType(Nothing, charptr) parseLiteral(SolsParser* parser) {
|
||||
GroundValue value;
|
||||
switch (peek.as.success.as.literal.type) {
|
||||
case SLT_INT: {
|
||||
value = groundCreateValue(INT, peek.as.success.as.literal.as.intv);
|
||||
value = Ground.New.Value.Int(peek.as.success.as.literal.as.intv);
|
||||
break;
|
||||
}
|
||||
case SLT_DOUBLE: {
|
||||
value = groundCreateValue(DOUBLE, peek.as.success.as.literal.as.doublev);
|
||||
value = Ground.New.Value.Double(peek.as.success.as.literal.as.doublev);
|
||||
break;
|
||||
}
|
||||
case SLT_STRING: {
|
||||
value = groundCreateValue(STRING, peek.as.success.as.literal.as.stringv);
|
||||
value = Ground.New.Value.String(Ground.New.String(parseEscapeSequences(peek.as.success.as.literal.as.stringv)));
|
||||
break;
|
||||
}
|
||||
case SLT_BOOL: {
|
||||
value = groundCreateValue(BOOL, peek.as.success.as.literal.as.boolv);
|
||||
value = Ground.New.Value.Bool(peek.as.success.as.literal.as.boolv);
|
||||
break;
|
||||
}
|
||||
case SLT_CHAR: {
|
||||
value = groundCreateValue(CHAR, peek.as.success.as.literal.as.charv);
|
||||
value = Ground.New.Value.Char(peek.as.success.as.literal.as.charv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
node.as.success.accessArg = (GroundArg) {
|
||||
.type = VALUE,
|
||||
.value.value = value
|
||||
};
|
||||
node.as.success.accessArg = Ground.New.Arg.Value(value);
|
||||
addChildToSolsNode(parser->currentParent, node.as.success);
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
@@ -1167,6 +1363,53 @@ static inline ResultType(Nothing, charptr) parseIf(SolsParser* parser) {
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
|
||||
static inline ResultType(Nothing, charptr) parseElse(SolsParser* parser) {
|
||||
ResultType(SolsNode, charptr) elseNode = createSolsNode(SNT_ELSE);
|
||||
if (elseNode.error) {
|
||||
return Error(Nothing, charptr, elseNode.as.error);
|
||||
}
|
||||
|
||||
if (parser->currentParent->children.count <= 0) {
|
||||
return Error(Nothing, charptr, "Expecting if statement before else");
|
||||
}
|
||||
SolsNode* ifNode = &parser->currentParent->children.at[parser->currentParent->children.count - 1];
|
||||
if (ifNode->type != SNT_IF) {
|
||||
return Error(Nothing, charptr, "Expecting if statement before else");
|
||||
}
|
||||
|
||||
ResultType(SolsToken, Nothing) next = parserPeek(parser, 1);
|
||||
if (next.error) {
|
||||
return Error(Nothing, charptr, "Expecting 'if' or code block after 'else'");
|
||||
}
|
||||
|
||||
if (next.as.success.type == STT_KW_IF) {
|
||||
parserConsume(parser);
|
||||
ResultType(Nothing, charptr) res = parseIf(parser);
|
||||
if (res.error) {
|
||||
return res;
|
||||
}
|
||||
addChildToSolsNode(&elseNode.as.success, parser->currentParent->children.at[parser->currentParent->children.count - 1]);
|
||||
parser->currentParent->children.count--;
|
||||
} else if (next.as.success.type == STT_OPEN_CURLY) {
|
||||
parserConsume(parser);
|
||||
ResultType(Nothing, charptr) res = parseCodeBlock(parser);
|
||||
if (res.error) {
|
||||
return res;
|
||||
}
|
||||
|
||||
addChildToSolsNode(&elseNode.as.success, parser->currentParent->children.at[parser->currentParent->children.count - 1]);
|
||||
parser->currentParent->children.count--;
|
||||
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting 'if' or code block after 'else'");
|
||||
}
|
||||
|
||||
// Add ourselves to the end of the if node
|
||||
addChildToSolsNode(ifNode, elseNode.as.success);
|
||||
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
|
||||
static inline ResultType(Nothing, charptr) parseCloseCurly(SolsParser* parser) {
|
||||
(void)parser;
|
||||
return Error(Nothing, charptr, "Extra closing curly brace");
|
||||
@@ -1204,19 +1447,11 @@ static inline ResultType(Nothing, charptr) parseLambda(SolsParser* parser) {
|
||||
}
|
||||
|
||||
// Pattern of type, name, comma
|
||||
|
||||
SolsType tmpType;
|
||||
|
||||
if (next.as.success.type == STT_TYPE) {
|
||||
tmpType = next.as.success.as.type;
|
||||
} else if (next.as.success.type == STT_IDENTIFIER) {
|
||||
tmpType = createIdentifiedSolsType(next.as.success.as.idName).as.success;
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting a type or identifier of type in lambda argument list");
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
parserConsume(parser);
|
||||
|
||||
char* argName;
|
||||
next = parserPeek(parser, 1);
|
||||
|
||||
@@ -1227,7 +1462,7 @@ static inline ResultType(Nothing, charptr) parseLambda(SolsParser* parser) {
|
||||
}
|
||||
|
||||
// Add type to constructed SolsType
|
||||
addChildToSolsType(&type.as.success, tmpType, argName);
|
||||
addChildToSolsType(&type.as.success, typeNode.as.success.as.type, argName);
|
||||
parserConsume(parser);
|
||||
|
||||
next = parserPeek(parser, 1);
|
||||
@@ -1245,32 +1480,17 @@ static inline ResultType(Nothing, charptr) parseLambda(SolsParser* parser) {
|
||||
}
|
||||
|
||||
// Parse type at the end
|
||||
ResultType(SolsToken, Nothing) retType = parserPeek(parser, 1);
|
||||
if (retType.error) {
|
||||
return Error(Nothing, charptr, "Expecting return type or identifier of type after lambda argument list");
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
if (retType.as.success.type == STT_TYPE) {
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
if (type.as.success.returnType == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to allocate memory for type");
|
||||
}
|
||||
*type.as.success.returnType = retType.as.success.as.type;
|
||||
} else if (retType.as.success.type == STT_IDENTIFIER) {
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
if (type.as.success.returnType == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to allocate memory for type");
|
||||
}
|
||||
*type.as.success.returnType = createIdentifiedSolsType(retType.as.success.as.idName).as.success;
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting return type or identifier of type after lambda argument list");
|
||||
}
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
*type.as.success.returnType = typeNode.as.success.as.type;
|
||||
|
||||
// Add type to node
|
||||
node.as.success.as.type = type.as.success;
|
||||
|
||||
parserConsume(parser); // Consumes return type
|
||||
|
||||
// Skip newlines before the opening curly brace
|
||||
while (parserPeek(parser, 1).as.success.type == STT_LINE_END) {
|
||||
parserConsume(parser);
|
||||
@@ -1327,10 +1547,7 @@ static inline ResultType(Nothing, charptr) parseDef(SolsParser* parser) {
|
||||
return Error(Nothing, charptr, nameNode.as.error);
|
||||
}
|
||||
nameNode.as.success.line = nameTok.as.success.line;
|
||||
nameNode.as.success.accessArg = (GroundArg) {
|
||||
.type = VALREF,
|
||||
.value.refName = nameTok.as.success.as.idName
|
||||
};
|
||||
nameNode.as.success.accessArg = Ground.New.Arg.ValueRef(Ground.New.Identifier(nameTok.as.success.as.idName));
|
||||
addChildToSolsNode(&node.as.success, nameNode.as.success);
|
||||
|
||||
// Parse type signature
|
||||
@@ -1349,19 +1566,11 @@ static inline ResultType(Nothing, charptr) parseDef(SolsParser* parser) {
|
||||
}
|
||||
|
||||
// Pattern of type, name, comma
|
||||
|
||||
SolsType tmpType;
|
||||
|
||||
if (next.as.success.type == STT_TYPE) {
|
||||
tmpType = next.as.success.as.type;
|
||||
} else if (next.as.success.type == STT_IDENTIFIER) {
|
||||
tmpType = createIdentifiedSolsType(next.as.success.as.idName).as.success;
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting a type or identifier of type in def argument list");
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
parserConsume(parser);
|
||||
|
||||
char* argName;
|
||||
next = parserPeek(parser, 1);
|
||||
|
||||
@@ -1372,7 +1581,7 @@ static inline ResultType(Nothing, charptr) parseDef(SolsParser* parser) {
|
||||
}
|
||||
|
||||
// Add type to constructed SolsType
|
||||
addChildToSolsType(&type.as.success, tmpType, argName);
|
||||
addChildToSolsType(&type.as.success, typeNode.as.success.as.type, argName);
|
||||
parserConsume(parser);
|
||||
|
||||
next = parserPeek(parser, 1);
|
||||
@@ -1390,32 +1599,17 @@ static inline ResultType(Nothing, charptr) parseDef(SolsParser* parser) {
|
||||
}
|
||||
|
||||
// Parse return type after argument list
|
||||
ResultType(SolsToken, Nothing) retType = parserPeek(parser, 1);
|
||||
if (retType.error) {
|
||||
return Error(Nothing, charptr, "Expecting return type or identifier of type after def argument list");
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
if (retType.as.success.type == STT_TYPE) {
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
if (type.as.success.returnType == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to allocate memory for type");
|
||||
}
|
||||
*type.as.success.returnType = retType.as.success.as.type;
|
||||
} else if (retType.as.success.type == STT_IDENTIFIER) {
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
if (type.as.success.returnType == NULL) {
|
||||
return Error(Nothing, charptr, "Failed to allocate memory for type");
|
||||
}
|
||||
*type.as.success.returnType = createIdentifiedSolsType(retType.as.success.as.idName).as.success;
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting return type or identifier of type after def argument list");
|
||||
}
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
*type.as.success.returnType = typeNode.as.success.as.type;
|
||||
|
||||
// Add type to node
|
||||
node.as.success.as.type = type.as.success;
|
||||
|
||||
parserConsume(parser); // Consumes return type
|
||||
|
||||
// Skip newlines before the opening curly brace
|
||||
while (parserPeek(parser, 1).as.success.type == STT_LINE_END) {
|
||||
parserConsume(parser);
|
||||
@@ -1620,6 +1814,171 @@ static inline ResultType(Nothing, charptr) parseUse(SolsParser* parser) {
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
|
||||
static inline ResultType(Nothing, charptr) parseFrom(SolsParser* parser) {
|
||||
ResultType(SolsToken, Nothing) type = parserConsume(parser);
|
||||
if (type.error) {
|
||||
return Error(Nothing, charptr, "Expecting literal string after 'from'");
|
||||
}
|
||||
if (!(type.as.success.type == STT_LITERAL && type.as.success.as.literal.type == SLT_STRING)) {
|
||||
return Error(Nothing, charptr, "Expecting literal string after 'from'");
|
||||
}
|
||||
|
||||
SolsNode fromNode;
|
||||
|
||||
char* typeStr = type.as.success.as.literal.as.stringv;
|
||||
if (strcmp(typeStr, "c") == 0) {
|
||||
fromNode = ({
|
||||
ResultType(SolsNode, charptr) _res = createSolsNode(SNT_FROM_C);
|
||||
if (_res.error) {
|
||||
return Error(Nothing, charptr, _res.as.error);
|
||||
}
|
||||
_res.as.success;
|
||||
});
|
||||
// Get symbol location and name
|
||||
ResultType(SolsToken, Nothing) location = parserConsume(parser);
|
||||
if (location.error) {
|
||||
return Error(Nothing, charptr, "Expecting literal string after \"c\"");
|
||||
}
|
||||
if (!(location.as.success.type == STT_LITERAL && location.as.success.as.literal.type == SLT_STRING)) {
|
||||
return Error(Nothing, charptr, "Expecting literal string after 'from'");
|
||||
}
|
||||
|
||||
ResultType(SolsNode, charptr) locationNode = createSolsNode(SNT_LITERAL, location.as.success.as.literal);
|
||||
if (locationNode.error) {
|
||||
return Error(Nothing, charptr, locationNode.as.error);
|
||||
}
|
||||
addChildToSolsNode(&fromNode, locationNode.as.success);
|
||||
|
||||
ResultType(SolsToken, Nothing) symbol = parserConsume(parser);
|
||||
if (symbol.error) {
|
||||
return Error(Nothing, charptr, "Expecting string after location string");
|
||||
}
|
||||
if (!(symbol.as.success.type == STT_LITERAL && symbol.as.success.as.literal.type == SLT_STRING)) {
|
||||
return Error(Nothing, charptr, "Expecting literal string after 'from'");
|
||||
}
|
||||
|
||||
ResultType(SolsNode, charptr) symbolNode = createSolsNode(SNT_LITERAL, symbol.as.success.as.literal);
|
||||
if (symbolNode.error) {
|
||||
return Error(Nothing, charptr, symbolNode.as.error);
|
||||
}
|
||||
addChildToSolsNode(&fromNode, symbolNode.as.success);
|
||||
|
||||
} else if (strcmp(typeStr, "ground") == 0) {
|
||||
fromNode = ({
|
||||
ResultType(SolsNode, charptr) _res = createSolsNode(SNT_FROM_GROUND);
|
||||
if (_res.error) {
|
||||
return Error(Nothing, charptr, _res.as.error);
|
||||
}
|
||||
_res.as.success;
|
||||
});
|
||||
// no need to do other stuff here, Ground isn't weird
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting string after 'from' to be either \"c\" or\"ground\"");
|
||||
}
|
||||
|
||||
ResultType(SolsToken, Nothing) defToken = parserConsume(parser);
|
||||
if (defToken.error || defToken.as.success.type != STT_KW_DEF) {
|
||||
return Error(Nothing, charptr, "Expecting 'def' after from statement");
|
||||
}
|
||||
|
||||
// Parse definition
|
||||
ResultType(SolsNode, charptr) defNode = createSolsNode(SNT__);
|
||||
if (defNode.error) {
|
||||
return Error(Nothing, charptr, defNode.as.error);
|
||||
}
|
||||
|
||||
{
|
||||
ResultType(SolsToken, Nothing) nameTok = parserConsume(parser);
|
||||
if (nameTok.error || nameTok.as.success.type != STT_IDENTIFIER) {
|
||||
return Error(Nothing, charptr, "Expecting function name after 'def'");
|
||||
}
|
||||
|
||||
ResultType(SolsToken, Nothing) openBracket = parserConsume(parser);
|
||||
if (openBracket.error || openBracket.as.success.type != STT_OPEN_PAREN) {
|
||||
return Error(Nothing, charptr, "Expecting '(' after function name in 'def'");
|
||||
}
|
||||
|
||||
ResultType(SolsType, charptr) type = createSolsType(STT_FUN);
|
||||
if (type.error) {
|
||||
return Error(Nothing, charptr, type.as.error);
|
||||
}
|
||||
|
||||
// Add function name as the first child node
|
||||
ResultType(SolsNode, charptr) nameNode = createSolsNode(SNT_IDENTIFIER, nameTok.as.success.as.idName);
|
||||
if (nameNode.error) {
|
||||
return Error(Nothing, charptr, nameNode.as.error);
|
||||
}
|
||||
nameNode.as.success.line = nameTok.as.success.line;
|
||||
nameNode.as.success.accessArg = Ground.New.Arg.ValueRef(Ground.New.Identifier(nameTok.as.success.as.idName));
|
||||
addChildToSolsNode(&defNode.as.success, nameNode.as.success);
|
||||
|
||||
// Parse type signature
|
||||
for (;;) {
|
||||
ResultType(SolsToken, Nothing) next = parserPeek(parser, 1);
|
||||
if (next.error) {
|
||||
return Error(Nothing, charptr, "Expecting ')' at end of def argument list");
|
||||
}
|
||||
if (next.as.success.type == STT_CLOSE_PAREN) {
|
||||
parserConsume(parser);
|
||||
break;
|
||||
}
|
||||
|
||||
if (type.error) {
|
||||
return Error(Nothing, charptr, type.as.error);
|
||||
}
|
||||
|
||||
// Pattern of type, name, comma
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
char* argName;
|
||||
next = parserPeek(parser, 1);
|
||||
|
||||
if (next.as.success.type == STT_IDENTIFIER) {
|
||||
argName = next.as.success.as.idName;
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting identifier after type in def argument list");
|
||||
}
|
||||
|
||||
// Add type to constructed SolsType
|
||||
addChildToSolsType(&type.as.success, typeNode.as.success.as.type, argName);
|
||||
parserConsume(parser);
|
||||
|
||||
next = parserPeek(parser, 1);
|
||||
if (next.error) {
|
||||
return Error(Nothing, charptr, "Expecting a comma or closing bracket");
|
||||
}
|
||||
if (next.as.success.type == STT_CLOSE_PAREN) {
|
||||
parserConsume(parser);
|
||||
break;
|
||||
}
|
||||
if (next.as.success.type != STT_COMMA) {
|
||||
return Error(Nothing, charptr, "Expecting a comma or closing bracket");
|
||||
}
|
||||
parserConsume(parser);
|
||||
}
|
||||
|
||||
// Parse return type after argument list
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
type.as.success.returnType = malloc(sizeof(SolsType));
|
||||
*type.as.success.returnType = typeNode.as.success.as.type;
|
||||
|
||||
// Add type to node
|
||||
defNode.as.success.as.type = type.as.success;
|
||||
}
|
||||
|
||||
addChildToSolsNode(&fromNode, defNode.as.success);
|
||||
addChildToSolsNode(parser->currentParent, fromNode);
|
||||
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
|
||||
static inline ResultType(Nothing, charptr) parseInlineGround(SolsParser* parser) {
|
||||
ResultType(SolsToken, Nothing) token = parserPeek(parser, 0);
|
||||
if (token.error) {
|
||||
@@ -2267,15 +2626,11 @@ ResultType(Nothing, charptr) parseNew(SolsParser* parser) {
|
||||
if (newNode.error) {
|
||||
return Error(Nothing, charptr, newNode.as.error);
|
||||
}
|
||||
ResultType(SolsToken, Nothing) nameTok = parserConsume(parser);
|
||||
if (nameTok.error || !(nameTok.as.success.type == STT_IDENTIFIER || nameTok.as.success.type == STT_TYPE)) {
|
||||
return Error(Nothing, charptr, "Expecting identifier after 'new'");
|
||||
}
|
||||
if (nameTok.as.success.type == STT_IDENTIFIER) {
|
||||
newNode.as.success.as.type = createIdentifiedSolsType(nameTok.as.success.as.idName).as.success;
|
||||
} else if (nameTok.as.success.type == STT_TYPE) {
|
||||
newNode.as.success.as.type = nameTok.as.success.as.type;
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
newNode.as.success.as.type = typeNode.as.success.as.type;
|
||||
addChildToSolsNode(parser->currentParent, newNode.as.success);
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
@@ -2420,62 +2775,23 @@ ResultType(Nothing, charptr) parseOpenSquare(SolsParser* parser) {
|
||||
if (openSquare.error) {
|
||||
return Error(Nothing, charptr, openSquare.as.error);
|
||||
}
|
||||
// Get previous node
|
||||
if (parser->currentParent->children.count == 0) {
|
||||
return Error(Nothing, charptr, "Expecting identifier of type before '['");
|
||||
}
|
||||
SolsNode prev = parser->currentParent->children.at[parser->currentParent->children.count - 1];
|
||||
if (prev.type != SNT_IDENTIFIER) {
|
||||
return Error(Nothing, charptr, "Expecting identifier of type before '['");
|
||||
|
||||
if (parser->current > 0) {
|
||||
parser->current--;
|
||||
if (parser->currentParent->children.count > 0) {
|
||||
parser->currentParent->children.count--;
|
||||
}
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Identifier required before open square");
|
||||
}
|
||||
|
||||
addChildToSolsNode(&openSquare.as.success, prev);
|
||||
parser->currentParent->children.count--;
|
||||
|
||||
// Collect generic arguments and add to node
|
||||
for (;;) {
|
||||
ResultType(SolsToken, Nothing) token = parserConsume(parser);
|
||||
if (token.error) {
|
||||
return Error(Nothing, charptr, "Expecting ']' to end generic argument list");
|
||||
}
|
||||
|
||||
if (token.as.success.type == STT_IDENTIFIER) {
|
||||
SolsType type = ({
|
||||
ResultType(SolsType, charptr) _result = createIdentifiedSolsType(token.as.success.as.idName);
|
||||
if (_result.error) {
|
||||
return Error(Nothing, charptr, _result.as.error);
|
||||
}
|
||||
_result.as.success;
|
||||
});
|
||||
addChildToSolsNode(&openSquare.as.success, ({
|
||||
ResultType(SolsNode, charptr) _result = createSolsNode(SNT_TYPE, type);
|
||||
if (_result.error) {
|
||||
return Error(Nothing, charptr, _result.as.error);
|
||||
}
|
||||
_result.as.success;
|
||||
}));
|
||||
} else if (token.as.success.type == STT_TYPE) {
|
||||
addChildToSolsNode(&openSquare.as.success, ({
|
||||
ResultType(SolsNode, charptr) _result = createSolsNode(SNT_TYPE, token.as.success.as.type);
|
||||
if (_result.error) {
|
||||
return Error(Nothing, charptr, _result.as.error);
|
||||
}
|
||||
_result.as.success;
|
||||
}));
|
||||
} else {
|
||||
return Error(Nothing, charptr, "Expecting identifier or type in generic argument list");
|
||||
}
|
||||
|
||||
// Expect comma or ']'
|
||||
ResultType(SolsToken, Nothing) comma = parserConsume(parser);
|
||||
if (comma.error) {
|
||||
return Error(Nothing, charptr, "Expecting ',' or ']' after generic argument");
|
||||
}
|
||||
if (comma.as.success.type == STT_CLOSE_SQUARE) {
|
||||
break;
|
||||
}
|
||||
ResultType(SolsNode, charptr) typeNode = parseType(parser);
|
||||
if (typeNode.error) {
|
||||
return Error(Nothing, charptr, typeNode.as.error);
|
||||
}
|
||||
|
||||
openSquare.as.success.as.type = typeNode.as.success.as.type;
|
||||
|
||||
addChildToSolsNode(parser->currentParent, openSquare.as.success);
|
||||
return Success(Nothing, charptr, {});
|
||||
}
|
||||
@@ -2573,6 +2889,7 @@ ResultType(Nothing, charptr) parse(SolsParser* parser) {
|
||||
case STT_LITERAL: PARSER_HANDLE(Literal);
|
||||
case STT_KW_PUTS: PARSER_HANDLE(Puts);
|
||||
case STT_KW_IF: PARSER_HANDLE(If);
|
||||
case STT_KW_ELSE: PARSER_HANDLE(Else);
|
||||
case STT_KW_WHILE: PARSER_HANDLE(While);
|
||||
case STT_KW_GROUND: PARSER_HANDLE(InlineGround);
|
||||
case STT_KW_LAMBDA: PARSER_HANDLE(Lambda);
|
||||
@@ -2581,6 +2898,7 @@ ResultType(Nothing, charptr) parse(SolsParser* parser) {
|
||||
case STT_KW_ENUM: PARSER_HANDLE(Enum);
|
||||
case STT_KW_NEW: PARSER_HANDLE(New);
|
||||
case STT_KW_USE: PARSER_HANDLE(Use);
|
||||
case STT_KW_FROM: PARSER_HANDLE(From);
|
||||
case STT_KW_DEF: PARSER_HANDLE(Def);
|
||||
case STT_OP_SET: PARSER_HANDLE(Set);
|
||||
case STT_OP_ADD: PARSER_HANDLE(Add);
|
||||
|
||||
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
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "parser/parser.h"
|
||||
#include "codegen/codegen.h"
|
||||
|
||||
#include <groundvm.h>
|
||||
#include <ground.h>
|
||||
|
||||
|
||||
static char out_buf[65536];
|
||||
|
||||
@@ -5,7 +5,7 @@ if exists("b:current_syntax")
|
||||
endif
|
||||
|
||||
" Keywords
|
||||
syn keyword solsKeyword puts if while def lambda return use struct new private protected constructor destructor duplicator as ground
|
||||
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
|
||||
|
||||
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