Skip to content

tinyBigGAMES/MyraLang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Myra

Discord Follow on Bluesky

What is Myra?

Myra is C++23. You just write it in Pascal syntax.

Under the hood, Myra compiles to C++ 23 and uses Zig as the build system. That means you get everything C++ 23 gives you: the full standard library, every platform target Zig/clang supports, every optimization the compiler can produce, without writing a line of C++. You write clean, structured Pascal-style code. Myra handles the rest.

module exe hello;

begin
  writeln("Hello from Myra! 🚀");
end.

When you need to drop to raw C++, to call a C++ API directly, include a header, or write a helper that C++ expresses better, you can do that inline, without leaving your source file. cppstart header / cppstart source / cppend inject raw C++ into the generated output. cpp("expression") embeds a C++ expression at any call site. It is not an escape hatch. It is a designed feature. The standard library itself uses it.

Myra takes its syntax philosophy from Oberon: start with Pascal and remove everything that is not essential. What remains is clean, readable, and unambiguous. begin..end blocks, := assignment, strong static typing, and a module system that replaces header files entirely. No cruft, no legacy baggage. Just the parts of Pascal that were always right.

The entire toolchain ships in the box. Zig, clang, the C++ runtime, the standard library, the LSP server, the debugger adapter: everything needed to go from source to native binary is included in the release. There is nothing to install, configure, or set up. You unzip, add bin\ to your PATH, and write code.

🎯 Who is Myra For?

Myra is for developers who want native performance and low-level control without fighting the language. If you are building any of the following, Myra is worth a look:

  • Systems software: Write low-level code with full pointer arithmetic, packed structs, union types, and direct memory management. Myra does not hide the machine from you.
  • Game engines and tools: Call C libraries (SDL3, raylib, etc.) directly via FFI with no boilerplate. Target Windows and Linux from the same codebase. Shared library interop is a first-class feature, not an afterthought.
  • DLL / shared library development: Export clean C-linkage APIs from a Myra dll or lib module and consume them from any language that speaks C ABI.
  • Cross-platform CLI tools: Compile once, run on Windows and Linux64. WSL2 integration means you can build and test Linux binaries without leaving Windows.
  • Embedded tooling: Small, predictable binaries with configurable optimization levels (releasesmall, releasefast, releasesafe). The Zig backend produces tight output.
  • Learning systems programming: Pascal-family syntax is famously readable and explicit. Myra adds modern ideas while keeping the code approachable for people coming from high-level languages.

✨ Key Features

Myra is a complete language for systems-level development. These are the capabilities that ship today:

  • 🔧 Pascal-family syntax: Clean, readable, case-insensitive. begin..end blocks, := assignment, strong typing throughout. Familiar to Pascal and Delphi developers; readable even to those who are not.
  • 🎯 Native binaries: Compiles to real x86-64 executables, DLLs, and static libraries. No VM, no bytecode, no interpreter. The output runs bare metal.
  • 🌐 Cross-platform: Target Windows (Win64) or Linux (Linux64) from the same source. Cross-compile from Windows via WSL2 with no additional configuration.
  • 🔗 FFI / C interop: Call any C library with external declarations and "C" linkage. Full varargs support for printf-style APIs. Perfect ABI compatibility — structs, unions, anonymous unions, and bit fields map directly to their C equivalents with no manual adjustment.
  • 🔄 C Header Importer: TMyraCImporter converts C headers into ready-to-use Myra modules automatically. Point it at a .h file and it preprocesses with Zig/Clang, parses all declarations, and emits a complete .myra binding with the correct types, signatures, and cross-platform directives — no manual tweaking required.
  • 📦 Module system: Three module kinds: exe (executable), dll (shared library), lib (static library). A clean import mechanism wires them together without header files.
  • 🧬 Rich type system: Records with inheritance and field alignment, classes with methods and virtual dispatch, unions, enums, sets, fixed and dynamic arrays, typed and untyped pointers, routine types, and bit fields.
  • ⚠️ Structured exception handling: try/except/finally with full hardware exception support for divide-by-zero, access violations, and other CPU-level faults.
  • 🔄 Routine overloading: Multiple routines with the same name resolved by parameter signature. The C++ backend handles name mangling transparently.
  • 📊 Sets: Pascal-style bit-set types backed by a 64-bit integer. Full set arithmetic: membership (in), union (+), intersection (*), and difference (-).
  • 📝 Managed strings: Reference-counted UTF-8 string and UTF-16 wstring types with automatic lifecycle management. Emoji, CJK, and accented characters work without any special handling.
  • 💾 Full memory control: new/dispose for class instances, getmem/freemem for raw allocation, resizemem for reallocation. You decide what lives where.
  • 🔢 Variadic routines: Define your own variadic routines with .... Access argument count and values via varargs.count and varargs.next(T).
  • 🏷️ Version info and icons: Embed metadata and application icons into Windows executables via directives. No post-build steps or resource compilers required.
  • 🔀 Conditional compilation: $ifdef/$ifndef/$elseif/$else/$endif with predefined platform symbols. Write platform-specific code in the same file cleanly.
  • 🛠️ Built-in CLI: The myra command scaffolds new projects, builds, runs, and cleans with a single command. No Makefiles, no CMake, no build configuration required for the common case.
  • 🔌 Language Server Protocol: A full LSP server ships with Myra and works with any LSP-compatible editor. Run it out-of-process as MyraLSP.exe or embed it in-process via the TMyraLSPServer class.
  • 🐛 Integrated debugger: DAP-compatible debugger integration via LLDB. The $breakpoint directive records source locations into a .breakpoints file that the debugger loads automatically. An interactive debug REPL is included for command-line debugging.
  • Built-in test blocks: test "name" begin ... end; blocks for inline unit tests, compiled and run as part of the test suite with colour-coded pass/fail output.

🚀 Getting Started

Every Myra program is a module. The module kind (exe, dll, or lib) is declared at the top of the file and determines what artifact gets built. An executable module has a begin..end. body that serves as the program entry point. There is no main() to wire up, no boilerplate to write.

Here is a complete program that demonstrates cross-platform conditional compilation and the writeln format string syntax:

module exe hello;

//$target win64
//$target linux64

begin
  $ifdef TARGET_WIN64
  writeln("Hello from Myra, running on WIN64");
  $elseif TARGET_LINUX64
  writeln("Hello from Myra, running on LINUX64");
  $else
  writeln("Hello from Myra, running on UNKNOWN");
  $endif

  writeln("Name: {}, Age: {}", "Jarrod", 42);
  writeln("Pi is approximately {:.4f}", 3.14159);
  writeln("Hex: 0x{:X}, Octal: {:o}", 255, 255);

  writeln("Hello 🌍 World!");
