Luna is an experimental, statically typed programming language focused on concise source, strong inference, functional composition, predictable native semantics, and first-class systems interoperability.
The project is currently building an interpreter-first language core in F#. Source is parsed into a source-oriented syntax tree, resolved and type-checked into typed HIR, and executed by a reference interpreter. That pipeline defines Luna semantics before native, WebAssembly, or JavaScript backends are introduced.
Luna combines familiar object-oriented syntax with functional language features:
- immutable bindings by default, with explicit mutation;
- constraint-based static type inference;
- expression-oriented functions and control flow;
- first-class functions and lexical closures;
- discriminated unions and exhaustive pattern matching;
Option<T>andResult<T, TError>instead of ordinary null values;- proper direct and mutual tail recursion without language-stack growth;
- checked integer arithmetic and explicit conversions;
- structural interfaces, also spelled
contract, with trait-like defaults; - experimental external
impl Contract for Typeconformance with coherent, deterministic witness selection; - typed synchronous events with subscription handles, kept distinct from competing-consumer channels and asynchronous broadcast topics;
- higher-order collection pipelines and future provider-backed queries;
- value-first structs with explicit
Box<T>,Rc<T>, andArc<T>ownership in the future native runtime; - read-only-by-default
span<T>scoped views andmemory<T>storable/async leases, with explicit<mut T>writable forms and separate storage owners; - read-only parameters by default, with explicit future
mutaccess andtakeownership transfer; - logical
async fun(...): Tsource signatures whose calls produceTask<T>without repeating task machinery in every declaration; - explicit C ABI and unsafe boundaries for systems integration.
The canonical source extension is .luna. The short .ln extension has identical semantics and tooling behavior.
let offset = 1
let increment: fun(i32): i32 = fun(value) => value + offset
let answer = increment(41)
log.Info($"answer = {answer}")
Single-file applications may use top-level statements, so an explicit main() function is optional.
Experimental external conformance can adapt a type without modifying its declaration:
pub contract Drawable {
Draw(): void
}
impl Drawable for ThirdPartyCircle {
Draw(): void {
RenderCircle(this)
}
}
Discriminated unions define a closed set of named cases. Cases may carry typed values, and matching can unpack those values exhaustively:
pub union LookupResult
| Found value: i32
| Missing
| Failed message: string
pub describe(result: LookupResult): string {
result is
| Found value => $"found {value}"
| Missing => "nothing was found"
| Failed message => $"lookup failed: {message}"
}
let result = Found 42
log.Info(describe(result))
Pattern expressions also support literal values, ranges, bindings, guards, and _ discards:
let score = 8
let label = score is
| 1..5 => "low"
| candidate when candidate % 2 == 0 => $"even: {candidate}"
| _ => "unclassified"
Postfix ? unwraps Some or Ok. A None or Error returns immediately from the current compatible function. Result<T> uses the standard ErrorInfo error type; Result<T, TError> selects a domain-specific error:
pub increment(value: Result<i32, string>): Result<i32, string> {
let number = value?
Ok(number + 1)
}
let result = increment(Ok(41))
let answer = result is
| Ok value => value
| Error message => {
log.Error(message)
0
}
The operand is evaluated once. Result<T, TError>? requires the enclosing function to return a compatible Result with the same error type; Option<T>? similarly requires an Option return.
Bindings are immutable unless the declared name ends in ?:
let count?: i64 = 0
while count < 10 {
count = count + 1
}
log.Info($"count = {count}")
The two uses of ? are distinguished by context: let count? declares mutable storage, while value? is an Option/Result propagation expression.
The current F# compiler and reference interpreter support executable vertical slices for:
- source text, spans, lexing, parsing, and structured diagnostics;
- module declarations,
open, top-level statements, and named functions; - scalar values, checked integers, strings, interpolation, and multiline strings;
- bindings, mutable locals, blocks,
if,while, andloop/exit; - function values, higher-order calls, closures, and captured values;
- direct and mutual proper tail calls;
- pattern expressions and user-defined discriminated unions;
- generic functions and generic unions, including trailing default type arguments on unions;
- core
Option<T>andResult<T, TError>unions; - postfix
?propagation for compatibleOptionandResultfunctions; - bootstrap
std.loglevels:Debug,Info,Warn,Error, andFatal.
The repository is still in the Language Alpha stage. LLVM/native emission, JavaScript and WebAssembly backends, the public standard library beyond bootstrap logging, package management, networking, web frameworks, Godot integration, and self-hosting remain planned work rather than current implementation.
Luna source (.luna or .ln)
-> SourceText and source spans
-> tokens
-> syntax AST
-> declaration collection and name resolution
-> type inference and semantic checks
-> typed HIR
-> F# reference interpreter
The syntax AST records what the programmer wrote. Typed HIR records what the program means, including resolved symbols, inferred types, overload choices, pattern decisions, conversions, and tail transfers. The interpreter executes typed HIR rather than reinterpreting syntax.
src/
Luna.Compiler/ compiler frontend, typed HIR, and interpreter
luna/ command-line host
luna.slnx .NET solution
tests/
Luna.Compiler.Tests/ compiler and interpreter tests
fixtures/language/ paired .luna and .ln conformance fixtures
.docs/
luna-lang-spec.md canonical language specification
impl-roadmap.md implementation order and phase gates
decisions/ accepted architectural decisions
Luna uses the .NET 10 SDK pinned in global.json, which also
selects Microsoft.Testing.Platform for the .NET 10 dotnet test experience.
cd src
dotnet build luna.slnx
dotnet test --solution luna.slnxThe test suite uses MSTest with Shouldly assertions and deterministic Bogus data where generated inputs are useful.
The current CLI exposes check and run:
cd src
dotnet run --project luna/luna.fsproj -- check ../path/to/program.luna
dotnet run --project luna/luna.fsproj -- run ../path/to/program.luna
dotnet run --project luna/luna.fsproj -- run --log-level debug ../path/to/program.ln
dotnet run --project luna/luna.fsproj -- check ../path/to/project-directory
dotnet run --project luna/luna.fsproj -- run ../path/to/core.luna ../path/to/app.lncheck and run accept one or more files or directories. Directory inputs are
searched recursively for both .luna and .ln files in deterministic path
order, and overlapping inputs are de-duplicated. Luna does not require a magic
app or program filename.
Until the CLI is packaged, invoke it through dotnet run as shown above.
- Language specification
- Implementation roadmap
- Toolchain feature map
- Concurrency and parallelism design
- Rust library interoperability and optional LiteRT-LM strategy
- Standard-library design
- Architectural decisions
- Future proving projects
The specification describes accepted language direction, while the roadmap and passing tests determine what is implemented today.
The planned bootstrap sequence is:
F# compiler and reference interpreter
-> target-neutral Luna IR
-> LLVM native backend
-> small native runtime and C ABI
-> Luna becomes capable of expressing its compiler
-> incrementally self-hosted Luna compiler
Self-hosting and replacing LLVM are separate milestones. LLVM may remain Luna's production machine-code backend even after the compiler is written in Luna.
Luna's initial foreign calling convention is the platform C ABI, spelled with
bare extern. A declaration without a body imports C; a public declaration
with a body exports Luna:
extern fun puts(value: Pointer<u8>): i32
pub extern fun luna_add(left: i32, right: i32): i32 {
left + right
}
The planned shared-library build emits a native library, generated C header, and versioned ABI manifest so C, Rust, and other C-interop hosts consume the same verified contract. This remains design-stage work until the native backend and runtime ABI are implemented. See ADR-0012.