v0.19.0 Latest release · changelog

Noxy
Statically Typed Programming Language

A statically typed language with a high-performance stack-based Virtual Machine written in Go, created by Estêvão Fonseca. Structs, dynamic arrays, maps, generics, closures, modules and Go-style concurrency.

Arrays, maps and structs are independent values at any depth — ref is the only sharing mechanism, and copy-on-write keeps the copies lazy.

🦉 Mascot: Our purple owl symbolizes wisdom and elegance in software development.

Web Interpreter GitHub View Examples
hello.nx
print("Hello from Noxy!")

// Typed variables and f-strings
let x: int = 10
let y: int = 20
print(f"Sum: {x + y}")

// Structs are values
struct Product
    id: int
    name: string
    price: float
end

let product: Product = Product(1, "Laptop", 2500.50)
print(f"Product: {product.name}")

Language Features

Static Type System

Every variable is declared with a type and keeps it. Primitives, structs, arrays, maps, exact function types and module-qualified types such as io.File are checked at compile time; any and bare func mark the explicit dynamic boundaries.

Value Semantics

Arrays, maps and structs are independent values at any depth: assignment, calls, container reads and channels never alias. Copy-on-write makes the copy free until the first write, and == compares composites by content.

Explicit Sharing with ref

One mechanism for sharing storage: ref. In-place mutation across calls, self-referencing structures, channels and routines — never hidden aliasing.

Generics

Generic functions and structs (func first<T>, struct Stack<T>) are monomorphized at compile time and always instantiated by inference — zero runtime cost, no explicit instantiation syntax.

First-Class Functions

Exact function types (func(int) -> int), anonymous functions, closures that capture variables, and functions as arguments and return values.

Concurrency

Go-style routines and channels — spawn, make_chan, when/case select — plus supervised tasks (spawn_task/task_await) that report results and failures as data. Values passed by argument or channel are race-free by construction.

Errors & Cleanup

Runtime errors for bugs, result structs for untrusted data. call_result turns a failure into a value at the boundary, and defer runs cleanup in LIFO order on every exit path.

Modules & Packages

use m, use m as alias, use m select f, T. Module state is read-only from outside, struct identity is nominal across modules, and a Git-based package manager installs dependencies with noxy --get.

Standard Library & VM

io, strings, time, sys, net, http, json, crypto, sqlite, rand and errors — on a bytecode compiler and stack-based VM written in Go, with an interactive REPL, diagnostics on stderr and proper exit codes.

Language Syntax

Variables & f-strings

let x: int = 42
let pi: float = 3.14
let name: string = "Noxy"
let active: bool = true
let data: bytes = b"hello"

print(f"{name}: x = {x}, active = {active}")
print(fmt("pi = %.2f", pi))   // pi = 3.14

Value Semantics & ref

// Composites are values: no aliasing
let a: int[] = [1, 2, 3]
let b: int[] = a
b[0] = 99
print(a[0])   // 1

// ref is the only sharing mechanism
let alias: ref int[] = ref a
alias[0] = 99
print(a[0])   // 99

Arrays

// Dynamic arrays
let nums: int[] = []
append(ref nums, 10)
append(ref nums, 20)
print(length(nums), pop(ref nums), contains(nums, 10))

// Fixed size
let fixed: int[5] = [1, 2, 3, 4, 5]
let zeroed: int[100] = zeros(100)
print(fixed[4], length(zeroed))   // 5 100

// range is a builtin (no import), Python semantics
for i in range(10, 0, -3) do print(i) end   // 10 7 4 1

Maps

let scores: map[string, int] = {"Alice": 100, "Bob": 95}
scores["Carol"] = 88

print(has_key(scores, "Alice"))   // true
print(scores["Carol"])            // 88

for person in scores do
    print(person, scores[person])
end

Structs

struct Person
    name: string
    age: int
    active: bool
end

let person: Person = Person("John", 25, true)
person.age = 26

