← Docs/M++ Reference
Language Reference

M++ Language Reference

Complete documentation for M++ — a statically-typed, LLVM-compiled systems language with no external runtime. From hello world to low-level memory management.

Getting Started

M++ source files use the .mpp extension. Every program needs a main function as its entry point.

Hello World

main.mpp
fn main() {
    print("Hello, world!");
}

Compiling & Running

The mpp CLI compiles source to native binaries via LLVM.

shell
mpp build          # build from mpp.toml
mpp main.mpp       # compile single file
mpp build --run    # build and run

Project Structure

For multi-file projects, create a mpp.toml in your project root:

mpp.toml
name = "hello"
version = "0.1.0"
main = "src/main.mpp"

Then run mpp build to compile. The output binary is placed in the project root by default.

Variables & Constants

Variables are declared with let. They are immutable by default. Use let mut for mutable bindings.

let name = "R2ND";              // immutable, type inferred
let mut counter = 0;            // mutable
let x: int = 42;                // explicit type
const MAX_BUFFER: int = 4096;   // compile-time constant

Type Inference

The compiler infers types from the right-hand side. Explicit annotations are optional but can improve clarity.

let a = 3.14;       // inferred as float
let b = true;       // inferred as bool
let c = "hello";    // inferred as string
let d: i64 = 100;   // explicit sized integer

Constants

const values are evaluated at compile time. They must have an explicit type annotation and cannot be reassigned.

const PI: float = 3.14159265;
const APP_NAME: string = "MyApp";
const MAX_RETRIES: int = 3;
Note: Attempting to reassign an immutable variable or a const is a compile-time error.

Primitive Types

M++ has a small set of built-in primitive types. Sized integer variants are available for low-level control.

TypeDescriptionExample
intPlatform-sized signed integer (64-bit on x86_64)42
float64-bit floating point (IEEE 754)3.14
boolBoolean true or falsetrue
stringUTF-8 string"hello"
ptrRaw pointernull
voidNo value / unit type

Sized Integer Types

TypeBitsRange
u880 to 255
i88−128 to 127
u16160 to 65,535
i1616−32,768 to 32,767
u32320 to 4,294,967,295
i3232−2³¹ to 2³¹−1
u64640 to 2⁶⁴−1
i6464−2⁶³ to 2⁶³−1

Hex & Binary Literals

let a: u8  = 255;
let b: u32 = 0xFF;        // hex literal
let c: i64 = 0b10101010;  // binary literal
let d: u64 = 1_000_000;   // underscore separators allowed
Tip: Use sized types when working with FFI, binary protocols, or when exact memory layout matters. Prefer int and float for general-purpose code.

Operators

Arithmetic

OperatorDescriptionExample
+Addition / string concatenationa + b
-Subtractiona - b
*Multiplicationa * b
/Divisiona / b
%Modulo (remainder)a % b

Comparison

OperatorDescription
==Equal to
!=Not equal to
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal

Logical

OperatorDescriptionExample
&&Logical AND (short-circuits)a && b
||Logical OR (short-circuits)a || b
!Logical NOT!flag

Bitwise

OperatorDescription
&Bitwise AND
|Bitwise OR
^Bitwise XOR
~Bitwise NOT
<<Left shift
>>Right shift

Assignment & Compound Assignment

OperatorEquivalent
=Assign
+=a = a + b
-=a = a - b
*=a = a * b
/=a = a / b
%=a = a % b
&=a = a & b
|=a = a | b
^=a = a ^ b
<<=a = a << b
>>=a = a >> b

Other Operators

OperatorDescriptionExample
asType castx as float
sizeofSize of type in bytessizeof(int)
&xAddress-of (get pointer)let p = &x;
*pDereference pointerlet val = *p;
..Range (exclusive end)0..10
let mut x = 10;
x += 5;                    // x is now 15
let y = x as float;        // cast to float: 15.0
let mask: u8 = 0b1111_0000;
let low = mask & 0x0F;     // bitwise AND: 0
let high = mask >> 4;      // right shift: 15

Control Flow

If / Else

