commit d1b25c37523717c5bb1d17560c87660a869cfd59
Author: thing1 <thing1@seacrossedlovers.xyz>
Date: Wed, 14 Jan 2026 10:23:29 +0000
init commit
Diffstat:
6 files changed, 283 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+./splc
diff --git a/Makefile b/Makefile
@@ -0,0 +1,32 @@
+.POSIX:
+.SUFFIXES:
+
+HARE=hare
+HAREFLAGS=-lc
+
+DESTDIR=
+PREFIX=/usr/local
+BINDIR=$(PREFIX)/bin
+
+HARE_SOURCES != find . -name '*.ha'
+
+all: splc
+
+splc: $(HARE_SOURCES)
+ $(HARE) build $(HAREFLAGS) -o $@ cmd/$@/
+
+check:
+ $(HARE) test $(HAREFLAGS)
+
+clean:
+ rm -f splc
+
+install:
+ install -Dm755 splc $(DESTDIR)$(BINDIR)/splc
+
+uninstall:
+ rm -f $(DESTDIR)$(BINDIR)/splc
+
+.PHONY: all check clean install uninstall
+
+
diff --git a/cmd/splc/main.ha b/cmd/splc/main.ha
@@ -0,0 +1,30 @@
+use fmt;
+use strings;
+use spl::lexer;
+use spl::parser;
+
+const in = "func main() i32 { a i32 = 64 == 64; }";
+
+export fn main() void = {
+ let l = &lexer::lexer{in = strings::toutf8(in), pos = 0, prev = []};
+ defer lexer::finish(l);
+
+ let ts: []lexer::token = [];
+ defer free(ts);
+
+ for (true) {
+ let t = match (lexer::next(l)) {
+ case let t: lexer::token => yield t;
+ case let e: lexer::unknowntoken =>
+ fmt::println(lexer::strerror(e, l))!;
+ continue;
+ };
+
+ if (t.ty == lexer::ttype::EOF) break;
+
+ append(ts, t)!;
+ };
+
+ for (let t .. ts)
+ fmt::println("token:", strings::fromutf8(l.in[t.data.0 .. t.data.1])!)!;
+};
diff --git a/spl/lexer/lex.ha b/spl/lexer/lex.ha
@@ -0,0 +1,165 @@
+use io;
+use ascii;
+use fmt;
+use strings;
+
+export type range = (size, size);
+
+export type unknowntoken = !range;
+export type other = !range;
+export type error = !(unknowntoken | other);
+
+type asciifn = fn(r: rune) bool;
+
+export type ttype = enum {
+ EOF = -1,
+ OBRACE = '(',
+ CBRACE = ')',
+ OCBRACE = '{',
+ CCBRACE = '}',
+ OSBRACE = '[',
+ CSBRACE = ']',
+ ASSIGN = '=',
+ SEMI = ';',
+
+ ADD = '+',
+ SUB = '-',
+ MUL= '*',
+ DIV = '/',
+ MOD = '%',
+
+ LT = '<',
+ GT = '>',
+ NOT = '!',
+
+ EQU, // ==
+ LTE, // <=
+ GTE, // >=
+ OR, // ||
+ AND, // &&
+
+ FUNC, // func
+ IF, // if
+
+ NAME,
+ NUMBER,
+};
+
+export type lexer = struct {
+ in: []u8,
+ pos: size,
+ prev: []size
+};
+
+export type token = struct {
+ ty: ttype,
+ // range of data
+ // "func main() i32 ..."
+ // ^ ^ ^ ^^^ ^ ^
+ // ^^
+ data: range,
+};
+
+export fn strerror(e: error, l: *lexer) str = match (e) {
+ case let e: unknowntoken => yield fmt::asprintf("Unknown token \"{}\"", strings::fromutf8(l.in[e.0 .. e.1])!)!;
+ case => yield "unknown error";
+};
+
+export fn finish(l: *lexer) void = {
+ free(l.prev);
+};
+
+fn readblock(l: *lexer, pred: *asciifn) range = {
+ let start = l.pos;
+ for (pred(l.in[l.pos]: rune); l.pos += 1)
+ continue;
+ return (start, l.pos);
+};
+
+fn isnumber(r: rune) bool = ascii::isdigit(r) || (r == '-');
+fn isname(r: rune) bool = ascii::isalpha(r) || isnumber(r);
+fn iswhitespace(r: rune) bool = ascii::isblank(r) || (r == '\n');
+
+fn lexstr(l: *lexer, s: str) (range | void) = {
+ let start = l.pos;
+
+ for (let c .. strings::toutf8(s)) {
+ if (l.in[l.pos] == c) l.pos += 1
+ else {
+ l.pos = start;
+ return;
+ };
+ };
+
+ return (start, l.pos);
+};
+
+export fn prev(l: *lexer) void = {
+ l.pos = l.prev[len(l.prev) - 1];
+ l.prev = l.prev[0 .. len(l.prev) - 1];
+};
+
+export fn next(l: *lexer) (token | error) = {
+ if (l.pos >= len(l.in))
+ return token{ty = ttype::EOF, data = (l.pos, l.pos)};
+
+ if (iswhitespace(l.in[l.pos]: rune)) {
+ l.pos += 1;
+ return next(l);
+ };
+ append(l.prev, l.pos)!;
+
+ match (lexstr(l, "==")) {
+ case let data: range => return token{ty = ttype::EQU, data = data};
+ case => yield;
+ };
+
+ match (lexstr(l, "<=")) {
+ case let data: range => return token{ty = ttype::LTE, data = data};
+ case => yield;
+ };
+
+ match (lexstr(l, ">=")) {
+ case let data: range => return token{ty = ttype::GTE, data = data};
+ case => yield;
+ };
+
+ match (lexstr(l, "||")) {
+ case let data: range => return token{ty = ttype::OR, data = data};
+ case => yield;
+ };
+
+ match (lexstr(l, "&&")) {
+ case let data: range => return token{ty = ttype::AND, data = data};
+ case => yield;
+ };
+
+ match (lexstr(l, "func")) {
+ case let data: range => return token{ty = ttype::FUNC, data = data};
+ case => yield;
+ };
+
+ match (lexstr(l, "if")) {
+ case let data: range => return token{ty = ttype::IF, data = data};
+ case => yield;
+ };
+
+ switch (l.in[l.pos]: ttype) {
+ case ttype::OBRACE, ttype::CBRACE, ttype::OCBRACE, ttype::CCBRACE,
+ ttype::OSBRACE, ttype::CSBRACE, ttype::ASSIGN,
+ ttype::SEMI, ttype::ADD, ttype::SUB, ttype::MUL,
+ ttype::DIV, ttype::MOD =>
+ defer l.pos += 1;
+ return token{ty = l.in[l.pos]: ttype, data = (l.pos, l.pos + 1)};
+ case => yield;
+ };
+
+ if (ascii::isalpha(l.in[l.pos]: rune))
+ return token{ty = ttype::NAME, data = readblock(l, &isname)}
+ else if (isnumber(l.in[l.pos]: rune)) {
+ return token{ty = ttype::NUMBER, data = readblock(l, &isnumber)};
+ };
+
+ defer l.pos += 1;
+ return (l.pos, l.pos + 1): unknowntoken;
+};
diff --git a/spl/parser/parse.ha b/spl/parser/parse.ha
@@ -0,0 +1,55 @@
+use spl::lexer;
+
+export type unexpectedeof = !void;
+export type unexpectedtoken = !void;
+export type error = !(unexpectedeof | unexpectedtoken);
+
+
+export type ty = struct {
+ basic: str
+};
+
+// TODO add modifier option here to allow for const, static, etc
+export type assign = struct {
+ name: str,
+ ty: ty,
+ value: expr
+};
+
+export type reassign = struct {
+ name: str,
+ value: expr
+};
+
+export type binop = struct {
+ ty: lexer::ttype,
+ // TODO replace int with litteral type that can store floats as well
+ left: (int | expr),
+ right: (int | expr),
+};
+
+export type fcall = struct {
+ name: str,
+ args: []expr
+};
+
+// TODO add modifier option here to allow for pub, priv, etc
+export type fdec = struct {
+ name: str,
+ body: []node,
+ args: []assign
+};
+
+// functions as right hand side expression
+// fcall is for fcalls
+// binop is for maths
+// expr is for bracketed expressions
+export type expr = (*(fcall | binop | expr), ty);
+// functions as left hand side expression
+export type node = (reassign | assign | fcall | fdec);
+
+fn get(ts: []lexer::token, pos: *size) token = {
+ if (ts[pos].ty == lexer::ttype::EOF) fmt::fatal("unexpected EOF");
+ defer pos += 1;
+ return ts[pos];
+};
diff --git a/splc b/splc
Binary files differ.