Open Manual

Programs

A Nytrix file can be a script, a module, or both. Use a script when the file is

the program. Use a module when other files import its names. Combine them when

you want exported functions plus local checks.

Choose the file shape

ShapeUse whenMain rule
ScriptThe file is the whole program or a focused check.Top-level statements run when the file is executed.
ModuleOther files import this API.module name(exports) controls the public surface.
Script plus moduleYou want public functions and local assertions together.Imports see exports; direct execution also runs checks.
Package entrypointA project has several files or dependencies.Keep startup in main and keep reusable work in modules.

Script

A script is a file with top-level statements.

use std.core

def name = "ny"
assert_eq("hello, " + name, "hello, ny", "greeting")

Run:

ny --color=never hello.ny

Silent success means all assertions passed.

Module

A module declares the names it exports.

use std.core

module stats(mean)

fn mean(list xs) number {
   mut total = 0
   for x in xs { total += x }
   total / xs.len
}

Imports use the module or package name:

use stats (mean)

Script and module together

A file can export functions and also contain direct-run checks. Imported users

see only the exports. Direct execution runs the #main block.

module mathx(double)

fn double(int x) int { x * 2 }

#main {
   assert_eq(double(21), 42, "double")
}

Entrypoint shape

Small tools can use top-level statements. Files that export reusable helpers can

put only direct startup work in #main. Imports see the helpers and skip the

startup block.

use std.core
use std.os.args as args

fn greet(str name) str {
   "hello, " + name
}

#main {
   def name = args.positionals().get(0, "ny")
   print(greet(name))
}

Imports

Put imports at the top. Aliases keep repeated module calls namespaced.

use std.core
use std.os.net as net
use std.math.parse.data.json as json

Import the owning module instead of relying on a broad namespace. A visible

alias such as json.json_decode or net.request keeps the origin at the call

site and in diagnostics.

Public surface

Public functions use explicit names and types when the type is part of the API.

fn parse_port(str raw) int {
   int(raw)
}

Grouped modules can publish profiles:

module local {
   export core(run)
   export debug(dump_state)
   internal(_state)
}

use local:debug

The default import sees core. A profile import sees core plus the selected

profile.

ADTs and typed APIs

Use enum when the API returns one of several tagged shapes.

enum Parse<T> {
   Ok(T value),
   Err(str message)
}

fn parse_flag(str raw) Parse<bool> {
   if(raw == "yes"){ Parse.Ok(true) }
   else { Parse.Err("expected yes") }
}

match parse_flag("yes") {
   Parse.Ok(v) -> assert(v, "flag")
   Parse.Err(msg) -> panic(msg)
}

Typed containers use angle brackets: list<int>, dict<str, int>,

Result<T, E>, and user ADTs such as Parse<bool>.

Use explicit public types when the type is part of the API contract. Inside a

script, inference is valid until a diagnostic or performance profile requires a

narrower type.

Complete project examples

Project examples live under etc/projects. They are complete files, not API

fragments. GitHub shows source links; the generated website embeds selected

project files below.

Keep project files for complete workflows and richer demos. Tiny one-function

snippets belong in learn pages or self-tests instead.

Rule 110

Open the source.

#!/usr/bin/env ny

;; Keywords: cli terminal automata rule110 example
;; Rule 110 - https://en.wikipedia.org/wiki/Rule_110
use std.core
use std.core.term

def SX = 2

fn rule(l, c, r) {
   if l && c && r { return 0 }
   if c || r { return 1 }
   0
}

fn step(cur, w) {
   def nxt = bytes(w)
   mut i = 0
   while i < w {
      def l = (i > 0) && (bytes_get(cur, i - 1) == 1)
      def c = (bytes_get(cur, i) == 1)
      def r = (i < w - 1) && (bytes_get(cur, i + 1) == 1)
      if rule(l, c, r) { bytes_set(nxt, i, 1) }
      else { bytes_set(nxt, i, 0) }
      i += 1
   }
   nxt
}

