The Myra Programming Language

Minimal.
Native. Hackable.

C++ is always one line away.

A systems programming language in the Pascal/Oberon tradition. Compiles to C++23 via Zig/Clang. Six targets from one source -- Windows, Linux, macOS, ARM64 and WebAssembly. The language definition ships as readable .mld files you can inspect, patch, and extend.

hello.myra
module exe hello;

type
  Color = choices(Red, Green, Blue);
  Point = record
    x: int32;
    y: int32;
  end;

routine greet(const name: string);
begin
  println("Hello, {}!", name);
end;

var
  p: Point;

begin
  @ifdef TARGET_WIN64
  greet("Windows");
  @else
  greet("Linux");
  @endif

  p.x := 42;  p.y := 99;
  println("({}, {})", p.x, p.y);
end.
Minimal by design Compiles to C++23 Seamless C++ interop Hackable .mld definition Six targets, one source Managed strings Record inheritance Objects with virtual dispatch FFI / C interop Variadic routines Structured exceptions Pascal-style sets Built-in test blocks DAP debugger Runs in the browser via WebAssembly Minimal by design Compiles to C++23 Seamless C++ interop Hackable .mld definition Six targets, one source Managed strings Record inheritance Objects with virtual dispatch FFI / C interop Variadic routines Structured exceptions Pascal-style sets Built-in test blocks DAP debugger Runs in the browser via WebAssembly

Everything you need.
Nothing you don't.

Oberon-inspired minimalism. Only the constructs that earn their place.

Native Performance

Compiles to real machine code via C++23. No VM, no garbage collection pauses. Decades of compiler optimisation work, for free.

Seamless C++ Interop

#include any C++ header, use std::vector, call std::sqrt, mix new/delete with Myra's create/destroy. If it's not a Myra keyword, it's C++.

Hackable Compiler

The language definition ships as readable .mld files. Fix a compiler bug. Add a keyword. Change code generation. The language is yours to shape.

Rich Type System

Records with inheritance and bit fields. Objects with virtual dispatch. Overlays. Typed sets. Fixed and dynamic arrays. Pointer types. Routine types.

Six Targets, One Source

Windows, Linux, macOS, ARM64 and WebAssembly from the same file. Conditional compilation with @ifdef TARGET_WIN64. Zig handles every cross-compile; wasm builds run straight in the browser.

Built-in Tests

test "name" begin...end; blocks live in the same source file. Typed assertions (testAssertEqualInt, testAssertTrue, etc.) with file and line reporting.

The language, in code.

Every example below compiles and runs. No hand-waving.

// Overloading -- same name, different signatures
routine max(const a: int32; const b: int32): int32;
begin
  if a > b then return a; end;
  return b;
end;

routine max(const a: float64; const b: float64): float64;
begin
  if a > b then return a; end;
  return b;
end;

// var parameter -- modified in place
routine swap(var a: int32; var b: int32);
var
  tmp: int32;
begin
  tmp := a;  a := b;  b := tmp;
end;

// Recursion
routine fib(const n: int32): int32;
begin
  if n <= 1 then return n; end;
  return fib(n - 1) + fib(n - 2);
end;
type
  Point = record
    x: int32;
    y: int32;
  end;

  // Record inheritance
  Point3D = record(Point)
    z: int32;
  end;

  // Packed -- no padding between fields
  Header = record packed
    magic:   uint32;
    version: uint16;
    flags:   uint8;
  end;

  // Bit fields
  Flags = record packed
    active: uint8 : 1;
    mode:   uint8 : 3;
    level:  uint8 : 4;
  end;

var
  p: Point3D;

begin
  p.x := 10;  p.y := 20;  p.z := 30;
  println("({}, {}, {})", p.x, p.y, p.z);
end.
type
  TAnimal = object
    Name_: string;

    method Init(AName: string);
    begin
      self.Name_ := AName;
    end;

    method Speak();
    begin
      println("{} says: ...", self.Name_);
    end;
  end;

  TDog = object(TAnimal)
    // Override -- virtual dispatch
    method Speak();
    begin
      println("{} says: Woof!", self.Name_);
    end;
  end;

var
  dog: pointer to TDog;
  base: pointer to TAnimal;

begin
  create(dog);
  dog.Init("Buddy");

  // Dispatch through base pointer
  base := pointer to TAnimal(dog);
  base.Speak();  // calls TDog.Speak()
  destroy(dog);
end.
routine getZero(): int32;
begin
  return 0;
end;

var
  x: int32;

begin
  // Software exception
  guard
    raiseexception("Something went wrong");
  except
    println("Caught: {}", getexceptionmessage());
  end;

  // With error code and finally
  guard
    raiseexceptioncode(42, "Custom error");
  except
    println("Code: {}", getexceptioncode());
  finally
    println("Always runs");
  end;

  // Hardware exception (div-by-zero)
  guard
    x := 10 div getZero();
  except
    println("HW exception: {}", getexceptionmessage());
  end;
end.
// User-defined variadic routine
routine sumInts(...): int32;
var
  i: int32;
  sum: int32;
  arg: int32;