end.

The writeln statement accepts a format string with {} placeholders that are matched left-to-right to the arguments that follow. Standard format specifiers are supported inside the braces: {:.4f} for fixed-point precision, {:X} for uppercase hex, {:o} for octal, and more. The write statement works identically but does not append a newline.

The $target directive (commented out above) lets you lock a source file to a specific platform. When left commented, the compiler target is controlled at the command line or from the build configuration. The TARGET_WIN64 and TARGET_LINUX64 symbols are injected automatically based on the active target, so you can write portable code that branches cleanly at compile time.

📖 Language Tour

Routines

Routines are the basic unit of abstraction in Myra. They are declared with the routine keyword and can return a value using return. Parameters are passed by value (as const) by default. Use var to pass by reference when you need the routine to modify the caller's variable. Routines can be overloaded, meaning multiple routines can share the same name as long as their parameter signatures differ.

Local type, const, and var sections can appear inside a routine body before the begin block, keeping definitions scoped to where they are needed.

module exe routines;

routine add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

// Routine overloading - same name, different types
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;

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

// var parameter - modified in place
routine inc(var x: int32);
begin
  x := x + 1;
end;

var
  x: int32;

begin
  writeln("add(3, 4) = {}", add(3, 4));
  writeln("max int = {}", max(3, 7));
  writeln("max float = {}", max(3.5, 2.8));
  writeln("fib(10) = {}", fib(10));

  x := 10;
  inc(x);
  writeln("after inc: x = {}", x);
end.

Records

Records are value types. They live on the stack (or inline in their containing structure) and are copied on assignment. Myra gives you full control over memory layout: you can pack a record to remove padding, specify an explicit alignment for SIMD or DMA use cases, define individual fields as bit fields with a : width specifier, and derive one record from another to inherit its fields.

Record literal syntax (RecordType{ field: value, ... }) lets you construct and assign records in a single expression, which is particularly useful for initialising nested structures or passing records to routines.

module exe records;

type
  // Basic record
  Point = record
    x: int32;
    y: int32;
  end;

  // Packed record - no padding between fields
  PackedRec = record packed
    a: int8;
    b: int8;
    c: int8;
  end;

  // Aligned record - useful for SIMD, DMA buffers, etc.
  Align16Rec = record align(16)
    a: int8;
    b: int8;
  end;

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

  // Bit fields (: width)
  Flags = record
    active: int32 : 1;
    mode:   int32 : 3;
    level:  int32 : 4;
  end;

var
  p: Point;
  p3: Point3D;
  f: Flags;

begin
  // Record literal syntax
  p := Point{ x: 10, y: 20 };
  writeln("Point: {}, {}", p.x, p.y);

  // Inherited fields accessible directly
  p3.x := 100;
  p3.y := 200;
  p3.z := 300;
  writeln("Point3D: {}, {}, {}", p3.x, p3.y, p3.z);

  f.active := 1;
  f.mode   := 7;
  f.level  := 1;
  writeln("Bits: {}, {}, {}", f.mode, f.level, f.active);
end.

Classes

Classes are reference types: they are always heap-allocated and accessed through pointers. They support fields, methods, single-level inheritance, virtual dispatch, and parent calls for delegating to an overridden method in the base class. Virtual dispatch happens automatically when you call a method through a base-class pointer, with no explicit vtable management needed.

Classes are allocated with new and released with dispose. There is no garbage collector; you manage lifetime explicitly. This keeps the memory model predictable and deterministic, which matters in games, embedded tools, and any context where latency spikes are unacceptable.

module exe classes;

type
  TAnimal = class
    Name_: string;
    Age: int32;

    method Init(AName: string; AAge: int32);
    begin
      Self.Name_ := AName;
      Self.Age   := AAge;
    end;

    method Speak();
    begin
      writeln("Animal {} says: ...", Self.Name_);
    end;

    method Describe();
    begin
      writeln("I am {}, age {}", Self.Name_, Self.Age);
    end;
  end;

  TDog = class(TAnimal)
    Breed: string;

    method Init(AName: string; AAge: int32; ABreed: string);
    begin
      Self.Name_ := AName;
      Self.Age   := AAge;
      Self.Breed := ABreed;
    end;

    // Override - virtual dispatch through base pointer
    method Speak();
    begin
      writeln("Dog {} says: Woof!", Self.Name_);
    end;

    // Override with parent call
    method Describe();
    begin
      parent.Describe();
      writeln("I am a {} breed", Self.Breed);
    end;
  end;

var
  animal: pointer to TAnimal;
  dog: pointer to TDog;
  cat_as_animal: pointer to TAnimal;

begin
  // Allocate on heap
  new(animal);
  animal^.Init("Generic", 5);
  animal^.Speak();

  new(dog);
  dog^.Init("Buddy", 3, "Golden Retriever");
  dog^.Speak();
  dog^.Describe();

  // Virtual dispatch through base pointer
  cat_as_animal := pointer to TAnimal(dog);
  cat_as_animal^.Speak(); // calls TDog.Speak()

  dispose(animal);
  dispose(dog);
end.

Enumerations and Constants

Constants in Myra are typed and can be formed from constant expressions. Enumerations define an ordered set of named values. By default the ordinal values start at zero and increment, but you can assign explicit integer values to any member, which is useful when the enum must match a C API or a wire protocol.

Enumeration values support comparison operators (=, <>, <, >, etc.) and can be cast to and from integer types for interop purposes.

module exe enums_consts;

const
  MAX_SIZE   = 100;
  PI         = 3.14159;
  APP_NAME   = "MyApp";
  EXPR_CONST = 10 + 5;

type
  Color    = (Red, Green, Blue, Yellow);
  Priority = (Low = 0, Medium = 5, High = 10, Critical = 20);

var
  c: Color;
  p: Priority;

begin
  writeln("MAX_SIZE = {}", MAX_SIZE);
  writeln("PI = {}", PI);

  c := Green;
  writeln("c = Green: {}", c = Green);
  writeln("Green > Red: {}", Green > Red);
  writeln("Green < Yellow: {}", Green < Yellow);

  p := Medium;
  writeln("Medium = {}", int32(p));
  writeln("Medium > Low: {}", p > Low);
  writeln("Medium < Critical: {}", p < Critical);
end.

Arrays

Myra supports both fixed-size and dynamic arrays. Fixed arrays have their bounds declared at compile time and live on the stack or inline in their containing record. Dynamic arrays are heap-allocated, reference-counted, and resized with setlength. Both kinds are zero-indexed by default, but fixed arrays can declare any integer range as their index bounds.