def tSize = get_terminal_size()
mut tW = tSize.get(0, 0)
mut tH = tSize.get(1, 0)

if tW <= 0 { tW = 80 }
if tH <= 0 { tH = 24 }
def target_gens = 27
def prefix_len  = 7
mut w = (tW - prefix_len) / (SX * 2)

if w < 1 { w = 1 }
write_str(color(f"Terminal Size: {tW}x{tH} | Displaying: {target_gens} Generations", "magenta") + "\n")
mut u = bytes(w)
bytes_set(u, w - 1, 1)
def block = "██"
def space = "  "
mut gen = 0
while gen < target_gens {
   mut line = ""
   mut i = 0
   while i < w {
      if bytes_get(u, i) { line = line + block }
      else { line = line + space }
      i += 1
   }
   mut i2 = w - 1
   while i2 >= 0 {
      if bytes_get(u, i2) { line = line + block }
      else { line = line + space }
      i2 -= 1
   }
   write_str(color(line, "magenta") + "\n")
   u = step(u, w)
   gen += 1
}

Langton's Ant

Open the source.

#!/usr/bin/env ny

;; Keywords: cli terminal automata langton ant example
;; Langton's Ant - https://en.wikipedia.org/wiki/Langton%27s_ant
use std.core
use std.core.term
use std.os.args as cli

def CH_BLACK = "█"
def CH_TRAIL = "░"
def COLOR_WHITE = 7
def COLOR_GRAY  = 8
def COLOR_HEAD  = 5
def DIR_UP    = 0
def DIR_RIGHT = 1
def DIR_DOWN  = 2
def DIR_LEFT  = 3
def H_UP_L    = "▄"
def H_UP_R    = "█"
def H_DOWN_L  = "█"
def H_DOWN_R  = "▀"
def H_LEFT_L  = "▄"
def H_LEFT_R  = "█"
def H_RIGHT_L = "█"
def H_RIGHT_R = "▄"

fn key_of(int x, int y) str { f"{x}:{y}" }
def term_size = get_terminal_size()
mut W = term_size.get(0, 80)
mut H = term_size.get(1, 24)

if W < 2 { W = 80 }
if H < 1 { H = 24 }
if W % 2 == 1 { W -= 1 }
def HALF_W = W / 2
def CANV = canvas(W, H)
mut black = dict(1024)
mut seen = dict(1024)
mut x = 0
mut y = 0
mut dir = DIR_UP
mut steps = 0
def max_steps = cli.first_positive_int()

fn set_pair(int x, int y, str left, str right, int color_idx) int {
   canvas_set(CANV, x, y, left, color_idx, 0)
   canvas_set(CANV, x + 1, y, right, color_idx, 0)
   0
}

fn draw_world() int {
   canvas_clear(CANV)
   def cx = HALF_W / 2
   def cy = H / 2
   def min_x = x - cx
   def min_y = y - cy
   mut sy = 0
   while sy < H {
      mut sx = 0
      while sx < HALF_W {
         def k = key_of(min_x + sx, min_y + sy)
         def px = sx * 2
         if black.contains(k) {
            set_pair(px, sy, CH_BLACK, CH_BLACK, COLOR_WHITE)
         } elif seen.contains(k) {
            set_pair(px, sy, CH_TRAIL, CH_TRAIL, COLOR_GRAY)
         }
         sx += 1
      }
      sy += 1
   }
   mut left = H_UP_L
   mut right = H_UP_R
   if dir == DIR_DOWN {
      left = H_DOWN_L
      right = H_DOWN_R
   } elif dir == DIR_LEFT {
      left = H_LEFT_L
      right = H_LEFT_R
   } elif dir == DIR_RIGHT {
      left = H_RIGHT_L
      right = H_RIGHT_R
   }
   set_pair((HALF_W / 2) * 2, H / 2, left, right, COLOR_HEAD)
   0
}