let score = 85;
if score >= 90 {
    print("A");
} else if score >= 80 {
    print("B");
} else {
    print("C");
}

While Loop

let mut i = 0;
while i < 10 {
    print(to_string(i));
    i += 1;
}

For..in (Range)

Ranges use .. syntax with an exclusive upper bound.

for i in 0..5 {
    print(to_string(i));  // prints 0, 1, 2, 3, 4
}

For-each (Arrays)

let names = ["Alice", "Bob", "Charlie"];
for name in names {
    print("Hello, " + name);
}

For-each (Maps)

let ages = { "Alice": 30, "Bob": 25 };
for key, value in ages {
    print(key + " is " + to_string(value));
}

Break & Continue

for i in 0..100 {
    if i % 2 == 0 {
        continue;  // skip even numbers
    }
    if i > 20 {
        break;     // stop at 20
    }
    print(to_string(i));
}

Match / Pattern Matching

match expressions support literal patterns, enum destructuring, and a wildcard _ default case.

match status_code {
    200 => { print("OK"); },
    404 => { print("Not Found"); },
    500 => { print("Server Error"); },
    _ => { print("Unknown status"); }
}

Pattern matching is especially powerful with enums (see the Enums section):

match shape {
    Shape.Circle(r) => {
        return 3.14159 * r * r;
    },
    Shape.Rect(w, h) => {
        return w * h;
    },
    _ => { return 0.0; }
}

Functions

Declaration

Functions are declared with fn. Return types come after ->.

fn add(a: int, b: int) -> int {
    return a + b;
}

fn greet(name: string) {
    print("Hello, " + name + "!");
}

Return Types

Functions without an explicit return type return void. Multiple returns are handled through structs or tuples.

fn divide(a: float, b: float) -> Result[float] {
    if b == 0.0 {
        return err("division by zero");
    }
    return ok(a / b);
}

First-Class Functions

Functions can be stored in variables, passed as arguments, and returned from other functions.

fn apply(f: fn(int) -> int, x: int) -> int {
    return f(x);
}

fn double(n: int) -> int {
    return n * 2;
}

let result = apply(double, 5);  // 10

Function Types

let op: fn(int, int) -> int = add;
let result = op(3, 4);  // 7

Methods (Receiver Syntax)

Methods are functions with a type prefix before the name. The first parameter self refers to the instance.

struct Counter {
    value: int,
}

fn Counter.increment(self) {
    self.value += 1;
}

fn Counter.get(self) -> int {
    return self.value;
}

let mut c = Counter { value: 0 };
c.increment();
print(to_string(c.get()));  // 1

Strings

String Literals

let greeting = "Hello, world!";
let multiline = "Line one
Line two
Line three";

Escape Sequences

SequenceMeaning
Newline
Tab
Carriage return
\Backslash
"Double quote
Null byte

Raw Strings

Raw strings disable escape sequence processing, useful for regex and file paths.

let path = r"C:UsersmathiDesktop";
let pattern = r"d+.d+";

String Interpolation

let name = "M++";
let version = 1;
print("Welcome to " + name + " v" + to_string(version));

String Concatenation

Use the + operator to concatenate strings.

let full = "Hello" + ", " + "world!";

Built-in String Functions

FunctionDescriptionExample
len(s)Length of stringlen("abc") → 3
substr(s, start, len)Substring extractionsubstr("hello", 1, 3) → "ell"
split(s, sep)Split into arraysplit("a,b,c", ",") → ["a","b","c"]
contains(s, sub)Check substring presencecontains("hello", "ell") → true
trim(s)Remove leading/trailing whitespacetrim(" hi ") → "hi"
replace(s, old, new)Replace occurrencesreplace("aab", "a", "x") → "xxb"
to_upper(s)Convert to uppercaseto_upper("hi") → "HI"
to_lower(s)Convert to lowercaseto_lower("HI") → "hi"
index_of(s, sub)Find index of substring (−1 if not found)index_of("abc", "b") → 1
join(arr, sep)Join array into stringjoin(["a","b"], "-") → "a-b"
char_at(s, i)Character at indexchar_at("abc", 0) → "a"
char_code(s, i)Character code at indexchar_code("A", 0) → 65
int_to_char(n)Integer to characterint_to_char(65) → "A"
let csv_line = "Alice,30,Engineer";
let fields = split(csv_line, ",");
print(fields[0]);                     // "Alice"
print(to_string(len(csv_line)));      // "18"

