v0.5Standard library, lambdas, near-C speed

A programming language built for AI agents to write.

Small and strict, with errors that tell an agent exactly what to write. One source file compiles to native code through C, or to JavaScript, Python, TypeScript, Rust and Go, and prints the same thing on every one.

  • MIT
  • Zero dependencies
  • Native via C
  • JavaScript
agent fib.nyra done

first six Fibonacci numbers

fn fib(n: int) -> int {
    if n < 2 { ret n }
    ret fib(n - 1) + fib(n - 2)
}

fn main() {
    for i in 0..6 {
        print("fib({i}) = {fib(i)}")
    }
}

nativeC · gcc

$ nyra run fib.nyra
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5

JavaScriptNode.js

$ nyra run fib.nyra --js
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5

Zero dependencies

The compiler is Rust with no crates. It builds anywhere Rust does and writes plain, readable C and JavaScript.

One IR, two targets

Native code through C, and JavaScript for Node and the browser. Same output, same evaluation order, same runtime errors.

Fits in one prompt

The whole language is a short spec an agent reads in one go. The compiler corrects what is left.

Design

Every rule removes a way to be wrong.

Nyra is shaped by how language models write code, not by what humans like to read. Fewer choices, fewer wrong ones.

E0005 · E0201 · E0230

Errors are instructions

Every error has a stable code, an exact position and a hint that says what to write. --json is for agents, nyra explain E0201 is for the why.

one spelling

One obvious way

No implicit conversions, no shadowing, no null: 1 + 2.0 is error E0210. A model that half-knows Rust, Go or TypeScript already knows the syntax.

newline ends it

No semicolons

Braces are required and stay on the line that opens them. A stray ; is error E0005, and the hint says: delete it.

copy on write

Values, not references

Assigning, passing and returning always make independent values. Copies share storage until one side writes, so nothing changes behind an agent's back.

free · keep · arena

Memory: automatic, and yours to steer

Reference counting frees memory when the last owner is gone: no garbage collector, no borrow checker. free, keep and arena say exactly when.

byte for byte

The same output everywhere

Both backends come from one intermediate representation, so evaluation order, number formatting and runtime errors match.

Errors are instructions

A compiler an agent can talk to.

Every error carries a stable code, the exact line and column, and a hint that says what to write. The agent reads it as JSON, applies the fix and checks again. No guessing, no scraping text.

main.nyraunchecked
terminal
$ nyra check main.nyra --json
{"ok":false,"errors":[{"code":"E0230","message":"cannot infer the type of the empty array `[]`","file":"main.nyra","line":2,"col":14,"hint":"write its type where it is declared, e.g. `var xs: [int] = []`"}]}
$ nyra check main.nyra --json
{"ok":true,"errors":[]}
$ nyra run main.nyra
[1]

A stable code

Codes never change meaning. nyra explain E0230 prints what it means, why the rule exists, the usual causes, and a wrong and a fixed program.

An exact position

File, line and column, the same on every backend. Runtime errors carry them too, with "runtime":true in the JSON.

A hint that says what to write

Not "syntax error", but the line to type: a closest-name suggestion, the missing type, the keyword you meant.

Compiler

One source, one IR, two targets.

A small Rust compiler with zero dependencies. Both backends read the same typed intermediate representation, so a program prints the same thing whether it runs natively or on JavaScript. Scroll to fly through it.

Sourcesq.nyra

Plain text, written by an agent. Newlines end statements, so there are no semicolons.

fn sq(x: int) -> int = x * x

fn main() {
    print(sq(5))
}

Tokenslexer

The lexer cuts the text into keywords, names, types and symbols.

fnsq(x:int)->int=x*xnewline

Treeparser

The parser builds a syntax tree. It recovers after an error, so one mistake does not hide the next.

fn sq x: int * x x

Typeschecker

The checker gives every node a type and collects every error in one pass.

fn sq x: int * x x int int int int int

IRnyra's real dump

One typed intermediate representation. Expressions are pure and every effect is its own statement, so the evaluation order is fixed once, for every backend.

fn sq(x: int) -> int {
    return (x * x)
}
fn main() {
    %0: int = call sq(5)
    print(%0)
}

C99→ native executable

static int64_t ny_sq(int64_t x) {
    return x * x;
}

gcc or clang turn it into a native binary.

JavaScript→ Node, browser

function sq(x) {
    return x * x + 0;
}

The + 0 turns an int -0 into 0: C has no -0 for ints, so JavaScript may not print one either.

Outputrun it either way

native

$ nyra run sq.nyra
25

JavaScript

$ nyra run sq.nyra --js
25

identical output, byte for byte

The IR above is the compiler's real dump for this program. Every example in the repository runs on all six backends in CI and must match byte for byte.

Memory

Values, not references.

Copy a value and you get an independent value. Under the hood the copy is free until one side writes: then, and only then, the memory splits.

copy.nyra
[9, 2][1, 2, 3]
a b 9 2 rc 1 1 2 3 rc 1

Automatic by default. Manual when you say so.

Reference counting frees memory the moment the last owner is gone, with no garbage collector and no pauses. When you want to be exact, three words say so, and the compiler checks them.

free(x)

Give the memory back now. Using x afterwards is a compile error (E0239), until you assign it a new value.

keep(x)

Mark a value as living for the whole program: it is never freed early.

arena { }

