MELP Language Reference

Canonical source: ORTAK/dil/ and MELP_KANONΔ°K/05_DIL_OZET.md  |  Status: alpha  |  Updated: August 2026

This page documents what the compiler does today, not what is planned. Where a construct is experimental it is marked πŸ”¬. If you find something here that the compiler does not accept, that is a bug in this page β€” please report it.

Build Path

A .mlp file is compiled to LLVM IR and then to a native binary. There is no interpreter and no runtime to install alongside the result.

# Run
bin/run_melp program.mlp

# Inspect the LLVM IR
bin/run_melp program.mlp --ir

# Produce a native binary
bin/melp_compiler program.mlp -o program

Ground Rules

RuleCorrect βœ…Wrong ❌
Argument separatorfoo(a; b; c)foo(a, b, c)
Decimal mark3,143.14
Comment-- comment// comment
Block terminatorend function (with a space)end_function, }
Return type comes firstnumeric function foo()function foo() -> numeric
One statement per linex = 42x = 42;
Logical operatorsand, or, not&&, ||, !
Modulox mod yx % y
String concatenationa & ba + b
Collectionslist()[1; 2; 3] β€” closed to users

The decimal mark is a comma, which is why the argument separator is a semicolon. This is a language decision, not a locale setting.

Types

MELP has three variable types. There is no null, no void and no none.

TypeRepresentationNotes
numerici64Default: raw i64, up to 18 digits. Exceeding that without the import is compile error E054.
stringi64 handleInline (≀7 bytes) or heap. Concatenation with &.
booleani1 / i64true = 1, false = 0.
numeric x     = 42
string  name  = "MELP"
boolean ready = true

-- The second numeric world: opt in per file
import bigdecimal
numeric pi     = 3,14159265358979323846
numeric amount = 19,99

Two worlds. By default numeric is a raw i64 β€” one CPU instruction, zero overhead. A file that writes import bigdecimal gains automatic big-number promotion driven by the hardware overflow flag, plus decimals stored as mantissa + scale, so 0,1 + 0,2 is exactly 0,3. Writing a decimal without the import is caught by error E055 rather than silently misread.

πŸ”¬ The bigdecimal world is not yet corpus-proven and is not recommended for production. In the default world, i64 overflow wraps silently β€” a deliberate cost.

Variables

The type name is the declaration β€” there is no dim, var or let ceremony, and typing is fully static.

numeric counter = 10
string  lang    = "MELP"
boolean active  = true

-- Reassignment β€” no type keyword
counter = counter + 1
lang    = lang & " language"

const was removed from the keyword list; the compiler may still recognise it, but it is not canonical MELP.

Operators

Arithmetic

x + y
x - y
x * y
x / y
x mod y     -- modulo (not %)

Comparison

x == y
x != y
x < y
x > y
x <= y
x >= y

Logical

x and y
x or y
not x

String Concatenation

string full = "Hello, " & name & "!"

Concatenation is O(nΒ²) today; for hundreds of thousands of joins, build the string incrementally rather than in a tight concatenation loop.

Conditions

if score >= 90 then
    println("A")
else if score >= 80 then
    println("B")
else
    println("C")
end if

then is optional. else if is written on one line. There is no ternary operator β€” it was considered and rejected.

Loops

There is one loop construct. No while, no for, and no continue β€” continue was removed from the language.

Conditional loop

numeric i = 0
loop i < 10
    println(i)
    i = i + 1
end loop

Infinite loop with an exit

loop
    if done then exit end if
    process()
end loop

Iterating a collection

loop each n in numbers
    println(n)
end loop

Functions

-- Return type comes first
numeric function add(numeric a; numeric b)
    return a + b
end function

-- The entry point
numeric function main()
    numeric result = add(3; 5)
    println(result)
    return 0
end function

OK is a value, not a type. There is no void: a function that "returns nothing" returns OK, carried as an i64 in the ABI. The one exception is the entry point: numeric function main() with an explicit return 0 is what the compiler accepts, and it is what the compiler's own source uses.

return returns a value from a function; in main it sets the process exit code. exit is different β€” it leaves a scope (a loop, an if, a match, a named scope), not a function.

Scopes

Everything that occupies memory is a scope. A variable's lifetime is the lifetime of its scope; when the scope closes, everything inside it is cleaned up. A child cannot write directly to a parent's variable β€” the scope boundary is the isolation boundary.

