Values are affine
A non-Copy value may move or be dropped, but cannot be used after a move or duplicated implicitly.
FORMA 0.2 Language Guide
This is a practical guide to the 0.2 prototype. It favors current compiler behavior over historical plans and labels profile or Experimental boundaries beside the feature they constrain.
Build the compiler from source, then check and run a small program.
git clone https://github.com/sfw/forma.git
cd forma
cargo build --release
./target/release/forma --version
# hello.forma
f greet(ref name: Str) -> Unit
print(f"Hello, {name}!")
f main()
name = "Forma"
greet(ref name)
print(name) # the shared loan ended; name is still owned here
forma check hello.forma
forma run hello.forma
forma check is the source of truth for the compiler revision you built.A non-Copy value may move or be dropped, but cannot be used after a move or duplicated implicitly.
ref creates a shared loan and ref mut an exclusive loan. Users do not write lifetime parameters.
Call-graph effects say what code may use. Runtime capabilities decide what this execution may do.
A passing sample is TESTED, a finite domain may be EXHAUSTIVE, and only a discharged solver obligation is PROVED.
Short keywords are canonical. Readable long aliases are accepted for many control and concurrency forms.
limit = 10 # immutable binding
count := 0 # mutable binding
count := count + 1 # update mutable binding
items := load_items()
items := transform(items) # transform consumes the old value and returns a new one
= and := express mutability, not ownership. Assigning a non-Copy value to another binding still moves it.
| Short | Long alias or meaning | Short | Long alias or meaning |
|---|---|---|---|
f | function | m | match |
s | struct | wh | while |
e | enum | lp | loop |
t | trait | br | break |
i | impl | ct | continue |
us | use/import | ret | return |
as | async | sp | spawn |
aw | await | md | module |
f clamp(value: Int, low: Int, high: Int) -> Int
if value < low then low
else if value > high then high
else value
f square(value: Int) -> Int = value * value
| Group | Forms | Notes |
|---|---|---|
| Arithmetic | + - * / % | Numeric operations |
| Comparison | == != < <= > >= | Produce Bool |
| Logical | && || ! | Boolean operations |
| Ranges | .., ..= | Exclusive and inclusive |
| Error/option | ?, !, ?? | Propagate, unwrap, or default |
| Values | true/T, false/F, none/N | Canonical and compact literals |
The generated EBNF and JSON grammar remain authoritative for precedence, every accepted alias, and lexical detail.
FORMA uses rank-1 inference, nominal generics, nominal traits, and static dispatch. Public function parameters and returns require annotations; local types can be inferred.
| Category | Examples | Profile note |
|---|---|---|
| Scalars | Int, Float, Bool, Char, sized integers | Core |
| Text | Str | Hosted; selected Native support |
| Tuple | (Int, Bool) | Core |
| Fixed array | [Int; 4], literal [0; 4] | Core; length is compile-time |
| Dynamic list | [Int], literal [1, 2, 3] | Hosted |
| Optional | Int? = Option[Int] | Core when payload is Core |
| Fallible | Int!Str = Result[Int, Str] | Depends on payloads |
| Reference | &T, &mut T | Second-class |
f fixed() -> [Int; 3] = [0; 3]
f dynamic() -> [Int]
values := vec_new()
values := vec_push(values, 1)
values
f consume(items: Vec[Item]) -> Unit
# items is owned here
f inspect(ref items: Vec[Item]) -> Int
items.len()
f update(ref mut items: Vec[Item]) -> Unit
# exclusive access for this loan
ref permits reading while preserving the owner.ref mut is exclusive and permits mutation.clone(value) explicitly duplicates a Clone value.mv value may force or document a move; ordinary owned transfer does not require it.References cannot be stored in ordinary aggregates, captured by escaping closures, or sent to another task. They may be returned only when derived from a reference parameter.
s Point { x: Int, y: Int }
# valid: returned reference is derived from p
f get_x(p: &Point) -> &Int = &p.x
# invalid: a reference to a local would escape
# f bad() -> &Int
# value = 1
# &value
Copy, Clone, Drop, Send, and Sync receive structural validation. A type with Drop is never Copy. Each initialized owned place is destroyed exactly once.
s Point { x: Int, y: Int }
e Direction = North | South | East | West
e Maybe[T] = Present(T) | Absent
f origin() -> Point
Point { x: 0, y: 0 }
i Point
f translated(&self, dx: Int, dy: Int) -> Point
Point { x: self.x + dx, y: self.y + dy }
Enums and pattern matching form algebraic data types. Core LLVM currently documents uniform scalar-payload enums; broader Hosted behavior may exceed Core backend support.
f describe(value: Bool?) -> Str
m value
Some(true) -> "yes"
Some(false) -> "no"
None -> "unknown"
f sum_to(limit: Int) -> Int
total := 0
i := 0
wh i <= limit
total := total + i
i := i + 1
total
Matches over finite algebraic domains must be exhaustive. Guards do not establish exhaustiveness; unreachable arms are diagnosed. Infinite or open domains require a wildcard unless the compiler knows a finite refined domain.
Expected failure uses Option or Result. Forma does not use exceptions for ordinary recoverable errors.
f load(path: Str) -> Str!Str
content = file_read(path)?
Ok(content)
f port(value: Str?) -> Int
text = value ?? "8080"
str_to_int(text) ?? 8080
f transform(values: [Int]) -> [Int]
map(values, |value: Int| value * 2)
? propagates a Result error.?? supplies an Option default.! unwraps and panics on failure; prefer explicit handling at public boundaries.f identity[T](value: T) -> T = value
t Named
f name(&self) -> Str
f label[T: Named](ref value: T) -> Str
value.name()
Dispatch is static. At most one implementation may apply to a concrete trait/type pair, and the implementation must belong to the package defining the trait or type. Overlapping blanket implementations and specialization are disallowed in 0.2.
Resolution order is deterministic: inherent methods, explicitly imported traits, then prelude traits. Remaining ambiguity is an error, never a source-order choice.
Effects are inferred through the call graph and describe authority a function may use. They do not grant authority. Runtime flags grant capabilities to one execution.
forma run reader.forma --allow-read
forma run writer.forma --allow-write
forma run client.forma --allow-network
forma run tool.forma --allow-exec
forma run configured.forma --allow-env
forma run ffi.forma --allow-unsafe
forma run trusted.forma --allow-all
as f compute(value: Int) -> Int
value * value
as f main()
task = sp compute(12)
result = aw task
print(result)
Send.Contracts are parsed language nodes, not comments. Function contracts describe transitions; struct invariants describe every valid externally observable value.
@inv(balance >= 0, "balance cannot be negative")
s Account
balance: Int
@pre(n >= 0, "n must be non-negative")
@post(result >= 1)
f factorial(n: Int) -> Int
if n <= 1 then 1 else n * factorial(n - 1)
@nonempty(items)
@sorted(result)
@permutation(items, result)
f verified_sort(items: [Int]) -> [Int]
sort_ints(items)
@inv fields are in scope by name. Forma checks invariants after construction, at function entry and return, and when a ref mut borrow returns. Exclusive mutation may be temporarily inconsistent, but must restore validity before return. old(...) and result remain function-contract concepts.
Runtime enforcement is not formal proof. Formal verification treats invariant-bearing parameters as entry assumptions, symbolically tracks projected mutation, and generates obligations at struct construction, direct pure-call, and return boundaries. For named structs and tuples whose leaves are supported Bool or signed 64-bit Int values, Z3 can report invariant establishment and preservation as PROVED or produce a reproducible COUNTEREXAMPLE. Untrusted JSON, TOML, network, and database data should be validated by a fallible decoder before constructing an invariant-bearing value.
0.2 profile boundary: the Hosted interpreter enforces function contracts and struct invariants. The LLVM path type-checks their declarations but does not yet inject native runtime checks.
| Command | What it establishes | Primary statuses |
|---|---|---|
--level test | Generated inputs passed within the reported sample, seed, and bounds. | TESTED or COUNTEREXAMPLE |
--level exhaustive | Every tuple in a supported finite domain was checked up to --max-domain. | EXHAUSTIVE, COUNTEREXAMPLE, or UNKNOWN |
--level formal | The SMT backend attempted proof for its pure supported subset. | PROVED, COUNTEREXAMPLE, or UNKNOWN |
# Reproducible generated contract tests
forma verify rules.forma --level test --examples 200 --seed 42 --report
# Complete supported finite domain
forma verify rules.forma --level exhaustive --max-domain 4096 --report
# Formal attempt; Experimental subset
forma verify rules.forma --level formal --report
# Automation output
forma verify src/ --level test --report --format json
TESTEDGenerated examples passed. Not a formal proof.
EXHAUSTIVEThe reported finite domain was completely enumerated.
PROVEDSupported SMT obligations were discharged.
COUNTEREXAMPLEAn execution or model violates a contract.
UNKNOWNUnsupported, too large, timed out, or otherwise not proved.
SKIPPED / UNTESTEDNo evidence was produced for this function.
Verification runs with capabilities restricted by default. --allow-side-effects opts generated examples into full capabilities; effectful or unsafe code without a formal model remains non-proof work.
Formal mode invokes z3 by default. FORMA_SMT_SOLVER can select another solver command that accepts the same stdin flags. A missing, failed, or timed-out solver produces UNKNOWN.
The current formal subset follows acyclic control flow path by path and supports checked signed 64-bit arithmetic, Bool, tuples, named structs, field and tuple projection, structural equality, projected field updates, direct pure-call inlining, and struct-invariant establishment and return preservation. Unsatisfiable preconditions or invariant assumptions are rejected as vacuous. Loops, recursive or indirect calls, effects, arrays and vectors, indexing, enums, and reference/dereference reasoning remain explicit UNKNOWN boundaries.
forma explain rules.forma --format human
forma explain rules.forma --format json --examples=3 --seed 42
One file defines one module in the initial model. Imports are importer-relative or package-rooted, explicit exports are deterministic, and pub controls visibility.
us math.tools
us collections.{Vec, Map}
us std.fs -> filesystem
pub md api
pub f answer() -> Int = 42
demo/
├── forma.toml
├── forma.lock
└── src/
└── main.forma
[package]
name = "demo"
version = "0.1.0"
[deps]
math = { path = "../math" }
0.2 resolves deterministic local path dependencies. Registry and Git sources are future package-manager work and are rejected rather than guessed. Hierarchical namespace-preserving scopes will grow from today’s deterministic explicit-export boundary.
forma check app.forma
forma check app.forma --partial
forma check app.forma --error-format json
forma run app.forma
forma build app.forma -o app -O 2
forma fmt app.forma
forma fmt app.forma --write
forma fmt app.forma --check
forma build requires a compiler built with cargo build --release --features llvm and a compatible LLVM 18 installation. The LLVM backend is Experimental beyond its documented Core subset.
forma typeof app.forma --position 12:8
forma complete app.forma --position 12:8
forma lsp
Diagnostics, hover/completion data, symbols, navigation, references, signatures, and formatting reuse the shared compiler session. Richer member ranking, rename/refactor flows, and deeper cross-file UX remain evolution work.
forma grammar --format ebnf > forma.ebnf
forma grammar --format json > forma-grammar.json
forma explain app.forma --format markdown
forma verify app.forma --report --format json
forma new my-app
forma init
forma repl
forma lex app.forma
forma parse app.forma
The generated builtin registry is the exact API index. It records each signature, parameter ownership mode, inferred effects, required capability, interpreter support, native support, and verification support.
# Machine-readable compiler surface
docs/builtins.json
# Forma modules built on that surface
std/core.forma std/io.forma std/string.forma
std/vec.forma std/map.forma std/iter.forma
std/json.forma std/datetime.forma
Dynamic collections, files and paths, JSON, HTTP/TCP/UDP/TLS/DNS, SQLite, processes, environment, time, compression, channels, and mutexes are profile- and capability-labeled.
Unsafe memory uses checked allocation handles in the interpreter and requires --allow-unsafe. Native raw pointers stay inside unsafe. Unsupported unsafe behavior makes formal verification UNKNOWN.
Do not infer a builtin from its name or from another language. Agents should query completion or read the generated registry, then confirm usage with forma check.
Portable affine semantics: scalars, tuples, fixed arrays, structs, uniform scalar-payload enums, calls, conditionals, finite matches, and loops.
Managed interpreter facilities: dynamic collections, strings, files, databases, networking, processes, tasks, channels, and mutexes.
Runtime-backed facilities currently implemented by the native toolchain, including selected strings, math, memory, and collections.
Weaker compatibility guarantees: whole-program LLVM parity, SMT verification, and user-defined observable destructor bodies.
Support is transitive through direct calls and reported per function. A Core-looking wrapper around a Hosted builtin remains unsupported by Core LLVM or formal verification.
The viewer below is restricted to showcase files that pass the current 0.2 ownership-aware forma check gate. The linked repository files remain the source of truth.
These four cards are a memory aid. The complete generated language and builtin surface follows below.
owned: value: T
shared: ref value: T
mutable: ref mut value: T
clone: clone(value)
move: mv value--level test
--level exhaustive
--level formal
--examples N
--max-domain N
--seed N
--max-steps N
--timeout MS--allow-read
--allow-write
--allow-network
--allow-exec
--allow-env
--allow-unsafe
--allow-allCore
Hosted
Native
ExperimentalThis index is generated from Forma 0.2’s grammar, builtin registry, parser, and standard library. It includes every canonical keyword and alias, literal form, grammar production and operator, type and language form (including @inv), named contract pattern, standard-library export, CLI/evidence term, and compiler-registered builtin.
docs/grammar.json + docs/builtins.json + parser + std/
Forma 0.2
Loading complete reference…
Searches exact generated metadata; no network request is required.