The len() intrinsic returns the number of elements in either kind of array. For dynamic arrays, setlength grows or shrinks the allocation while preserving existing elements. Shrinking is safe. Elements beyond the new length are discarded.

module exe arrays;

type
  IntArr5 = array[0..4] of int32;

var
  // Fixed array - zero-based
  nums: IntArr5;
  // Fixed array - one-based
  oneBased: array[1..3] of int32;
  // Dynamic array
  dyn: array of int32;
  i: int32;

begin
  // Fixed array
  nums[0] := 10; nums[1] := 20; nums[2] := 30;
  nums[3] := 40; nums[4] := 50;
  writeln("Fixed len: {}", len(nums));

  // Dynamic array - setlength allocates/resizes
  setlength(dyn, 3);
  dyn[0] := 10; dyn[1] := 20; dyn[2] := 30;
  writeln("Dyn len: {}", len(dyn));

  // Grow - existing elements preserved
  setlength(dyn, 5);
  writeln("After grow: {} {} {} {} {}", dyn[0], dyn[1], dyn[2], dyn[3], dyn[4]);

  // Shrink
  setlength(dyn, 2);
  writeln("After shrink: {} {}", dyn[0], dyn[1]);
end.

Sets

Pascal-style sets are one of Myra's most expressive features for flag and membership logic. A set is backed by a 64-bit integer where each bit represents the presence or absence of an element. Sets support the full algebra: union (+), intersection (*), difference (-), and membership testing (in). This makes them significantly more readable than hand-rolled bitmask operations while compiling down to the same bitwise instructions.

Set literals use curly-brace syntax ({1, 3, 5}). Membership tests read naturally: if 1 in s1 then.

module exe sets;

var
  s1: set;
  s2: set;
  s3: set;

begin
  s1 := {1, 3, 5};       // bits 1, 3, 5
  s2 := {3, 5, 10};      // bits 3, 5, 10

  // Union, intersection, difference
  s3 := s1 + s2;         // {1, 3, 5, 10}
  writeln("Union: {}", int64(s3));

  s3 := s1 * s2;         // {3, 5}
  writeln("Intersection: {}", int64(s3));

  s3 := s1 - s2;         // {1}
  writeln("Difference: {}", int64(s3));

  // Membership test
  s1 := {1, 3, 5, 10};
  if 1 in s1 then
    writeln("1 in s1: true");
  end;
  if not (2 in s1) then
    writeln("2 not in s1: true");
  end;
end.

Strings

Myra has two managed string types. string stores UTF-8 encoded text and is the default for all general-purpose string work. wstring stores UTF-16 encoded text and is useful when interfacing with Windows APIs that require wide strings. Both types are reference-counted and freed automatically. There is no StrNew/StrDispose dance, no null terminator to manage by hand.

String literals can contain any Unicode codepoint directly in source: accents, CJK characters, emoji, whatever your editor can represent. The utf8() intrinsic converts a wstring to string so it can be passed to writeln or any routine that expects UTF-8.

Raw string literals are prefixed with @" and treat everything literally. No escape sequences are processed inside them, making them ideal for regular expressions, Windows paths, and any text that would otherwise need heavy escaping. Wide string literals are prefixed with L".

module exe strings;

var
  s:  string;
  ws: wstring;

begin
  s := "Hello, Myra!";
  writeln("Basic: {}", s);

  // UTF-8: accents, CJK, emoji all work natively
  s := "Héllo Wörld! 你好 🎉";
  writeln("UTF-8: {}", s);

  // Raw string - no escape processing
  s := @"C:\Users\Dev\no\escaping\needed";
  writeln("Raw: {}", s);

  // wstring - UTF-16
  ws := L"Wide Hello!";
  writeln("WString: {}", utf8(ws));

  ws := L"Héllo Wörld! 你好 🎉";
  writeln("Wide UTF-16: {}", utf8(ws));
end.

Control Flow

Myra provides the full set of structured control flow constructs you would expect from a Pascal-family language. for..to and for..downto handle counted iteration. while..do tests before the loop body. repeat..until tests after. case dispatches on an ordinal value with support for single values, comma-separated value lists, and inclusive ranges, all in the same statement.

Every block is closed with end, making nesting unambiguous without relying on indentation rules or brace counting.

module exe control;

var
  i: int32;
  x: int32;

begin
  // for..to and for..downto
  for i := 0 to 4 do
    write(" {}", i);
  end;
  writeln("");

  for i := 5 downto 1 do
    write(" {}", i);
  end;
  writeln("");

  // repeat..until
  i := 0;
  repeat
    i := i + 1;
  until i = 5;
  writeln("repeat ended at: {}", i);

  // while..do
  i := 10;
  while i > 0 do
    i := i - 3;
  end;
  writeln("while ended at: {}", i);

  // case with single values, comma lists, and ranges
  x := 5;
  case x of
    1:       writeln("one");
    2, 3:    writeln("two or three");
    4..6:    writeln("four to six");
  else
    writeln("other");
  end;
end.

Exceptions

Myra uses try/except/finally for structured error handling. The except block runs if an exception is raised inside the try block. The finally block always runs, whether an exception occurred or not, making it the right place to release resources. Both blocks can appear together in a single try statement.

Hardware exceptions raised by the CPU (divide-by-zero, null pointer dereference, illegal instruction) are caught by the same mechanism. The getexceptioncode() and getexceptionmessage() intrinsics retrieve details about the active exception from within an except block. Use raiseexception() to raise a software exception with a message, or raiseexceptioncode() to raise one with both a code and a message.

module exe exceptions;

routine getZero(): int32;
begin
  return 0;
end;

begin
  // Basic try/except
  try
    raiseexception("Test error");
  except
    writeln("Caught: code={}, msg={}", getexceptioncode(), getexceptionmessage());
  end;

  // try/except/finally
  try
    raiseexceptioncode(42, "Custom error");
  except
    writeln("Except: code={}", getexceptioncode());
  finally
    writeln("Finally always runs");
  end;

  // Hardware exception - divide by zero
  try
    writeln("Result: {}", 10 div getZero());
  except
    writeln("Hardware exception caught: {}", getexceptionmessage());
  end;
end.

Memory Management

Myra gives you two levels of memory control depending on what you are building. For class instances, use new to allocate on the heap and dispose to release. For raw blocks of memory such as buffers, C-style arrays, and interop structures, use getmem to allocate, freemem to release, and resizemem to grow or shrink an existing allocation while preserving its contents.