fn should_quit(int key) bool { is_quit_key(key) || key == 113 || key == 81 }
tui_begin()
defer { tui_end() }
seen = seen.set(key_of(x, y), true)
draw_world()
canvas_refresh(CANV)
while true {
   if should_quit(poll_key()) { break }
   def k = key_of(x, y)
   if black.contains(k) {
      black = black.delete(k)
      dir = (dir + 3) % 4
   } else {
      black = black.set(k, true)
      dir = (dir + 1) % 4
   }
   seen = seen.set(k, true)
   if dir == DIR_UP { y -= 1 }
   elif dir == DIR_RIGHT { x += 1 }
   elif dir == DIR_DOWN { y += 1 }
   else { x -= 1 }
   seen = seen.set(key_of(x, y), true)
   draw_world()
   canvas_refresh(CANV)
   steps += 1
   if max_steps > 0 && steps >= max_steps { break }
}

Conway's Game of Life

Open the source.

#!/usr/bin/env ny

;; Keywords: cli terminal automata conway life example
;; Conway's Game of Life - https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
use std.core
use std.core.term
use std.math.random as rng
use std.os.args as cli
use std.os.time as time

def CELL_ALIVE = 1
def CELL_DEAD = 0
def CHAR_ALIVE = "█"
def CHAR_DEAD = " "
def COLOR_ALIVE = 5
def COLOR_DEAD = 0

fn rand_mod(int n) int {
   if n <= 1 { return 0 }
   (rng.rand() / 65536) % n
}

def term_size = get_terminal_size()
mut W = term_size.get(0, 80)
mut H = term_size.get(1, 24)

if W < 2 { W = 80 }
if H < 1 { H = 24 }
if W % 2 == 1 { W -= 1 }
def HALF_W = W / 2
def CANV = canvas(W, H)
def TOTAL = HALF_W * H
mut grid = bytes(TOTAL)
mut next_grid = bytes(TOTAL)
mut frames = 0
def max_frames = cli.first_positive_int()

fn set_pair(int x, int y, str ch, int color_idx) int {
   canvas_set(CANV, x, y, ch, color_idx, 0)
   canvas_set(CANV, x + 1, y, ch, color_idx, 0)
   0
}

fn seed_grid(any g) int {
   mut i = 0
   while i < TOTAL {
      bytes_set(g, i, rand_mod(2) == 0 ? CELL_ALIVE : CELL_DEAD)
      i += 1
   }
   0
}

fn draw_full(any g) int {
   mut y = 0
   while y < H {
      def row = y * HALF_W
      mut x = 0
      while x < HALF_W {
         def idx = row + x
         def sx = x * 2
         if bytes_get(g, idx) {
            set_pair(sx, y, CHAR_ALIVE, COLOR_ALIVE)
         } else {
            set_pair(sx, y, CHAR_DEAD, COLOR_DEAD)
         }
         x += 1
      }
      y += 1
   }
   0
}

fn evolve(any cur, any out) int {
   mut alive = 0
   mut y = 0
   while y < H {
      def row = y * HALF_W
      mut x = 0
      while x < HALF_W {
         def idx = row + x
         mut n = 0
         if y > 0 {
            def prev = (y - 1) * HALF_W
            if x > 0 { n += bytes_get(cur, prev + x - 1) }
            n += bytes_get(cur, prev + x)
            if x < HALF_W - 1 { n += bytes_get(cur, prev + x + 1) }
         }
         if x > 0 { n += bytes_get(cur, row + x - 1) }
         if x < HALF_W - 1 { n += bytes_get(cur, row + x + 1) }
         if y < H - 1 {
            def nxt = (y + 1) * HALF_W
            if x > 0 { n += bytes_get(cur, nxt + x - 1) }
            n += bytes_get(cur, nxt + x)
            if x < HALF_W - 1 { n += bytes_get(cur, nxt + x + 1) }
         }
         def was = bytes_get(cur, idx)
         mut live = CELL_DEAD
         if was == CELL_ALIVE {
            if n == 2 || n == 3 { live = CELL_ALIVE }
         } elif n == 3 {
            live = CELL_ALIVE
         }
         bytes_set(out, idx, live)
         if live { alive += 1 }
         if live != was {
            if live { set_pair(x * 2, y, CHAR_ALIVE, COLOR_ALIVE) }
            else { set_pair(x * 2, y, CHAR_DEAD, COLOR_DEAD) }
         }
         x += 1
      }
      y += 1
   }
   alive
}

