spl2

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit d1b25c37523717c5bb1d17560c87660a869cfd59
Author: thing1 <thing1@seacrossedlovers.xyz>
Date:   Wed, 14 Jan 2026 10:23:29 +0000

init commit

Diffstat:
A.gitignore | 1+
AMakefile | 32++++++++++++++++++++++++++++++++
Acmd/splc/main.ha | 30++++++++++++++++++++++++++++++
Aspl/lexer/lex.ha | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aspl/parser/parse.ha | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asplc | 0
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.