// A copy never reaches the original
let copy: Person = person
copy.age = 99
print(person.age)   // 26

Functions & Closures

func add(a: int, b: int) -> int
    return a + b
end

// Exact function types
func apply(f: func(int) -> int, v: int) -> int
    return f(v)
end

// Closure capturing a variable
let factor: int = 3
let triple: func(int) -> int = func(x: int) -> int
    return x * factor
end

print(add(2, 3), apply(triple, 5))   // 5 15

Generics

struct Stack<T>
    items: T[]
end

func push<T>(s: ref Stack<T>, item: T)
    append(ref s.items, item)
end

func peek<T>(s: Stack<T>) -> T
    return s.items[length(s.items) - 1]
end

// T is always inferred — here from the annotation
let ints: Stack<int> = Stack([])
push(ref ints, 10)
push(ref ints, 20)
print(peek(ints))   // 20

Control Flow

let x: int = 10
if x > 10 then
    print("greater")
elif x == 10 then
    print("exactly ten")
else
    print("smaller")
end

let i: int = 0
while i < 3 do
    i = i + 1
end

for item in ["a", "b", "c"] do
    print(item)
end

Self-Referencing with ref

struct Node
    value: int
    next: ref Node
end

func push_back(node: ref Node, value: int)
    if node.next == null then
        let fresh: Node = Node(value, null)
        node.next = ref fresh   // rebind the field
    else
        push_back(node.next, value)
    end
end

let head: Node = Node(1, null)
push_back(ref head, 2)
print(head.next.value)   // 2

Imports & Modules

use strings
use time as t
use io

print(strings.to_upper("hello"))   // HELLO
print(t.now() > 0)                 // true

// Structs of a module are qualified types
let info: io.FileInfo = io.stat("notes.txt")
print(info.exists)

Standard Library

// io, strings, time, sys, net, http,
// json, crypto, sqlite, rand, errors
use time
use sys

print(time.now())            // unix timestamp
print(length(sys.argv()))    // command-line args
print(sys.version)           // v0.19.0

let user: map[string, any] = {"name": "Ana", "age": 30}
print(json_dumps(user))      // {"age":30,"name":"Ana"}

Concurrency

func worker(id: int, out: chan string)
    chan_send(out, f"hello from {id}")
end

let out: chan string = make_chan(0)
spawn(worker, 1, out)
print(chan_recv(out))   // hello from 1

// Supervised task: outcome as data
let task: any = spawn_task(worker, 2, out)
print(chan_recv(out))   // hello from 2
let outcome: map[string, any] = task_await(task)
print(outcome["status"])   // ok

Channel Select

let a: chan string = make_chan(1)
let b: chan string = make_chan(1)
chan_send(a, "from a")

// Runs the first ready case, exactly once
when
    case msg = chan_recv(a) then
        print(msg)             // from a
    case chan_recv(b) then
        print("from b")
    default
        print("nothing ready")
end

Errors & defer

use errors select *

func parse(text: string) -> int
    return to_int(text)   // raises on bad input
end

// call_result turns a runtime failure into data
let r: CallResult = call_result(parse, "abc")
if r.ok then
    print(r.value)
else
    print("invalid:", r.failure.message)
end

func work()
    defer print("cleanup runs last")
    print("working")
end
work()

Practical Examples

Hello World

A basic example showing the fundamental syntax of the Noxy language.

print("Hello from Noxy!")

// Basic operations
let x: int = 10
let y: int = 30
print(f"Sum: {x + y}")

// Struct example
struct Product
    id: int
    name: string
    price: float
end

let product: Product = Product(1, "Laptop", 2500.50)
print(f"Product: {product.name}")
print(fmt("Price: %.2f", product.price))

Value Semantics

Arrays, maps and structs are independent values at any depth. ref is the only way to share storage, and copy-on-write means the copy costs nothing until someone writes.

struct Counter
    hits: int[]
end

// No 'ref': the callee gets an independent value at any depth.
func touch(c: Counter)
    c.hits[0] = 999
