From 842e22e9eb0f3dff7dabdaa41bcc2133e8f015f5 Mon Sep 17 00:00:00 2001 From: WormHeamer Date: Thu, 31 Jul 2025 22:37:38 -0400 Subject: initial commit --- doc/demo.txt | 94 ++++++++++++++++++++++++++++++++++++++++++++++ doc/test.txt | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 doc/demo.txt create mode 100644 doc/test.txt (limited to 'doc') diff --git a/doc/demo.txt b/doc/demo.txt new file mode 100644 index 0000000..58c0839 --- /dev/null +++ b/doc/demo.txt @@ -0,0 +1,94 @@ +type vec2 = [2]f32 + +type Sprite = enum u8 { + BLANK, + PLAYER +} + +type Player = struct { + pos, vel vec2 +} + +extern { + blit proc(spr Sprite, x, y i32), + exp func(v f32) f32 +} + +proc frame(dt f32) { + /* unary .. can splat an array as parameters */ + blit(.PLAYER, ..plr.pos) + plr.pos += plr.vel * dt + plr.vel *= exp(-dt * 5) +} + +/* statements + + type Ident = T Alias Ident to type T + extern Ident T Define an external variable (or function!) of the given type + extern { a A, b B... } Block of extern declarations, formatted like a struct + +*/ + +/* expressions + + N Integer + N.N | NeN | N.NeN Float + [-+~] A Unary operator on term A + A [+-/*%&|^] B Binary operator on term A and expression B + A := B Type inference assignment + A, B, C := D + T { ... } Literal of type T; mostly only makes sense for structs, + but this can be used to do something like u8 { 2 } too. + +/* types + + IDENT type of the given identifier + ^T pointer to type T + enum { FOO, BAR... } enum + enum T { FOO, BAR... } enum with backing type T, which must be an integer type + struct { a A, b B... } struct + A..B range (kinda an anonymous enum) + A and B must be ordinal types (enum value, integer, or character) + [N]T array of T of length N + [E]T array of T indexed by enum or range E + []T slice + (Ta, Tb...) tuple of given types; can be destructured to multiple assignment + set[E] a set of enum or range E + proc(a A, b B...) function pointer + func(a A, b B...) T function pointer with return value T + +*/ + +Expr = LValue | Literal +LValue = Ident | LValue '.' Ident +Literal = Number +Number = Integer | Float +Integer = /[0-9]+/ +Float = Integer 'e' Integer | Integer '.' Integer | Integer '.' Integer 'e' Integer + + IDENT Variable + LVALUE . IDENT Member access + [0-9]+ Integer + [0-9]+ . [0-9]+ Float + [0-9]+ . [0-9]+ e [0-9]+ Float + F(E1, E2...) function call with the given expression arguments + +*/ + +/* import first tries to scan local directory for the package, then standard + * system directory; in said directory it will look for an IR cache file, + * or compile a new one if it's not present (or if its last-modified time + * is older than the compiler executable), which is imported with a name + * equivalent to the last part of the path. + * + * codegen is only performed on procedures that get called from main and their + * own dependencies, recursively. this allows to keep output binaries small. + * maybe supply some sort of export qualifier to do something similar for + * procedures that aren't main. + */ + +import "io" + +proc main { + io.print("Hello, world!") +} diff --git a/doc/test.txt b/doc/test.txt new file mode 100644 index 0000000..da8f501 --- /dev/null +++ b/doc/test.txt @@ -0,0 +1,119 @@ +type string = []char + +func split(src: string, by: char, a: ^Arena): [dyn]string do + let r: [dyn]string = {} + let s: string = "" + for c in src do + if c == by then + r.push(s, a) + s = "" + else + s.push(c, a) + end + end + return r +end + +proc main do + let a = Arena() + let lines = split(stdin.readAll(@a), '\n', @a) + for i in 0..len(lines) do + print(lines[i]) + end +end + +--- + +fn next_line(src, line: ^[]u8) -> bool { + if len(src) < 1 { + return false + } + let idx = find(src, '\n') + if idx <> -1 { + line^ := src^[0..idx] + } else { + line^ := src^ + } + src^ = src^[len(line)..] + return true +} + +fn main { + let a = Arena() + let buf = readall(stdin, &a) + let line: []u8 + while next_line(&buf, &line) { + print(line) + } +} + +--- + +/* builtin slice type */ + +fn print(str []u8) { + for i in str { + putchar(str[i]) + } + putchar('\n') +} + +/* iterators are just functions that return an initial state object and a + * step function that acts on it to produce a value */ + +type LineIterFn = fn(it ^[]u8) ([]u8, bool) + +fn lines(src []u8) ([]u8, LineIterFn) { + fn step(it ^[]u8) []u8, bool { + if len(it^) < 1 { return {}, false } + idx := find(it^, '\n') + line := if idx == -1 { it^ } else { it^[0..idx] } + it^ = it^[len(line)..] + return line, true + } + return src, step +} + +/* if multiple values used in if/while condition, only the last one is checked */ + +fn main { + a := Arena {} + buf := readall(stdin, &a) + for line in lines(buf) { + print(line) + } +} + +/* generics? */ + +fn reverse[T any](buf []T) ([]T, fn(it ^[]T) (T, bool)) { + fn step(it ^[]T) (T, bool) { + if len(^it) < 1 { return {}, false } + n := len(it^) - 1 + v := it^[n] + it^ := it^[0..n] + return v, true + } + return buf, step +} + +fn foo { + s := "Hello, world!" + // print(reverse(s)) <- wouldn't actually work, because iterators can't be cast back to slices + a := Arena {} + print(collect(reverse(s), &a)) +} + +struct MapIter[In any, Out any] { + ctx: rawptr, + iter: fn(it rawptr) In, + map: fn(x ^In) Out +} + +fn map[In any, Out any](p rawptr, f func(it rawptr) In, f func(x ^In) Out) (MapIter[In, Out], fn(it ^MapIter[In, Out]) (Out, bool)) { + fn step(it ^MapIter[In, Out]) (Out, bool) { + v, ok := it.iter(it.ctx) + if !ok { return {}, false } + return it.map(&v), true + } +} -- cgit v1.2.3