View on GitHub

Nexus Language

A LLM-friendly language

Standard Library

All stdlib modules are in nxlib/stdlib/. Import with:

import { Console }, * as stdio from nxlib/stdlib/stdio.nx

I/O Ports

I/O is capability-gated via ports. Each port has a system_handler that declares require { PermX }, propagating the permission to the caller when injected. Mock handlers without require need no runtime permissions.

Console (stdio.nx)

Requires PermConsole. CLI flag: --allow-console.

port Console do
    fn print(val: string) -> unit
    fn println(val: string) -> unit
end
let main = fn () -> unit require { PermConsole } do
    inject stdio.system_handler do
        Console.println(val: "Hello")
    end
    return ()
end

File System (fs.nx)

Requires PermFs. CLI flag: --allow-fs.

Types:

type Handle = Handle(id: i64)   // linear file handle

Port methods:

port Fs do
    // Query
    fn exists(path: string) -> bool
    fn read_to_string(path: string) -> string

    // Mutating (raise on failure)
    fn write_string(path: string, content: string) -> unit effect { Exn }
    fn append_string(path: string, content: string) -> unit effect { Exn }
    fn remove_file(path: string) -> unit effect { Exn }
    fn create_dir_all(path: string) -> unit effect { Exn }
    fn read_dir(path: string) -> List<Handle> effect { Exn }

    // File descriptor operations (consume-and-return pattern)
    fn open_read(path: string) -> %Handle effect { Exn }
    fn open_write(path: string) -> %Handle effect { Exn }
    fn open_append(path: string) -> %Handle effect { Exn }
    fn read(handle: %Handle) -> { content: string, handle: %Handle }
    fn fd_write(handle: %Handle, content: string) -> { ok: bool, handle: %Handle }
    fn fd_path(handle: %Handle) -> { path: string, handle: %Handle }
    fn close(handle: %Handle) -> unit
end

The fd operations use a consume-and-return pattern: the linear handle is consumed and a fresh handle is returned in the result record, enabling stateless handlers.

let %h = Fs.open_read(path: "data.txt")
let %r = Fs.read(handle: %h)
match %r do
    case { content: c, handle: %h2 } ->
        Fs.close(handle: %h2)
end

Network (net.nx)

Requires PermNet. CLI flag: --allow-net.

Types:

type Header = Header(name: string, value: string)
type Response = Response(status: i64, body: string)
type Server = Server(id: i64)     // linear server handle
type Request = Request(method: string, path: string, headers: string, body: string, req_id: i64)

Port methods:

port Net do
    // HTTP client
    fn get(url: string) -> string
    fn request(method: string, url: string, headers: List<Header>) -> string
    fn request_with_body(method: string, url: string, headers: List<Header>, body: string) -> string
    fn request_response(method: string, url: string, headers: List<Header>, body: string) -> Response

    // HTTP server
    fn listen(addr: string) -> %Server effect { Exn }
    fn accept(server: &Server) -> Request
    fn respond(req: Request, status: i64, body: string) -> bool
    fn respond_with_headers(req: Request, status: i64, headers: List<Header>, body: string) -> bool
    fn stop(server: %Server) -> unit
end

Helper functions:

fn header(name: string, value: string) -> Header
fn response_status(res: Response) -> i64
fn response_body(res: Response) -> string
fn request_method(req: &Request) -> string
fn request_path(req: &Request) -> string
fn request_body(req: &Request) -> string

Random (random.nx)

Requires PermRandom. CLI flag: --allow-random.

port Random do
    fn next_i64() -> i64
    fn range(min: i64, max: i64) -> i64
    fn next_bool() -> bool
end

Clock (clock.nx)

Requires PermClock. CLI flag: --allow-clock.

port Clock do
    fn sleep(ms: i64) -> unit
    fn now() -> i64
end

Process (proc.nx)

Requires PermProc. CLI flag: --allow-proc.

port Proc do
    fn exit(status: i64) -> unit
end

Data Structures

List (list.nx)

