Astreum

Language Tour

Walk through the core language constructs with short snippets. The evaluator reads postfix expressions left to right: each value pushes onto a stack, then operators pop their operands and push results.

Stack and arithmetic

Numbers now use unified arithmetic operators.

(1 2 +)      ; -> 3
(1 2 *)      ; -> 2
(5 3 -)      ; -> 2
(10 3 /)     ; -> 3
(10 3 %)     ; -> 1
(4.0 sqrt)   ; -> 2.0
(1.5 2.5 +)  ; -> 4.0
(2 (1 2 +) dip) ; evaluate (1 2 +) without losing the saved 2 -> 3 2

Bitwise and shifts

Bitwise operators work on Bytes values. Use hex literals when you want raw byte input.

(0x05 0x03 &)    ; -> 0x01
(0x05 0x03 |)    ; -> 0x07
(0x05 0x03 ^)    ; -> 0x06
(0x05 ~)         ; one's complement within the byte width
(0x01 0x04 <<)  ; -> 0x10
(0x10 0x04 >>>) ; -> 0x01
(0x10 0x04 >>)  ; arithmetic right shift
(0x03 0x01 rol)  ; rotate left
(0x03 0x01 ror)  ; rotate right

Defining variables with def

(value name def) evaluates the value, then binds the name in the current environment.

(10 x def)    ; binds x to Int(10)
(x)           ; evaluates to 10

Unbound symbols silently push NIL rather than raising an error.

Functions with fn

(body (params) fn) pops parameters, then body from the stack. Parameters bind from the top of the stack. fn keeps lexical parentage, while def inside the body writes to the outer target environment.

(
  (x y +) (a b) fn
) sum def
(5 6 sum)   ; -> 11

def inside a function writes globally when the function is created with fn.

Lambda - isolated scope

lambda works like fn but creates an environment with no parent and no def_target.

(
  (x outer def)
  (x) () lambda
) get_x def
(get_x)     ; resolves only within the lambda's isolated scope

Conditionals with if

Write conditionals as (cond then else if). The condition is evaluated first. Truthiness is non-zero Bytes, non-zero Int, non-zero Float, or a non-NIL Link.

(0 1 0 if)      ; false -> 0
(0 1 1 if)      ; true -> 1
(0 1 1.0 if)    ; Float truthiness also works
(0 (2 3 +) 1 if) ; true branch is a list expression -> 5

Quote and eval

quote prevents evaluation of its argument, while eval re-enters the evaluator on a value.

(' (1 2 +))          ; pushes the list (1 2 +) unevaluated
( (1 2 +) quote eval ) ; same as evaluating (1 2 +) -> 3
( (1 2 +) quote )      ; wraps the list in a quotation

Expression construction

link, head, and tail let you build and destructure list expressions.

(1 2 link)       ; builds Link(head=1, tail=2)
(1 2 link head)  ; -> 1
(1 2 link tail)  ; -> 2
(1 is_atom)      ; 1 (Int is an atom)
((1 2) is_atom)  ; 0 (it is a Link)
(1 1 is_eq)      ; 1 (deep equality)
(1 2 is_eq)      ; 0
("42" symbol)    ; String -> Symbol "42"
("42" int)       ; String -> Int(42)
(42 str)         ; any atom -> String("42")
(42 float)       ; Int -> Float(42.0)
(42 bytes)       ; Int -> Bytes(b"*")
( (1 2 link) ref )  ; resolve content-hash of (1 2) from storage
( (1 2 link) load ) ; deep-resolve the full sub-tree

Comments

(1 2 +) ; line comment to end of line
(1 #;(2 3 +) 4 +) ; #; skips the middle expression

Semicolon starts a line comment. #; skips the following complete expression, even if nested.

Working with modules

Create a helper at modules/math.aex:

(
  (1 version def)
  (x y +) (a b) fn sum def
)

Import it from src/main.aex:

(
  (math "../modules/math.aex" import)
  (math.sum main def)
)

Import qualifies all symbols under the prefix. Intra-module references are rewritten at load time, so sum becomes math.sum.

Actors

spawn creates a named actor with its own mailbox and daemon thread. send enqueues a message; receive blocks until a message arrives. Actor names must be Symbols, so use symbol to construct them explicitly.

((1 2 +) "worker" symbol spawn)
(42 "worker" symbol send)
("worker" symbol receive)

Actors run in background threads. In deterministic mode, spawn, send, receive, and eval push NIL instead of executing.

Putting it together

A small module that defines a sum helper and then uses it from a script:

; modules/math.aex
(
  (1 version def)
  (x y +) (a b) fn sum def
)
; src/main.aex
(
  (math "../modules/math.aex" import)
  (3 4 math.sum)
)

The imported helper is available as math.sum and evaluates to 7 when called with 3 and 4.