fn should_quit(int key) bool { is_quit_key(key) || key == 113 || key == 81 }
rng.seed(time.ticks())
tui_begin()
defer { tui_end() }
seed_grid(grid)
draw_full(grid)
canvas_refresh(CANV)
while true {
   if should_quit(poll_key()) { break }
   def alive = evolve(grid, next_grid)
   if alive == 0 {
      seed_grid(next_grid)
      draw_full(next_grid)
   }
   canvas_refresh(CANV)
   def tmp = grid
   grid = next_grid
   next_grid = tmp
   frames += 1
   if max_frames > 0 && frames >= max_frames { break }
}

Matrix Rain

Open the source.

#!/usr/bin/env ny

;; Keywords: cli terminal matrix rain digital example
;; Matrix rain - https://en.wikipedia.org/wiki/Digital_rain
use std.core
use std.core.term
use std.math.random as rng
use std.os.args as cli
use std.os.time as time

def COLOR_DARK = 8
def COLOR_LIGHT = 5
def COLOR_WHITE = 7
def COLOR_BG = 0
def THRESH_HI = 29
def THRESH_MD = 16
def PAL = [1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14]
def HEAD = [15, 15, 14, 11, 10, 7]
def GLYPHS = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","0","1","2","3","4","5","6","7","8","9","!","@","#","$","%","^","&","*","(",")","[","]","{","}","<",">","?","/","\\","|","+","=","-","_","~",":",";",".",",","`","'","日","一","木","山","水","火","土","金","月","天","地","人","雨","風","花","龍","鳥","虫","牛","馬","竹","弓","大","中","小","上","下","左","右","出"]

fn rand_mod(int n) int {
   if n <= 1 { return 0 }
   (rng.rand() / 65536) % n
}

fn pick(a) {
   a.get(rand_mod(a.len), 15)
}

def term_size = get_terminal_size()
mut W = term_size.get(0, 80)
mut H = term_size.get(1, 24)

if W < 1 { W = 80 }
if H < 1 { H = 24 }
def CANV = canvas(W, H)
def CBUF = CANV.get(2)
def ATTR = CANV.get(3)
def COL = CANV.get(4)
def BLEN = CANV.get(5)
def INTENSITY = bytes(W * H)
def CHARS = bytes(W * H)
def max_frames = cli.first_positive_int()
mut frame = 0

rng.seed(time.ticks())
mut drop_y = list(W)
mut drop_speed = list(W)
mut drop_color = list(W)
mut drop_seq = list(W)
mut col = 0
while col < W {
   drop_y = drop_y.append(0 - rand_mod(H * 4))
   drop_speed = drop_speed.append(12 + rand_mod(50))
   drop_color = drop_color.append(pick(PAL))
   drop_seq = drop_seq.append(rand_mod(GLYPHS.len))
   col += 1
}