scope search
    loop each i in rows
        loop each j in columns
            if found(i; j) then
                exit search   -- clean exit from nested loops
            end if
        end loop
    end loop
end scope

A scope is already alive and runs by being called β€” counter() creates a new instance on every call. Sharing across a scope boundary happens through three controlled gates:

tunnelpeekchannel
What it carriesOwnership (temporary custody)A snapshot copyA copy of a message
AccessA↔B, privateAβ†’B, one-way, read-onlyMany-to-many, broadcast
The sourceSuspendedActiveIndependent

πŸ”¬ tunnel and peek are parsed, validated and code-generated but currently desugar to a plain assignment; channel is a flat global rather than a FIFO queue yet. The isolation rules they enforce are real today; the richer runtime semantics are not.

Struct, Enum, Match

Data is defined with struct, behaviour with functions. No classes, no inheritance, no virtual methods. A struct is a dead template; new revives it on the heap, and it dies when the calling scope closes.

struct Point
    numeric x
    numeric y
end struct

Point p = Point(10; 20)
println(p.x)

-- On the heap; freed when the calling scope closes
Point q = new struct Point(30; 40)
enum Colour
    Red
    Green
    Blue
end enum

Colour c = Colour.Blue

match c
    case Colour.Red then println("red")
    case Colour.Green then println("green")
    case Colour.Blue then println("blue")
end match

Enum variants are written bare β€” no case keyword in the declaration. case appears only inside match, qualified by the enum name. πŸ”¬ Payload-carrying variants are described in the language spec but are not yet what the compiler accepts; the form above is.

Lists

list() is the collection type. The [...] array literal syntax is closed to users.

-- .add() returns the list, so it is reassigned
names = names.add("Ali")
names = names.add("Can")

loop each n in names
    println(n)
end loop

πŸ”¬ The user-facing collection API is still settling. The form above is the one the compiler's own source uses; treat the surface as unstable and check MELP_LANGUAGE_SPEC.md before relying on it. An algorithmic stack or queue is built by hand on a list today.

Error Handling

An error is resolved inside the scope where it was born and does not spill outward. The construct in use today is the expect block.

expect
    numeric result = risky_operation()
    println(result)
end expect

Nothing is left to runtime guesswork. Where another language might pick a plausible interpretation and continue, MELP stops with an explicit error code. A missing implementation must fail loudly rather than exit 0 β€” silent wrongness is treated as the more dangerous failure.

πŸ”¬ The Result<T; E> type and the ? propagation operator are experimental. try/catch/finally and throw are not part of the language.

debug Blocks

A debug block is a letter to the future: a running, conditional note that stops the program when the world outgrows the assumption it records.

debug
    if member_count > 10000 then stop
    -- Past ten thousand members this design may need
    -- a new database architecture. β€” 2026, the first team
end debug

Compiled with MELP_RELEASE=1, debug blocks are stripped entirely β€” zero cost in the production binary. In a debug build you can break on every block from gdb or lldb.

Imports & FFI

import "accounts.mlp"      -- auto-declare
import bigdecimal          -- the second numeric world

-- C ABI foreign function
external function write(numeric fd; string buf; numeric len)

Circular imports are detected at compile time and reported as errors.

Honesty line: the moment you write external, the memory-safety guarantee ends β€” this is MELP's equivalent of Rust's unsafe. MELP also has no answer to supply-chain security: the import mechanism is still crawling and there is no chain of trust.

Deliberate Absences

These are not features awaiting implementation. They were considered and rejected, and the reasons matter more than the list.

Not in MELPWhy
async, await, spawn, threads, coroutines, futures, callbacksImported patterns. MELP's route is scope + channel + event loop.
nullThe billion-dollar mistake. There is no null value and no null type.
voidOK is a real value; there is nothing to name "no value".
mutex, atomicMutation happens at one point in one scope β€” there is nothing to guard.
Stack, Queue typesLIFO is the life order of scopes; FIFO is the delivery order of a channel.
continueRemoved from the language.
Classes, inheritance, virtual methodsstruct + enum + match is enough.
Ternary operator, tuplesConsidered and rejected.
Garbage collector, borrow checker, &/lifetime annotationsThe scope boundary already determines lifetime.

Tutorial

A step-by-step guide to trying the language from scratch: πŸš€ MELP in 30 Minutes β€” installation, first program, variables, file operations, structs and a small web server example.

← Back to Features MELP in 30 Minutes β†’ Try It in the Web IDE β†’