end

// With 'ref': the callee shares the caller's storage.
func bump(c: ref Counter)
    c.hits[0] = c.hits[0] + 1
end

let counter: Counter = Counter([1, 2, 3])

touch(counter)
print(counter.hits[0])   // 1 - the copy was mutated, not the original

bump(ref counter)
print(counter.hits[0])   // 2 - ref shares the original

let backup: Counter = counter
bump(ref counter)
print(backup.hits[0])    // 2 - assignment already copied

print([1, 2] == [1, 2])  // true - composites compare by content

Generics

Generic functions and structs are monomorphized at compile time. There is no explicit instantiation syntax: type parameters are always inferred from the arguments or from the annotation of the receiving let or return type.

struct Pair<K, V>
    key: K
    value: V
end

func swap<K, V>(p: Pair<K, V>) -> Pair<V, K>
    return Pair(p.value, p.key)
end

func largest<T>(items: T[]) -> T
    let best: T = items[0]
    for item in items do
        if item > best then
            best = item
        end
    end
    return best
end

let p: Pair<string, int> = Pair("answer", 42)
let s: Pair<int, string> = swap(p)
print(s.key, s.value)         // 42 answer

print(largest([3, 9, 4]))             // 9
print(largest(["pear", "apple"]))     // pear

Binary Tree

A binary search tree with in-order traversal. Children are ref Node fields: a null field is forwarded as a null reference, a filled one shares the node — no subtree is ever copied.

struct Node
    data: int
    left: ref Node
    right: ref Node
end

func insert(node: ref Node, value: int)
    if value < node.data then
        if node.left == null then
            let fresh: Node = Node(value, null, null)
            node.left = ref fresh
        else
            insert(node.left, value)
        end
    else
        if node.right == null then
            let fresh: Node = Node(value, null, null)
            node.right = ref fresh
        else
            insert(node.right, value)
        end
    end
end

func in_order(node: ref Node)
    if node == null then
        return
    end
    in_order(node.left)
    print(node.data)
    in_order(node.right)
end

let root: Node = Node(50, null, null)
insert(ref root, 30)
insert(ref root, 70)
insert(ref root, 20)
insert(ref root, 40)
in_order(ref root)   // 20 30 40 50 70

Linked List

A singly linked list with insertion and printing. Every function that must reach the caller's list takes it by ref; the new node is a variable so the next field can be rebound to it, and current = current.next moves the cursor without copying the tail.

struct Node
    value: int
    next: ref Node
end

// 'ref' shares the caller's node; without it the callee gets a copy.
func push_back(node: ref Node, value: int)
    if node.next == null then
        let fresh: Node = Node(value, null)
        node.next = ref fresh
    else
        push_back(node.next, value)
    end
end

func print_list(node: ref Node)
    let current: ref Node = node
    while current != null do
        print(current.value)
        current = current.next
    end
end

let list: Node = Node(10, null)
push_back(ref list, 20)
push_back(ref list, 30)
print_list(ref list)   // 10 20 30

HashMap

A hashmap with string keys, a polynomial hash function and separate chaining. Walking a bucket chain through ref keeps the traversal free of copies — reading a slot into a plain variable would give you a copy to mutate.

struct Entry
    key: string
    value: string
    next: ref Entry
end

let buckets: Entry[16]

func hash(key: string, size: int) -> int
    let h: int = 0
    for c in key do
        h = (h * 31 + ord(c)) % size
    end
    return h
end

func put(key: string, value: string)
    let index: int = hash(key, 16)
    if buckets[index] == null then
        buckets[index] = Entry(key, value, null)
        return
    end
    // Walk the chain through a ref: no copy, we mutate in place.
    let node: ref Entry = ref buckets[index]
    while node.next != null do
        node = node.next
    end
    let fresh: Entry = Entry(key, value, null)
    node.next = ref fresh
end