let upper = to_upper("hello world");
print(upper);                         // "HELLO WORLD"

let idx = index_of(csv_line, "30");
print(to_string(idx));                // "6"

Arrays

Array Literals

let numbers = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob"];
let empty: int[] = [];

Indexing

Arrays are zero-indexed. Out-of-bounds access causes a runtime panic.

let first = numbers[0];  // 1
let last = numbers[4];   // 5

Mutation

let mut items = [10, 20, 30];
items[1] = 25;                // [10, 25, 30]
append(items, 40);            // [10, 25, 30, 40]
let removed = pop(items);     // removed = 40, items = [10, 25, 30]

Built-in Array Functions

FunctionDescription
len(arr)Number of elements
append(arr, val)Add element to end
pop(arr)Remove and return last element
arr_copy(arr)Create a shallow copy

Iteration

let scores = [95, 87, 92, 78, 100];
let mut total = 0;
for score in scores {
    total += score;
}
let avg = total / len(scores);
print("Average: " + to_string(avg));
Note: Array bounds are checked at runtime. Accessing an invalid index will panic with a descriptive error message.

Maps

Map Literals

let ages = { "Alice": 30, "Bob": 25, "Charlie": 35 };
let config: map[string, string] = {};

Access & Mutation

let age = map_get(ages, "Alice");   // 30
map_set(ages, "Diana", 28);          // add entry
map_del(ages, "Bob");                // remove entry

Built-in Map Functions

FunctionDescription
map_set(m, key, val)Set or update a key-value pair
map_get(m, key)Get value by key
map_has(m, key)Check if key exists (returns bool)
map_del(m, key)Delete a key-value pair
map_keys(m)Return array of all keys

Iteration

let headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123",
    "Accept": "*/*",
};

for key, value in headers {
    print(key + ": " + value);
}

// Check before access
if map_has(headers, "Authorization") {
    let auth = map_get(headers, "Authorization");
    print("Auth: " + auth);
}

Structs

Definition

struct Vec2 {
    x: float,
    y: float,
}

Initialization & Field Access

let v = Vec2 { x: 3.0, y: 4.0 };
print(to_string(v.x));  // "3.0"
print(to_string(v.y));  // "4.0"

Methods

Methods use receiver syntax: fn TypeName.method(self).

fn Vec2.length(self) -> float {
    return sqrt(self.x * self.x + self.y * self.y);
}

fn Vec2.scale(self, factor: float) -> Vec2 {
    return Vec2 { x: self.x * factor, y: self.y * factor };
}

let v = Vec2 { x: 3.0, y: 4.0 };
print(to_string(v.length()));  // "5.0"

let doubled = v.scale(2.0);
print(to_string(doubled.x));  // "6.0"

Drop / Destructors

If a struct defines a drop method, it is called automatically when the value goes out of scope.

struct FileHandle {
    fd: int,
    path: string,
}

fn FileHandle.drop(self) {
    // called automatically when FileHandle goes out of scope
    close_fd(self.fd);
    print("Closed: " + self.path);
}
Tip: Use drop for RAII-style resource management — file handles, network connections, locks, etc.

Enums

Simple Enums

enum Color {
    Red,
    Green,
    Blue,
}

let c = Color.Red;
match c {
    Color.Red => { print("Red!"); },
    Color.Green => { print("Green!"); },
    Color.Blue => { print("Blue!"); },
}

Sum Types (Enums with Payloads)

Enum variants can carry data, making them algebraic sum types.

enum Shape {
    Circle(float),
    Rect(float, float),
    Point,
}

fn area(s: Shape) -> float {
    match s {
        Shape.Circle(r) => {
            return 3.14159 * r * r;
        },
        Shape.Rect(w, h) => {
            return w * h;
        },
        Shape.Point => {
            return 0.0;
        },
    }
}

