Tour
The language, by example
Fourteen short programs, from hello-world to a real release script. Every snippet on this page is adapted from the conformance suite — it compiles and runs today.
01Hello, world
An NL program is a set of classes in namespaces. Execution starts at a static main; its return value is the process exit code.
namespace hello;
class Main {
public static int main(string[] args) {
system.Out.println("Hello, world!");
return 0;
}
}
$ nlc Main.nl -o Main.nlp && nlvm Main.nlp → Hello, world!
02Variables and control flow
Statically typed locals, auto inference, C-style if/while/for with break and continue, compound assignment. The compiler runs definite-assignment analysis: reading a variable that might not be initialized is a compile error, not a runtime surprise.
public static int gcd(int a, int b) {
while (b != 0) {
int t = b;
b = a % b;
a = t;
}
return a;
}
public static int main(string[] args) {
auto total = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) { continue; }
total += i;
}
system.Out.println(system.Int.toString(total)); // 25
system.Out.println(system.Int.toString(gcd(48, 18))); // 6
return 0;
}
03Classes, constructors, named & optional parameters
Constructors are declared with construct. Parameters can have defaults, and call sites can name them — argument order stops mattering, and the compiler still checks everything: a required parameter you forgot, a parameter passed twice, a positional argument after a named one are all compile errors.
class Contact {
public string lastName;
public string firstName;
public int age;
public construct(string lastName, string firstName, int age = 0) {
this.lastName = lastName;
this.firstName = firstName;
this.age = age;
}
public string describe() {
return this.lastName + ", " + this.firstName;
}
}
auto john = new Contact(lastName: "Doe", firstName: "John");
auto jane = new Contact(firstName: "Jane", lastName: "Smith", age: 30);
04Interfaces and inheritance
Single inheritance with extends, interfaces with implements, super(...) constructor delegation and super.method() calls, and runtime type tests with instanceof.
interface Shape {}
class Circle implements Shape {
public construct() {}
}
class Base {
public int value;
public construct(int v) { this.value = v; }
public int describe() { return this.value; }
}
class Derived extends Base {
public construct(int v) { super(v); }
public int describe() { return super.describe() + 100; }
}
Shape s = new Circle();
if (s instanceof Circle) { /* true — runtime type is checked */ }
05Enums with values and methods
Enums can be plain (auto-numbered ordinals) or backed by a type, with explicit values — and they can carry methods.
enum Status: int
{
OK = 200,
NotFound = 400,
InternalServerError = 500,
public bool isSuccess() { return this.value >= 200 && this.value < 300; }
}
Status status = Status.OK;
system.Out.println(system.Bool.toString(status.isSuccess())); // true
system.Out.println(system.Int.toString(status.value)); // 200
06Exhaustive match
match is an expression. When matching over an enum, covering every case means no default arm is needed — and if you later add a variant, every non-exhaustive match becomes a compile error (E047). Case analysis that stays correct as the code evolves.
enum Color { Red, Green, Blue }
Color c = Color.Green;
string name = match(c) {
Color.Red: "red",
Color.Green: "green",
Color.Blue: "blue",
};
system.Out.println(name); // green
07Null safety and union types
No value is nullable unless its type says so. string|null is a union type; assigning null to a plain string, or using a nullable value where a non-null one is required, is rejected at compile time. ?? falls back on null; ?: falls back on any falsy value (null, false, 0). Both short-circuit.
string|null a = null;
system.Out.println(a ?? "default"); // default
int|null port = null;
int actual = port ?? 8080; // 8080
int count = 0;
int shown = count ?: 1; // 1 — ?: treats 0 as falsy
string|null b = "value";
system.Out.println(b ?? expensive()); // "value" — expensive() never runs
08Exceptions: checked, traced, structured
Exceptions are classes; catch clauses are checked for order (an unreachable catch is a compile error) and checked exceptions must be caught or declared with throws. Every exception carries a stackTrace captured natively by the VM, and the call-depth guard turns runaway recursion into a catchable StackOverflowException instead of a crash.
class MyException extends Exception {
public construct(string message) { super(message); }
}
public static int compute() {
int result = 0;
try {
throw new MyException("boom");
}
catch (MyException ex) {
result = 10; // most specific handler wins
}
catch (Exception ex) {
result = -1;
}
finally {
result += 100; // always runs
}
return result; // 110
}
09Function types and closures
Function signatures are types, written the way you'd say them: (int, int) => int. They work as variables, fields, parameters and return types — and can declare throws. Closures capture their environment.
(int, int) => int add = (int a, int b) => a + b;
system.Out.println(system.Int.toString(add(3, 5))); // 8
(string) => int len = (string s) => s.length();
(string|null) => int throws Exception strict =
(string|null s) => throws Exception {
if (s == null) { throw new Exception("String is null"); }
return s.length();
};
10ref parameters
By-reference parameters are explicit on both sides — in the signature and at the call site. No invisible mutation: if a call can change your variable, you can see it.
class Utils {
public static void swap(ref int a, ref int b) {
int temp = a;
a = b;
b = temp;
}
}
int x = 10;
int y = 20;
Utils.swap(ref x, ref y); // x == 20, y == 10
11Template classes
Classes can be generic over one or more type parameters with template <type T>. Each instantiation — Box<int>, Box<float> — is monomorphized to its own concrete type at compile time; there's no boxing or erasure.
template <type T>
class Box {
private T value;
public construct(T value) { this.value = value; }
public T get() { return this.value; }
public void set(T value) { this.value = value; }
}
Box<int> a = new Box<int>(10);
Box<float> b = new Box<float>(3.5);
a.set(20);
system.Out.println(system.Int.toString(a.get())); // 20
system.Out.println(system.Float.toString(b.get())); // 3.5
12Readonly classes and properties
readonly enforces immutability at the class level or the property level. A readonly field can only be assigned inside the constructor — any later assignment, even from the class's own methods, is a compile error (E013 for a readonly class, E014 for a readonly property).
class readonly Money {
public int cents;
public construct(int cents) { this.cents = cents; }
}
class Point {
public readonly int x;
public int y;
public construct(int x, int y) { this.x = x; this.y = y; }
}
auto price = new Money(1999);
// price.cents = 0; // compile error E013
auto p = new Point(1, 2);
p.y = 5; // OK — y isn't readonly
// p.x = 5; // compile error E014
13Nodiscard methods
nodiscard marks a method whose return value must not be silently dropped. Calling it as a bare statement doesn't fail the build — the compiler reports warning W001 and keeps compiling.
class Result {
private bool ok;
public construct(bool ok) { this.ok = ok; }
public bool isSuccess() { return this.ok; }
}
class FileHandler {
public nodiscard Result openFile(string filename) {
return new Result(true);
}
}
auto handler = new FileHandler();
auto result = handler.openFile("data.txt");
if (result.isSuccess()) { system.Out.println("opened"); }
handler.openFile("data.txt"); // Warning W001 — return value is discarded
14A real program: the release script
This is not a toy — it's tools/Release.nl, the script that actually tags and pushes every nlvm release. Files, regular expressions and subprocesses from the standard library, checked IO errors, null-checked regex results.
namespace nlvm.tools;
class Release
{
public construct() {
}
public string findChangelogVersion() throws IOException
{
string changelog = system.io.File.readAllText("CHANGELOG.md");
auto header = system.text.Regex.matchFirst("## \\[([^\\]]+)\\]", changelog);
if (header == null) {
return "";
}
return header.groups[1];
}
public void createTag(string version, string tagMessage) throws Exception
{
auto result = system.ps.Process.run(new string[]{"git", "tag", "-a", version, "-m", tagMessage});
if (result.exitCode != 0) {
throw new Exception(result.stderr);
}
}
public void pushTag(string version) throws Exception
{
auto result = system.ps.Process.run(new string[]{"git", "push", "origin", version});
if (result.exitCode != 0) {
throw new Exception(result.stderr);
}
}
}