Comments
-- This is a comment. The computer ignores it.
-- Use comments to explain your code to other humans!
Values and Variables
let name: String := "Alice"      -- a piece of text (String)
let age: Integer := 10           -- a whole number (Integer)
let height: Real := 4.5          -- a decimal number (Real)
let likes_cats: Boolean := true  -- true or false (Boolean)

Real literals must include at least one digit after the decimal point. Valid examples: 4.5, 10.0, .5, 12.0e-3. Invalid: 10., 12.e-3.

Printing
print("Hello, world!")
print(name)
print("I am " + age + " years old")
Math
let sum: Integer := 3 + 4       -- 7
let diff: Integer := 10 - 3     -- 7
let product: Integer := 5 * 6   -- 30
let quotient: Integer := 20 / 4 -- 5
let remainder: Integer := 10 % 3 -- 1
let power: Integer := 2 ^ 8     -- 256

Integer literals can also be written with explicit bases:

let flags: Integer := 0b1111_0000
let perms: Integer := 0o755
let color: Integer := 0xFF_AA_33

Use lowercase prefixes 0b, 0o, and 0x. _ may be used as a digit separator.

Comparing Things
x = y                            -- equal?
x /= y                           -- not equal?
x == y                           -- same object?
x != y                           -- different object?
x < y    x <= y                  -- less than? less or equal?
x > y    x >= y                  -- greater than? greater or equal?
x and y                          -- both true?
x or y                           -- at least one true?
not x                            -- flip true/false

Nex has two kinds of equality:

  • = and /= compare by value
  • == and != compare by identity
print([1, 2] = [1, 2])           -- true
print("abc" = "abc")             -- true

let a := [1, 2]
let b := a
let c := [1, 2]
print(a == b)                    -- true
print(a == c)                    -- false
Choosing: if / then / else
if age >= 18 then
  print("You can vote!")
elseif age >= 13 then
  print("You are a teenager")
else
  print("You are a kid")
end
Inline Choice: when
let label: String := when age >= 18 "adult" else "minor" end
Loops: from / until
from let i: Integer := 1 until i > 5 do
  print(i)
  i := i + 1
end
-- prints 1 2 3 4 5
Repeat
repeat 3 do
  print("hello!")
end
-- prints hello! three times
Across

Iterate over any collection (Array, String, Map) using its cursor:

across [10, 20, 30] as x do
  print(x)
end
-- prints 10 20 30

Strings iterate by character:

across "abc" as ch do
  print(ch)
end
-- prints #a #b #c

Maps iterate as [key, value] pairs:

across {"name": "Alice", "age": "10"} as pair do
  print(pair.get(0))
end
Functions
function greet(name: String)
do
  print("Hello, " + name + "!")
end

greet("Bob")

A function that gives back a value:

function double(n: Integer): Integer
do
  result := n * 2
end

print(double(5))                 -- 10

For mutually recursive functions, declare the signatures first:

function is_even(n: Integer): Boolean
function is_odd(n: Integer): Boolean

function is_even(n: Integer): Boolean
do
  if n = 0 then result := true
  else result := is_odd(n - 1) end
end

function is_odd(n: Integer): Boolean
do
  if n = 0 then result := false
  else result := is_even(n - 1) end
end

No default arguments. Nex has no default parameter values; a call must pass exactly as many arguments as the function declares. Free functions cannot be overloaded either — each function name must be unique, so you cannot define two greet functions that differ only in the number of parameters. Class methods, however, can be overloaded by arity (see Optional arguments via method overloading), which is the idiomatic way to get optional-argument ergonomics.

Arrays
let colors: Array [String] := ["red", "green", "blue"]
print(colors.get(0))            -- "red"
colors.add("yellow")            -- add to the end
print(colors.length)            -- 4
Maps
let pet: Map [String, String] := {"name": "Max", "kind": "dog"}
print(pet.get("name"))            -- "Max"
pet.put("kind", "cat")            -- update a value
Sets

Use a set when you want an unordered collection of distinct values.

let evens: Set[Integer] := #{0, 2, 4}
print(evens.contains(2))          -- true

An empty set uses the explicit set literal syntax:

let empty: Set[Integer] := #{}

Build a set from an array (duplicates are removed):

let numbers: Set[Integer] := create Set[Integer].from_array([1, 2, 2, 3])
print(numbers.size)               -- 3

Set operations:

let a: Set[Integer] := #{1, 2, 3}
let b: Set[Integer] := #{3, 4}

print(a.union(b))                 -- #{1, 2, 3, 4}
print(a.intersection(b))          -- #{3}
print(a.difference(b))            -- #{1, 2}
Concurrency: spawn, Task, and Channel

Use spawn to start a lightweight task:

let t: Task[Integer] := spawn do
  result := 1 + 2
end

print(t.await)                    -- 3
print(t.is_done)                  -- true

Use Channel[T] to communicate between tasks:

let ch: Channel[Integer] := create Channel[Integer]

spawn do
  ch.send(42)
end

print(ch.receive)                 -- 42
ch.close