let s = Shape.Circle(5.0);
print(to_string(area(s)));  // "78.53975"

Destructuring in Match

enum Message {
    Text(string),
    Binary(ptr, int),
    Disconnect,
}

fn handle(msg: Message) {
    match msg {
        Message.Text(content) => {
            print("Text: " + content);
        },
        Message.Binary(data, size) => {
            print("Binary: " + to_string(size) + " bytes");
        },
        Message.Disconnect => {
            print("Client disconnected");
        },
    }
}

Traits & Impl

Trait Definition

Traits define shared behavior as a set of method signatures.

trait Drawable {
    fn draw(self);
    fn bounds(self) -> Rect;
}

Impl Blocks

Use impl to implement a trait for a type.

impl Drawable for Circle {
    fn draw(self) {
        draw_circle(self.x, self.y, self.radius);
    }

    fn bounds(self) -> Rect {
        return Rect {
            x: self.x - self.radius,
            y: self.y - self.radius,
            w: self.radius * 2.0,
            h: self.radius * 2.0,
        };
    }
}

Trait Bounds

Constrain generic parameters to types that implement specific traits.

fn render[T: Drawable](item: T) {
    item.draw();
}

fn render_all[T: Drawable](items: T[]) {
    for item in items {
        item.draw();
    }
}

Polymorphism

trait Serializable {
    fn to_json(self) -> string;
}

impl Serializable for User {
    fn to_json(self) -> string {
        return "{"name":"" + self.name + ""}";
    }
}

impl Serializable for Config {
    fn to_json(self) -> string {
        return "{"debug":" + to_string(self.debug) + "}";
    }
}

fn save[T: Serializable](item: T) {
    let json = item.to_json();
    write_file("out.json", json);
}

Generics

Generic Functions

fn identity[T](x: T) -> T {
    return x;
}

let a = identity[int](42);
let b = identity[string]("hello");

Generic Structs

struct Pair[A, B] {
    first: A,
    second: B,
}

let p = Pair[string, int] { first: "age", second: 30 };
print(p.first + ": " + to_string(p.second));

Multiple Type Parameters

fn map_pair[A, B, C](p: Pair[A, B], f: fn(A) -> C) -> Pair[C, B] {
    return Pair[C, B] { first: f(p.first), second: p.second };
}

Monomorphization

M++ monomorphizes generics at compile time — each concrete type combination generates specialized code. There is no runtime dispatch overhead for generics.

Tip: Monomorphization means identity[int] and identity[string] compile to separate, optimized functions. Zero cost at runtime.

Closures & Lambdas

Lambda Syntax

let double = |x: int| -> int { return x * 2; };
let add = |a: int, b: int| -> int { return a + b; };

print(to_string(double(5)));   // "10"
print(to_string(add(3, 4)));   // "7"

Capturing Variables

Closures capture variables from their enclosing scope.

let multiplier = 3;
let scale = |x: int| -> int { return x * multiplier; };
print(to_string(scale(10)));  // "30"

Higher-Order Functions

fn apply_twice(f: fn(int) -> int, x: int) -> int {
    return f(f(x));
}

let inc = |x: int| -> int { return x + 1; };
print(to_string(apply_twice(inc, 5)));  // "7"

Function Types

Lambda types are written as fn(params) -> return_type.

fn make_adder(n: int) -> fn(int) -> int {
    return |x: int| -> int { return x + n; };
}

let add5 = make_adder(5);
print(to_string(add5(10)));  // "15"

Common Patterns

// Callback pattern
fn on_complete(callback: fn(string)) {
    // ... do work ...
    callback("done");
}

on_complete(|msg: string| {
    print("Status: " + msg);
});

Error Handling

Result[T]

M++ uses Result[T] for fallible operations. Results are either ok(value) or err(message).

fn parse_port(s: string) -> Result[int] {
    let n = to_int(s);
    if n < 0 || n > 65535 {
        return err("port out of range");
    }
    return ok(n);
}

Checking Results

