Syntax Reference
A complete reference for Weave’s types, operators, control flow, and modules.
Comments
# Comments begin with # and run to end of line
# Multi-line comments don't exist. Just keep adding new comment lines.Data Types
Numbers
a = 1
b = 3.14
# Numbers are 64-bit values. The binary representation is u64, i64, or f64
# depending on the needs of the program. Negation or division converts as needed.
half = 10 / 3 # auto-converts to floatStrings
str = "This is a string"
# Escape sequences
str = "Line 1\nLine 2"
# String interpolation: expressions inside {} are evaluated
name = "World"
greeting = "Hello {name}!" # "Hello World!"
result = "Sum: {1 + 2}" # "Sum: 3"
obj = [key: "value"]
nested = "Key: {obj[:key]}" # expressions can be complex
# Escape literal braces with {{ and }}
literal = "Use {{braces}}" # "Use {braces}"
# Concatenation with +
"hello" + " " + "world" # "hello world"Booleans
flag = true
done = false
type(true) # :booleanSymbols
Symbols are immutable constant strings that begin with :. They’re used as keys, format identifiers, and lightweight enums.
:active
:csv
:also_a_symbol
type(:active) # :symbolNull
# There is no null keyword. Null arises from:
# - Accessing a missing container index: [][0]
# - A function with no explicit return value
# - An if expression with no else branch when the condition is false
missing = [][0]
type(missing) # :null
# Null is falsy
if missing { "truthy" } else { "falsy" } # "falsy"Variables and Assignment
x = 42
name = "Weave"
# Augmented assignment
x += 10 # x = x + 10
x -= 3 # x = x - 3
x *= 2 # x = x * 2
x /= 4 # x = x / 4
x %= 5 # x = x % 5
# Works on container elements too
c = [a: 10]
c[:a] += 5 # c[:a] is now 15Operators
Arithmetic
| Operator | Description |
|---|---|
+ |
Addition / string concatenation |
- |
Subtraction |
* |
Multiplication |
/ |
Division (int to float auto-conversion) |
% |
Modulo |
Comparison
| Operator | Description |
|---|---|
== |
Equality |
!= |
Not equal |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
Logical
| Operator | Description |
|---|---|
&& |
Logical AND (short-circuit) |
|| |
Logical OR (short-circuit) |
! |
Logical NOT |
Output
# puts() prints with trailing newline
puts("Hello, World!")
puts(42)
puts([1, 2, 3])
# print() prints without trailing newline
print("Loading...")Control Flow
If / Else
if is an expression — it returns a value.
x = 5
flag = true
count = 15
fn process(items) { items }
items = ["a", "b", "c"]
result = if x > 0 { "positive" } else { "non-positive" }
# Without else, returns null when condition is false
maybe = if flag { "yes" }
# Multi-line
if count > 10 {
puts("Many items")
process(items)
} else {
puts("Few items")
}While
i = 0
while i < 10 {
puts(i)
i += 1
}
# while is a statement (evaluates to null)
# Note: there are no break or continue keywordsFunctions
# Define with fn keyword
fn add(a, b) {
a + b # last expression is implicitly returned
}
# Default parameters (required params must come first)
fn greet(name, greeting: "Hello") {
greeting + " " + name
}
puts(greet("World")) # "Hello World"
puts(greet("World", "Hi")) # "Hi World"
# Named arguments use the same key: value syntax as containers
fn div(a, b) { a / b }
div(b: 3, a: 2) # 0.6666...Return
# return exits a function early with a value
fn find_first(items, target) {
i = 0
while i < items.len {
if items[i] == target { return i }
i += 1
}
return -1
}
# Bare return (no value) produces null and is discouraged.
# Prefer structuring code to avoid it:
fn maybe_process(flag) {
if flag { do_work() }
}Lambdas
Lambdas are anonymous functions declared with ^:
double = ^(x) { x * 2 }
double(5) # 10
# Multi-line
process = ^(x) {
x = x + 1
x * 2
}
# Inline with pipeline operators
[1, 2, 3] *> ^(x) { x * 2 } # [2, 4, 6]
# Lambdas are scoped to their declaring block
fn outer(a, b) {
add = ^(a, b) { a + b }
add(a + 1, b + 1)
}
# add is not accessible hereVariadic Arguments
fn add(a, b) { a + b }
# *args collects remaining arguments into a container
fn partial(func, arg1) {
^(*args) { func(arg1, *args) }
}
add_5 = partial(add, 5)
add_5(10) # 15Closures
Functions capture variables from their enclosing scope:
fn make_adder(n) {
a = 1
^(x) { x + n + a } # captures n and a
}
add_6 = make_adder(5)
add_6(10) # 16Containers
Containers are Weave’s universal data structure — part list, part map.
# List-style
l = [1, 2, 3]
# Map-style with key: value pairs
m = [a: 1, b: 3, c: 5]
# Mixed
data = [1, :a, [27, "nested"]]
# Access by index or key
m[0] # 1
m[:b] # 3
l[-1] # 3 (negative index from end)
# Assignment
m[:a] = 3
l << "new item" # appendProperties
| Property | Description |
|---|---|
.keys |
Container of symbol keys (insertion order) |
.len |
Number of elements (works on strings too) |
.sort |
Returns sorted copy. .sort(:desc) for descending |
.to_str |
Concatenates values as a string |
m = [a: 1, b: 2]
m.keys # [:a, :b]
m.len # 2
"hello".len # 5Slicing
list = [10, 20, 30, 40, 50]
list[1:3] # [20, 30]
str = "Hello"
str[0:3] # "Hel"Set Operations
| Operator | Name | Description |
|---|---|---|
+ |
Concatenation | Includes duplicates |
| |
Union | No duplicates |
& |
Intersection | Common elements |
!& |
Symmetric difference | Elements in one but not both |
- |
Difference | Elements in left but not right |
a = [1, 2, 3]
b = [2, 3, 4]
a + b # [1, 2, 3, 2, 3, 4]
a | b # [1, 2, 3, 4]
a & b # [2, 3]
a !& b # [1, 4]
a - b # [1]Iteration
Iterating over a container iterates over values only, not key-value pairs:
sum = ^(x, acc: 0) { acc + x }
[a: 1, b: 2, 3, 4] &> sum # 10 (never sees :a or :b)Modules
# Modules are plain .wv files
# Path maps to file: import foo.bar -> ./foo/bar.wv
# Import the module namespace
import foo.bar
value = foo.bar[:x]
# Import with alias
import foo.bar as fb
# Import specific symbols
from foo.bar import x
from foo.bar import add as sum
# Import multiple symbols
from foo.bar import x, y, z
# Wildcard import
from foo.bar import *Module resolution order: ./, .weave/, ~/.weave/. Imports are cached — a module runs once per process.
Error Handling: Conditions and Restarts
Weave uses a condition/restart system inspired by Common Lisp. This separates error detection from error policy — code that encounters a problem offers recovery strategies, and calling code decides which to use.
Reporting a Condition
filename = "missing.txt"
result = handle {
file_not_found: ^(source, c) { c.resume(:skip) }
} for {
report(:file_not_found, path: filename) {
abort: ^() { exit(1) },
skip: ^() { [] },
create: ^() { write(filename, ""); "" }
}
}The first argument is a symbol identifying the condition type. Optional key-value pairs provide metadata. The strategy block offers named lambdas for recovery.
Handling Conditions
filename = "config.toml"
fn load_config() { read(filename) }
result = handle {
file_not_found: ^(source, c) {
puts("Missing file in {source}, creating...")
c.resume(:use_value, [host: "localhost"])
}
} for {
load_config()
}Handlers receive source (where the condition occurred) and c (context container with .type, .stack, .resume, plus any metadata from the report).
Catch-All and Notifications
fn risky_operation() {
report(:known_error) { fix: ^() { "recovered" } }
}
handle {
known_error: ^(source, c) { c.resume(:fix) },
_: ^(source, c) {
puts("Unexpected: {c.type}")
c.resume(:abort)
}
} for {
risky_operation()
}
# Empty strategy block creates a notification (continues silently if unhandled)
x = 42
report(:debug_info, value: x) {}