fn should_quit(int key) bool { is_quit_key(key) || key == 113 || key == 81 }
tui_begin()
defer { tui_end() }
while true {
   if should_quit(poll_key()) { break }
   mut x = 0
   while x < W {
      def next_y = drop_y.get(x, 0) + drop_speed.get(x, 0)
      drop_y.set(x, next_y)
       def y = next_y / 20
       if y >= 0 && y < H {
          def idx = y * W + x
          def seq = drop_seq.get(x, 0)
          bytes_set(CHARS, idx, seq % GLYPHS.len)
          drop_seq.set(x, seq + 1)
          bytes_set(INTENSITY, idx, THRESH_HI + 8)
       }
       if y > H + rand_mod(6) {
          drop_y.set(x, 0 - rand_mod(H * 4))
          drop_speed.set(x, 12 + rand_mod(50))
          drop_color.set(x, pick(PAL))
          drop_seq.set(x, rand_mod(GLYPHS.len))
       }
      x += 1
   }
   mut idx = 0
   def n = W * H
   while idx < n {
      def fade = bytes_get(INTENSITY, idx)
      if fade > 0 {
         mut fg = drop_color.get(idx % W, COLOR_DARK)
         mut bold = 0
         if fade > THRESH_HI {
            fg = pick(HEAD)
            bold = 1
         } elif fade > THRESH_MD {
            bold = 1
         }
         def code = bytes_get(CHARS, idx)
         def ch = GLYPHS.get(code, " ")
         CBUF[idx] = ch
         store8(BLEN, ch.len, idx)
         store8(COL, fg, idx)
         store8(ATTR, bold, idx)
         bytes_set(INTENSITY, idx, fade - 1)
      } else {
         CBUF[idx] = " "
         store8(BLEN, 1, idx)
         store8(COL, COLOR_BG, idx)
         store8(ATTR, 0, idx)
      }
      idx += 1
   }
   canvas_refresh(CANV)
   frame += 1
   if max_frames > 0 && frame >= max_frames { break }
}

FFI

Open the source.

#!/usr/bin/env ny

;; Keywords: native ffi raw-memory pointer example
;; Store vertices in raw memory and call C sqrt through a header import.
use std.core
use std.math (float)

#include <math.h> as "c"
def STRIDE = 12

fn near(any a, any b) bool {
   def d = float(a) - float(b)
   d > -0.0001 && d < 0.0001
}

fn store_vertex(any ptr, int idx, f64 x, f64 y, f64 z) {
   def at = idx * STRIDE
   store32_f32(ptr, x, at + 0)
   store32_f32(ptr, y, at + 4)
   store32_f32(ptr, z, at + 8)
}

fn read_vertex(any ptr, int idx) list {
   def p = ptr_add(ptr, idx * STRIDE)
   [load32_f32(p, 0), load32_f32(p, 4), load32_f32(p, 8)]
}

def vertices = malloc(STRIDE * 2)
memset(vertices, 0, STRIDE * 2)
store_vertex(vertices, 0, 3.0, 4.0, 0.0)
store_vertex(vertices, 1, 1.0, 2.0, 2.0)
def a = read_vertex(vertices, 0)
def b = read_vertex(vertices, 1)
def len_a = c.sqrt(float(a.get(0) * a.get(0) + a.get(1) * a.get(1)))
def b_ptr = vertices + STRIDE
assert(near(len_a, 5.0), "C sqrt through direct ABI")
assert(near(load32_f32(b_ptr, 8), 2.0), "raw pointer arithmetic")
assert(b == [1.0, 2.0, 2.0], "raw memory round trip")
print("a:", a, "length:", len_a)
print("b:", b)
free(vertices)

Args

Open the source.

#!/usr/bin/env ny

;; Keywords: os args cli argv example
;; Print argc/argv values.
use std.core
use std.os.args

print(f"Argc: {argc()}")
def argv = args()
mut i = 0
while i < len(argv) {
   def arg = argv.get(i, "")
   def show = arg.len > 0 ? arg : "none"
   print(f"Argv[{i}]: {show}")
   i += 1
}

Local Server

Open the source.

#!/usr/bin/env ny

;; Keywords: os http server routes html json example
;; Small HTTP server with HTML, CSS, text, and JSON routes.
use std.core
use std.os (exit)
use std.os.net.server as web