begin
  sum := 0;
  for i := 0 to varargs.count - 1 do
    arg := varargs.next(int32);
    sum := sum + arg;
  end;
  return sum;
end;

begin
  println("sum = {}", sumInts(10, 20, 30));   // 60
  println("sum = {}", sumInts(1, 2, 3, 4, 5)); // 15
  println("sum = {}", sumInts());             // 0
end.
var
  s1: set;
  s2: set;
  s3: set;

begin
  s1 := [1, 3, 5];     // bit positions 1, 3, 5
  s2 := [3, 5, 10];

  // Set arithmetic
  s3 := s1 + s2;  // union: [1,3,5,10]
  s3 := s1 * s2;  // intersection: [3,5]
  s3 := s1 - s2;  // difference: [1]

  // Membership test
  if 1 in s1 then
    println("1 is in s1");
  end;

  // Range literal
  s1 := [1..5];   // bits 1 through 5
end.
module exe ffi_demo;

// Per-target library paths -- TARGET_* is defined by the compiler
@ifdef TARGET_LINUX64
@copydll "output/zig-out/lib/libmathlib.so";
@librarypath "output/zig-out/lib";
@elseif TARGET_WIN64
@librarypath "output/zig-out/bin";
@endif

@linklibrary "mathlib";

// clink = C linkage (extern "C", unmangled). cpplink is the default.
routine clink add(const a: int32; const b: int32): int32; external "mathlib";
routine clink quadruple(const x: int32): int32; external "mathlib";

// Variables can be imported too
var version: int32; external "mathlib";

var
  r: int32;

begin
  r := add(3, 5);
  println("add(3, 5) = {}", r);
  println("quadruple(4) = {}", quadruple(4));
  println("lib version = {}", version);
end.
module exe mixed;

// C++ headers pass through verbatim
#include <cmath>
#include <string>
#include <vector>

var
  LS: std::string;
  LV: std::vector<int32_t>*;

begin
  // C++ functions in Myra expressions
  println("sqrt(16) = {}", std::sqrt(16.0));

  // C++ types as Myra variables
  LS := "hello from C++";
  println("len = {}", LS.length());

  // C++ new/delete alongside Myra
  LV := new std::vector<int32_t>();
  LV->push_back(42);
  println("size = {}", LV->size());
  delete LV;
end.

One source.
Six targets.

The same Myra source compiles to Windows, Linux, macOS, ARM64 and WebAssembly. The compiler defines a TARGET_* symbol for the active target, so @ifdef TARGET_WIN64 handles the differences. Zig performs every cross-compile -- there is no second toolchain to install.

  • Identical language semantics across every target
  • TARGET_* symbols injected automatically
  • win64 runs natively; linux64 runs through WSL2
  • wasm32 emits a self-contained HTML -- double-click to run, no server
  • Zig handles all cross-compilation internals
win64 -- Windows x86-64 Runs natively
linux64 -- Linux x86-64 Runs via WSL2
wasm32 -- WebAssembly / WASI Runs in browser
winarm64 -- Windows ARM64 Supported
linuxarm64 -- Linux ARM64 Supported
macos64 -- macOS Apple Silicon Supported

Three kinds.
One language.

Every Myra source file is a module. The kind declaration determines the output artifact.

exe
Executable

A program with a begin..end. entry point. Produces a native executable. The most common module kind.

lib
Static Library

Compiled to .lib (Windows) or .a (Linux). Use exported to expose symbols. Importable with import.

dll
Shared Library

Compiled to .dll or .so. Export C-compatible APIs with exported routine clink. Ideal for plugins and FFI.

A library that describes itself.

The compiler knows about exactly one library path: the standard library. Everything else carries its own build configuration. Dropping in a new dependency requires zero compiler changes.

raylib_import.myra — the library
module lib raylib_import;

// It owns every path it needs.
// The langdef knows nothing about raylib.
@includepath "res/libs/vendor/raylib/include";

@ifdef TARGET_WIN64
  @librarypath "res/libs/vendor/raylib/win64/bin";
  @copydll "res/libs/vendor/raylib/win64/bin/raylib.dll";
@elseif TARGET_LINUX64
  @librarypath "res/libs/vendor/raylib/linux64/bin";
@endif

@linklibrary "raylib";

#include "raylib.h"

end.
game.myra — the consumer
module exe game;

// Name where it lives, then import it.
// That is the whole integration.
@modulepath "res/libs/vendor/raylib";

import
  raylib_import;

const
  CScreenWidth  = 800;
  CScreenHeight = 450;

begin
  InitWindow(CScreenWidth, CScreenHeight, "Raylib Window");
  SetTargetFPS(60);

  while not WindowShouldClose() do
    BeginDrawing();
      ClearBackground(RAYWHITE);
    EndDrawing();
  end;

  CloseWindow();
end.

See Myra in action.

Infographic, walkthroughs, and a deep dive into the compiler architecture.

Myra Infographic
🎵
Deep Dive -- Compiler Architecture
Use the expand button to view full size

"Start with Pascal. Remove everything that is not essential."

-- The Myra design principle