FunctionDescription
is_ok(r)Returns true if result is ok
is_err(r)Returns true if result is err
unwrap(r)Extract value, panic on err
let r = parse_port("8080");
if is_ok(r) {
    let port = unwrap(r);
    print("Port: " + to_string(port));
}

Match on Result

match parse_port("8080") {
    ok(port) => { print("Port: " + to_string(port)); },
    err(msg) => { print("Error: " + msg); }
}

The ? Operator

The ? operator propagates errors to the calling function. If the result is err, it returns early with that error.

fn connect(host: string, port_str: string) -> Result[Connection] {
    let port = parse_port(port_str)?;
    let conn = tcp_connect(host, port)?;
    return ok(conn);
}

Option<T>

Option<T> represents a value that may or may not exist. Use Some(value) and None.

fn find_user(id: int) -> Option<User> {
    if id == 0 {
        return None;
    }
    return Some(User { id: id, name: "Alice" });
}

match find_user(1) {
    Some(user) => { print("Found: " + user.name); },
    None => { print("User not found"); }
}
Warning: Calling unwrap() on an err result or None option will panic. Prefer match or the ? operator for safe handling.

Modules & Imports

File Imports

Import other M++ files with import. Imports are flat — all public symbols become available.

import "math.mpp";
import "utils/helpers.mpp";

fn main() {
    let result = add(1, 2);  // from helpers.mpp
    print(to_string(sqrt(16.0)));  // from math.mpp
}

Inline Module Blocks

mod utils {
    fn clamp(val: int, min: int, max: int) -> int {
        if val < min { return min; }
        if val > max { return max; }
        return val;
    }
}

Private Visibility

Use priv to restrict visibility. Private symbols are accessible only within the same module or file.

priv fn internal_helper() -> int {
    return 42;
}

fn public_api() -> int {
    return internal_helper();  // OK within same file
}
Note: By default, all top-level functions and types are public. Use priv explicitly to hide implementation details.

C FFI

Extern Functions

Declare C functions with extern fn. M++ links them at compile time.

extern fn MessageBoxA(hwnd: ptr, text: string, caption: string, flags: u32) -> i32;

fn main() {
    MessageBoxA(null, "Hello from M++!", "R2ND", 0u32);
}

Type Mapping

C TypeM++ Type
int / int32_ti32
long long / int64_ti64
unsigned charu8
floatfloat
char*string
void*ptr
NULLnull

Linking

Specify link flags in mpp.toml to link against system or third-party libraries.

mpp.toml
name = "win-app"
main = "src/main.mpp"
link = ["-luser32", "-lgdi32", "-lopengl32"]
// Use Win32 API directly
extern fn CreateWindowExA(
    exStyle: u32, className: string, windowName: string,
    style: u32, x: i32, y: i32, w: i32, h: i32,
    parent: ptr, menu: ptr, instance: ptr, param: ptr
) -> ptr;

Memory Management

Manual Allocation

let buf = alloc(1024);
defer free(buf);

memset(buf, 0, 1024);
// ... use buf ...
// free() called automatically when function exits

Memory Functions

FunctionDescription
alloc(size)Allocate size bytes, returns ptr
free(ptr)Free previously allocated memory
memcpy(dst, src, n)Copy n bytes from src to dst
memset(ptr, val, n)Set n bytes to val

Reference Counting

For shared ownership, M++ provides reference-counted pointers.

let data = rc_new("shared string");
let copy = rc_clone(data);      // increment ref count

let val = rc_get(data);         // access the value
rc_set(data, "updated");        // update the value

rc_release(copy);               // decrement ref count
rc_release(data);               // ref count hits 0, memory freed
FunctionDescription
rc_new(val)Create reference-counted value
rc_clone(rc)Clone (increment count)
rc_get(rc)Read the inner value
rc_set(rc, val)Update the inner value
rc_release(rc)Decrement count, free if zero

Slices

Slices are views into contiguous memory, defined by a pointer and length.

let arr = [10, 20, 30, 40, 50];
let s = arr[1..4];  // slice: [20, 30, 40]

for val in s {
    print(to_string(val));
}
Warning: Manual memory management requires discipline. Always pair alloc with free — preferably via defer. Use reference counting for shared ownership.