Buffered channels:

let ch: Channel[Integer] := create Channel[Integer].with_capacity(2)
ch.send(10)
ch.send(20)
print(ch.size)                    -- 2

Use select to wait on multiple operations:

select
  when jobs.receive as job then
    print(job)
  when worker.await as value then
    print(value)
  timeout 1000 then
    print("timed out")
  else
    print("idle")
end
Classes
class Pet
  feature
    name: String
    sound: String

  create
    make(name: String, sound: String) do
      this.name := name
      this.sound := sound
    end

  feature
    speak do
      print(name + " says " + sound)
    end
end
Creating Objects
let cat: Pet := create Pet.make("Mimi", "meow")
cat.speak                        -- "Mimi says meow"
Constructors
class Circle
  create
    make(r: Real) do
      radius := r
    end

  feature
    radius: Real

    area(): Real do
      result := 3.14159 * (radius ^ 2)
    end
end

let c: Circle := create Circle.make(5.0)
print(c.area)                    -- 78.53975
Optional arguments via method overloading

Nex has no default parameter values, but a class may define several methods with the same name and different numbers of parameters. The right one is chosen by the number of arguments at the call site. Have the shorter version forward to the longer one to supply a default:

class Greeter
  feature
    greet(name: String): String do
      result := greet(name, "!")        -- forward with a chosen default
    end

    greet(name: String, punct: String): String do
      result := "Hello, " + name + punct
    end
end

let g: Greeter := create Greeter
print(g.greet("Ann"))              -- "Hello, Ann!"
print(g.greet("Bob", "."))         -- "Hello, Bob."

Overloads are distinguished only by the number of arguments, not their types, so you cannot have two same-name methods with the same arity that differ only in parameter type. (Free functions cannot be overloaded at all — see Functions.)

Inheritance
class Animal
  feature
    name: String
    speak do print(name) end
  create
    with_name(n: String) do name := n end
end

class Dog
  inherit Animal
  feature
    speak do
      print(name + " says Woof!")
    end
  create
    with_name(n: String) do Animal.with_name(n) end
end
Once Fields

A field declared with once can be set in a constructor but never reassigned afterward. The typechecker enforces this at compile time.

class Point
  feature
    once x: Integer
    once y: Integer
  create
    make(px: Integer, py: Integer) do
      x := px
      y := py
    end
end

Assigning a once field outside a constructor is a compile-time error.

Design by Contract

Tell Nex what must be true before, after, and always:

class Wallet
  feature
    money: Real

    spend(amount: Real)
      require                    -- must be true BEFORE
        enough: amount <= money
      do
        money := money - amount
      ensure                     -- must be true AFTER
        less: money = old money - amount
      end

  invariant                      -- must ALWAYS be true
    not_negative: money >= 0.0
end
Error Handling
let attempts: Integer := 0
do
  attempts := attempts + 1
  if attempts < 3 then
    raise "not ready yet"
  end
  print("done on attempt " + attempts)
rescue
  print("failed, trying again...")
  retry                        -- jump back to do and try again
end

raise throws an error. rescue catches it. retry jumps back to the do block and runs it again from the top.

Case / Of
case direction of
  "up"    then print("going up")
  "down"  then print("going down")
  else         print("standing still")
end
Sealed Classes

A sealed deferred class closes its hierarchy — only its declared subclasses may extend it.

sealed deferred class Result
end

class Ok
  inherit Result
  feature value: Integer
  create make(v: Integer) do value := v end
end

class Err
  inherit Result
  feature msg: String
  create make(m: String) do msg := m end
end
Scoped Blocks
let x: Integer := 10
do
  let x: Integer := 99          -- shadows the outer x
  print(x)                      -- 99
end
print(x)                        -- 10
Type Conversion: convert
convert <value> to <name>:<Type>
  • Returns true if conversion succeeds, else false.
  • On success, <name> is bound to the converted value.
  • On failure, <name> is bound to nil.
if convert vehicle_1 to my_car:Car then
  my_car.sound_horn
end
Match Statement

Dispatches on the runtime type of an expression. Against a sealed type, the typechecker verifies every variant is covered — a missing branch is a compile-time error.

match r of
  when Ok as ok then
    print(ok.value)
  when Err as err then
    print(err.msg)
end

An else branch covers remaining cases and suppresses the exhaustiveness check.

match r of
  when Ok as ok then
    print(ok.value)
  else
    print("not ok")
end
Anonymous Functions
let add: Function := fn (a, b: Integer): Integer do result := a + b end
print(add(3, 4))                -- 7
Generics
class Box [T]
  feature
    value: T
  create
    make(v: T) do value := v end
end

let b: Box [Integer] := create Box[Integer].make(42)
b.value                         -- 42

Generic functions:

function first[T](values: Array[T]): T
do
  result := values.get(0)
end

print(first([10, 20, 30]))      -- 10
print(first(["a", "b", "c"]))   -- "a"
Assignment
x := 10                         -- set an existing variable
let y: Integer := 20            -- create a new variable
this.name := "Nex"              -- set a field inside a method