def HTML = "<!doctype html><html lang=en>
<meta charset=utf-8>
<meta name=viewport content='width=device-width,initial-scale=1'>
<title>Ny Server</title>
<link rel=stylesheet href=/style.css>
<main>
<header>
<p>Nytrix HTTP</p>
<h1>It works.</h1>
</header>
<section>
<p>Served by <code>etc/projects/os/server.ny</code></p>
<nav>
<a href=/health>health</a>
<a href=/plain>plain</a>
<a href=/style.css>css</a>
</nav>
</section>
</main>
</html>"
def CSS = ":root{--a:#9b5cff;--e:#3a2a57}
body{
margin:0 ;
max-width:560px ;
padding:48px 16px ;
margin-inline:auto ;
font:15px/1.5 system-ui ;
background:#000 ;
color:#f2f2f2 ;
}

h1{margin:4px 0 ;font:700 clamp(36px,8vw,42px)/1 system-ui}
p{margin:0 ;color:#999}
header{padding-bottom:16px ;border-bottom:1px solid var(--e)}
section{padding-top:16px}
nav{display:flex ;gap:8px;flex-wrap:wrap;margin-top:12px}
a{
padding:6px 10px ;
border:1px solid var(--e) ;
border-radius:1px ;
color:inherit ;
text-decoration:none ;
}

a:first-child{
background:var(--a) ;
border-color:var(--a) ;
color:#000 ;
}
"

fn asset(str body, str ctype) dict {
   web.response(body, 200, {"content-type": ctype, "cache-control": "no-cache"})
}

fn app(dict req) dict {
   def method = req.get("method", "GET")
   if method != "GET" && method != "HEAD" { return web.method_not_allowed("GET, HEAD") }
   def path = req.get("path", "/")
   if path == "/" || path == "/index.html" { return asset(HTML, "text/html; charset=utf-8") }
   if path == "/style.css" { return asset(CSS, "text/css; charset=utf-8") }
   if path == "/plain" { return web.text("ny host ok\n") }
   if path == "/health" { return web.json({"ok": true, "service": "ny-server"}) }
   web.not_found("not found: " + path + "\n")
}

def result = web.serve_cli(app, {"name": "Ny Server", "port": 8080})

if !result.get("ok", false) {
   print("server failed: " + result.get("error", "unknown error"))
   exit(1)
}

Run:

ny etc/projects/os/server.ny

Sound

Open the source.

#!/usr/bin/env ny

;; Keywords: os sound audio synth wav example
;; Play a generated synth source, write it as WAV, then load that WAV back.
use std.core
use std.os (exit, file_exists, file_remove, msleep, pid, temp_dir, ticks)
use std.os.path as path
use std.os.sound
use std.os.sound.source.synth

fn wait_playback(any inst, int limit_ms) int {
   mut waited = 0
   while is_playing(inst) && waited < limit_ms {
      msleep(50)
      waited += 50
   }
   if is_playing(inst) { stop(inst) }
   waited
}

fn play_source(str label, any sound, int limit_ms) bool {
   print(f"{label}: loaded={sound != 0}")
   if sound == 0 { return false }
   def inst = play(sound)
   if inst != 0 { wait_playback(inst, limit_ms) }
   inst != 0
}

fn play_file(str label, str file, int limit_ms) bool {
   def sound = load(file)
   print(f"{label}: exists={file_exists(file)} loaded={sound != 0}")
   if sound == 0 { return false }
   def inst = play(sound)
   if inst != 0 { wait_playback(inst, limit_ms) }
   true
}

fn temp_sound_path(ext) str {
   path.join(temp_dir(), "nytrix-sine-" + to_str(pid()) + "-" + to_str(ticks()) + ext)
}

if !init() {
   print("audio backend unavailable")
   exit(0)
}

print(f"backend={get_backend_name()}")
def tone = make_sine_source(440.0, 0.75, 48000, 0.45, false)
play_source("synth.sine", tone, 1000)
def wav_file = temp_sound_path(".wav")

if write_synth_wav(tone, wav_file) {
   play_file("generated.wav", wav_file, 1000)
   match file_remove(wav_file) { _ -> {} }
} else {
   print("generated.wav: write failed")
}

shutdown()

UI Projects

ui.md covers the engine viewer and focused UI input/project files.

Related