Typed pointers (pointer to T) support the address-of operator (address of), dereference (^), and pointer arithmetic. You can cast between pointer types with an explicit cast expression. This gives you the same memory-level access you would have in C, with the same explicitness that makes the code easy to audit.

module exe memory;

var
  p:  pointer;
  pb: pointer to int8;
  x:  int32;
  px: pointer to int32;

begin
  // Raw allocation
  p := getmem(100);

  pb := pointer to int8(p);
  pb^ := 42;
  writeln("Value at offset 0: {}", pb^);

  pb := pointer to int8(p) + 50;
  pb^ := 99;
  writeln("Value at offset 50: {}", pb^);

  // Grow - preserves existing data
  p := resizemem(p, 200);
  pb := pointer to int8(p);
  writeln("Preserved offset 0: {}", pb^);

  freemem(p);

  // Typed pointer and address-of
  x := 42;
  px := address of x;
  writeln("Via pointer: {}", px^);
  px^ := 100;
  writeln("x is now: {}", x);
end.

Pointers and Unions

Named pointer types let you give a typed pointer an alias, which improves readability and makes complex declarations easier to work with. Unions declare a set of fields that all share the same memory region. Reading through one field and writing through another is a well-defined operation in Myra, matching C union semantics exactly.

Unions can be standalone types or embedded anonymously inside a record. The anonymous union form is the idiomatic way to represent a tagged variant type where a tag field discriminates between interpretations of the shared payload.

module exe pointers_unions;

type
  // Named pointer type
  PInt32 = pointer to int32;

  // Standalone union - all fields share memory
  IntOrFloat = union
    i: int32;
    f: float32;
  end;

  // Record with anonymous union inside (tagged variant)
  Variant = record
    tag: int32;
    union
      asInt:   int32;
      asFloat: float32;
    end;
  end;

var
  p: PInt32;
  x: int32;
  u: IntOrFloat;
  v: Variant;

begin
  x := 12345;
  p := address of x;
  writeln("Via PInt32: {}", p^);

  // Write int, read back as float bits
  u.i := 0x3F800000;  // IEEE 754 for 1.0
  writeln("Union float: {}", u.f);

  // Tagged variant
  v.tag := 1;
  v.asInt := 99;
  writeln("Variant int: {}", v.asInt);
end.

Variadic Routines

User-defined variadic routines accept a variable number of arguments. Declare the variadic parameter with ... and use varargs.count to get the argument count and varargs.next(T) to retrieve each argument in order. Type safety is the caller's responsibility. varargs.next(T) reinterprets the next slot as type T, matching the semantics of C's va_arg.

varargs.copy() returns a snapshot of the current argument cursor, enabling multi-pass iteration over the same argument list, useful when you need to scan arguments twice: once to count, once to process.

module exe variadic;

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;

routine printAll(const prefix: string; ...);
var
  i: int32;
begin
  for i := 0 to varargs.count - 1 do
    writeln("{}: {}", prefix, varargs.next(int32));
  end;
end;

begin
  writeln("sumInts(10, 20, 30) = {}", sumInts(10, 20, 30));
  writeln("sumInts(1, 2, 3, 4, 5) = {}", sumInts(1, 2, 3, 4, 5));
  writeln("sumInts() = {}", sumInts());
  printAll("item", 100, 200, 300);
end.

Module System

Myra's module system maps one source file to one output artifact. The module kind is declared on the first line of the file. There are three kinds: exe for executables, dll for shared libraries, and lib for static libraries. This explicit declaration means the build system always knows what to produce without external configuration files for the common case.

Symbols are module-private by default. Use the public keyword to export a symbol from a lib or dll module.

Executable (exe)

An executable module has a begin..end. body that serves as the entry point:

module exe myprogram;

begin
  writeln("Hello!");
end.

Static Library (lib)

A static library is compiled to .lib on Windows and .a on Linux. Import it into another module with the import statement. Only symbols marked public are visible to importers.

module lib math;

// Private helper - not exported
routine dbl(const x: int32): int32;
begin
  return x + x;
end;

// Public - visible to importers
public routine "C" add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

public routine "C" quadruple(const x: int32): int32;
begin
  return dbl(dbl(x));
end;

end.

Shared Library (dll)

A shared library is compiled to .dll on Windows and .so on Linux. Export symbols with public. External callers use the C ABI when the routine is declared with "C" linkage.

module dll mylib;

public routine "C" add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

public var version: int32 = 1;

end.

Importing Modules

Use import to bring a lib module into scope. Imported symbols are accessed through the module name as a namespace qualifier:

module exe myapp;

import math;

begin
  writeln("{}", math.add(3, 5));
  writeln("{}", math.quadruple(5));
end.

FFI: Calling C Libraries

Myra's FFI story is straightforward: declare the function signature, name the library, and call it. Use the external keyword to declare a C function that lives in a DLL or shared object. Use "C" linkage to tell the compiler to emit an unmangled symbol name, which is what virtually every C library expects. Varargs (...) are fully supported for printf-style functions.

The optional name "alias" form lets you map a Myra identifier to a differently-named export, which is useful when the C symbol name conflicts with a Myra keyword or contains characters that are not valid in identifiers.

module exe ffi_demo;

// Declare an external C function
routine "C" printf(const fmt: pointer to char; ...): int32;
external "msvcrt.dll";

// External with name alias
routine "C" myFunc(): int32;
external "mylib.dll" name "my_actual_export";

// External variable
var gCounter: int32;
gCounter: external "mylib.dll";

Use $linklibrary to tell the build system which library to link, and $librarypath to add a directory to the linker search path:

module exe uselib;

$linklibrary "mylib.lib"
$librarypath "libs/"

Use $copydll to copy a shared library to the output directory at build time, so the executable can find it at runtime:

$copydll "libs/mylib.dll"

C Header Importer

TMyraCImporter automates the conversion of C headers into native Myra modules. It invokes Zig's bundled Clang frontend to preprocess the header — expanding macros, resolving includes, and handling platform ifdefs — then parses the result and emits a complete .myra module file. The generated module contains all types (structs, unions, anonymous unions, enums, typedefs, bit fields), constants extracted from #define values, and external routine declarations with the correct "C" linkage. The output is ready to import and use directly; no manual editing is required for the common case.

The importer is cross-platform aware from the start. You configure per-target DLL names, library paths, and $copydll directives separately for each platform, and the generated module uses $ifdef TARGET_WIN64 / $elseif TARGET_LINUX64 blocks to select the right settings at compile time.

Here is the complete configuration used to import raylib:

