What Can MELP Do?
From language design to binary safety β the core features.
Every claim on this page carries an evidence stamp: β working Β· π¬ partial / infrastructure exists Β· βΈοΈ design intent, not a measured fact. A claim without an honesty line is not published β that is a house rule, not a disclaimer.
π§ 1. Scope-Oriented Programming β The Model π¬ Partial
Scope is the universal lifetime unit.
MELP's fundamental abstraction is the scope β the way the object is in OOP, the function in FP, the actor in the Actor Model. But the claim is not "we use scopes". The claim is that scope is the single unifying lifetime abstraction: MELP proposes a unified Scope-Oriented Programming model where scope is the primary lifetime abstraction governing memory, resources, concurrency, state, and UI.
| # | Domain scope governs | Rule |
|---|---|---|
| 1 | Memory | scope closes β memory is returned |
| 2 | State | state has no separate life; it lives in a scope |
| 3 | Concurrency | separate scope β no shared state β no race can form |
| 4 | Resources / teardown order | destruction is the reverse of construction; no manual cleanup |
| 5 | Interface tree | the widget tree is the scope tree; a button is the visible face of a scope |
| 6 | Freeze / suspension | a branch freezes, its state is preserved, its siblings keep flowing |
Layering β scope β custody β destroy. Scope is the high-level organising principle; custody is one particular resource-lifetime model running on top of it; destroy is that model's executor. The three are not the same thing. This narrows the claim and strengthens it at once: we do not say "custody is the best memory model" β custody is the first proven realisation, not the only possible one. If custody failed, the conclusion would be "this realisation is wrong", not "Scope-Oriented Programming is wrong".
Honesty line β the relatives. The pieces of this idea
exist in the literature. None of them is new on its own; what
distinguishes MELP is the combination. RAII (C++) binds cleanup
to the type and covers resources only; region-based memory
(TofteβTalpin) covers memory only; ownership/borrowing (Rust, Cyclone)
covers memory and resources; structured concurrency
(coroutineScope, StructuredTaskScope,
Trio nurseries) covers task lifetime only; component trees (React,
Flutter) cover UI and state, and as a framework rule rather than a
language one; process isolation (Erlang/BEAM) covers memory,
concurrency and failure. MELP's scope aims to cover all six.
Erlang note: we are not alone in having a single unit β
Erlang's unit is single too (the process). The difference is that
Erlang's isolation is harder (separate memories, the wall
cannot be pierced), while MELP's wall is drawn by the compiler and the
guarantee ends the moment you write external.
In exchange, you cannot build a GUI tree or memory custody out of a
process. Different trade-offs β neither is superior.
Evidence status: of the six domains, two are measured β memory (4/4 verified deaths, zero leaks, from the runtime registry) and concurrency's "no data race can be constructed" (four adversarial programs, structural refusal). State, interface and teardown order are architecturally resolved but have no end-to-end evidence board yet.
On novelty: lexical scope is ubiquitous in computer science and every ingredient above has precedent. What MELP proposes is the synthesis β scope as the single lifetime abstraction across all six domains at once β and we have not found that combination named or defined elsewhere. We stop short of "the first", though: a formal literature survey has not been done, and until it has, that word appears nowhere on this site. The claim is not smaller for it β only the sentence is.
π‘οΈ 2. Memory Safe β No GC, No Annotations β Working
In MELP every value belongs to the scope it was born in, and is released when that scope closes (custody). Cleaning up memory is the scope's job, not the programmer's β neither a runtime garbage collector nor a manual annotation system like Rust's borrow checker is required.
| Language | Speed | Memory Safety | Extra burden |
|---|---|---|---|
| C / C++ | β | β Manual | β |
| Java / Python | β GC | β | Garbage collector |
| Rust | β | β | Borrow checker annotations |
| MELP | β | β | None β the scope boundary is enough |
Lifetime = scope: a variable's lifetime is the lifetime of the scope it sits in β there is no independent "state" layer that must be managed separately. When you need longer-lived data you declare it in a higher scope; the rule does not change, only the owner does.
Honesty line β four open gaps, stated plainly.
(1) Scope of custody. Custody today covers big numbers
and printed strings β that front is valgrind-proven (zero leaks).
General string temporaries are not yet under custody and do leak. This
is a known, addressed limit that the unified-custody work will close.
"The programmer does not manage memory" is true today within that
scope.
(2) Bounds checking. The architecture of the answer is
drawn but the optional sentinel-collection layer is on the shelf; today
bounds are handled through the existing list
path.
(3) Overflow. In the default world, i64 overflow wraps
silently β a deliberate cost, and one we are now stating rather than
leaving unannounced. Checked-mode intrinsics exist as infrastructure.
(4) The FFI boundary. The moment you write
external, the guarantee ends β MELP's
equivalent of Rust's unsafe. The trust base
under all of this is a young C runtime shim; a permanent valgrind suite
is the backbone of the safety claim and is still an outstanding debt.
β‘ 3. LLVM Native Binary β Working
MELP is not a syntax experiment. Source is compiled to LLVM IR, and then
through llc and the platform linker into a real
ELF binary. No GC, no interpreter, no overhead.
# Inspect the LLVM IR
bin/run_melp example.mlp --ir
# Produce a native binary
melp_compiler example.mlp -o a.out
# Run it β no lli, no interpreter
./a.out
The resulting binary is executed directly by the operating system, without
lli or any runtime. LLVM optimizer passes can be
applied to reach performance comparable with C/C++.
π 4. Concurrency β A Data Race Cannot Be Constructed β / βΈοΈ
MELP's answer to concurrency is not a primitive you reach for; it is the
absence of the condition that makes races possible. The scope boundary
is the isolation boundary: a child cannot write to its parent's
variables, so two scopes have nothing to contend over. Sharing goes through
three controlled gates only β
tunnel (temporary custody),
peek (read-only snapshot) and
channel (a copy of a message). Because mutation
happens at a single point, the language has no
mutex and no atomic
keyword β there is nothing for them to guard.
β Measured: four deliberately adversarial programs β each one an honest attempt to construct a data race β were rejected structurally rather than caught at runtime. The failure is at the language level, not a diagnostic bolted on top.
βΈοΈ Honesty line β what we do NOT claim. "Isolation is a
licence for free parallelism" is a design intent, not a measured
fact. There is no legitimate gate implemented yet, and the runtime
is still single-flow: today MELP does not execute your scopes in parallel.
We are also not shipping async,
await, spawn,
threads, coroutines, futures or callbacks β these are deliberately
rejected imported patterns, not features awaiting implementation. Erlang
has known the right answer for thirty years while the mainstream still
locks mutexes; our route is scope + channel + event loop.
π 5. There Is No State Management β Because State Has No Separate Life π¬ Partial
Flutter manages state; MELP keeps it alive.
The root of the Flutter/React pain is this: the interface tree dies and is reborn on every build, while the data must persist β two lifetimes at war with each other. StatefulWidget, setState, Provider, hooks... every one of them is a layer for managing that mismatch.
MELP does not manage the mismatch, it removes it: the widget tree is the scope tree, and scope is lifetime. Data lives as long as the scope it was born in. The question "where do I put this state, should I lift it up?" cannot arise by definition β because in MELP there is no concept of "state" with a lifecycle of its own.
Honesty line: variables obviously exist β the claim is not "there is no data", it is "there is no separately-lived state concept and no layer managing it". The closest relatives are Phoenix LiveView (process = state owner) and Svelte (the spirit of compiling the reactivity layer away). Our difference: unifying the lifetimes is a language rule, not a framework trick, and it extends to the lifecycle through freeze. π¬ It works in the desktop Melpion; tunnel/channel are partial and it has not yet been proven in a large application.
π§ 6. Freeze β Suspend, Don't Kill π¬ Partial
Life has three verbs: live, sleep, die.
If everything is a scope, and a scope is a unit of life, one consequence is unavoidable: any running branch can be frozen from the outside together with its subtree. State is preserved exactly β counters, timers, everything in memory. It then resumes where it left off, or is closed with cleanup. Freezing is selective: sibling scopes keep flowing.
The most concrete payoff shows up in security incidents. When you detect an attack on a server, the classic choice is painful: you kill the process, the attack stops, and the volatile evidence β session keys, injected code, connection state β evaporates with it. In MELP you freeze that subsystem instead:
| Result | Why |
|---|---|
| The attack cannot advance | A frozen scope cannot take a single step |
| Evidence is preserved | The process never died, so memory stands as it was |
| The evidence perimeter is exact | A scope owns all of its memory (custody) β its boundaries are known |
| Service continues | Other regions / sibling scopes keep flowing |
Working today: the "selective freeze" scenario on the Live Demo page shows this in the browser β three scopes run independently, one is frozen, its siblings keep flowing, and when it is woken it continues from the tick it stopped at. The tick values come from the WASM runtime; nothing is stored in the interface. See it in the Live Demo β
Honesty line: the right word is not "firewall" but
quarantine β freeze does not filter traffic, it halts the
subsystem entirely. This is an application-level control, not an
operating-system-level security boundary; victim data in RAM stays in RAM,
so operational procedure is still required. Kotlin's
suspend is function-level and cooperative from
the inside; Erlang's sys:suspend is
process-level (the closest relative); OS SIGSTOP and VM pause are
coarse-grained and language-blind. π¬ Today it works through an external
host API; a scope freezing itself from within the program, and full
integration into the language syntax, are still in development.
π’ 7. The Type System β Three Types, Zero Type Anxiety β / π¬
MELP has exactly three variable types:
| Type | Default representation | Range |
|---|---|---|
numeric | raw i64 β one CPU instruction | up to 18 digits; with import bigdecimal, unbounded + decimals |
string | handle β inline or heap | text of any length |
boolean | β | true / false |
In classic languages the programmer agonises over "should this be
int8 or int64, float or
double?" In MELP that anxiety does not exist. You declare the
type you need, and the type name is the declaration β there is no
dim/var/let
ceremony, and typing stays fully static.
numeric small = 1
numeric big = 9999999999999
string message = "Hello"
boolean active = true
-- The decimal mark is a comma, which is why
-- ';' separates arguments.
import bigdecimal
numeric pi = 3,14159265358979323846
numeric amount = 19,99
The two-world model: numeric is
raw i64 by default β a single CPU instruction, zero overhead, proven at the
IR level. When a file says import bigdecimal,
that file gains automatic big-number promotion driven by the hardware
overflow flag, plus 3,14 decimal support stored
as mantissa + scale (not floating point), so
0,1 + 0,2 is exactly
0,3. The principle:
nobody pays for what they are not going to use.
Honesty line: the default world (i64) is proven β
and
free. The import bigdecimal world is π¬ β the
mechanism works, but until the test corpus covers it, it is not
recommended for production. Python carries every number on the heap and
therefore always pays; Rust and C wrap or are undefined on overflow and
therefore never pay, accepting the risk. "Free by default, automatic
correctness on request" is the distinctive position. Note the cost we
accept: in the default world, i64 overflow wraps silently. Go and Rust
infer types too, but with a rich type vocabulary; MELP's claim is
plainness β three types, zero ceremony.
The decimal comma is a feature, not a locale.
3,14 β the decimal separator used by most of
the world's population is absent from every mainstream language. It is
present in MELP, and it is why ; is the
argument separator. Using it without the import is caught by an explicit
compile error rather than a silent misreading. We know of no direct
equivalent (spreadsheet formulas do this by locale β that is not a
language).
βοΈ 8. OK Is a Value β Errors Resolve Inside the Scope β
/ π¬
There is no void type in MELP. Functions that
"return nothing" actually return OK β a real
value meaning success, carried as an i64 in the ABI. Mapping
OK onto LLVM's void
is a codegen error, and once was a real one: it was the root of an exit-code
corruption bug.
Errors are handled with the same logic: an error is resolved inside the scope where it was born and does not spill outward. If it must travel, you mark that explicitly. There is no invisible exception chain; error propagation is readable in the code itself.
Honesty line β including a correction. This page
previously said that no return 0 ceremony is
written at the end of main. That was wrong
about today's compiler. The canonical entry point is
numeric function main() with
return 0 β that is what compiles, and it is
what the compiler's own source uses.
OK function main() was a design intent that
never reached the code. The "no void, OK is a value" philosophy holds for
every other function; main is the exception.
The error-handling structure in use today is the
expect block (parser and codegen β
). The
? propagation operator β a relative of Rust's,
differing in that OK is a value rather than a
type and that errors are bound to the scope lifecycle β and the
Result type are still experimental π¬.
β 9. There Is No Stack and No Queue β Because They Are in the Fabric π¬ Partial
MELP has no built-in Stack or
Queue data structure β and needs none, because
both are already woven into the language.
LIFO is the life order of scopes. Scopes close in the reverse of the order they opened; destruction is the reverse of construction. The programmer does not manage a stack β they open and close scopes.
FIFO is the delivery order of a channel β with its slogan: the coffee will not go cold. The queue of the scope that finished its work drains first; the ready order is served while it is hot, and no message sits on the counter while later jobs run.
Honesty line: every language rests on a call stack (LIFO is always there underneath) and Go's channels are FIFO too. MELP's claim is to be the single model that removes the need to present these additionally as a data structure or a concurrency principle. π¬ The channel/tunnel implementation is partial; an algorithmic stack or queue is built by hand on top of a list today β a known gap, noted here rather than hidden, and not part of the claim.
π 10. The debug Block β A Letter From the Past to the Future β
Working
A comment may go unread. A letter runs.
The quietest loss of institutional memory is this: the code stays, the intent dies. The architect who wrote the system retires; nobody reads the comments they left behind. Worse, those comments eventually become lies β "the member count will never pass ten thousand" was written, and was true, and then the world moved and the program silently became wrong.
In MELP you turn the assumption into a sentry that runs:
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
Ten years later, long after that architect has left, on the day the member count passes ten thousand, the program stops and makes someone read the letter. Assumptions never quietly go stale.
Zero cost in production: compiled with
MELP_RELEASE=1, debug
blocks are stripped entirely β not a trace remains in the production
binary. Today you can already break on every block in a debugger
(gdb/lldb).
Honesty line: the closest relatives are JavaScript's
debugger; statement, C's
assert and Eiffel's design by contract. MELP's
version is distinguished by combining block form + condition +
explanation: assert crashes on violation
(taking the evidence and the service with it), while MELP's
stop aims to freeze the violated scope β
state preserved, siblings still flowing, letter read. Violation becomes
quarantine plus briefing rather than catastrophe. That freeze integration
is still at design stage.
One open language decision, stated openly: stripping in
release is in tension with the very scenarios that make this feature most
valuable β a letter should not be deleted exactly where it is needed. The
design is therefore heading towards levelled blocks:
debug stripped as today, alongside a
debug keep that survives into production so a
critical sentry can still run in the environments that need it most β at a
cost of one comparison. Making release-strip opt-in is the alternative under
consideration. The choice is scheduled for the first language session after
the peripherals phase; we are naming the candidates rather than the verdict
because the verdict is not measured yet.
π 11. The Dead Module Philosophy β Working
A natural consequence of the scope model: modules are dead by default. No module allocates resources or occupies memory on its own. The scope that uses a module brings it to life; when that scope closes, the module dies with it β zero idle resources, no garbage collector needed.
Consequence: memory cleanup is the scope's job, not the
programmer's β without a GC and without a borrow checker. Every value
belongs to the scope it was born in (custody); when the scope closes they
are released together, and the custody of a value returned via
return is transferred to the caller.
Honesty line: custody today covers big numbers and printed strings β that front is valgrind-proven (zero leaks). General string temporaries are not yet under custody and do leak; a known, addressed limit that the unified-custody work will close.
π 12. Named Scopes β scope β
Working
scope name gives you a clean conditional exit out
of nested blocks. No flag variables and no exceptions are needed in deep
loops. Every structure opens with an ID card and closes with a signature β
the closing name echoes the opening one, the way an HTML tag does.
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
Note: exit leaves a scope;
return terminates the program with an exit
code. There is one loop construct β loop β
with no while and no
for. The scope
keyword itself is optional rather than mandatory: a verifiable
declaration of intent.
π¨ 13. The Interface Is a Drawing β GUI = Scope Tree π¬ Partial
In most languages the interface is written in a separate language: XML, QML,
HTML, JSX. In MELP the interface is defined with the language's own scopes β
because the two are structurally the same thing. The mapping between
<a><b/></a> and
scope a β¦ end scope is one to one: element =
scope, nesting = ownership, closing tag = name echo, id = scope name,
attribute = property.
The practical consequence: you design your interface by drawing it. You draw in a design tool (Figma or similar) and export SVG; layer names correspond to scope names, and you write the behaviour in MELP. Because the widget tree is the scope tree, the rule "a child dies with its parent" applies in the interface exactly as it does in memory.
"SVG gives the tree a shape; scope gives the tree a life β in HTML the tags are dead, in MELP they live, sleep and die."
Where it is today: an SVG designer and the
.svge format work inside the MELP editor
extension β it recognises widgets (label, textbox, combobox, checkbox,
button) and generates the skeleton of the MELP event handlers
bound to them.
Honesty line: what works is the designer and
.svge β MELP skeleton generation. The
"one click from drawing to running application" flow does
not exist yet β today you write the body of the generated
skeleton yourself. SVG projection in the browser and full embedding of the
GUI into the language syntax are in development. The closest relatives are
QML, SwiftUI and Flutter; our difference is that the interface is not a
separate DSL but a natural extension of the language's memory and lifetime
model.
π§± 14. Struct + Function β No Classes β Working
In MELP data is defined with struct and behaviour
with free functions. The same modelling power without an OOP hierarchy, at
far lower complexity. A struct is a dead template:
new means "revive the dead template", and it is
the only place new is used β a scope is already
alive and runs by being called.
struct Point
numeric x
numeric y
end struct
Point p = Point(10; 20)
Point q = new struct Point(1; 2)
enum Colour
Red
Green
Blue
end enum
Colour c = Colour.Blue
match c
case Colour.Red then println("red")
case Colour.Blue then println("blue")
end match
ποΈ 15. Modular Architecture β Working
Every module has a single responsibility; a central orchestrator is forbidden. Build order is derived automatically from the dependency graph, and circular dependencies are reported as compile-time errors.
Design principle: modules cannot write to each other's
variables β the scope boundary is the isolation boundary. Sharing passes
through three controlled gates only:
tunnel (temporary custody),
peek (read-only copy),
channel (a copy of a message).
Honesty line β supply chain, in three classes. This section is about how the compiler's own source is organised, not about dependency management. Supply-chain risk splits into three, and MELP sits differently in each β collapsing them into one verdict would be dishonest in either direction.
β
Package-ecosystem class (left-pad, log4shell, typosquatting):
structurally absent. MELP has no third-party package ecosystem,
so transitive dependencies β and the risk that rides on them β cannot form.
β Build-chain class (xz): fully exposed, no answer.
Every MELP binary rests on LLVM/clang, libgmp, libc and the linker. The xz
backdoor was precisely this class β a compromise of the build process, not of a
package. We have no mitigation for it today.
π¬ The seed (Thompson's Trusting Trust): route
drawn, not yet walked. The compiler is built from a golden seed. Were
that seed ever compromised, the backdoor would reproduce itself and our
bit-for-bit N1=N2 gate would still go green β the gate proves
determinism, not the cleanliness of the seed. Against this MELP has one
structural advantage: the IR it emits is text, so a
seed-injected backdoor must appear there, an inspection point most self-hosting
languages do not offer. The seed shelf and N-1 chain are the raw material for
diverse double-compiling. Neither is exercised yet.
Closed source does not fix this. MELP is closed source, and it would be easy β and wrong β to present that as a supply-chain answer. It is not: it removes independent audit, reproducible-build verification and the user's own ability to check what they run, so it lowers verifiability rather than raising it. The distinction that matters: the trust a developer holds and the trust a user can independently verify are not the same thing.
π 16. Platform Independence π¬ Partial
Write once, run anywhere. Thanks to the LLVM backend the same source produces native binaries for Linux, macOS and Windows, with no separate cross-compilation tooling.
Current status: Linux x86-64 is fully supported. macOS and Windows support is expected with the beta release.
π― 17. Self-Hosting β The Compiler Compiles Itself β Working
The MELP compiler is written in MELP and compiles its own source. Self-hosting is a maturity threshold for a language, and in MELP it works today β the C++ bootstrap era is behind us.
More than that, it is a mandatory gate on every change: the compiler compiles its own source (N1), the resulting compiler recompiles the same source (N2), and the two outputs must be bit-for-bit identical. If they differ, the change is reverted. The compiler proves itself again every day.
The evidence chain: golden β N1 β N2 β N3, with
diff = 0 at every step, followed by compiling
and running an independent user program. If that chain is not green, no
change is accepted.
Honesty line: a bootstrap test exists in every
self-hosting language. MELP's difference is that it is a mandatory gate on
every change, with no exceptions. This is our answer to build
rot; the answer to assumption rot is the
debug block above.
π Origins β Where the Name Came From Historical β not part of the current claim
MELP began life as MLP Β· Multi Language Programming. The original
idea was that program logic should be independent of the human language and
the syntax style it is written in: one compiler reading Turkish, English,
Russian or Japanese source, with a new language added by writing a
keywords.json file rather than touching the
compiler; and alongside that, a choice of three syntax styles β natural
MELP, C-like braces, Python-like colons β normalised into a single internal
representation before the compiler proper ever saw the code.
That layer is no longer part of the language claim. The name stayed; the focus moved to scope. What MELP proposes today is stated in Β§1 above: scope as the primary lifetime abstraction. We record the origin here because it is true and because it explains the name β not as a feature on offer.
Why it was set down: a language earns attention with one idea, not with a list. Multi-language keywords were a normaliser feature β real, but peripheral to what makes MELP worth using, and it competed for the front page with the thing that actually distinguishes the language. The honest position is the one on this page: one claim, at full size, with its evidence marked.
Try It
You can test all of this from the browser, with nothing to install.