Moxy is a lightweight superset of C — it's C with surgical improvements that eliminate boilerplate while producing clean, readable C output. Write modern, expressive code that transpiles to portable C11.
C is fast, portable, and everywhere. But it's also verbose. You need #include <stdio.h> to print, printf("%d\n", x) to format, manual tagged unions to handle variants, and hand-rolled dynamic arrays for anything beyond stack allocation.
Moxy keeps everything good about C — same types, same control flow, same mental model — and removes the ceremony. The generated C is clean enough to read, debug, and maintain by hand if needed.
| Feature | Moxy | C Equivalent |
|---|---|---|
| String type | string name = "moxy"; |
const char* name = "moxy"; |
| Boolean type | bool ok = true; |
#include <stdbool.h> + bool ok = true; |
| Auto-format print | print(x); |
printf("%d\n", x); |
| Tagged enums | enum Shape { Circle(float r) } |
Manual tag enum + union struct |
| Scoped construction | Shape::Circle(3.14) |
Compound literal |
| Pattern matching | match s { Shape::Circle(r) => ... } |
switch + tag + field access |
| Error handling | Result<int> r = Ok(42); |
Manual result struct |
| Dynamic arrays | int[] nums = [1, 2, 3]; |
Manual malloc + realloc |
| Hash maps | map[string,int] m = {}; |
Manual implementation |
| Range iteration | for i in 0..10 { ... } |
for (int i = 0; i < 10; i++) |
| Collection iteration | for x in list { ... } |
Manual index loop |
| Pipe operator | x |> double_it() |> add(1) |
add(double_it(x), 1) |
| Lambdas | (int x) => x * 2 |
Function pointer + separate definition |
| Async/Futures | Future<int> f(int x) { return x*2; } |
pthread spawn + join |
| Await | int val = await f(21); |
pthread_join + extract |
| ARC | int[] nums = [1, 2, 3]; |
Ref-counted heap alloc + auto release |
| Standard library | #include "std/math.mxy" |
N/A |
| File includes | #include "math.mxy" |
N/A (textual inlining) |
Everything else is standard C: if/else, for, while, return, structs, unions, typedefs, pointers, switch/case, do/while, goto, sizeof, all arithmetic/comparison/logical/bitwise operators, functions with typed parameters, recursion, global variables, and comments.
curl -fsSL https://github.com/wess/moxy/releases/latest/download/install.sh | shOr set a custom install directory:
INSTALL_DIR=~/.local/bin curl -fsSL https://github.com/wess/moxy/releases/latest/download/install.sh | shbrew tap wess/moxy https://github.com/wess/moxy
brew install moxycurl -fsSLO https://github.com/wess/moxy/releases/latest/download/moxy_VERSION_amd64.deb
sudo dpkg -i moxy_*.debcurl -fsSLO https://github.com/wess/moxy/releases/latest/download/moxy-VERSION-1.x86_64.rpm
sudo rpm -i moxy-*.rpmA PKGBUILD is included in each release for use with makepkg:
curl -fsSLO https://github.com/wess/moxy/releases/latest/download/PKGBUILD
makepkg -siasdf plugin add moxy https://github.com/wess/moxy.git
asdf install moxy latest
asdf set --home moxy latestRequires a C compiler and goose:
git clone --recursive https://github.com/wess/moxy.git
cd moxy
goose buildThe binary is at ./build/debug/moxy.
| Platform | Architecture | Format |
|---|---|---|
| macOS | Intel (amd64) | tar.gz |
| macOS | Apple Silicon (arm64) | tar.gz |
| macOS | Intel + Apple Silicon | Homebrew |
| Linux | amd64 | tar.gz |
| Linux | arm64 | tar.gz |
| Linux | amd64 (static/musl) | tar.gz |
| Debian/Ubuntu | amd64, arm64 | .deb |
| Fedora/RHEL | x86_64 | .rpm |
| Arch Linux | x86_64, aarch64 | PKGBUILD |
All release assets include SHA-256 checksums in checksums.txt.
moxy run hello.mxyThat's it — one command to transpile, compile, and execute.
transpile:
<file.mxy> transpile to C on stdout
run <file.mxy> [args] transpile, compile, and execute
build <file.mxy> [-o out] transpile and compile to binary
test [files...] discover and run *_test.mxy files
project:
new <name> create new project
init initialize project in current directory
build [--release] [-p member] build project or workspace member
run [--release] [-p member] build and run project or member
clean remove build directory
install [--prefix PATH] release build and install
packages:
add <git-url> [opts] add dependency (--name, --version)
remove <name> remove dependency
update update all dependencies
tools:
fmt [file.mxy] [--check] format source files
lint [file.mxy] lint source files for issues
Flags:
--enable-async— enablesFuture<T>andawaitsupport (links-lpthread). Place before the command:moxy --enable-async run file.mxy--enable-arc— enables automatic reference counting for lists and maps. Heap-allocates collections with a refcount and insertsretain/releasecalls at scope boundaries. Place before the command:moxy --enable-arc run file.mxy
run passes extra arguments through to the compiled program. build produces a binary (defaults to the source filename without .mxy). test discovers *_test.mxy files recursively or runs specific files you pass (async tests are auto-detected and linked with pthreads; ARC tests with arc in the filename are auto-detected). fmt formats source files in-place (or checks with --check). lint checks for unused variables, empty blocks, and shadowed variables. Both fmt and lint discover .mxy files recursively when no file is given, and read settings from moxyfmt.yaml if present. All commands respect CC and CFLAGS environment variables.
Name test files with a _test.mxy suffix and use assert:
// math_test.mxy
int square(int n) {
return n * n;
}
void main() {
assert(square(5) == 25);
assert(square(0) == 0);
assert(square(-3) == 9);
}
moxy test test tests/math_test.mxy ... ok (42ms)
1 passed, 0 failed (1 total) in 0.0s
assert(expr) prints the source line number and exits with code 1 on failure.
void main() {
print("hello, world!");
}
void main() {
int age = 30;
float height = 5.11;
string name = "moxy";
bool active = true;
char initial = 'M';
long population = 8000000;
double pi = 3.14159265;
short temp = 72;
print(name);
print(age);
print(height);
print(active);
}
print() automatically picks the right format string for each type. No printf format specifiers needed.
int add(int a, int b) {
return a + b;
}
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
void main() {
print(add(3, 4)); // 7
print(factorial(5)); // 120
}
Functions can call each other in any order — forward declarations are generated automatically.
void main() {
int x = 15;
// if / else if / else
if (x > 20) {
print("big");
} else if (x > 10) {
print("medium");
} else {
print("small");
}
// for loops
for (int i = 0; i < 5; i++) {
print(i);
}
// while loops
int countdown = 3;
while (countdown > 0) {
print(countdown);
countdown--;
}
}
Iterate over ranges, lists, and maps without manual indexing:
void main() {
// range: 0, 1, 2, 3, 4
for i in 0..5 {
print(i);
}
// list iteration
int[] nums = [10, 20, 30];
for n in nums {
print(n);
}
// map iteration with key-value
map[string,int] ages = {};
ages.set("alice", 30);
ages.set("bob", 25);
for k, v in ages {
print(k);
print(v);
}
}
for x in 0..n generates a standard C for loop. for x in list iterates over list elements. for k, v in map iterates over map key-value pairs.
// Simple enum
enum Color {
Red,
Green,
Blue
}
// Tagged enum (algebraic data type)
enum Shape {
Circle(float radius),
Rect(int w, int h),
None
}
void main() {
Shape s = Shape::Circle(3.14);
match s {
Shape::Circle(r) => print(r),
Shape::Rect(w) => print(w),
Shape::None => print("nothing"),
}
Color c = Color::Red;
match c {
Color::Red => print("red"),
Color::Green => print("green"),
Color::Blue => print("blue"),
}
}
Tagged enum variants can carry typed fields. The match binding captures the first field of each variant.
void main() {
int[] nums = [10, 20, 30];
nums.push(40);
print(nums.len); // 4
print(nums[0]); // 10
print(nums[3]); // 40
string[] words = ["hello", "world"];
words.push("moxy");
print(words[2]); // moxy
print(words.len); // 3
}
Lists support push, len, and index access. They grow automatically.
void main() {
Result<int> ok = Ok(42);
Result<int> fail = Err("not found");
match ok {
Ok(v) => print(v),
Err(e) => print(e),
}
match fail {
Ok(v) => print(v),
Err(e) => print(e),
}
}
Built-in error handling without exceptions. Result<T> wraps a success value of type T or an error string.
void main() {
map[string,int] ages = {};
ages.set("alice", 30);
ages.set("bob", 25);
print(ages.get("alice")); // 30
print(ages.has("bob")); // 1 (true)
print(ages.len); // 2
}
Maps support set, get, has, and len. String keys use strcmp, numeric keys use ==.
Chain function calls left-to-right with |>:
int double_it(int x) {
return x * 2;
}
int add(int a, int b) {
return a + b;
}
void main() {
int result = 5 |> double_it();
print(result); // 10
// chained pipes
5 |> double_it() |> add(3) |> print() // 13
// bare function name (no parens) also works
10 |> double_it |> print() // 20
}
The pipe operator passes the left-hand value as the first argument to the right-hand function. a |> f(b) becomes f(a, b). Pipes are left-associative, so a |> f() |> g() becomes g(f(a)).
Pass inline functions as arguments:
int apply(int x, int fn(int)) {
return fn(x);
}
void main() {
// single expression
int a = apply(5, (int x) => x * 2);
print(a); // 10
// block body
int b = apply(10, (int x) => {
int result = x + 1;
return result * 3;
});
print(b); // 33
}
Lambdas are lifted to top-level C functions at compile time. They can be passed anywhere a function pointer is expected.
Run concurrent work with Future<T> and await (requires --enable-async):
Future<int> compute(int x) {
return x * 2;
}
void main() {
int val = await compute(21);
print(val); // 42
}
Functions returning Future<T> automatically spawn a pthread. await joins the thread and extracts the result. Works with any type:
Future<string> greet(string name) {
return name;
}
Future<void> do_work() {
return;
}
moxy --enable-async run async.mxyWith --enable-arc, lists and maps become heap-allocated, reference-counted objects. The compiler inserts retain/release calls at scope boundaries and assignments. When the refcount hits zero, the object is freed. Your Moxy source code stays the same:
void main() {
int[] nums = [1, 2, 3];
nums.push(4);
print(nums.len); // 4
int[] alias = nums; // rc=2, shared reference
alias.push(5);
print(nums.len); // 5 (same object)
}
moxy --enable-arc run arc.mxyARC-managed types are passed as pointers and automatically retained/released when entering and leaving functions, if-blocks, loops, and match arms. Returning an ARC value transfers ownership — the returned object is not released.
Moxy ships with an embedded standard library. Import modules with #include:
#include "std/math.mxy"
#include "std/string.mxy"
#include "std/debug.mxy"
void main() {
print(max_int(3, 7)); // 7
print(clamp_int(15, 0, 10)); // 10
print(str_contains("hello", "ell")); // 1
todo("not implemented yet"); // exits with message
}
| Module | Functions |
|---|---|
std/math.mxy |
abs_int, min_int, max_int, clamp_int |
std/string.mxy |
str_len, str_eq, str_contains, str_starts_with, str_ends_with |
std/io.mxy |
eprintln, readln |
std/debug.mxy |
panic, todo, unreachable |
std/test.mxy |
assert_eq_int, assert_eq_str, assert_true, assert_false |
Standard library modules are embedded in the binary — no external files needed at runtime.
Split code across multiple files:
// math.mxy
int square(int x) {
return x * x;
}
// main.mxy
#include "math.mxy"
#include <stdlib.h>
void main() {
print(square(5)); // 25
}
#include "file.mxy"— textually inlines the.mxyfile before compilation (recursive)#include <header.h>— passes through to C output#include "header.h"— passes through to C output
Duplicate C headers are automatically deduplicated against auto-generated ones.
int max_retries = 3;
string version = "1.0";
void main() {
print(version);
print(max_retries);
}
// line comment
/* block
comment */
For multi-file projects, Moxy uses a moxy.yaml config file (powered by goose):
moxy new myapp # scaffold a new project
cd myapp
moxy run # build and run from moxy.yaml
moxy build --release # optimized build
moxy install # install to /usr/local/binProjects support dependencies from git:
moxy add https://github.com/user/lib.git --name lib --version v1.0
moxy remove lib
moxy update # update all dependenciesAsync and ARC flags are auto-detected from the source when running in project mode.
For multi-project repositories, Moxy supports Cargo-style workspaces. A root moxy.yaml lists member directories, each with their own moxy.yaml:
# root moxy.yaml
workspace:
members:
- "mylib"
- "myapp"Members can be libraries or binaries:
# mylib/moxy.yaml
project:
name: "mylib"
type: "lib"# myapp/moxy.yaml
project:
name: "myapp"
dependencies:
mylib:
path: "../mylib"Build and run workspace members:
moxy build # build all members in dependency order
moxy build -p mylib # build just one member
moxy run # build all, run the binary member
moxy run -p myapp # build and run a specific member
moxy build --release # release build of entire workspaceLibraries produce static archives (build/lib/lib{name}.a). Binaries that depend on workspace libraries automatically get the right include paths and link flags. Members are built in topological order based on their dependencies.
moxy fmt # format all .mxy files recursively
moxy fmt file.mxy # format a single file
moxy fmt --check # check formatting without modifying
moxy lint # lint all .mxy files recursively
moxy lint file.mxy # lint a single fileThe formatter reads settings from moxyfmt.yaml if present. The linter checks for unused variables, empty blocks, and shadowed variables.
Moxy generates clean, readable C. For example:
int[] nums = [1, 2, 3];
nums.push(4);
print(nums[0]);
Becomes:
list_int nums = list_int_make((int[]){1, 2, 3}, 3);
list_int_push(&nums, 4);
printf("%d\n", nums.data[0]);Along with the list_int struct and helper functions. Pipes desugar at compile time:
5 |> double_it() |> add(3)
Becomes:
add(double_it(5), 3)With --enable-arc, the same list code generates ref-counted heap objects:
int[] nums = [1, 2, 3];
nums.push(4);
print(nums[0]);
Becomes:
list_int *nums = list_int_make((int[]){1, 2, 3}, 3); // rc=1
list_int_push(nums, 4); // pointer, no &
printf("%d\n", nums->data[0]); // -> not .
list_int_release(nums); // auto-inserted at scope exitAsync functions spawn pthreads:
Future<int> compute(int x) { return x * 2; }
int val = await compute(21);
Becomes:
typedef struct { pthread_t thread; int result; int started; } Future_int;
// ... args struct, thread wrapper, launcher ...
Future_int _aw0 = compute(21);
void *_aw0_ret;
pthread_join(_aw0.thread, &_aw0_ret);
int val = *(int *)_aw0_ret;
free(_aw0_ret);The output compiles with any C11 compiler.
| Document | Description |
|---|---|
| Language Reference | Complete type, syntax, and operator reference |
| Tutorial | Step-by-step introduction to every feature |
| Architecture | How the transpiler pipeline works |
| API Reference | Internal C API for each transpiler module |
| Examples | .mxy source files demonstrating all features |
Copy the extension into your Zed extensions directory:
cp -r tools/editors/zed ~/.config/zed/extensions/moxyProvides syntax highlighting, auto-indent, bracket matching, and comment toggling for .mxy files.
src/
token.h — token kinds
lexer.h/c — tokenizer
ast.h/c — AST node definitions
parser.h/c — recursive descent parser
codegen.h/c — C code generator with monomorphization
flags.h/c — global feature flags (--enable-async, --enable-arc)
diag.h/c — error and warning diagnostics
fmt.h/c — source formatter
lint.h/c — static analysis linter
mxyconf.h/c — moxyfmt.yaml config parser
mxystdlib.h/c — embedded standard library (auto-generated)
main.c — CLI entry point, preprocessor, project mode
std/
math.mxy — abs, min, max, clamp
string.mxy — length, equality, contains, starts/ends with
io.mxy — eprintln, readln
debug.mxy — panic, todo, unreachable
test.mxy — typed assertions
tests/
*_test.mxy — test suite
tools/
asdf/ — asdf version manager plugin
editors/zed/ — Zed editor extension
moxylsp/ — Moxy language server (LSP)
examples/
features.mxy — comprehensive feature showcase
lambda.mxy — lambda / closure examples
ccompat.mxy — C compatibility (structs, pointers, switch, bitwise)
async.mxy — async/futures (requires --enable-async)
arc.mxy — ARC example (requires --enable-arc)
stdlib.mxy — standard library usage
math.mxy — helper functions (included by features.mxy)
docs/
book/ — language reference and architecture
tutorial/ — getting started guide
api/ — transpiler API reference
Moxy is a five-stage transpiler written in ~6,200 lines of C:
.mxy source → Preprocessor → Lexer → Parser → AST → Codegen → .c output
- Preprocessor scans for
#includedirectives, inlining.mxyfiles (with stdlib fallback) and collecting C headers - Lexer tokenizes the source into a flat token array
- Parser builds an AST using recursive descent with Pratt-style expression parsing
- Codegen walks the AST in two passes — first collecting generic type instantiations and lambdas, then emitting C with monomorphized type definitions, forward declarations, and function bodies
See Architecture for the full technical breakdown.
MIT