LImporter := TMyraCImporter.Create();
try
  LImporter.SetSavePreprocessed(True);
  LImporter.SetDllName(tpWin64,   'raylib');
  LImporter.SetDllName(tpLinux64, 'raylib');
  LImporter.AddLibraryPath(tpWin64,   'res/libs/raylib/bin');
  LImporter.AddCopyDLL(tpWin64,       'res/libs/raylib/bin/raylib.dll');
  LImporter.AddLibraryPath(tpLinux64, 'res/libs/raylib/bin');
  LImporter.AddCopyDLL(tpLinux64,     'res/libs/raylib/bin/libraylib.so.550');
  LImporter.SetOutputPath('res\\libs\\raylib\\src');
  LImporter.AddIncludePath('res\\libs\\raylib\\include\\');
  LImporter.AddSourcePath('res\\libs\\raylib\\include\\');
  LImporter.SetHeader('res\\libs\\raylib\\include\\raylib.h');
  LImporter.SaveToConfig('res\\libs\\raylib\\raylib.toml');
  if LImporter.Process() then
    WriteLn('SUCCESS')
  else
    WriteLn('ERROR: ', LImporter.GetLastError());
finally
  LImporter.Free();
end;

This produces res/libs/raylib/src/raylib.myra — a complete binding for raylib that compiles and runs on both Win64 and Linux64 from the same source file. The generated module header looks like this:

module lib raylib;

$ifdef TARGET_WIN64
$librarypath "res/libs/raylib/bin"
$copydll "res/libs/raylib/bin/raylib.dll"
$elseif TARGET_LINUX64
$librarypath "res/libs/raylib/bin"
$copydll "res/libs/raylib/bin/libraylib.so.550"
$endif

$ifdef TARGET_WIN64
const cDllName = "raylib";
$elseif TARGET_LINUX64
const cDllName = "raylib";
$endif

All C declarations follow. Structs map to Myra records, unions map to Myra unions (including anonymous unions embedded in records), enums become Myra enum types, #define integer and float constants become public const values, and function declarations become external routines.

The importer provides post-processing hooks for the cases where generated output needs adjusting:

  • AddExcludedType(name) — skip a type and all declarations that reference it (useful for platform types like va_list)
  • AddExcludedFunction(name) — skip a specific function declaration
  • InsertTextBefore(target, text) / InsertTextAfter(target, text) — inject lines at a specific point in the output
  • InsertFileBefore(target, path) / InsertFileAfter(target, path) — splice in the contents of a file at a specific point
  • ReplaceText(old, new) — perform a targeted substitution in the output
  • AddModuleImport(name) — add an import statement to the generated module
  • SaveToConfig(path) / LoadFromConfig(path) — persist and reload the full importer configuration as a TOML file so imports are reproducible

C++ Escape Hatches

Because Myra compiles to C++ 23, it provides a set of escape hatches that let you drop raw C++ directly into generated code when the situation demands it. These are intentionally low-level tools. They exist for cases where you need access to the C++ standard library, need to declare a helper function with C++ idioms, or need to compute a value using a C++ expression that has no direct Myra equivalent. They are not the normal way to write Myra code, but they make the standard library modules possible without requiring FFI declarations for every C++ API.

There are three forms.

cppstart header / cppend: placed at module level, injects raw C++ text into the header section of the generated file. Use this for #include directives and any declarations that must appear before the module's code:

module lib Files;

cppstart header
#include <fstream>
#include <filesystem>
#include <string>
cppend

cppstart source / cppend: placed inside a routine body or at module level, injects a block of raw C++ statements into the generated source:

cppstart source
static inline std::fstream* myra_files_stream(void* p) {
    return reinterpret_cast<std::fstream*>(p);
}
cppend

public routine OpenRead(const AFileName: string): TFileStream;
begin
  cppstart source
    auto* __s = new std::fstream(AFileName, std::ios::in);
    if (!__s->is_open()) { delete __s; __s = nullptr; }
  cppend
  return cpp("(void*)__s");
end;

cpp("expression"): an inline expression escape. The string literal is emitted verbatim as a C++ expression at the call site:

public routine ReadChar(): char;
begin
  return char(cpp("std::cin.get()"));
end;

public routine Flush();
begin
  cpp("std::cout.flush()");
end;

The convention in Myra's standard library is to use double-underscore prefixed names (e.g. __s, __r, __ok, __sz) for variables declared inside cppstart source blocks to avoid name collisions with Myra-generated identifiers.

Directives and Conditional Compilation

Directives begin with $ and are processed at compile time. They control which code gets compiled, what the compiler emits, and how the build system links the output. Directives are not preprocessor macros. They are part of the language grammar and interact cleanly with the rest of the compiler pipeline.

The conditional compilation directives ($ifdef, $ifndef, $elseif, $else, $endif) let you include or exclude code based on defined symbols. This is the idiomatic way to write platform-specific code in Myra without splitting it across separate files.

module exe conditional;

// Define your own symbols
$define DEBUG

$ifdef DEBUG
  $message hint "Debug build active"
$endif

// Target-specific declarations
$ifdef TARGET_WIN64
routine "C" Sleep(const ms: uint32);
external "kernel32.dll";
$endif

$ifdef TARGET_LINUX64
routine "C" usleep(const us: uint32): int32;
external "libc.so.6";
$endif

begin
  $ifdef DEBUG
  writeln("Running in DEBUG mode");
  $endif

  $ifdef TARGET_WIN64
  Sleep(0);
  $endif
end.

Predefined symbols (automatically set by the compiler based on $target):

Symbol When defined
MYRA Always
TARGET_WIN64 $target win64
WIN64, MSWINDOWS, WINDOWS $target win64
TARGET_LINUX64 $target linux64
LINUX, POSIX, UNIX $target linux64
CPUX64 win64 or linux64
CONSOLE_APP $subsystem console (default)
GUI_APP $subsystem gui

Full directive reference:

Directive Description
$subsystem type console (default) or gui, controls whether the executable has a console window
$target platform Set target: win64, linux64
$optimize level debug, releasesafe, releasefast, releasesmall
$define name Define a conditional symbol
$undef name Undefine a symbol
$ifdef name Compile if symbol defined
$ifndef name Compile if symbol not defined
$elseif name Else-if branch
$else Else branch
$endif Close conditional
$message level "text" Emit hint/warn/error/fatal at parse time
$breakpoint Record DAP debugger breakpoint location
$linklibrary "path" Link a library
$librarypath "path" Add linker search path
$modulepath "path" Add module search path
$copydll "path" Copy DLL to output directory
$exeicon "path" Embed icon in Windows EXE
$addverinfo Enable version info embedding
$vimajor, $viminor, $vipatch Version number fields
$viproductname, $videscription Version info strings
$vifilename, $vicompanyname, $vicopyright Version info strings
$unittestmode on|off Toggle unit test mode in emitter