func get(key: string) -> string
    let index: int = hash(key, 16)
    if buckets[index] == null then
        return ""
    end
    let node: ref Entry = ref buckets[index]
    while node != null do
        if node.key == key then
            return node.value
        end
        node = node.next
    end
    return ""
end

put("owl", "purple")
put("lang", "noxy")
print(get("owl"))       // purple
print(get("lang"))      // noxy
print(get("missing"))   // (empty)

Safe Division

Result pattern for operations whose failure is an expected outcome: the result struct carries an ok flag the caller must branch on.

struct Result
    is_ok: bool
    value: int
    error: string
end

func Ok(value: int) -> Result
    return Result(true, value, "")
end

func Err(error_name: string) -> Result
    return Result(false, 0, error_name)
end

func safe_divide(a: int, b: int) -> Result
    if b == 0 then
        return Err("DIVISION_BY_ZERO")
    end
    return Ok(a / b)
end

let result: Result = safe_divide(10, 0)
if result.is_ok then
    print(f"Result: {result.value}")
else
    print(f"Error: {result.error}")
end

Closures

Functions can capture variables from their outer scope, allowing for state encapsulation. The returned function has an exact type, so calls through it are checked at compile time.

func make_account(initial_balance: int) -> func(int) -> int
    let balance: int = initial_balance

    // Return a function that captures 'balance'
    return func(amount: int) -> int
        balance = balance + amount
        return balance
    end
end

let account: func(int) -> int = make_account(100)
print("Initial: 100")
print(f"Deposit 50: {account(50)}")     // 150
print(f"Withdraw 30: {account(-30)}")   // 120

HTTP Server

Built-in HTTP/1.1 server with explicit limits and per-phase deadlines. The handler is a plain function from HttpRequest to HttpResponse.

use http_server select *

func handler(req: HttpRequest) -> HttpResponse
    if req.path == "/" then
        return response_text("Hello from Noxy!")
    elif req.path == "/json" then
        return response_json("{\"status\": \"ok\"}")
    end
    return response_404()
end

// Create and start the server
let server: HttpServer = new_server("127.0.0.1", 8080)
print("Server running on http://127.0.0.1:8080")

// 'ref server' is required: the module boundary erases the exact
// signature, so the compiler cannot borrow the variable for you.
serve(ref server, handler)

Concurrency (Producer-Consumer)

Using channels to communicate between concurrent routines. Arguments to spawn and values sent over a channel are independent values — data handed to another routine is race-free by construction.

use time

func worker(id: int, c: chan string)
    time.sleep(100)
    chan_send(c, f"Message from {id}")
end

func main()
    let c: chan string = make_chan(0)
    spawn(worker, 1, c)
    spawn(worker, 2, c)

    // Receive 2 messages
    print(chan_recv(c))
    print(chan_recv(c))
end

main()

Supervised Tasks

spawn_task launches a routine whose outcome the caller can observe: task_await returns an envelope with the status, the value, or a structured failure — a runtime error inside the task becomes data instead of taking the program down.

func compute(n: int) -> int
    if n < 0 then
        return to_int("boom")   // raises: captured by the task boundary
    end
    return n * n
end

let ok_task: any = spawn_task(compute, 12)
let bad_task: any = spawn_task(compute, -1)

let done: map[string, any] = task_await(ok_task)
print(done["status"], done["value"])   // ok 144

let failed: map[string, any] = task_await(bad_task)
print(failed["status"])                // error
let failure: map[string, any] = failed["error"]
print(failure["kind"])                 // runtime

Installation and Usage

Prerequisites

  • Go 1.24 or higher
  • Git

Installation

# Clone the repository
git clone https://github.com/estevaofon/noxy.git
cd noxy

# Build the project
go build -o noxy ./cmd/noxy

Running Programs

# Run a program
./noxy program.nx

# Interactive REPL
./noxy

# Or via go run
go run ./cmd/noxy program.nx

Packages

# Install a package from Git into noxy_libs/
./noxy --get github.com/user/repo@v1.0.0

# Then import it by module path
# use github_com.user.repo.module as m