Defer

defer schedules a statement to execute when the current scope exits, regardless of how it exits (return, error, etc.). Multiple defers execute in LIFO (last-in, first-out) order.

Basic Usage

fn process_file(path: string) {
    let f = open(path);
    defer close(f);

    let data = read(f);
    // ... process data ...
    // close(f) runs automatically here
}

LIFO Order

fn example() {
    defer print("first");
    defer print("second");
    defer print("third");
    print("running");
}
// Output:
//   running
//   third
//   second
//   first

Cleanup Guarantees

defer is ideal for cleanup tasks: closing files, freeing memory, releasing locks, restoring state.

fn with_lock(mutex: ptr) {
    lock(mutex);
    defer unlock(mutex);

    // critical section — unlock runs even if we return early
    if !ready() {
        return;  // unlock still runs
    }
    do_work();
    // unlock runs here too
}
Tip: Place defer immediately after acquiring a resource. This makes the acquire/release pair visually adjacent and hard to forget.

Standard Library

M++ ships with a growing standard library. Import modules as needed.

ModuleDescriptionKey Functions
stringsString manipulation utilitieslen, substr, split, contains, trim, replace
jsonJSON parsing and serializationjson_parse, json_stringify, json_get
ioFile and stream I/Oopen, read, write, close, read_line
mathMath functions and constantssqrt, abs, pow, sin, cos, floor, ceil, PI
argsCommand-line argument parsingget_args, parse_flags
envEnvironment variablesenv_get, env_set, env_has
hashHashing utilitieshash_sha256, hash_md5, hash_crc32
csvCSV parsing and writingcsv_parse, csv_write, csv_row
datetimeDate and timenow, format_time, parse_time, timestamp
logStructured logginglog_info, log_warn, log_error, log_debug
netNetworking (TCP/UDP/HTTP)tcp_connect, tcp_listen, http_get, http_post
pathFile path manipulationpath_join, path_ext, path_dir, path_base
processProcess managementexec, spawn, wait, exit
threadThreading primitivesspawn_thread, join, sleep
syncSynchronizationmutex_new, mutex_lock, mutex_unlock, channel
testingTest frameworkassert, assert_eq, test, run_tests

Example: JSON

import "json";

let data = json_parse("{"name":"Alice","age":30}");
let name = json_get(data, "name");
print(name);  // "Alice"

let obj = { "status": "ok", "code": 200 };
let s = json_stringify(obj);
print(s);  // {"status":"ok","code":200}

Example: File I/O

import "io";

let content = read("config.txt");
print(content);

write("output.txt", "Hello from M++!");

Example: Testing

import "testing";

test "addition works" {
    assert_eq(add(2, 3), 5);
    assert_eq(add(-1, 1), 0);
}

test "string length" {
    assert(len("hello") == 5);
}

Compilation

CLI Commands

CommandDescription
mpp buildBuild project from mpp.toml
mpp build --runBuild and run immediately
mpp build --releaseBuild with optimizations
mpp <file>.mppCompile single file
mpp testRun all tests

mpp.toml

The project manifest configures build settings, dependencies, and linking.

mpp.toml
name = "my-project"
version = "1.0.0"
main = "src/main.mpp"
link = ["-luser32", "-lgdi32"]
opt = "2"

[dependencies]
r2nd-ui = "0.4.0"
FieldDescription
nameProject name (used for output binary)
versionSemantic version string
mainEntry point source file
linkLinker flags (array of strings)
optOptimization level: 0, 1, 2, 3, or s
[dependencies]External package dependencies

Compilation Pipeline

M++ compiles through LLVM for native performance:

.mpp source → M++ frontend (parse + type check) → LLVM IR → LLVM optimizer → native binary
# Debug build (fast compile, no optimization)
mpp build

# Release build (slower compile, full optimization)
mpp build --release

# View generated LLVM IR
mpp build --emit-ir

Cross-Compilation

Target different platforms via the --target flag:

mpp build --target x86_64-linux
mpp build --target aarch64-linux
mpp build --target x86_64-windows