Intrinsics

Intrinsics are built-in operations that look and feel like routine calls but are handled directly by the compiler. They have no overhead and cannot be reimplemented in user code. Use them whenever you need a capability that requires compiler cooperation, such as querying the length of an array, getting the size of a type, or retrieving exception information from inside a handler.

Intrinsic Description
len(expr) Number of elements in an array or characters in a string
sizeof(T) or sizeof(expr) Size in bytes of a type or expression
utf8(wstr) Convert a wstring to string
getmem(size) Allocate size bytes on the heap
resizemem(ptr, size) Resize a heap allocation, preserving contents
paramcount() Number of command-line arguments passed to the program
paramstr(n) The Nth command-line argument as a string
getexceptioncode() Integer code of the active exception (use inside except)
getexceptionmessage() Message string of the active exception (use inside except)

Built-in Test Blocks

Myra has first-class support for unit tests baked into the language. test blocks are appended after the module's closing end. and are part of the source file but are only compiled and executed when the test runner invokes the module in test mode. Each test block has a name string that appears in the test output, making failures easy to locate.

Tests can have their own var section for local state. The test assertion intrinsics handle failure signalling and reporting automatically. All tests are independent. A failure in one does not abort the suite.

Test assertion intrinsics:

Intrinsic Description
TestAssertEqualInt(expected, actual) Fail if two integer values are not equal
TestAssertEqualBool(expected, actual) Fail if two boolean values are not equal
TestAssertTrue(expr) Fail if expression is not true
TestAssertFalse(expr) Fail if expression is not false
TestAssertNil(ptr) Fail if pointer is not nil

When an assertion fails, the test runner reports which assertion failed, the file and line number, and the expected vs actual values. Execution continues with the next test.

module exe math_module;

$unittestmode on

routine add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

routine isPositive(const a: int32): boolean;
begin
  return a > 0;
end;

begin
  // main program body
end.

test "addition"
begin
  TestAssertEqualInt(5, add(2, 3));
  TestAssertEqualInt(0, add(-1, 1));
end;

test "boolean checks"
begin
  TestAssertTrue(isPositive(5));
  TestAssertFalse(isPositive(-5));
  TestAssertEqualBool(true, isPositive(1));
end;

test "pointer check"
var
  p: pointer;
begin
  p := nil;
  TestAssertNil(p);
end;

The $unittestmode on directive tells the emitter to include the test framework scaffolding. The test runner prints a summary showing pass/fail counts and highlights any failed assertions with the source location.

🛠️ The myra CLI

The myra command is the primary interface to the Myra toolchain. It handles project creation, compilation, execution, and cleanup, all from the current directory, without any build configuration files for the common case.

Commands

myra <COMMAND> [OPTIONS]

COMMANDS:
  init <n> [type]   Create a new Myra project
  build                Compile Myra source and build output
  run                  Build and execute the program
  clean                Remove all generated files
  version              Display version information
  help                 Display help

myra init: Creating a New Project

myra init scaffolds a new project directory with the correct structure and a starter source file.

myra init <n> [exe|lib|dll]

The second argument is optional and defaults to exe. The three project types map directly to Myra's module kinds:

Type Output Description
exe Executable Stand-alone program with a begin..end. entry point
lib Static library .lib / .a, imported by other modules at link time
dll Shared library .dll / .so, loaded at runtime with C-linkage exports

After myra init MyGame, your project looks like this:

MyGame/
  src/
    MyGame.myra      ← starter source file

The starter source matches the project type:

// exe starter
module exe MyGame;

begin
  writeln("Hello from Myra!");
end.
// lib starter
module lib MyLib;

public routine "C" add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

end.

Next steps after init:

cd MyGame
myra build
myra run

myra build: Compiling a Project

Run myra build from inside a project directory (the one that contains the src/ folder). The compiler derives the project name from the directory name and looks for src/<n>.myra as the entry point.

cd MyGame
myra build

The build pipeline runs in five steps: tokenization, parsing, semantic analysis, C++ code generation, and Zig compilation. Status messages are printed for each phase. On success you will see:

Build completed successfully!

Build output lives in the project directory:

MyGame/
  generated/          ← intermediate C++ sources (managed by the compiler)
  zig-out/
    bin/MyGame.exe    ← final executable (Windows)
    bin/MyGame         ← final executable (Linux)
  build.zig           ← generated Zig build file
  .zig-cache/         ← Zig's incremental build cache

myra run: Build and Execute

myra run is equivalent to myra build followed by running the resulting executable. Build output is printed first, then the program runs immediately:

myra run

If the build fails, the program does not run. If the target is linux64 and WSL2 is installed, the Linux binary is executed automatically through WSL.

myra clean: Remove Generated Files

myra clean removes everything the build system created: the generated/ directory, zig-out/, .zig-cache/, and build.zig. Your source files are untouched.

myra clean

Examples

# Create an exe project
myra init MyGame

# Create a static library project
myra init MyLib lib

# Create a shared library project
myra init MyPlugin dll

# Build the current project
myra build

# Build and immediately run
myra run

# Clean all generated output
myra clean

# Show version
myra version

🔌 Language Server Protocol (LSP)

Myra ships with a full Language Server Protocol implementation. It provides rich editor features such as completion, hover, go-to-definition, and rename, for any editor that speaks LSP. The server works in two deployment modes: out-of-process as a standalone executable, or in-process as an embeddable library class.

Supported LSP Capabilities

Capability Description
Diagnostics Compiler errors and warnings published on every document change
Completion Context-aware completion for keywords, built-in types, symbols, and snippets
Hover Type information and documentation for the symbol under the cursor
Go to Definition Jump to the declaration of any routine, type, or variable
Go to Type Definition Jump to the type declaration for any expression
Find All References List every use of a symbol across the open documents
Document Symbols Outline of all declarations in the current file
Workspace Symbols Search across all open files by name
Signature Help Parameter hints when typing a routine call
Inlay Hints Inline type annotations for variable declarations and return values
Rename Rename a symbol everywhere it is used
Folding Ranges Code folding for begin..end blocks and routines
Semantic Tokens Full token classification for syntax highlighting
Call Hierarchy Navigate callers and callees of any routine
Document Formatting Format the document: normalise keyword casing, spacing, and trailing whitespace
Code Actions Quick fixes and formatting actions

All features read directly from the semantically enriched AST produced by the Myra compiler pipeline. There is no separate symbol table or secondary type resolution. The same analysis that powers the compiler powers the editor.

