The Þog of 2022-06-04 — Fake S-Expression Systems Language
sina toki e toki pona la, o lukin e ni!
Lisp and Scheme weren't designed for writing bare-metal programs on the type of computers we have today. Disassembled WebAssembly text is sort of close, but what would the point be to writing platform-specific assembly with parentheses everywhere?
Let's ruin Lisp by turning it into Worse C. Thankfully, this is only theoretical, so it won't cause harm by having programmers attempt to actually use this.
Informal syntax and grammar
- Everything is surrounded with parentheses. Addition is written as `(+ 1 1)`.
- kebab-case is permitted in identifiers; snake_case should be used for library functions interfacing with C or Rust. Identifiers are case-sensitive.
- Strings are delimited by `"`. They are always encoded as UTF-8 `[]u8`.
- Single-quotes `'` delimit a single Unicode codepoint, encoded as UTF-32 `u32`.
- Type information is written as `identifier.type`, with no whitespace. Types are not inferred automatically. `.&type` indicates an immutable pointer, and `.*type` indicates a mutable one. `.[]type` indicates a fat-pointer array that has a fixed length. `identifier.` with nothing after the dot indicates the unit type that has no storage or return value.
- `int` and `uint` are aliases for the platform's native integer types. On 32-bit platforms, these are `i32` and `u32`. On 64-bit platforms they are `i64` and `u64`.
- `real` is an alias to the platform's native floating point type. This will be `f32` on most systems.
- `bool` is a specialized form of `u8`. `True` and `False` are the only valid values a variable of this type can have.
Builtins
- `+ - * /` — Addition, subtraction, multiplication, division `(+ 1 2)`
- `^` — Pointer dereference, array access with offset `(^ var [offset])`
- `&` — Address of variable `(& var)`
- `.` — Structure member access `(. member var)`
- `:` — Variable assignment `(: var value)`
- `not and or xor` — Bitwise and boolean operations `(or a b)`
- `fn` — Locally-visible function `(fn (name.type arg.type) ...)`
- `fn.export` — Externally-visible function
- `fn.import` — Declare externally linked function, no body permitted
- `let` — Scope-local variable binding `(let (name.type value))`
- `ret` — Return from function `(ret [value])`
- `if` — Only runs first body if first argument is equivalent to True. Otherwise, runs second body if supplied. `(if expr true-body [false-body])`
- `while` — Repeatedly evaluates body while first argument is equivalent to True. `(while expr ...)`
- `trace` — Debugging aid, displays arguments space-separated `(trace "hello")`
Examples
"Hello world!"
(fn.export (main.)
(trace "Hello world!"))
Array and single characters
(fn.export (main.)
(let (hello.[]u32 'h 'e 'l 'l 'o))
(trace hello))
Calculate the magnitude of a 3-dimensional vector
(fn (vec3-length.real x.real y.real z.real)
(ret (sqrt (+ (* x x) (* y y) (* z z))))
(fn.export (main.)
(trace (vec3-length 0.0 0.5 1.0))) ; -> ~1.118
Calculate the length of a C-style NUL terminated string
(fn (my-strlen.uint str.&u8)
(let (i.uint 0))
(while (^ str i) (: i (+ i 1)))
(ret count))
(fn.export (main.)
(trace (my-strlen "How long is this?"))) ; -> 17