Docs / playground: merelang.github.io/mere — tutorial, language reference, and a Wasm playground with an interactive counter demo wired through the Phase 48 frontend FFI.
Make Explicit Region-bound Effects.
OCaml implementation of Mere, a new programming language (Old English for "lake"; 4 letters; region metaphor). An ML-family mini language that has reached a practical level — its memory model (region / view / Trivial[R]), effect system (capability passing + refined borrow annotations), and three codegen backends (C / LLVM IR / Wasm) all work at feature parity.
Former tentative name: lang-ml (finalized as Mere on 2026-06-19).
- 1573 tests passing
- 4-backend feature parity: interp + C / LLVM IR / Wasm runtime — all match interp diff = 0 PERFECT across 16 realistic examples (~1500 LoC) (Phase 24-27); subsequent phases grew the example set to 136.
- Memory model: region / view / Trivial[R] /
withDrop work at the type level, in the interpreter, and in all three codegen backends. - Effect system: capability-passing pattern +
signature ... = (...)argument bundling +using [cap]sugar + builtin Logger / Metrics. - Refined borrow annotations: 4 modes
&R T/&mut R T/&shared write R T/&exclusive R T+ borrow checker (place expressions + if/match branch propagation + full conflict matrix coverage). - Q-010 standard collections:
Vec[R, T]/OwnedVec[T]/StrBuf[R]/Map[R, K, V]work in interpreter + all 3 backends (Phase 15.x). - Polymorphism: HM inference + let-poly + per-instantiation specialization of polymorphic user let-recs (Phase 23.3 / 25.5 / 26.4) + Phase 36's narrow value restriction (don't generalize when let-binding a type that contains a mutable container).
- Inner-fn lifting:
let rec inner = fn ... -> ...is lifted to top level with free variables prepended (Phase 25.3 / 26.3). - Top-level value bindings: non-fn lets like
let total = mk_metrics();are accessible from fn bodies (Phase 30.2 globalizes them across all 3 backends). - Wasm runtime: validated at runtime via
scripts/run_wasm.js(Node.js host harness — puts / read_file / write_file) (Phase 27.2). - FFI:
extern fn <name>: <ty>;calls libc functions directly from all 4 backends (Phase 32; supports curried multi-arg; MVP types are int/bool/str/unit only). - Language surface:
module M { ... }(nestable) +M.fqualified access;import "./path";(importer-relative + canonical) for file splitting;open M;. - Phase 36 syntactic sugar (13 kinds): range
a..b, operator section(+ 1), cons1 :: xs, reverse pipef <| x, applyf @@ x, lambda shorthand\x -> ..., string interpolation"x = {show n}",?(Option) /?!(Result) early-return, list comprehension[f x | x <- xs, p x],if let pat = e then ... else ...,for x in xs do body,while cond do body. - Phase 36 prelude expansion (16 entries):
range/list_filter/list_take/list_drop/list_find/list_append/list_concat/list_flat_map/list_zip/list_for_all/list_any/list_member/list_sum/list_product/list_max/list_min(34 entries total). - REPL: multi-line input,
:env/:show/:load/:reset, Rust-style code frame error display. - Design context is kept in separate internal design notes.
| Category | Details |
|---|---|
| Type system | Hindley-Milner inference, let-polymorphism, polymorphic builtins |
| Primitives | int, float (IEEE 754), bool, str, unit |
| Data | tuple, record, sum types, list literal sugar [1, 2, 3] (also pretty-printed by show) |
| Control | if-then-else, if-then (unit), match + when guards + as-patterns + or-patterns |
| Patterns | wildcard / var / lit / char 'X' / tuple / constructor / list [h, ...t] / record / as / or |
| Functions | multi-arg typed fn / mutually recursive let rec ... and ... / higher-order / closures |
| Operators | + - * / % == != < <= > >= && || ++ |> << >> (int arithmetic); float uses f_add etc. |
| Name management | let _ = ...;, let (a, b) = ...;, signature, type X = T (alias) |
| Memory model | region (region R { ... }) / view (view V[R] of T { ... }) / &R T reference / escape check / Trivial[R] constraint / R.alloc(v) sugar |
| Borrow annotations | &R T / &mut R T / &shared write R T / &exclusive R T + borrow checker (place + if/match propagation) |
| Effects | capability passing (cap = record value), signature argument bundling + spread, using [cap] sugar, builtin Logger / Metrics |
| with Drop | with c = ... in body auto-invokes the close field at scope end; multiple bindings close LIFO |
| Error handling | Result type + result_map / result_and_then / result_or_else (prelude); fail "msg" / try_or default fn for catchable failures |
| Modules | module M { ... } (nestable), M.f references, internal short-name rewrite, open M;, type/record decls allowed inside modules |
| import | import "./path"; pulls in another file (importer-relative + canonical) |
| Collections | Vec[R, T] / OwnedVec[T] / StrBuf[R] / Map[R, K, V] + higher-order API (iter/map/fold/filter/to_list/to_owned) — insertion-order Map iter (Phase 27.1) |
| stdlib | 90+ builtins: I/O / conversion / strings (str_split / str_join / str_compare / str_index_of etc.) / numerics / polymorphic helpers / float / errors / Logger / Metrics |
| codegen | C / LLVM IR / Wasm (WAT) backends at parity + Wasm runtime validation (details in codegen.md). Q-010 collections (4 kinds) + higher-order API + conversions + len ad-hoc poly + per-instantiation specialization of polymorphic user let-rec (Phase 23.3 / 25.5 / 26.4) + inner-fn lifting (Phase 25.3 / 26.3) + top-level value bindings globalized to file scope (Phase 30.2). |
| FFI | extern fn <name>: <ty>; calls libc functions from all 4 backends (interp + C / LLVM / Wasm). Curried multi-arg; types int / bool / str / unit (Phase 32). |
| REPL | persistent env, multi-line input, :type :env :show NAME :load FILE :reset :help |
| Error UX | Rust-style multi-line code frame, ANSI colors (TTY only), Levenshtein-based typo suggestions (including record fields and qualified names), type-conversion hints |
$ dune exec ./bin/mere.exe -- -e '5 |> (fn x -> x + 1) |> show'
"6"
$ dune exec ./bin/mere.exe -- -e 'let rec fact = fn n -> if n < 1 then 1 else n * fact (n - 1) in fact 10'
3628800
$ dune exec ./bin/mere.exe -- -e 'type opt = None | Some of int; match Some 42 with | None -> 0 | Some n -> n + 1'
43
$ dune exec ./bin/mere.exe -- examples/module_basic.mere
# Calls Math.inc / Math.square / Math.pow from `module Math { ... }`
$ dune exec ./bin/mere.exe -- examples/import_demo.mere
# import "examples/lib_list_ops.mere"; computes ListOps.sum [1..5]
$ dune exec ./bin/mere.exe -- contrib/json/json.mere
# Runs the self-test of the JSON parser in Mere (140 lines; promoted to contrib/ in Phase 40)
$ dune exec ./bin/mere.exe -- examples/pipeline.mere
# A realistic example combining region / view / effects / `with`
$ dune exec ./bin/mere.exe -- examples/toy_sql.mere
# 1165-line toy SQL engine (tokenizer + parser + executor + JOIN);
# 59 tests pass with diff = 0 across all 4 backends (interp + C + LLVM + Wasm)
$ dune exec ./bin/mere.exe -- examples/calc.mere
# Phase 36: recursive-descent arithmetic parser + `?!` Result chain.
# `1 + 2 * 3` → 7; `(1 + 2) * 3` → 9; `10 / 0` → ERR division
$ dune exec ./bin/mere.exe -- examples/maze_solver.mere
# Phase 36: BFS pathfinding through an ASCII 8x12 maze + path visualization- Tutorial — start here (includes
module/import/ REPL) - Language reference — syntax and semantics
- Stdlib reference — builtin tables
- Patterns / cookbook — common idioms
- Memory model — memory management options, region/view, current and future
- Codegen — three-backend (C / LLVM IR / Wasm) strategy + per-slice table
- Changelog — milestones from project start (2026-06-06) onward
examples/— runnable.merefiles (examples/README.md has a categorized index). From basics (FizzBuzz / word count) and Q-010 collection codegen demos (vec_codegen_*.mere/owned_vec_codegen.mere/strbuf_codegen.mere/map_codegen.mere), to realistic applications (PERFECT diff = 0 on all 4 backends):template_engine/word_freq/mini_shell/chained_parse/state_machine/ini_parser/regex_lite; a 1165-linetoy_sql(Phase 29 dogfood; 59 tests; 4-backend PERFECT); and the 47 sugar-dogfood examples added in Phase 36:calc/maze_solver(BFS) /game_of_life/sudoku_check/tic_tac_toe/eight_queens/knapsack/roman_numerals/morse_code/luhn_check/caesar_cipher/csv_summary/comprehension/if_let_demo/for_loop_demo/while_loop_demo/sugar_showcaseand more.contrib/— library candidates (contrib/README.md). One step more "reuse-oriented" thanexamples/. Currentlycontrib/json/(parser + writer) andcontrib/markdown/(HTML / text / TOC converter). These will graduate to separate repos once a package manager is in place.
dune build
dune exec ./bin/mere.exe -- examples/factorial.mere
dune exec ./bin/mere.exe -- -e '1 + 2 * 3'
dune exec ./bin/mere.exe -- -te 'fn x -> x + 1' # print the type
dune exec ./bin/mere.exe -- -r # REPL
dune runtest # 1573 tests
# C codegen
dune exec ./bin/mere.exe -- -ce 'let x = 5 in x * 2' > out.c
clang out.c -o out && ./out # → 10
# LLVM IR codegen
dune exec ./bin/mere.exe -- -lle '1 + 2 * 3' | llc - -o sum.s
clang sum.s -o sum && ./sum # → 7
# Wasm codegen (requires wabt / Node.js)
dune exec ./bin/mere.exe -- -we '1 + 2 * 3' > sum.wat
wat2wasm sum.wat -o sum.wasm
node scripts/run_wasm.js sum.wasm # → 7 (via the host harness)All three backends (C / LLVM / Wasm) match at feature parity — ints, functions, strings, tuples, records, variants, closures, polymorphism, recursive variants, complex patterns, show, region, view, with Drop, list pretty-printing, the four Q-010 collections (Vec / OwnedVec / StrBuf / Map), polymorphic user let-recs, inner-fn lifting, top-level value bindings globalized, and str_compare's sign-normalized output (parity reached incrementally through Phases 15.x → 31.0; 16 realistic examples retain diff = 0 PERFECT).
A built-in pretty-printer normalizes source style — 2-space indent, operator-precedence-driven paren insertion, else if chain flattening, list / range / lambda-shorthand sugar reconstruction.
dune exec mere -- fmt examples/factorial.mere # write formatted to stdout
dune exec mere -- fmt -i src/foo.mere src/bar.mere # rewrite in place
dune exec mere -- fmt --check src/*.mere # exit 1 if any file differs--check lists files that would change and is suitable for CI / pre-commit:
# .git/hooks/pre-commit (example)
#!/bin/sh
files=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.mere$')
[ -z "$files" ] || dune exec mere -- fmt --check $filesKnown MVP limitations: comments are dropped (the lexer discards them), module M { ... } blocks are emitted as flat M.foo bindings, and a few Phase 36 sugars (operator sections, string interpolation) are emitted in their desugared form.
mere/
├── bin/mere.ml # CLI entry point
├── lib/ # Core (library: mere)
│ ├── loc.ml / ast.ml / lexer.ml / parser.ml
│ ├── typer.ml # HM inference + sum types + records + let-poly + borrow checker
│ ├── eval.ml # tree-walking interpreter
│ ├── codegen_c.ml # C codegen
│ ├── codegen_llvm.ml # LLVM IR codegen
│ ├── codegen_wasm.ml # Wasm (WAT) codegen
│ ├── pipeline.ml # process / type_of (?base_dir for importer-relative)
│ ├── repl.ml # interactive REPL (multi-line / :env / :show / :load / :reset)
│ ├── formatter.ml # `mere fmt` pretty-printer
│ ├── diagnostic.ml # Rust-style code frame + ANSI colors
│ └── version.ml
├── test/test_basic.ml # 1573 tests
├── scripts/run_wasm.js # Wasm runtime host harness (Node.js: puts / read_file / write_file)
├── examples/ # *.mere sample programs
└── docs/ # tutorial / language-reference / stdlib-reference / patterns / memory-model / codegen / changelog
Mere = Old English for "lake". The region metaphor (a body of water bounded from its surroundings), the minimal ML-family ring, and a modest "just a ..." nuance. The former tentative name lang-ml was finalized to Mere on 2026-06-19, at the milestone when the design core (effect / type / memory) all worked.
MIT License (see LICENSE).
For contributions see CONTRIBUTING.md (contains language that leaves room for future MIT OR Apache-2.0 dual licensing).