Out-of-Process: MyraLSP.exe

The simplest deployment is to run MyraLSP.exe as an external server process. The server communicates over stdin/stdout using the standard LSP JSON-RPC 2.0 framing (Content-Length: N\r\n\r\n{...}).

Most editors that support LSP can be configured to launch MyraLSP.exe as the language server for .myra files.

VS Code (.vscode/settings.json):

{
  "myra.server.path": "C:/path/to/MyraLSP.exe"
}

Or configure it manually in your extension host settings using the languageserver extension pattern:

{
  "languageserver": {
    "myra": {
      "command": "C:/path/to/MyraLSP.exe",
      "filetypes": ["myra"]
    }
  }
}

Neovim (via nvim-lspconfig):

local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')

if not configs.myra then
  configs.myra = {
    default_config = {
      cmd = { 'MyraLSP.exe' },
      filetypes = { 'myra' },
      root_dir = lspconfig.util.root_pattern('src'),
    },
  }
end

lspconfig.myra.setup({})

Sublime Text (via LSP package):

{
  "clients": {
    "myra": {
      "enabled": true,
      "command": ["C:/path/to/MyraLSP.exe"],
      "selector": "source.myra"
    }
  }
}

Once the server is running, open any .myra file and the editor will receive diagnostics automatically. All other LSP features activate on-demand as you interact with the file.

In-Process: Embedding TMyraLSPServer

For tooling that embeds the Myra compiler directly, such as custom editors, IDEs, and language workbenches, the TMyraLSPServer class can be instantiated and driven programmatically. This avoids the overhead of process spawning and pipe I/O for tight integrations.

The server accepts any pair of TStream objects for input and output. Wire up memory streams, pipes, or sockets as your host application requires:

uses
  Myra.LSP;

var
  LServer: TMyraLSPServer;
  LInput: TMemoryStream;
  LOutput: TMemoryStream;
begin
  LServer := TMyraLSPServer.Create();
  try
    // Provide your own I/O streams
    LServer.SetStreams(LInput, LOutput);
    LServer.Run();   // blocks until 'exit' notification
  finally
    LServer.Free();
  end;
end;

The TMyraLSPService class one level below the server exposes the full logic layer without any JSON. You can call GetCompletions, GetHover, GetDefinition, and so on directly with Myra types if you want to bypass JSON serialisation entirely.

Architecture

The LSP implementation has three layers:

TMyraLSPDocument   ← holds source text, runs the full compiler pipeline on change
TMyraLSPService    ← pure logic, answers every LSP query by reading the enriched AST
TMyraLSPServer     ← JSON-RPC 2.0 framing and dispatch loop

Each open document maintains a TMyraLSPDocument that runs the Myra lexer, parser, and semantic analyser on every content change. The enriched AST is kept alive so that all LSP queries, completion, hover, references, rename, operate on the same fully analysed tree that the compiler uses.

🐛 Debugger

Myra includes an integrated debugger built on the Debug Adapter Protocol (DAP) via LLDB. The debugger connects to lldb-dap (the LLDB DAP adapter) and exposes full source-level debugging for Myra programs: breakpoints, stepping, call stack inspection, variable evaluation, and expression evaluation.

How It Works

When you place a $breakpoint directive in your Myra source, the compiler records the source file and line number into a .breakpoints TOML file alongside the compiled executable. The debugger loads this file automatically at launch and sets the corresponding breakpoints in LLDB before execution begins.

module exe demo;

routine compute(const x: int32): int32;
begin
  $breakpoint    // ← the debugger will stop here
  return x * x;
end;

begin
  writeln("Result: {}", compute(7));
end.

After myra build, the output directory will contain MyProgram.breakpoints, a TOML file listing every $breakpoint location. The debugger reads this file before launch and registers each location with LLDB. This means you never have to manually re-enter your breakpoints after a recompile.

The Debug REPL

The TMyraDebugREPL class provides an interactive command-line debug session. It wraps TMyraDebug with a prompt-driven interface suitable for scripting and terminal-based workflows.

Start a session by providing the path to the compiled executable:

(lldb-dap) r                  ← run/restart
(lldb-dap) c                  ← continue after a breakpoint hit
(lldb-dap) n                  ← step over
(lldb-dap) s                  ← step into
(lldb-dap) finish             ← step out
(lldb-dap) bt                 ← show call stack
(lldb-dap) locals             ← show local variables
(lldb-dap) p myVar            ← evaluate expression
(lldb-dap) b myfile.myra:42   ← set breakpoint at line 42
(lldb-dap) bl                 ← list all breakpoints
(lldb-dap) bd 1               ← delete breakpoint #1
(lldb-dap) bc                 ← clear all breakpoints
(lldb-dap) threads            ← list all threads
(lldb-dap) verbose on         ← enable raw DAP message logging
(lldb-dap) quit               ← exit

REPL Command Reference

Command Description
h, help Show command reference
b <file>:<line> Set a breakpoint at the given source location
bl List all breakpoints with verified status
bd <id> Delete breakpoint by index
bc Clear all breakpoints
threads Show all running threads
bt Show call stack (backtrace) for the current thread
locals Show local variables in the current stack frame
p <expr> Evaluate an expression in the current context
c Continue execution until the next breakpoint or exit
n Step over: execute one line, stepping over routine calls
s Step into: execute one line, entering routine calls
finish Step out: run until the current routine returns
r Run or restart the program
file <path> Load a different executable
verbose on|off Toggle raw DAP message logging to the console
quit Terminate the debug session and exit

Programmatic Debugger API

TMyraDebug exposes the full DAP session as a clean Delphi API for embedding in tools and IDEs:

uses
  Myra.Debug;

var
  LDebugger: TMyraDebug;
  LFrames: TArray<TStackFrame>;
  LVars: TArray<TVariable>;
  LResult: string;
begin
  LDebugger := TMyraDebug.Create();
  try
    LDebugger.LLDBDAPPath := 'C:\LLVM\bin\lldb-dap.exe';

    // Load breakpoints from compiler output
    LDebugger.LoadBreakpointsFromFile('MyProgram.breakpoints');

    // Start LLDB-DAP adapter, initialize, and launch
    LDebugger.Start();
    LDebugger.Initialize();
    LDebugger.Launch('MyProgram.exe');
    LDebugger.SetBreakpointsFromFile();  // registers loaded breakpoints
    LDebugger.ConfigurationDone();

    // Event loop
    LDebugger.ProcessPendingEvents(500);

    if LDebugger.State = dsStopped then
    begin
      // Inspect the stopped location
      LFrames := LDebugger.GetCallStack(LDebugger.CurrentThreadID);
      LVars   := LDebugger.GetLocalVariables(LFrames[0].ID);

      // Evaluate an expression
      LDebugger.EvaluateExpression('myVar + 1', LFrames[0].ID, LResult);

      LDebugger.ContinueExecution();
    end;

    LDebugger.Disconnect();
  finally
    LDebugger.Free();
  end;
