Xlerb Language Specification

A stack-oriented language for the BEAM

Data Types

1 2 3
-42
100_000

Integers

Whole numbers, optionally negative. Use optional underscores to separate digit groups.

3.14
-0.001
1e10
2.5e-3

Floats

Decimals, with scientific notation.

"hello, xlerb!"
"line one\nline two"
"µ-ziq"

Strings

UTF-8 binary strings.

:ok
:error
:my_atom

Atoms

Efficiently interned strings.

true
false

Booleans

Forth uses `-1`, `0` for `true`/`false`, Xlerb uses standard BEAM booleans.

Words

: square dup * ;

Basic Definition

Words are defined with : and end ;

This defines square which duplicates the top-of-stack, and multiplies the top two elements.

: 0 factorial drop 1 ;
: _ factorial dup 1 - factorial * ;

Pattern Matching

Patterns before the word name match the top of stack. _ matches anything.

Multiple definitions create multi-clause words—first match wins.

: :ok    _ unwrap! swap drop ;
: :error _ unwrap! raise ;

example: Tagged Dispatch

You can match on an arbitrary number of stack elements.

Unwrap the value from the result, or raise.

: ^2 dup * ;
: +=+ [Bitwise bxor 2] elixir ;

Allowed Symbols

Word names can contain most characters.

Quotations

[dup *]
[1 2 +]

Syntax

Quotations are delimited by [ and ]

They represent code as data that can be passed around and executed later.

200 [dup *] i
\ => 40000

Invoking Quotations

The word i invokes a quotation, executing its contents against the current stack.

[2 + 3] rev
\ => [3 + 2]

Quotation Manipulation

Quotations can be transformed like any list-like data structure. For example, rev reverses the elements of a quotation.

[3 + 2] [swap] do
\ => [+ 3 2]

Do

The word do runs a quotation using another quotation as the stack.

[whatever receive .s flush recurse] spawn

Recurse

Inside a quotation, recurse performs a tail-call, for anonymous recursion.

Modules

: math [
  : square dup * ;
  : cube dup dup * * ;
] ; module

Definition

Modules group related words. Define a module by giving a name to a quotation, and following it with module.

Compiles directly to BEAM modules, which export functions from list to list.

5 math:square
\ => 25

Calling

Module words are called with module:word syntax.

: utils [
  : helper dup + ; private
  : double helper ;
] ; module

Private Words

The private keyword after a definition makes it module-private.

Case

[
  : 0 -> "zero" ;
  : 1 -> "one" ;
  : _ -> "other" ;
] case

Syntax

Case expressions are just a quotation defining overloads of -> which match on the current stack.

: get-result [
  : :ok _ -> swap drop "success: " ++ ;
  : :error -> drop "failed" ;
] case ;

Example: Tagged Results

Like Elixir/Erlang, pattern match for. Patterns are read with the top of the stack rightmost.

[
  : 1 -> "monday" ;
  : 2 -> "tuesday" ;
  : 3 -> "wednesday" ;
  : 4 -> "thursday" ;
  : 5 -> "friday" ;
  : _ -> "weekend" ;
] case

Multiple Branches

First matching branch executes. Use _ as a catch-all.

Compiles directly to Erlang's case expression.

Receive

[ : -> ; ] receive
whatever receive

Receive Anything

Waits for a message and pushes it onto the stack. An empty -> pattern matches any message.

The word whatever pushes this quotation.

[
  : _ :ping -> ! :pong send drop ;
  : _ _ -> drop drop ;
] receive

Pattern Matching

Define multiple -> clauses to handle different message shapes. All Xlerb messages are stacks.

pid ! :hello "world" 42 send

Sending Messages

! marks message start. Elements are pushed, then send packs and dispatches to the PID before the marker.

Sends [:hello, "world", 42] to pid.

[quotation] spawn
self

Processes

spawn executes a quotation in a new process and pushes its PID.

self pushes the current process's PID.

Pack & Unpack

1 2 3 &{&1, &2, &3} pack
\ => {3, 2, 1}

Pack Tuples

& introduces a capture pattern. &1, &2, etc. reference stack positions (&1 is the top of stack).

pack constructs the structure from stack values.

\ Stack: {10, 20, 30}
&{&1, &2, &3} unpack
\ Stack: 10 20 30

Unpack Tuples

unpack destructures the top of stack according to the pattern, pushing captured values.

100 200 &%{x: &1, y: &2} pack
\ => %{x: 200, y: 100}

Pack Maps

Maps use %{key: &n} or %{&n => &m} syntax. Atom keys use the shorthand colon syntax.

%{name: "alice", age: 30} &%{name: &1} unpack
\ => "alice"

Unpack Maps

Extract specific keys from maps. Unmatched keys are ignored.

1 2 3 &[&1, &2, &3] pack
\ => [3, 2, 1]

[1, 2, 3] &[&1 | &2] unpack
\ Stack: 1 [2, 3]

Lists

List patterns support both element and cons (|) syntax for head/tail destructuring.

10 20 &%{point: {&1, &2}} pack
\ => %{point: {20, 10}}

Nested Structures

Patterns can be nested arbitrarily—maps containing tuples, lists of maps, etc.

Interop

"hello" "world" [String concat 2] elixir
\ => "helloworld"

Elixir Calls

elixir calls an Elixir function. The quotation specifies [Module function arity].

arity arguments are popped taken from the stack, and the result pushed.

\ Stack: [1, 2, 3]
[lists reverse 1] erlang
\ => [3, 2, 1]

Erlang Calls

erlang calls an Erlang function. Module names are lowercase atoms.

Same calling convention as elixir.

: sha256 :sha256 [crypto hash 2] erlang ;
"hash-this-value" sha256

Example: Wrapping FFI calls

Keep FFI calls contained by wrapping them in a definition.

: puts [IO puts 1] elixir ;
: ++ [Kernel <> 2] elixir ;

Common Patterns

Wrap frequently-used Elixir/Erlang functions as words. Operators like <> work as function names.

Stack Operations

dup       \ a -- a a
drop      \ a --
swap      \ a b -- b a
rot       \ a b c -- c a b

Primitives

Core stack manipulation words. Stack effect notation shows before/after states.

.         \ print and pop top
.s        \ print entire stack

Inspection

Debug words for examining stack state.

+  -  *

Arithmetic

Binary operators pop two values, push result. a b - computes a - b.