the big syntax updated
It's been N months since the last 'syntax so far' post so it's probably time to revisit absolutely everything. If you're reading the blog in order then this is a spoilers page. Revisit it any time you want to know how the language should look.
(updated 11th April 2025)
// booleans
true
false
// nothing
ø
// integers
1234567890
-1234567890
1_234_567_890
-1_234_567_890
0b0110101001
0o81828375
-0o81828375
0xF23BEEF
-0xF23BEEF
// floats
1234.56789
-1234.56789
1_234.567_89
-1_234.567_89
// raw strings
`a raw string`
delim`a raw stringdelim`
// escaping strings
'a\tstring\n'
foo'a\tstring\nfoo'
// template strings
"Hello ~{name}"
x"Hello ~[name]x"
"Hello ~xnamex"
// variables and assignment
foo := 1
foo: UnsignedInteger = 1
foo: UnsignedInteger
// constant variables
pi :: 3.1415
// immediate arrays
a, b, c
{a, b, c}
{a}
{}
// typed immediate arrays
{Integer | 1, 2, 3}
{Integer | 1}
foo: (Array of: Integer) = {1, 2, 3}
// copying/combining arrays
{...other-array}
{...array-one, 4, 5, 6, ...array-two}
// immediate maps
{"a": 1, "b": 2, "c": 3}
{"a": 1}
the-key := "a".
{(the-key): 1}.
// typed immediate maps
{String, Integer | "a": 1, "b": 2, "c": 3}
{String, Integer | "a": 1}
{String, Integer | }
foo: (Map of: String to: Integer) = {"a": 1, "b": 2, "c": 3}
// copying/combining maps
{...other-map}
{...map-one, a: 4, b: 5, c: 6, ...map-two}
// class definition
Person :: {name: String, height: Length}
// class subtyping
RGBA32 ::
{...Color, ...Vector4 of: UnsignedInteger8,
r, g, b, a: UnsignedInteger8}.
// untagged-union definition
Pet :: {UntaggedUnion | as-cat: Cat, as-dog: Dog}
// tagged-union definition
Pet :: Cat + Dog.
[my-pet: Pet] [ ...code... ].
// tagged-union resolving
[cat speak] [cat: Cat | 'meow'].
[dog speak] [dog: Dog | 'woof'].
[pet speak] [pet: Pet | 'hello'].
my-pet := io download-a-pet.
my-pet speak.
// enum definition
Cardinality :: {north, east, south, west}
Cardinality :: {north = 1, east, south, west}
Cardinality :: {UnsignedInteger | north, east, south, west}
// method calls
// unary method call
person name
// binary method call
// (order of operations: subexpr, mul/div, add/sub, comparison, misc)
1 + 2
2 * (3 + 4)
foo copy> bar copy> baz
2 special* (3 special+ 4)
// keyword method call
person name: 'jane'
person shake: body-part near: another-person
// deferred method calls
--- person free
person --- [free]
// object creation
foo := {Person | name: 'jane', height: 175cm}
foo: Person = {name: 'jane', height: 175cm}
foo := {&Person | new, name: 'jane', height: 175cm}
foo: &Person = {new, name: 'jane', height: 175cm}
--- foo free
// focused-object mutation
foo {name: 'bob', age: 27}
// blocks
// declare everything
[a: Integer, b: Integer -> return: Integer | return a + b + 1]
// declare parameter names and types
[a: Integer, b: Integer -> return | return a + b + 1]
[a: Integer, b: Integer | a + b + 1]
// declare parameter names and return type
[a, b -> return: Integer | return a + b + 1]
[a, b -> Integer | a + b + 1]
// declare parameter names and return name
[a, b -> return | return a + b + 1]
// declare parameter names
[a, b | a + b + 1]
// default input/output
[+ 1]
// block closure
x := 1.
my-block := [a: String, b: Integer -> return: Integer]
[return a length + b + x].
// signature
// unary signature
person name
// binary signature
a + b
a else> b
// keyword signature
person name: a-name
person shake: body-part near: another-person
// methods
// declare everything
[self my-method: arg] [self: MyClass, arg: String -> return: String |
return `prefix ~[arg] suffix`]
// same as above but with implied return with last statement
[self my-method: arg] [MyClass, String -> String |
`prefix ~[arg] suffix`]
// declare parameter types, infer return type with last statement
[self my-method: arg] [MyClass, String | `prefix ~[arg] suffix`]
// declare parameter types, no return value
[self my-method: arg] [MyClass, String -> ø | `prefix ~[arg] suffix`]
// declare return type, infer parameters
[self my-method: arg] [-> return: String | return `prefix ~[arg] suffix`]
[self my-method: arg] [-> String | `prefix ~[arg] suffix`]
// infer parameters and return type
[self my-method: arg] [`prefix ~[arg] suffix`]
// method+block declaration
my-block := [a add: b] [a: Integer, b: Integer -> return: Integer
| return a + b].
my-block evaluate: 1, 2.
1 add: 2.
// reference type
p: &Person;
jane := {&Person | allocate, name: 'jane', height: 175cm}.
bob := {Person | name: 'bob', height: 180cm}.
database save: jane.
database save: bob.
// array access
bob := my-array[0].
my-array[12] = jane.
// array slicing
my-array[start:]
my-array[:stop]
my-array[start:stop]
my-array[::length]
my-array[start::length]
// packages
package: 'my package'.
using: 'opengl'.
import: 'opengl'.
// scoping
public Person :: {name: String}.
package Person :: {name: String}.
private Person :: {name: String}.
private [a add: b] [a: Integer, b: Integer -> return: Integer
| return a + b].
// scoping class members
Person ::
{public name: String
package nickname: String
private superheroname: String}.
// class specialisation
Array ::
{#specialise: [] [-> Class | element-class = Any],
#specialise: [of: element-class] [Any -> Class | ],
#specialise: [of: element-class length: length] [Any, UnsignedInteger -> Class | ],
...Iterable of: element-class,
...Orderable of: element-class,
public length: UnsignedInteger,
private origin: (MemoryAddress of: element-class)}.
generic-array: Array.
people-array: (Array of: Person).
people-buffer: (Array of: Person length: 10).
// default block parameter values and block signature
my-block := [a add: b]
[a: Integer = 1, b: Integer = 2 -> return: Integer
| return a + b].
my-block evaluate.
my-block evaluate: 10.
my-block evaluate: 10, -10.
10 add: -10.
// default class member values
RGBA32 ::
{r, g, b: UnsignedInteger8
a: UnsignedInteger8 = 255}.
// default keyword selector parameter values
[display stroke-path: path color: color]
[display: &Drawable, path: (Path of: Float)
color: RGBA32 = display current-color
-> ø
| cr := display cairo-context.
cr apply-path: path.
cr apply-color: color.
cr stroke ].
render-buffer stroke-path: {start: {x: 0, y: 0}, end: {x: 10, y: 10}}.
render-buffer
stroke-path: {start: {x: 10, y: 10}, end: {x: 20, y: 20}}
color: {r: 255, g: 0, b: 0}.
// maybe (a 'success' type and a 'failure type')
maybe-jane: Person / Failure.
maybe-jane then: [jane | stdout print: jane].
maybe-jane else: [stdout print: 'no Jane'].
maybe-jane else: [the-reason | stdout print: the-reason].
// non-failure based maybe
person_else_socket: Person / Socket.
person_else_socket then: [person: Person | ...].
person_else_socket else: [socket: Socket | ...].
// Input/Output
// Copying
// input copy> output
copy-of-people: people.
people copy> copy-of-people.
// Iterating/Processing
// input do> ø
'/important-data' directory files do> [delete].
{'a', 'b', 'c'} do> [index, value | ...].
// Mapping
// input map> transform copy> output
plus-one: (List of: Integer).
{1, 2, 3, 4} map> [+ 1] copy> plus-one.
// Narrowing
// input where> boolean-transform do> output
people where> [age ≥ 18] do> stdout.
// Reducing
total := 0.
people do> [person -> ø | total := total max: person age].
// Serialising
output := 'people.json' as-filename open-write --- [close].
people map> as-json copy> output else> panic.
// Buffering
buffer: (Buffer of: Byte capacity: 1024).
input-socket copy> buffer.
// Sub-buffering
buffer: (Buffer of: Byte capacity: 1024).
input-socket copy> buffer[::16].