end;

Debugger State Machine

The debugger follows a well-defined state progression:

NotStarted → Initializing → Ready → Launched → Running → Stopped → Exited
                                                        ↑___________↓  (continue/step loops)

State transitions fire the OnStateChange callback, which you can use to update UI or log session events. Breakpoint hits fire OnBreakpointHit with the source file and line number. Program output appears through OnOutput.

🐧 Cross-Platform Development

Myra supports Windows (Win64) and Linux (Linux64) targets from a single codebase. The core language is identical on both platforms. Only the names of external libraries differ when you are calling OS APIs. Shared library names change (kernel32.dll vs libc.so.6), but all your application logic, data structures, algorithms, and module organisation stay the same.

Cross-compilation from Windows to Linux64 is a first-class workflow. When you target linux64, Myra uses Zig as the cross-compiler backend to produce a Linux ELF binary without requiring a Linux toolchain on the host. If WSL2 is installed, Myra automatically runs the resulting binary through WSL to execute it.

Setting the Target

// In the source file:
$target win64
// or
$target linux64

You can also override the target at the command line without modifying the source. The compiler injects the appropriate TARGET_* and platform alias symbols automatically based on the active target.

WSL Setup (One Time)

To build and run Linux binaries from a Windows host, install WSL2 with Ubuntu:

wsl --install -d Ubuntu

Then inside WSL, install the base build tools (needed for standard C runtime linking):

sudo apt update && sudo apt install build-essential

That is the full setup. Myra handles the rest: locating WSL, invoking the binary, and routing stdout/stderr back to your terminal.

Target Platforms

Target Status
Windows x64 (win64) ✅ Supported
Linux x64 (linux64) ✅ Supported (native; via WSL on Windows)
macOS x64 (macos64) 🔜 Planned
Windows ARM64 (winarm64) 🔜 Planned
Linux ARM64 (linuxarm64) 🔜 Planned

🔨 Getting Myra

Download the Latest Release

The release is the only way to use Myra. Downloading or cloning the source alone will not give you a working compiler. The release package contains everything required to compile and run Myra programs: the compiler, the LSP server, the Zig build backend, the C++ runtime, the standard library, and the debugger adapter. None of these components are built from source; they ship pre-compiled and ready to use.

⬇️ Download the latest release

Unzip the release to a directory of your choice. Add bin\ to your PATH so that myra is available from any terminal. That is the complete installation. There is nothing else to install, configure, or compile.

Myra/
  bin/
    myra.exe            ← CLI compiler and project tool
    MyraLSP.exe         ← LSP server (out-of-process mode)
    res/
      runtime/          ← Myra C++ runtime (required at compile time)
      libs/std/         ← Standard library modules
      tests/            ← Test suite (.myra files)
      zig/              ← Bundled Zig compiler (the build backend)
      lldb/             ← LLDB-DAP debugger adapter

System Requirements

Requirement
Host OS Windows 10/11 x64
Linux target WSL2 + Ubuntu (install once with wsl --install -d Ubuntu)

To target Linux from Windows: install WSL2 with Ubuntu if you haven't already. Myra discovers it automatically and uses it to run Linux binaries without any additional configuration.


Building the Compiler from Source

This section is for contributors who want to modify the Myra compiler itself. If you just want to write Myra programs, stop at the release download above. You do not need this.

Building from source produces new myra.exe, MyraLSP.exe, and Testbed.exe binaries. It does not produce the toolchain (Zig, runtime, standard library, debugger). Those come from the release and are required even when developing the compiler.

Prerequisites

Minimum Tested
Host OS Windows 10 x64 Windows 11 x64
Compiler host Delphi 11 (Alexandria) Delphi 12 (Athens)

Get the Source

Option 1: Download ZIP

Option 2: Git Clone

git clone https://github.com/tinyBigGAMES/Myra.git

Compile

  1. Open src\Myra Programming Language.groupproj in Delphi
  2. Build all projects in the group (Myra, MyraLSP, Testbed)
  3. The Testbed project compiles and executes every test in bin\res\tests\ and reports colour-coded results

Running the Test Suite

The test suite lives in bin\res\tests\. Each .myra file contains a /* EXPECT: ... */ block describing the expected output. The test runner compiles the file, runs the result, compares the actual output against the expectation, and prints colour-coded results: green for pass, red for fail. You can run individual tests by index or run the full suite in one shot.

🤝 Contributing

Myra is an open project and contributions are welcome at every level. Whether you are fixing a typo in the documentation, tracking down a compiler bug, or proposing a new language feature, your involvement is appreciated.

Here is how to get involved:

  • Report bugs: Open an issue on GitHub with a minimal reproduction case. The smaller the example, the faster the fix.
  • Suggest features: Describe the use case first, then the syntax you have in mind. Features that emerge from real problems get traction fastest.
  • Submit pull requests: Bug fixes, documentation improvements, new test cases, and well-scoped features are all welcome. Keep changes focused and include a test if you can.
  • Review and discuss: Even reviewing open pull requests and issues helps move the project forward.
  • Give feedback: Star the repo if you find it useful. It helps others discover the project.

Join our Discord to discuss development, ask questions, share what you are building with Myra, or just hang out with people who care about this kind of programming.

💙 Support the Project

Myra is a labour of love, built in the open. If it saves you time, sparks an idea, or becomes part of something you ship, here are a few ways you can give back:

  • Star the repo: It costs nothing and helps others find the project
  • 🗣️ Spread the word: Write a post, mention it on social media, or share it in a community you are part of
  • 💬 Join us on Discord: Share what you are building and help shape what comes next
  • 💖 Become a sponsor: Support ongoing development via GitHub Sponsors. Sponsorship directly funds time spent on the compiler, runtime, tooling, and documentation.

Every bit of support keeps the project alive and moving forward.

📄 License

Myra is licensed under the Apache License 2.0. See LICENSE for details.

🔗 Links

Myra™ Programming Language.

Copyright © 2025-present tinyBigGAMES™ LLC All Rights Reserved.

About

Myra is a statically-typed, compiled language in the Pascal/Oberon tradition. It transpiles to C++23 and uses Zig as a build backend, producing real x86-64 executables and libraries for Windows and Linux. The backend is invisible. You write Myra. You get binaries. Myra - Pascal. Refined.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors