| name | march-lang |
| description | March programming language reference — syntax, builtins, stdlib API, testing patterns, and common pitfalls. Use this skill whenever working on March source files (.march), writing or fixing March stdlib modules, writing March tests, modifying the March compiler (lexer/parser/AST/desugar/typecheck/eval), or debugging March compilation errors. |
March Language Reference
Purpose: Eliminate the 30-turn exploration phase. Read this before writing a single line of March code.
1. WRONG vs RIGHT — Most Common Bugs
These are the patterns most frequently generated incorrectly. Memorize these.
Conditionals: then does not exist, and else is mandatory
-- WRONG: March has no `then` keyword
-- ("I don't recognize `then` here — March uses do/end blocks instead.")
if x > 0 then x end
-- WRONG: missing `else` is a parse error
-- ("March `if` expressions always need an `else` branch")
if x > 0 do x end
-- RIGHT (inline)
if x > 0 do x else 0 end
-- RIGHT (multi-statement branches)
if x > 0 do
x
else
0
end
Lambdas: only arrow form, never do...end
-- WRONG: lambdas cannot use do...end blocks
let f = fn x do x + 1 end
List.map(xs, fn x do x * 2 end)
-- RIGHT
let f = fn x -> x + 1
List.map(xs, fn x -> x * 2)
Zero-arg lambdas: fn -> or fn () ->
-- RIGHT: short form
let f = fn -> 42
f() -- OK
-- RIGHT: explicit form (identical)
let f = fn () -> 42
f() -- OK
-- WRONG: `fn _ ->` is a 1-arg lambda (calling with 0 args = "arity mismatch: expected 1, got 0")
let f = fn _ -> 42
f() -- ERROR
Multi-line lambda bodies: use let chains after ->
-- WRONG: fn (x) do ... end is not valid syntax
let f = fn (x) do
let y = x + 1
y * 2
end
-- RIGHT: use let bindings after ->
let f = fn (x) ->
let y = x + 1
y * 2
Visibility: no pub keyword, no module keyword
-- WRONG
pub fn my_function(x : Int) : Int do x end
module Foo do ... end
-- RIGHT
fn my_function(x : Int) : Int do x end -- public
pfn helper(x : Int) : Int do x end -- private
mod Foo do ... end
Type variants: no leading |
-- WRONG
type Color = | Red | Green | Blue
-- RIGHT
type Color = Red | Green | Blue
-- RIGHT with payloads
type Shape = Circle(Float) | Rect(Float, Float) | Point
HTTP method matching: returns Atom, not a Method type
-- WRONG: no Method type; HttpServer.method() returns an Atom
match HttpServer.method(conn) do
Get -> ...
Post -> ...
end
-- RIGHT: use atom literals
match HttpServer.method(conn) do
:get -> ...
:post -> ...
_ -> ...
end
do is required after if, match, mod
-- WRONG: missing do
if x > 0
x
end
match xs
Nil -> 0
Cons(h, _) -> h
end
-- RIGHT
if x > 0 do x else 0 end
match xs do
Nil -> 0
Cons(h, _) -> h
end
Multi-head functions are supported
-- OK: multi-head syntax (parser merges into single EMatch)
fn fib(0) do 0 end
fn fib(1) do 1 end
fn fib(n) do fib(n-1) + fib(n-2) end
2. March Syntax Quick Reference
Modules
mod ModuleName do
-- declarations here
end
Functions
-- Public (visible outside module)
fn name(param1 : Type1, param2 : Type2) : ReturnType do
body
end
-- Private (module-internal)
pfn name(param : Type) : ReturnType do
body
end
-- Multi-head (pattern-matched)
fn fact(0) do 1 end
fn fact(n) do n * fact(n - 1) end
Lambdas (always arrow form)
fn x -> x + 1 -- single param
fn (a, b) -> a + b -- multiple params
fn -> 42 -- zero args (short form)
fn () -> 42 -- zero args (explicit form, identical)
fn _ -> 42 -- one-arg wildcard (ignores arg)
fn (x, _) -> x -- tuple destructure
Types
-- Sum type (no leading |)
type Shape = Circle(Float) | Rect(Float, Float) | Point
-- Generic type
type Option(a) = None | Some(a)
type Result(a, e) = Ok(a) | Err(e)
type List(a) = Nil | Cons(a, List(a))
-- Private type
ptype Tree = Leaf(Int) | Node(Tree, Tree)
Pattern Matching
match expr do
Pattern1 -> result1
Pattern2(x) -> result2
Pattern3(a, b) -> result3
_ -> default
end
-- Multi-expression arm (use let bindings)
match expr do
Some(v) ->
let y = v + 1
let z = y * 2
z
None -> 0
end
-- Nested patterns
match pair do
(Some(x), Some(y)) -> x + y
(Some(x), None) -> x
(None, _) -> 0
end
-- As-pattern: bind a name to the whole matched value while destructuring it
match opt do
Some(x) as whole ->
println(whole)
x
None -> 0
end
-- Record pattern: destructures fields; `{ x }` is shorthand for `{ x: x }`.
-- In a match arm, the field list is OPEN: `{ x: 0 }` alone matches any record
-- with an x field of 0, whatever else it has; naming a field the record
-- lacks is a compile error. A `let` binding and a bare record-pattern
-- function parameter still require every field (no independent expected
-- type to open against), e.g. `fn area({ w: w, h: h }) do w * h end`.
match point do
{ x: 0, y: 0 } -> "origin"
{ x: x, y: y } -> "at " ++ int_to_string(x) ++ "," ++ int_to_string(y)
end
-- Or-pattern: `p1 | p2 | p3` matches an arm against several alternatives.
-- Alternatives MAY bind, but every alternative must bind the same names at
-- the same types (they share one arm body, reached with those names as
-- parameters): `A(x) | B(x) -> x` is fine, `A(x) | B(y) -> x + y` is a
-- compile error. Split into separate arms when they need to differ.
match n do
1 | 2 | 3 -> "small"
_ -> "big"
end
If / Else
Each if needs its own end. An else if chain is a nested if in the else
position, so a two-branch chain closes with end end and a three-branch chain
with end end end — see stdlib/uri.march:62. There is no elif. A missing
end reports "I got stuck here" at the next declaration, not at the if.
if a do 1 else if b do 2 else 3 end end -- two ifs, two ends
if condition do expr end
if condition do
then_expr
else
else_expr
end
-- Single-line
if x > 0 do x else 0 end
Let Bindings (no in, newlines separate)
let x = 1 + 2
let y = x * 3
y + 1 -- last expr in block is the result
Pipe Operator
x |> f -- f(x)
x |> f(a) -- f(x, a) (x inserted as first arg)
-- Chain
value
|> transform1
|> transform2(arg)
|> transform3
String Concatenation (no interpolation syntax)
"Hello " ++ name ++ "!"
"Count: " ++ int_to_string(n)
Doc Strings
doc "Single-line documentation."
fn my_fn(x : Int) : Int do x end
doc """
Multi-line documentation.
"""
fn another_fn() : Unit do () end
Atoms
:get :post :put :delete :patch
:ok :error :true :false
Tuples
let pair = (1, "hello")
let (a, b) = pair -- destructure in let
fn f((x, y)) -> x + y -- destructure in param
Lists
Nil -- empty list
Cons(1, Cons(2, Nil)) -- linked list (prefer List.range/stdlib)
match xs do
Nil -> ...
Cons(h, t) -> ...
end
Visibility summary
| Syntax | Visibility |
|---|
fn name(...) | Public |
pfn name(...) | Private |
type Foo = ... | Public |
ptype Foo = ... | Private |
3. Forge Commands
# Project creation
forge new my_app # application
forge new my_lib --lib # library
forge new my_tool --tool # CLI tool
# Build & run
forge build # compile to .march/build/debug/<name>
forge build --release # optimized binary
forge run # interpret directly (fast dev)
forge build --dump-phases # emit IR to .march/phases/phases.json
# Testing
forge test # run all tests under test/
forge test --verbose
forge test --filter PATTERN
forge test --coverage
# Code quality
forge format # format all .march files
forge format --check # check only
forge format --stdin # editor integration
# REPL
forge interactive # launch REPL with project context
forge i # alias
# Dependencies
forge deps # install from forge.toml
forge deps update # update all
forge deps update NAME # update one
forge install PATH_OR_URL # install as system CLI tool
# Search (Hoogle-style)
forge search map # name search (fuzzy)
forge search "" --type "String -> Int" # type signature search
forge search "" --doc "concatenate" # doc keyword search
forge search fold --type "List(a) -> b -> (b -> a -> b) -> b" # combined: name + exact type signature
forge search "" --json > out.json # JSON output
forge search sort --rebuild # force index rebuild
forge search map --limit 5
forge search --callers List.map # reverse reference: who calls/uses it
# Benchmarks
forge bench # compile and run benchmarks under bench/
# Compiler inspection
forge phases # serve phase viewer at localhost:7777
forge phases --port 9000
# Cleaning
forge clean # remove .march/build/
forge clean --cas # also remove cached dependencies
forge clean --all # remove entire .march/
# Publishing
forge publish --dry-run
forge publish --old-source ./v1 # semver check
4. Forge Search (Hoogle-style)
Index cached at .march/search-index.json, built from stdlib.
Query modes (AND semantics when combined)
# Name search: exact > substring > fuzzy Levenshtein
forge search filter # finds "filter", "filter_map", etc.
forge search flt # fuzzy: finds "filter", "flat_map"
# Type signature: structural match, not substring — exact arity, each
# argument type matches positionally (order-sensitive), type variables are
# canonicalized (so `a -> a` and `x -> x` are the same query). Use `-> T` to
# match by return type alone, at any arity. A value starting with `-` (like
# `-> T`) MUST use `--type=VALUE`, not a space — `--type "-> Int"` is parsed
# as two separate options and fails with `unknown option '->'`.
forge search "" --type "String -> Int" # 1-arg fn: String -> Int
forge search "" --type "List(a) -> a -> Bool" # 2-arg fn: List(a), a -> Bool
forge search "" --type="-> Int" # any arity, returns Int
# CAUTION: search results print params comma-separated, e.g.
# `List.map(xs: List(a), f: a -> b) -> List(b)` — but a --type query chains
# EVERY argument and the return type with `->`, not `,`. Copy-pasting the
# printed signature's comma-separated param list as a query hard-errors
# (with a hint pointing at this) rather than silently matching nothing:
# the query for the entry above is `List(a) -> (a -> b) -> List(b)`.
# Doc keyword (all keywords must appear in doc string)
forge search "" --doc "sort stable"
# Combined
forge search fold --type "List(a) -> b -> (b -> a -> b) -> b" --doc "accumulator"
# Reverse reference: who calls/uses this? (distinct mode — ignores the other filters)
forge search --callers List.map # every resolved call site
forge search --callers Widget # constructor uses and qualified type references too
Search is forge-only — there is no -search flag on the march compiler.
--callers resolves through the typechecker, so it distinguishes same-named
declarations in different modules instead of matching text. Qualified type
references inside interface method signatures and impl headers are not tracked
(no enclosing function to attribute them to).
--type limitations to know about:
- Record fields are order-sensitive. A
{ ... } record type in the query
must list fields in the same order as the printed signature — copy the
field order straight from a forge search result rather than guessing.
type entries and _-typed params aren't reachable via --type.
type declarations (variants, aliases) have no return_type, so no
--type query — including -> T — will ever match one; use name search
(forge search Option) instead. Likewise a param whose type couldn't be
resolved (printed as _) never structurally matches a concrete-type query
position.
- A
-> T query matches independent of the entry's other variable
letters. --type="-> Option(a)" finds every entry returning
Option(_) regardless of what letter that variable has in the entry's
full signature (e.g. it finds Option.map, whose full signature is
Option(a), (a -> b) -> Option(b) — the return type alone is
re-canonicalized before comparing). Full-signature queries (no leading
->) do NOT get this treatment: there, a variable's identity across
params and the return type is meaningful, so List(a) -> a will not
match an entry whose return type is List(a) -> b.
5. Stdlib Manifest
116 stdlib modules are loaded by bin/main.ml. The most commonly used are listed
below (use forge search to discover the rest):
Auto-imported (Prelude)
Functions always in scope without use:
panic(msg: String) : a — runtime error
todo(msg: String) : a — unimplemented marker
unreachable() : a
unwrap(opt: Option(a)) : a — panics on None
unwrap_or(opt: Option(a), default: a) : a
head(xs) / tail(xs) / is_nil(xs) / length(xs) / reverse(xs)
fold_left(acc, xs, f) / filter(xs, pred) / map(xs, f)
identity(x) / compose(f, g) / flip(f) / const(x, _)
debug(x) / inspect(label, x) — tap for debugging
Data Structures
Option — is_some, is_none, expect, unwrap, unwrap_or, unwrap_or_else, map, flat_map, filter, or_else, zip, to_result, to_list
Result — is_ok, is_err, expect, unwrap, unwrap_err, unwrap_or, map, map_err, flat_map, or_else, collect, to_option
List — empty, singleton, repeat, range, range_step, head, tail, head_opt, tail_opt, last, nth, nth_opt, length, is_empty, reverse, append, map, flat_map, filter, filter_map, fold_left, fold_right, scan_left, concat, intersperse, find, find_index, any, all, sort_by, take, drop, take_while, drop_while, split_at, partition, drop_last, chunks, zip, zip_with, unzip, enumerate, sum_int, product_int, minimum_int, maximum_int, member, dedup
Map (AVL tree, needs comparator) — get, insert, remove, keys, values, fold, from_list, filter, merge, merge_with
HAMT (hash array mapped trie) — get, insert, remove, fold
Set — new, contains, insert, remove, union, intersection, difference, from_list, to_list, fold, eq
OrderedMap — new, get, put, remove, keys, values
SortedSet — new, insert, remove, contains, to_list
Queue — new, enqueue, dequeue, peek, is_empty, length, pop_front, pop_back
Array — empty, length, is_empty, get, set, push, pop, map, fold_left, to_list, from_list
NativeArray — new, length, get, set, fill
Tuple — first, second, swap, map_first, map_second
Range — new, to_list, each, map, filter
Strings / Text
String — byte_size, slice_bytes, contains, starts_with, ends_with, concat, replace, replace_all, split, split_first, join, trim, trim_start, trim_end, to_uppercase, to_lowercase, repeat, reverse, pad_left, pad_right, is_empty, grapheme_count, index_of, last_index_of, to_int, to_float, from_int, from_float
Char — code, from_code, is_ascii, is_alpha, is_digit, is_whitespace, is_uppercase, is_lowercase, to_uppercase, to_lowercase
IOList — empty, from_string, append, prepend, push, from_strings, to_string, byte_size, is_empty, hash
Regex — compile, matches, find, find_all, replace, replace_all, split
CSV — parse, parse_with_options, to_string, to_string_with_options
Bytes — length, is_empty, from_string, to_string, from_hex, to_hex, concat, slice, reverse, map
Math / Numbers
Math — abs, floor, ceil, round, sqrt, pow, sin, cos, tan, log, log10, exp, max, min, lerp, pi, e
BigInt — zero, one, neg_one, from_int, to_string, neg, abs, add, sub, mul, div, mod, eq, compare, show, hash
Decimal — from_string, from_int, from_float, to_string, to_int, to_float, add, sub, mul, div, round, abs, neg, eq, compare, show
Stats — mean, median, stdev, percentile, variance, variance_pop, mode, covariance, correlation, linear_regression
Random — new, int, float, bool, choice, shuffle, sample
DateTime — now, from_timestamp, to_timestamp, diff_seconds, compare, day_of_week, format, parse
Duration — new, to_milliseconds, to_seconds, add, subtract, multiply, compare, format
Encoding / Crypto
Base64 — encode, decode, url_encode, url_decode, mime_encode
Crypto — sha256, sha512, hmac, hash_password, verify_password, random_bytes, random_hex, secure_compare, base64_encode, base64_decode, base64_url_encode, base64_url_decode
UUID — v4, parse, to_string, nil, v5, version, is_valid
URI — parse, to_string, encode, decode, encode_query, decode_query, merge
JSON — parse, to_string, get, get_in (constructors: String, Number, Bool, Null, Array, Object)
IO / Files
File — read, read_lines, exists, stat, write, append, delete, copy, rename, with_lines, with_chunks
Dir — list, list_full, mkdir, mkdir_p, rmdir, rm_rf, exists
Path — join, normalize, dirname, basename, extension, absolute, relative, exists
Env — get, get_int, get_bool, get_all, set, require, require_int
Logger — debug, info, warn, error, log_with
Networking / HTTP
Http — method_to_string, status_code, status_ok…status_server_error, is_informational…is_server_error, request accessors (method/scheme/host/port/path/query/headers/body), setters (set_method…set_path)
HttpTransport — connect, request_on, stream_request_on, request, simple_get
HttpClient — new_client, add_request_step, with_redirects, with_retry, run, get, post, put_request, delete, stream_get; steps: step_default_headers, step_bearer_auth, step_basic_auth, step_base_url, step_content_type, step_raise_on_error
HttpServer — method, path, path_info, query_string, req_headers, req_body, get_req_header, get_resp_header, get_assign, put_resp_header, assign, send_resp, halt, text, json, html, redirect, run_pipeline, new, plug, max_connections, idle_timeout
TLS — default_client_config, h2_client_config, server_config, client_ctx, server_ctx, connect, accept, read, write, close, ctx_free, negotiated_alpn, peer_cn, https_get
WebSocket — new, send, receive, close, is_open
Actors / Concurrency
Process — spawn, exit, monitor, demonitor, self_pid, send
Actor — cast, call, reply
Task — async, await, await_ms, await_unwrap, await_many, await_many_ms, async_stream, async_stream_n
Channel — new, join, leave, push, broadcast, broadcast_from, serialize, parse
PubSub — new, subscribe_state, unsubscribe_state, broadcast, broadcast_from, topic_shard, topic_matches, broadcast_to
Presence — new, track_state, untrack_state, list_state, count_presences, is_present, connection_count, track, untrack
Flow — unfold, with_concurrency
Seq — fold, map, filter, take, drop, concat, zip_with, batch, fold_while
State / Storage
Vault (in-memory ETS-style store) — new, set, get, drop, set_ttl, update, whereis, keys
Config — from_env, from_env_int, from_env_bool, env, validate, put_endpoint, new_store, secret_key_base
Functional Utilities
Enum — map, flat_map, filter, fold, reduce, each, count, group_by, zip_with, sort_by, timsort_by, introsort_by, sort_small_by
Sort — by, mergesort_by, sort_small_by, insertion_sort_by, timsort_by, introsort_by
Iterable — fold
HTML / Markup
Html — escape, raw, safe_to_string, list, join, escape_attr, tag, render_partial, render_collection, layout, content_hash
Sigil — h (safe HTML), toml, xml, yaml
Data Formats
TOML — parse, parse_exn, to_string, get, get_in, get_str, get_int, get_float, get_bool, get_table, get_array
YAML — parse, parse_exn, to_string, get, get_in, get_str, get_int, get_bool, get_seq
XML — parse, parse_exn, to_string, to_string_pretty, root, tag, text, attr, children, elements, find, find_all
MsgPack — encode, decode, decode_all (constructors: null, bool, int, str, bin, array, map)
Compress — encode, encode_level, decode, encode_stream, decode_stream, accept_encoding, best_encoding
DNS — resolve, resolve_one, is_ip, error_message
Distributed / OTP
Gen (property-test value generators, QuickCheck-style) — int, int_sized, bool, float, element, map, bind, filter, one_of, frequency, tuple, option, list, list_of_size, string, string_of, sized
ClusterConn / ClusterAuth / NetKernel / NodeCall / NodeRpc — node connection, RPC, and auth primitives for multi-node clusters
GlobalRegistry / GlobalPid — cluster-wide process registration and lookup
Swim / SwimDriver / Membership — SWIM gossip membership protocol
CRDT / VectorClock / Merkle — conflict-free replicated data types, causal clocks, Merkle trees for anti-entropy
ConsistentHash / PeerRegistry / RingBuf — consistent hashing, peer tracking, ring buffers
Additional Collections
Deque — empty, singleton, is_empty, size, push_front, push_back, peek_front, peek_back, pop_front, pop_back, to_list, from_list, map, filter, fold_left, fold_right, concat, any, all
RRB — persistent sequence for bulk-parallel work. empty, singleton, from_list, from_array, tabulate, range, length, is_empty, get, push, concat, slice, fold, chunk, to_list, to_array, each, map, fold_left. chunk(v, n) → Array(Slice(a)); fold(s, zero, f) traverses a Slice.
Parallel — parallel map-reduce over RRB.Vec. pmap(v, f), pmap_n(v, f, workers), preduce(v, zero, f, merge), preduce_n(v, zero, f, merge, workers). Convenience: psum, psum_float, pcount, pany, pall. Uses task_spawn/task_await_unwrap internally; real multi-core in compiled mode, sequential in interpreter.
Testing
Test — fail, assert_true, assert_false, assert_eq_int, assert_eq_str, assert_eq_bool, assert_some, assert_none, assert_ok, assert_err
Data Science
DataFrame — from_columns, from_rows, inner_join, left_join, right_join, outer_join, summarize, sample, train_test_split, col_add_float, col_mul_float, col_add_col, window, melt, pivot
Plot — line_plot, bar_chart, scatter_plot, histogram
6. Idiomatic March Code Examples
Tail-recursive accumulator pattern (canonical idiom)
fn reverse(xs : List(a)) : List(a) do
fn go(lst : List(a), acc : List(a)) : List(a) do
match lst do
Nil -> acc
Cons(h, t) -> go(t, Cons(h, acc))
end
end
go(xs, Nil)
end
fn length(xs : List(a)) : Int do
fn go(lst : List(a), acc : Int) : Int do
match lst do
Nil -> acc
Cons(_, t) -> go(t, acc + 1)
end
end
go(xs, 0)
end
Recursive Fibonacci (from bench/fib.march)
mod Fib do
pfn fib(n : Int) : Int do
if n < 2 do n
else fib(n - 1) + fib(n - 2) end
end
pfn main() : Unit do
println(int_to_string(fib(40)))
end
end
Custom type with HOFs (from bench/list_ops.march)
ptype IntList = INil | ICons(Int, IntList)
pfn imap(xs : IntList, f : Int -> Int) : IntList do
pfn go(lst : IntList, acc : IntList) : IntList do
match lst do
INil -> irev(acc, INil)
ICons(h, t) -> go(t, ICons(f(h), acc))
end
end
go(xs, INil)
end
pfn main() : Unit do
let xs = irange(1, 1000000)
let ys = imap(xs, fn x -> x * 2)
let zs = ifilter(ys, fn x -> x % 3 == 0)
let total = ifold(zs, 0, fn (a, b) -> a + b)
println(int_to_string(total))
end
HTTP server with pipe + atom matching (from examples/http_hello.march)
mod HttpHello do
fn router(conn) do
match (HttpServer.method(conn), HttpServer.path_info(conn)) do
(:get, Nil) -> conn |> HttpServer.text(200, "Hello!")
(:post, Cons("users", Nil)) -> create_user(conn)
_ -> conn |> HttpServer.text(404, "Not Found")
end
end
fn main() do
HttpServer.new(8080)
|> HttpServer.plug(router)
|> HttpServer.listen()
end
end
Binary tree with pattern matching (from bench/binary_trees.march)
ptype Tree = Leaf | Node(Tree, Int, Tree)
pfn insert(t : Tree, v : Int) : Tree do
match t do
Leaf -> Node(Leaf, v, Leaf)
Node(l, x, r) ->
if v < x do Node(insert(l, v), x, r)
else if v > x do Node(l, x, insert(r, v))
else t end end -- one `end` per `if`, NOT one per chain
end
end
Parallel tasks (from bench/parallel.march)
pfn par_sum(t : Tree, depth : Int, threshold : Int) : Int do
match t do
Leaf(n) -> n
Node(l, r) ->
if depth >= threshold do sum(l) + sum(r)
else
let tl = task_spawn(fn () -> par_sum(l, depth + 1, threshold))
let tr = task_spawn(fn () -> par_sum(r, depth + 1, threshold))
task_await_unwrap(tl) + task_await_unwrap(tr)
end
end
end
Actor system (from examples/actors.march)
actor Counter do
state { value : Int }
init { value: 0 }
on Increment(n : Int) do
{ state with value: state.value + n }
end
on Reset() do
{ state with value: 0 }
end
end
fn main() : Unit do
let pid = spawn(Counter)
send(pid, Increment(10))
send(pid, Increment(5))
end
Option chaining
fn find_and_double(xs : List(Int), pred : Int -> Bool) : Option(Int) do
xs
|> List.find(pred)
|> Option.map(fn x -> x * 2)
end
Result collection
fn parse_all(inputs : List(String)) : Result(List(Int), String) do
inputs
|> List.map(fn s -> String.to_int(s)
|> Option.to_result("not a number: " ++ s))
|> Result.collect
end
7. Builtins Reference
Directly callable without module qualification (built into the evaluator).
Arithmetic operators
+ -. * *. / /. % ++ && || not
Integer
| Builtin | Description |
|---|
int_to_float(n) | Convert Int to Float |
int_to_string(n) | Convert Int to String |
int_abs(n) | Absolute value |
int_pow(base, exp) | Power |
int_div(a, b) / int_mod(a, b) | Division / modulo |
int_div_euclid(a, b) / int_mod_euclid(a, b) | Euclidean |
int_and(a, b) / int_or / int_xor / int_not | Bitwise |
int_shl(n, k) / int_shr | Bit shifts |
int_popcount(n) | Count set bits |
int_max_value / int_min_value | Bounds |
Float
| Builtin | Description |
|---|
float_to_int(f) | Truncate to Int |
float_to_string(f) | To String |
float_abs(f) | Absolute value |
float_floor(f) / float_ceil / float_round / float_truncate | Rounding |
float_is_nan(f) / float_is_infinite(f) | Checks |
float_infinity / float_neg_infinity / float_nan / float_epsilon | Constants |
float_from_string(s) / string_to_float(s) | Parse |
Math
| Builtin | Description |
|---|
math_sqrt(x) / math_cbrt(x) | Roots |
math_pow(x, y) | Power |
math_exp(x) / math_exp2 | Exponential |
math_log(x) / math_log2 / math_log10 | Logarithm |
math_sin(x) / math_cos / math_tan | Trig |
math_asin / math_acos / math_atan / math_atan2(y,x) | Inverse trig |
math_sinh / math_cosh / math_tanh | Hyperbolic |
String
| Builtin | Description |
|---|
string_length(s) | Character count |
string_byte_length(s) | Byte count |
string_concat(a, b) | Concatenate |
string_is_empty(s) | Empty check |
string_slice(s, start, len) | Byte-indexed slice |
string_contains(s, sub) | Substring check |
string_starts_with(s, pre) / string_ends_with(s, suf) | Prefix/suffix |
string_index_of(s, sub) / string_last_index_of | Find |
string_replace(s, old, new) / string_replace_all | Replace |
string_split(s, sep) / string_split_first | Split |
string_join(xs, sep) | Join list |
string_trim(s) / string_trim_start / string_trim_end | Trim |
string_to_uppercase(s) / string_to_lowercase | Case |
string_chars(s) / string_from_chars(cs) | Char list conversion |
string_repeat(s, n) / string_reverse(s) | Transform |
string_pad_left(s, w, fill) / string_pad_right | Padding |
string_grapheme_count(s) | Unicode graphemes |
string_to_int(s) | Parse → Option(Int) |
bool_to_string(b) | Bool to string |
Char
| Builtin | Description |
|---|
char_is_alpha(c) / char_is_digit / char_is_alphanumeric | Category |
char_is_whitespace(c) / char_is_uppercase / char_is_lowercase | Category |
char_to_uppercase(c) / char_to_lowercase | Case |
char_to_int(c) / char_from_int(n) | Byte value (char_from_int is n & 0xFF, wraps; not a code point) |
byte_to_char(b) | Byte to char (same payload; errors outside 0–255 instead of wrapping) |
List (primitive)
| Builtin | Description |
|---|
head(xs) | First (panics if empty) |
tail(xs) | Rest (panics if empty) |
is_nil(xs) | Empty check |
IO
| Builtin | Description |
|---|
print(x) / println(x) | Print |
print_int(n) / print_float(f) | Typed print |
read_line() | Read from stdin |
tap(x) | Print and return |
Map (hash map)
| Builtin | Description |
|---|
record_get(m, key) / record_put(m, key, val) | Get/set |
record_has_key(m, key) | Key check |
record_keys(m) / record_values(m) / record_entries(m) | Iteration |
record_from_list(pairs) | Create from list |
Actors / Process
| Builtin | Description |
|---|
self | Own PID |
spawn(actor) | Spawn actor |
send(pid, msg) | Send message |
receive | Receive message |
kill(pid) | Kill actor |
is_alive(pid) | Liveness check |
monitor(pid) / demonitor(ref) | Monitor |
mailbox_size(pid) | Mailbox size |
actor_cast(pid, msg) | Async send |
actor_call(pid, msg, timeout) | Sync send |
actor_reply(ref, result) | Reply to call |
Tasks
| Builtin | Description |
|---|
task_spawn(f) | Spawn task |
task_await(t) | Await result |
task_await_unwrap(t) | Await and unwrap |
task_yield() | Yield scheduler |
task_spawn_steal(f) | Work-stealing spawn |
task_spawn_link(f) | Linked task spawn |
Network / TLS
| Builtin | Description |
|---|
tcp_connect(host, port) | Connect |
tcp_send_all(fd, data) / tcp_recv_all(fd) | Send/receive |
tcp_recv_exact(fd, n) | Receive n bytes |
tcp_close(fd) | Close |
tls_connect(fd, ctx, host) | TLS connect |
tls_read(conn, n) / tls_write(conn, data) | TLS IO |
tls_close(conn) | TLS close |
Crypto
| Builtin | Description |
|---|
sha256(data) / sha512(data) / sha1(data) | Hash |
hmac_sha256(key, data) | HMAC-SHA256 |
pbkdf2_sha256(pass, salt, iter, len) | PBKDF2 |
base64_encode(data) / base64_decode(s) | Base64 |
random_bytes(n) | Secure random bytes |
uuid_v4() | Generate UUID v4 |
md5(data) | MD5 hash |
System
| Builtin | Description |
|---|
unix_time() | Current Unix timestamp |
sys_uptime_ms() | Uptime in ms |
sys_heap_bytes() | Heap memory usage |
sys_cpu_count() | CPU count |
sys_actor_count() | Live actor count |
process_env(name) / process_set_env(name, val) | Env vars |
process_cwd() | Working directory |
process_exit(code) | Exit process |
process_argv() | Command-line args |
process_spawn_sync(cmd, args) | Spawn subprocess |
Utility
| Builtin | Description |
|---|
panic(msg) | Runtime error |
todo(msg) | Unimplemented marker |
unreachable() | Assert unreachable |
eq(a, b) | Structural equality |
compare(a, b) | Comparison (-1/0/1) |
hash(x) | Hash value |
show(x) | Pretty print |
to_string(x) | Coerce to string |
negate(n) | Negate number |
8. Compiler Pipeline
Interpreter path (used by dune exec march, forge run, tests)
Source (.march)
→ Lexer (lib/lexer/lexer.mll) -- ocamllex tokenizer
→ Token Filter -- lookahead for do/end disambiguation
→ Parser (lib/parser/parser.mly) -- menhir LR parser → AST
→ Desugar (lib/desugar/desugar.ml) -- pipe desugar, multi-head fn → EMatch
→ Typecheck (lib/typecheck/typecheck.ml) -- bidirectional HM type inference
→ Eval (lib/eval/eval.ml) -- tree-walking interpreter
Key desugar transformations:
x |> f → f(x), x |> f(a) → f(x, a)
- Multi-head
fn foo(0)…; fn foo(n)… → single fn foo with EMatch
- Block
let without in → nested let expressions
Compiled path (used by forge build)
Source → Lexer → Parser → Desugar → Typecheck
→ TIR Lower (lib/tir/lower.ml) -- typed intermediate representation
→ Mono (lib/tir/mono.ml) -- monomorphization
→ Defun (lib/tir/defun.ml) -- defunctionalization / closure conversion
→ Perceus (lib/tir/perceus.ml) -- reference counting (FBIP)
→ Borrow (lib/tir/borrow.ml) -- borrow analysis
→ Fusion (lib/tir/fusion.ml) -- loop fusion / deforestation
→ LLVM Emit (lib/tir/llvm_emit.ml) -- LLVM IR → native binary
Key files:
bin/main.ml — entry point, stdlib loading, CLI flags (--compile, etc.)
lib/ast/ast.ml — AST types: span, expr, pattern, decl
lib/errors/errors.ml — Error/Warning/Hint diagnostic type with span
lib/search/search.ml — Hoogle-style search engine
lib/jit/ — REPL JIT compiler
9. Testing Patterns
Test file structure (Alcotest)
The OCaml test suites live under test/, split by area: test_compiler.ml, test_eval.ml, test_codegen.ml, test_stdlib_suite.ml, run via the run_compiler.ml / run_eval.ml / run_codegen.ml / run_stdlib.ml drivers. (An older monolithic test/test_march.ml no longer exists.)
(* Helper functions *)
let parse_module src =
let lexbuf = Lexing.from_string src in
March_parser.Parser.module_
(March_parser.Token_filter.make March_lexer.Lexer.token) lexbuf
let parse_and_desugar src =
March_desugar.Desugar.desugar_module (parse_module src)
let typecheck src =
let m = parse_and_desugar src in
let (errors, _) = March_typecheck.Typecheck.check_module m in
errors
(* Check for type errors *)
let test_valid () =
let ctx = typecheck {|mod T do fn f(x : Int) : Int do x end end|} in
Alcotest.(check bool) "no errors" false (March_errors.Errors.has_errors ctx)
(* Eval and check result *)
let eval_module src =
let m = parse_and_desugar src in
ignore (March_typecheck.Typecheck.check_module m);
March_eval.Eval.run_module m
let vint = function March_eval.Eval.VInt n -> n | _ -> failwith "not VInt"
let test_eval_fib () =
let env = eval_module {|mod T do
fn fib(0) do 0 end
fn fib(1) do 1 end
fn fib(n) do fib(n-1) + fib(n-2) end
fn main() do fib(10) end
end|} in
let v = March_eval.Eval.call_fn env "main" [] in
Alcotest.(check int) "fib(10)" 55 (vint v)
How to add a test
- Write
let test_my_feature () = ... in the relevant test/test_*.ml (e.g. test_compiler.ml)
- Add to the test list at the bottom:
("category", [
Alcotest.test_case "my feature" `Quick test_my_feature;
])
- Run
dune runtest
Speed markers: `Quick (fast), `Slow (longer).
Build commands
dune build # build everything
dune runtest # run all tests
# opam switch: march — dune/opam already in PATH, no eval $(opam env) needed
10. Project Layout
bin/main.ml compiler entry point
lib/ast/ast.ml AST types
lib/lexer/lexer.mll ocamllex lexer
lib/parser/parser.mly menhir parser
lib/desugar/desugar.ml pipe desugar
lib/typecheck/typecheck.ml HM type inference
lib/eval/eval.ml tree-walking interpreter
lib/tir/ typed IR (lower/mono/defun/perceus/borrow/fusion/llvm_emit)
lib/jit/ REPL JIT
lib/errors/errors.ml diagnostics
lib/search/search.ml Hoogle-style search
stdlib/ 116 March stdlib modules
runtime/ C runtime (GC, scheduler, HTTP, TLS, WASM)
forge/ build tool
lsp/ LSP server
test/ alcotest suites (run_*.ml drivers over test_*.ml)
specs/ design specs, progress tracking
bench/ benchmark .march programs (28 files)
examples/ example .march programs (30 files)