Syntax Reference

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 float

Strings

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)    # :boolean

Symbols

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)    # :symbol

Null

# 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 15

Operators

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 keywords

Functions

# 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 here

Variadic 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)    # 15

Closures

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)    # 16

Containers

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"    # append

Properties

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  # 5

Slicing

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) {}