Immutable singly-linked list: type List<T> = Nil | Cons(v: T, rest: List<T>)

fn empty<T>() -> List<T>
fn cons<T>(x: T, xs: List<T>) -> List<T>
fn is_empty<T>(xs: List<T>) -> bool
fn length<T>(xs: List<T>) -> i64
fn head<T>(xs: List<T>) -> T
fn tail<T>(xs: List<T>) -> List<T>
fn last<T>(xs: List<T>) -> T
fn reverse<T>(xs: List<T>) -> List<T>
fn concat<T>(xs: List<T>, ys: List<T>) -> List<T>
fn take<T>(xs: List<T>, n: i64) -> List<T>
fn drop_n<T>(xs: List<T>, n: i64) -> List<T>
fn nth<T>(xs: List<T>, n: i64) -> T
fn contains(xs: List<i64>, val: i64) -> bool
fn fold_left<T, U>(xs: List<T>, init: U, f: (acc: U, val: T) -> U) -> U
fn map<T, U>(xs: List<T>, f: (val: T) -> U) -> List<U>
fn map_rev<T, U>(xs: List<T>, f: (val: T) -> U) -> List<U>

Array (array.nx)

Linear mutable array: [| T |]

fn length<T>(arr: &[| T |]) -> i64
fn is_empty<T>(arr: &[| T |]) -> bool
fn get<T>(arr: &[| T |], idx: i64) -> T
fn set<T>(arr: &[| T |], idx: i64, val: T) -> unit
fn head<T>(arr: &[| T |]) -> T
fn last<T>(arr: &[| T |]) -> T
fn fold_left<T, U>(arr: &[| T |], init: U, f: (acc: U, val: T) -> U) -> U
fn any<T>(arr: &[| T |], pred: (val: T) -> bool) -> bool
fn all<T>(arr: &[| T |], pred: (val: T) -> bool) -> bool
fn find_index<T>(arr: &[| T |], pred: (val: T) -> bool) -> i64
fn for_each<T>(arr: &[| T |], f: (val: T) -> unit) -> unit
fn map_in_place<T>(arr: &[| T |], f: (val: T) -> T) -> unit
fn filter<T>(arr: &[| T |], pred: (val: T) -> bool) -> List<T>
fn partition<T>(arr: &[| T |], pred: (val: T) -> bool) -> Partition<T>
fn zip_with<A, B, C>(left: &[| A |], right: &[| B |], f: (left: A, right: B) -> C) -> List<C>
fn zip<A, B>(left: &[| A |], right: &[| B |]) -> List<Pair<A, B>>
fn consume<T>(%arr: [| T |], f: (val: %T) -> unit) -> unit

Set (set.nx)

Hash set with dictionary-passed key operations:

type SetKeyOps<K> = SetKeyOps(eq: SetEq<K>, hash: SetHash<K>)
type Set<K> = Set(key_ops: SetKeyOps<K>, entries: List<SetEntry<K>>)

fn make_key_ops<K>(eq: (left: K, right: K) -> bool, hash: (key: K) -> i64) -> SetKeyOps<K>
fn i64_key_ops() -> SetKeyOps<i64>
fn empty<K>(key_ops: SetKeyOps<K>) -> Set<K>
fn contains<K>(set: Set<K>, val: K) -> bool
fn insert<K>(set: Set<K>, val: K) -> Set<K>
fn remove<K>(set: Set<K>, val: K) -> Set<K>
fn from_list<K>(key_ops: SetKeyOps<K>, xs: List<K>) -> Set<K>
fn to_list<K>(set: Set<K>) -> List<K>
fn size<K>(set: Set<K>) -> i64
fn union<K>(left: Set<K>, right: Set<K>) -> Set<K>
fn intersection<K>(left: Set<K>, right: Set<K>) -> Set<K>
fn difference<K>(left: Set<K>, right: Set<K>) -> Set<K>

HashMap (hashmap.nx)

Hash map with dictionary-passed key operations:

type KeyOps<K> = KeyOps(eq: Eq<K>, hash: Hash<K>)
type HashMap<K, V> = HashMap(key_ops: KeyOps<K>, entries: List<Entry<K, V>>)
type Lookup<V> = Found(value: V) | Missing

fn make_key_ops<K>(eq: (left: K, right: K) -> bool, hash: (key: K) -> i64) -> KeyOps<K>
fn i64_key_ops() -> KeyOps<i64>
fn empty<K, V>(key_ops: KeyOps<K>) -> HashMap<K, V>
fn from_entries<K, V>(key_ops: KeyOps<K>, entries: List<Entry<K, V>>) -> HashMap<K, V>
fn entries<K, V>(map: HashMap<K, V>) -> List<Entry<K, V>>
fn size<K, V>(map: HashMap<K, V>) -> i64
fn contains_key<K, V>(map: HashMap<K, V>, key: K) -> bool
fn get<K, V>(map: HashMap<K, V>, key: K) -> Lookup<V>
fn get_or<K, V>(map: HashMap<K, V>, key: K, default: V) -> V
fn put<K, V>(map: HashMap<K, V>, key: K, value: V) -> HashMap<K, V>
fn remove<K, V>(map: HashMap<K, V>, key: K) -> HashMap<K, V>
fn keys<K, V>(map: HashMap<K, V>) -> List<K>
fn values<K, V>(map: HashMap<K, V>) -> List<V>

Utilities

String (string.nx)

fn length(s: string) -> i64
fn contains(s: string, sub: string) -> bool
fn substring(s: string, start: i64, len: i64) -> string
fn index_of(s: string, sub: string) -> i64
fn starts_with(s: string, prefix: string) -> bool
fn ends_with(s: string, suffix: string) -> bool
fn trim(s: string) -> string
fn to_upper(s: string) -> string
fn to_lower(s: string) -> string
fn replace(s: string, from_str: string, to_str: string) -> string
fn char_at(s: string, idx: i64) -> string
fn from_i64(val: i64) -> string
fn from_float(val: float) -> string
fn from_bool(val: bool) -> string
fn to_i64(s: string) -> i64
fn split(s: string, sep: string) -> List<string>

Math (math.nx)

fn abs(val: i64) -> i64
fn max(a: i64, b: i64) -> i64
fn min(a: i64, b: i64) -> i64
fn mod_i64(a: i64, b: i64) -> i64
fn abs_float(val: float) -> float
fn sqrt(val: float) -> float
fn floor(val: float) -> float
fn ceil(val: float) -> float
fn pow(base: float, exp: float) -> float
fn i64_to_float(val: i64) -> float
fn float_to_i64(val: float) -> i64

Result (result.nx)

type Result<T, E> = Ok(val: T) | Err(err: E)

fn is_ok<T, E>(res: Result<T, E>) -> bool
fn is_err<T, E>(res: Result<T, E>) -> bool
fn unwrap_or<T, E>(res: Result<T, E>, default: T) -> T
fn map<T, U, E>(res: Result<T, E>, f: (val: T) -> U) -> Result<U, E>
fn map_err<T, E, F>(res: Result<T, E>, f: (val: E) -> F) -> Result<T, F>
fn and_then<T, U, E>(res: Result<T, E>, f: (val: T) -> Result<U, E>) -> Result<U, E>
fn from_exn<T>(exn: Exn) -> Result<T, Exn>
fn to_exn<T>(res: Result<T, Exn>) -> T effect { Exn }

Exception Utilities (exn.nx)

fn to_string(exn: Exn) -> string
fn backtrace(exn: Exn) -> [string]

backtrace returns call-stack frames captured at raise point (interpreter only; returns [] in WASM builds).

Core (core.nx)

type Pair<A, B> = Pair(left: A, right: B)
type Partition<T> = Partition(matched: List<T>, rest: List<T>)

fn fst<A, B>(p: Pair<A, B>) -> A
fn snd<A, B>(p: Pair<A, B>) -> B
fn negate(val: bool) -> bool