Paste this file into your agent's context (system prompt, rules file, or retrieval store). It contains everything a model needs to write correct machino zero-shot, plus the tool loop to verify it.
machino is a small, statically typed language that compiles to WebAssembly.
Files end in .mno. There is exactly one way to write each construct. The
compiler gives structured JSON errors designed for you to repair against.
1. write program.mno
2. machino check program.mno --json # fix until "ok": true
3. machino test program.mno --json # fix until failed == 0
4. machino run program.mno [args...] # execute fn main() (native runtime)
5. machino build program.mno -o out.wasm # ship: runs in any WASM host
Every diagnostic has code, line, col, message, and often help with
the exact fix. Trust help.
# comment (to end of line)
import "lib/util.mno" # imports at the top; paths relative to this file
import "pkg:mathx/mathx.mno" # package import (machino_modules/, see below)
import "geometry.mno" as geo # namespaced: use geo::fn_name, geo::TypeName
struct Point { # structs: nominal record types
x: float
y: float
}
enum Option { # enums: sum types (tagged unions)
None
Some(int)
}
enum Result {
Ok(str)
Err(str)
}
# functions; statements end at NEWLINES, there are no semicolons
fn dist2(a: Point, b: Point) -> float {
let dx = b.x - a.x
let dy = b.y - a.y
return dx * dx + dy * dy
}
# contracts: checked at runtime, failures name the clause
fn safe_div(a: int, b: int) -> int
requires b != 0
ensures result * b <= a
{
return a / b
}
# generics: constraints Eq, Ord, Num, Hash; combine with + or where
# generic structs/enums construct directly: struct<T> Box { val: T }; Box(1)
fn<T: Ord> max2(a: T, b: T) -> T {
if a > b {
return a
}
return b
}
# first-class functions and capturing lambdas
fn map_ints(xs: [int], f: fn(int) -> int) -> [int] {
let out: [int] = []
for i in 0..len(xs) {
out = push(out, f(xs[i]))
}
return out
}
fn make_adder(n: int) -> fn(int) -> int {
return fn(x: int) -> int { return x + n } # captures n by value
}
# pattern matching on enums (exhaustive check at compile-time)
fn unwrap_or(opt: Option, default: int) -> int {
return match opt {
Option::Some(v) => v
Option::None => default
}
}
# host imports (FFI/capabilities); the host must provide them
extern fn clock_ms() -> int
# entry point: exactly this signature
fn main() {
let p = Point(1.0, 2.0) # positional constructor
p.x = 3.0 # field assignment (structs are references)
let xs = [1, 2, 3]
let ys = push(xs, 4) # push returns a NEW array
xs[0] = 10 # element assignment, bounds-checked
for i in 0..len(ys) { # for over int ranges (end exclusive)
if i == 2 { continue }
if i == 3 { break }
print(ys[i])
}
let msg = ("long strings wrap"
+ " only inside parentheses") # newlines end statements otherwise
print(msg)
let doubled = map_ints(xs, fn(x: int) -> int { return x * 2 })
print(doubled[0])
}
test "math" {
assert safe_div(10, 2) == 5
}
# snapshot test: passes only if print output equals the string exactly
test "output" expects "hello\n5" {
print("hello")
print(5)
}
- Types:
int(i64, checked arithmetic — overflow traps),float(f64),bool,str(byte string),[T], struct names, enum names,fn(T...) -> R. - No implicit conversions.
1 + 2.0is an error. Useto_float(i)/to_int(f). Float literals need digits on both sides:2.0, not2.. +on strings concatenates.%is int-only. Comparisons don't chain.push(xs, v)does NOT mutate; always rebind:xs = push(xs, v).- Enums: construct with
Enum::Variant(unit) orEnum::Variant(a, b, ...)(payloads). Deconstruct withmatch. - Match expressions must be exhaustive. The type checker ensures all cases
are covered. Patterns:
_(wildcard),var(binding), literals,Enum::Variant,Enum::Variant(pattern). - Structs/arrays are references;
==works only on scalars andstr. - No methods:
p.dist()is an error; writedist(p). - Lambdas:
fn(x: int) -> int { return x + n }is an expression; it captures enclosing variables by value and captured variables are read-only (E049). To share mutable state, capture a struct or array and mutate its contents. Lambdas can't recurse (no name) and have no contracts. Named functions also work asfn-typed values. - Calls apply to names only —
f(1)(2)doesn't parse. Bind the intermediate:let g = f(1)theng(2). - Each statement on its own line; wrap long expressions in
(...). - Every path in a value-returning function must
return. - In
ensures,resultis the return value; parameters are the original arguments. - Empty array literals need an annotation:
let xs: [int] = [].
print(x), len(x), push(xs, v), to_float(i), to_int(f),
char_at(s, i) / substr(s, a, b) / chr(byte) (byte-indexed),
len_cp / char_at_cp / substr_cp / chr_cp (Unicode codepoints),
hash(x) (int/bool/str → int; requires Hash),
spawn(f, args...) → task handle, join_int/join_float/join_bool/join_str,
chan_new() / chan_send_int|float|bool|str / chan_recv_* / chan_close
(typed channels; interpreter + linear WASM — compiled spawn targets must be
named top-level functions)
- numbers:
str_of_int,str_of_bool,str_of_float(f, decimals),parse_int(s)(requiresis_int_str(s)),is_int_str,is_digit,abs_int,min_int,max_int,sum_ints,index_of_int,sort_ints - float math:
sqrt(x)(requiresx >= 0.0),pow_int(base, exp)(requiresexp >= 0),pow_float(base, exp)(int exp, may be negative),floor(f)/ceil(f)/round(f)(-> int),abs_float,min_float,max_float - strings:
find,find_from,contains,starts_with,ends_with,split(s, sep),join(parts, sep),trim,is_space,to_upper,to_lower,repeat - maps:
StrMap/IntMap(concrete hash maps); genericstruct<K: Eq + Hash, V> HashMap— construct with empty typed arrays:let ks: [str] = []/let vs: [int] = []/let m = HashMap(ks, vs, empty_buckets(8), 8), thenhashmap_set/hashmap_get/hashmap_get_or/hashmap_has/hashmap_len - JSON: enum
Json(JNull | JBool(bool) | JNum(float) | JStr(str) | JArr([Json]) | JObj(JsonObj));json_parse(s) -> JsonParsed(JVal(Json) | JError(str)),json_serialize(v) -> str,json_obj_new(),json_obj_set(o, k, v),json_obj_get(o, k)(returnsJson::JNullwhen absent) - time (UTC, unix epoch ms):
time_iso(ms) -> str("1970-01-01T00:00:00Z"),time_from_ms(ms) -> Time(fieldsyear month day hour minute second millis),time_format(t),pad2
Provided by machino run (the native runtime):
extern fn clock_ms() -> int
extern fn sleep_ms(ms: int)
extern fn read_file(path: str) -> str
extern fn write_file(path: str, data: str) -> bool
extern fn file_exists(path: str) -> bool
extern fn read_line() -> str
extern fn getenv(name: str) -> str
extern fn http_get(url: str) -> str
extern fn args() -> [str]
extern fn exit(code: int)
extern fn tcp_listen(port: int) -> int
extern fn tcp_accept(listener: int) -> int
extern fn tcp_read(conn: int) -> str
extern fn tcp_write(conn: int, data: str) -> int
extern fn tcp_close(handle: int)
# DOM (virtual in machino run; real document in runners/run.html)
extern fn dom_document() -> int
extern fn dom_get_element_by_id(id: str) -> int
extern fn dom_query_selector(sel: str) -> int
extern fn dom_create_element(tag: str) -> int
extern fn dom_set_text(el: int, text: str)
extern fn dom_get_text(el: int) -> str
extern fn dom_set_html(el: int, html: str)
extern fn dom_get_html(el: int) -> str
extern fn dom_set_attr(el: int, name: str, value: str)
extern fn dom_get_attr(el: int, name: str) -> str
extern fn dom_append_child(parent: int, child: int)
extern fn dom_remove_child(parent: int, child: int)
extern fn dom_add_class(el: int, cls: str)
extern fn dom_set_style(el: int, prop: str, value: str)
extern fn dom_get_style(el: int, prop: str) -> str
extern fn dom_add_listener(el: int, event: str, handler: str)
extern fn dom_dispatch(el: int, event: str)
extern fn dom_dispatch_event(el: int, event: str, x: int, y: int, key: str, button: int, value: str)
extern fn dom_last_event_type() -> str
extern fn dom_last_event_target() -> int
extern fn dom_last_event_x() -> int
extern fn dom_last_event_y() -> int
extern fn dom_last_event_key() -> str
extern fn dom_last_event_button() -> int
extern fn dom_last_event_value() -> str
extern fn db_open(driver: str, conn: str) -> int
extern fn db_close(h: int)
extern fn db_exec(h: int, sql: str) -> str
extern fn db_query(h: int, sql: str) -> str
Servers: tcp_listen → loop tcp_accept → tcp_read → tcp_write →
tcp_close. See examples/http_server.mno. Prefer packages:
packages/dom (events: bind exported zero-arg handler name),
packages/vdom, packages/db (memory|sqlite|mysql|postgres|mongo),
packages/regex. Node WASM host (runners/run.mjs) provides DOM, DB, and
tcp_* (blocking via Atomics.wait). Browser: no raw TCP — use
packages/ws (WebSocket externs; host must implement them).
DOM events: dom_add_listener(btn, "click", "on_inc") then define
fn on_inc() { ... } (exported). Native/tests: dom_dispatch(btn, "click") or dom_dispatch_event(...) with
explicit fields. Browser: real listeners via runners/dom_host.mjs after
bindExports (coords/key/button/value from MouseEvent/KeyboardEvent/InputEvent).
machino pkg init myapp # creates machino.pkg in this directory
machino pkg add mathx ../mathx # local path dependency (copied)
machino pkg add strkit https://github.com/user/strkit 0.2.0 # git, at a tag
machino pkg sync # (re)install everything + machino.lock
Then import with the pkg: prefix: import "pkg:mathx/mathx.mno".
Dependencies land in machino_modules/ (commit machino.pkg and
machino.lock; ignore machino_modules). Transitive deps are flattened —
one source per package name.
Official starter libraries ship in the repo under packages/ (vec, mathx,
algo, httpkit, encoding, csv, cli, …). From an app next to a machino clone:
machino pkg add mathx /path/to/machino/packages/mathx
import "pkg:mathx/mathx.mno"
See packages/README.md and packages/demo for the full catalog.
Any import can be namespaced: import "lib.mno" as lib renames every
top-level item lib.mno defines to lib::name — call lib::helper(...),
annotate let p: lib::Point, match lib::Verdict::Yes. Without as,
imported names stay global (collisions are errors).
machino fmt file.mno [--check|--stdout]— canonical formatter (safe: refuses any edit that changes the token stream)machino query file.mno [name]— JSON signatures/contracts/generics of every top-level item; use it to look up an unfamiliar file's APImachino run file.mno --trace— one JSON object per user-function call/return on stderr:{"event":"call","fn":"fib","depth":1,"args":["5"]}machino fuzz file.mno [--runs N]— random-input testing driven by contracts; reports the failing input when a contract or trap firesmachino check file.mno --verify— static contract proofs with Z3 for int/bool functions; calls are inlined and constantforloops unrolled (build with--features smt)machino build file.mno --gc— WASM-GC backend (externs, channels, spawn with int/bool args); run withnode runners/run-gc.mjs(Node 22+)machino build file.mno --native— Clang/LLVM host executable (needsclangon PATH). Linksruntime/native/machino_rt.c; supports lambdas, spawn/channels, mark-sweep cycle GC,-O3 -flto -march=native, plus--target/--universal. Seedocs/native-runtime.md- Type annotations may name generic apps:
let m: HashMap<str, int> = …
| Code | Meaning | Usual fix |
|---|---|---|
| E030 | type mismatch | insert to_float/to_int, or fix the declared type |
| E035 | unknown variable | declare with let, or check spelling |
| E043 | operator/operand mismatch | no mixed int/float math |
| E025 | not all paths return | add a final return |
| E040 | empty array needs annotation | let xs: [int] = [] |
| E011 | expected end of statement | split lines, or wrap in (...) |
| E021 | name already defined | prelude names are reserved; rename |
| E048 | no such struct field | check the field list in the error's help |
| E049 | assigning to a captured variable | capture a struct/array and mutate its contents |
machino build emits a self-contained standard .wasm module (exports
main, memory, alloc; imports only env.print_*, env.fail, and your
extern fns). Run it with node runners/run.mjs out.wasm [args...], drop it
into runners/run.html in a browser, or embed it in any WebAssembly runtime.
Contracts, asserts, bounds checks, and overflow checks stay enforced in the
compiled binary, with the same messages as the interpreter.
The compiled module includes a mark-sweep garbage collector: loops that
allocate freely (string building, push in a loop, closures) run in bounded
memory. Memory is capped at 4 GiB; a program whose live data exceeds that
traps with out of memory.