A block whose values are freed together at the closing brace. Values leave only through ret, and outer values are read-only inside. Version 0.3 checks these rules; a faster bulk allocator comes later.

Language · v0.5

Small enough to hold in your head.

Version 0.3 adds real data: strings and chars with methods, arrays, structs, inout parameters, break and continue, and explicit memory control. Every program below was compiled and run; the output is what the compiler printed, identical on native and JavaScript.

Functions with typed signatures, one-line functions, let and var, compound assignment, string interpolation and if as a value.

basics.nyra
fn add(a: int, b: int) -> int = a + b

fn gcd(a: int, b: int) -> int {
    if b == 0 { ret a }
    ret gcd(b, a % b)
}

fn main() {
    let x = 5
    var total = 0
    total += add(x, 2)
    let size = if total > 6 { "big" } else { "small" }
    print("total {total} is {size}")
    print("gcd(48, 18) = {gcd(48, 18)}")
}
outputnative = JS
total 7 is big
gcd(48, 18) = 6

For agents

Point a model at the repo. It does the rest.

Nyra is brand new, so no model has seen it in training. It does not need to: the docs are short enough to read in one go, and the compiler corrects what is left.

prompt

Read https://github.com/SpAkXD/nyra, starting with llms.txt and docs/AI_GUIDE.md, then write me a Nyra program that prints the first 20 prime numbers.

  • llms.txtWhat Nyra is, the ten most important rules, links.
  • AI_GUIDE.mdWorkflow, do and don't rules, error codes with fixes, complete programs.
  • SPEC.mdThe complete language spec.
  • ERRORS.mdEvery error code: what it means, why, causes, a wrong and a fixed program.
  • examples/Runnable programs, each with its expected output in a .out file.
Write Check Fix Run error: apply the hint, check again "ok": true

Get started

Install, then run.

One binary: nyra. Native programs need a C compiler (gcc, clang or tcc, found automatically). The JavaScript backend needs Node.js instead.

  1. AInstall, option one

    Prebuilt binary

    Linux x86_64, Windows x86_64 and macOS Apple Silicon. Download the archive from the latest release, unpack it and put nyra on your PATH.

    Latest release

    On macOS run xattr -d com.apple.quarantine nyra once: the binary is not signed.

  2. BInstall, option two

    From source

    Any platform with Rust.

    shell
    git clone https://github.com/SpAkXD/nyra
    cd nyra
    cargo build --release

    The binary is target/release/nyra. Or: cargo install --git https://github.com/SpAkXD/nyra

  3. CFirst run

    Your first program

    hello.nyra
    fn main() {
        print("hello from nyra")
    }

    Save it as hello.nyra, then:

    shell
    nyra run hello.nyra
    nyra run hello.nyra --js
commands
  • nyra run hello.nyraCompile and run natively.
  • nyra run hello.nyra --jsThe same program on Node.js.
  • nyra build app.nyraA native executable next to the source. Unchanged programs skip the C compiler.
  • nyra check app.nyra --jsonOnly report errors, as JSON for agents. Exit code 0 means no errors.
  • nyra explain E0230What an error means, why, the usual causes, a wrong and a fixed program.

flags

Options

--js
use the JavaScript backend
--c
with build: write the generated C
-o <path>
output path for build
--json
errors as JSON, compile and runtime
--time
show how long each step took

status

Exit codes

0
success
1
compile errors
2
usage or tool problem
101
runtime error, identical on both backends

Benchmark · first results

Does an agent get it right on the first try?

83 tasks, four languages, three Claude models. Strong models write Nyra about as reliably as Python, small models are not there yet. Nyra programs run about 50× faster than Python; the code is about 1.25× as long. Every number is published, the bad ones too.

  • First-try correctness. How often a model's first program prints exactly the expected output.
  • Repairs. The same, when failures are followed by the compiler's feedback and a few retries.
  • Code length. Tokens and characters of the program the model wrote.
  • Fair by construction. Same tasks, same prompt, same repair budget for all four languages; Claude Opus 5.5, Sonnet 5.5 and Haiku 4.5 through the Anthropic API, earlier runs through OpenRouter. Nyra's prompt includes its spec and the others rely on pre-training: a known asymmetry, stated up front.

Roadmap

Small versions, each one tested on every backend.

Nyra is 0.x, so the syntax may still change before 1.0.

  1. v0.1

    Core done

    Functions, int float bool str, if, while, for, the C and JavaScript backends, JSON errors.

  2. v0.2

    Short code done

    One-line functions, +=, string interpolation, if as a value, a build cache, runtime errors and a typed IR.

  3. v0.3

    Real data done

    Strings and chars, arrays, structs, inout, break and continue, reference counting with free, keep and arena.

  4. v0.4

    Self-repair done

    Scripts without fn main, nyra check --fix, an MCP server for agents, Python, TypeScript, Rust and Go backends, and a harder benchmark tier.

  5. v0.5

    Libraries and speed now

    Lambdas and comprehensions, ex examples checked while compiling, a standard library (input, files, JSON, math, time, random), maps, script variables in functions, native code near C speed, and the first benchmark results.

  6. v1.0

    Ecosystem planned

    Packages and addons, a published VS Code extension, a docs site.

Open source

A solo project, built in the open.

Nyra is MIT licensed. Issues and pull requests are welcome, and cargo test must stay green. If an AI could not write a program you expected it to, open an issue with the program and the nyra check --json output: that is the most useful bug report.