The examples on this page are short and each shows one feature. Larger, complete programs are in the examples repository on GitHub: github.com/admlang/examples.
Hello, World
Every application declares def new(args string[]) int - its return value is the process exit code.
application Hello {
def new(args string[]) int {
println("Hello, 🌍")
return 0
}
}
String Interpolation
Any expression goes inside {}. A value whose type implements str()
formats itself - no explicit conversion needed.
module greet {
interface IStringer {
def str() string
}
type Thing {
def str() string {
return "Thing"
}
}
}
application Interpolation {
use greet
def new(args string[]) int {
let n = 3
let t = greet.Thing{}
let si greet.IStringer = t
println("n={n}") // n=3
println("t={t}") // t=Thing
println("sum={n + n * 2}") // sum=9
return 0
}
}
Arrays & Maps
Arrays support set-style operators and whole-array arithmetic; slices are views into the same storage. Maps iterate as key/value pairs.
application Collections {
def new(args string[]) int {
let a = int[1, 2, 3, 4]
let b = int[3, 4, 5, 6]
println(a & b) // [3, 4] intersection
println(a | b) // [1..6] union
println(a - b) // [1, 2] difference
println(a[-2:]) // [3, 4] last two
a[:] *= 10 // scale every element in place
println(a) // [10, 20, 30, 40]
let stock = map<string, int>{
"widget": 12,
"gasket": 3,
}
stock["bolt"] = 40
for let (name, count) in stock {
println("{name}: {count}")
}
return 0
}
}
Regex
Regexes are a primitive with their own literal syntax in RE2 form, not a library type - flags go
after the closing slash, and captures are referenced as $1, $2 or
${name} in replacements.
application Regex {
def new(args string[]) int {
let r = /[0-9]+/
r.test("a1b") // true
r.matchAll("a1b22c") // ["1", "22"]
r.split("a1b22c") // ["a", "b", "c"]
r.replace("a1b22c", "X", -1) // "aXbXc"
// the g flag replaces every match without a count
let rg = /[0-9]+/g
rg.replace("a1b22c", "X") // "aXbXc"
// capture groups, reordered in the replacement
let cap = /(a)([0-9]+)/
cap.replace("a12 b a3", "$2-$1", -1) // "12-a b 3-a"
// RE2 syntax: Perl classes, named groups, inline flags
let mail = /(?P<user>\w+)@(\w+)\.com/
mail.replace("ana@example.com", "$\{user\} at $2") // "ana at example"
/(?i)hello/.test("Say HELLO") // true
// strings accept a regex directly
"hello123".contains(/\d+/) // true
"hello123".endsWith(/\d+/) // true
return 0
}
}
Big Numbers
bigint is arbitrary-precision and uses the same operators as any other number -
no special call syntax for arithmetic that would overflow a machine integer.
application BigNumbers {
def new(args string[]) int {
let x bigint = bigint(0x1234567890abcdef)
let y bigint = bigint(7)
// exponentiation on a value far past 64 bits
let z bigint = (x**3) - (3*x) + y
if (x < z) && (z != x) {
println(z)
}
return 0
}
}
A literal too large for int infers bigint on its own - no cast needed.
Pattern Matching
match destructures tuples and struct shapes, and each case can carry a guard after
a ;.
module shapes {
struct User {
id int
email string
}
def classify(x int) string {
match x {
case 0:
return "zero"
case (n); n > 0:
return "positive: {n}"
default:
return "negative"
}
}
def sum(t (int, int)) int {
match t {
case (a, b):
return a + b
default:
return 0
}
}
def userId(u User) int {
match u {
case {id int, email string}; id > 0:
return id
default:
return 0
}
}
}
Error Handling
A function that can fail returns !T. try propagates the error to the
caller; onerror recover handles it locally.
module inventory {
struct Item {
name string
stock int
}
def restock(item Item, qty int) !Item {
if qty <= 0 {
fail "quantity must be positive"
}
return Item{name: item.name, stock: item.stock + qty}
}
}
application Warehouse {
use inventory
def new(args string[]) int {
let item = inventory.Item{name: "widget", stock: 12}
// recover with a fallback value
let a = inventory.restock(item, -1) onerror recover item
// inspect the error before deciding
let b = inventory.restock(item, 8) onerror (err error) {
println("restock failed: {err.message}")
recover item
}
println(b.stock)
return 0
}
}
Contracts
expects runs before the body, provides runs after - on every return
path. A failed assertion becomes an error, so the function returns !T.
module accounts {
def withdraw(balance int, amount int) !int {
expects {
assert amount > 0 onerror fail "amount must be positive"
assert amount <= balance onerror fail "insufficient funds"
}
let remaining = balance - amount
return remaining
provides {
assert remaining >= 0
assert remaining < balance
}
}
}
Transactions
A function declares how to save and undo its own state with begin and
rollback. Those blocks lie dormant until the function is called from inside a
transaction - then any fail unwinds every rollback automatically.
application Ledger {
let state int = 0
def apply(x int) {
begin {
state = state + x
}
rollback {
state = state - x
}
state = state + 1
}
def boom() !int {
fail "boom"
}
def run() !none {
transaction {
apply(5)
let v = try boom() // fails - every rollback runs
println(v)
} onerror (err error) {
println("rolled back: {err.message}")
recover none
}
return none
}
def new(args string[]) int {
run() onerror return 1
return 0
}
}
Generics
A where block adds members that only exist when the type argument satisfies the
constraint - User<Admin> has permissions,
User<string> doesn't.
module demo {
struct Pair<A, B> {
first A
second B
}
type Box<T> {
value T
def new(v T) { self.value = v }
def get() T { return self.value }
}
type Admin {}
type Account {}
type User<T> {
name string
where T is Admin | Account {
permissions string[]
def tag() string { return "privileged" }
}
}
def run() int {
let p Pair<int, string> = Pair<int, string>{first: 1, second: "hi"}
let b = new Box<int>(10)
let c = new Box<string>("x")
let u User<Admin> = new(name="bob")
return b.get() + c.get().len() + u.permissions.len()
}
}
Operator Overloading
An infix method binds a type to an operator symbol, so instances of it work with
ordinary operator syntax - including shifts and rotations.
module bits {
type Flags {
value int
infix def | (other Flags) Flags {
return Flags{value: value | other.value}
}
infix def & (other Flags) Flags {
return Flags{value: value & other.value}
}
infix def <<< (count int) Flags {
return Flags{value: value <<< count}
}
}
}
application Bits {
use bits
def new(args string[]) int {
let read = bits.Flags{value: 1}
let write = bits.Flags{value: 2}
let rw = read | write // calls infix def |
let hi = rw <<< 4 // calls infix def <<<
// the standard library does this too - strings repeat with *
println("ha" * 3) // hahaha
return 0
}
}
Lambdas
param => expr is shorthand for an anonymous function. Parameter types are
inferred when the callback's shape is already known.
application Lambdas {
def new(args string[]) int {
let a = int[1, 2, 3]
let doubled = a.mapElem(v => v * 2)
let labels = a.mapElem(v => sprint(v))
// an anonymous function that names itself can recurse
let f = def fib(n int) int {
return n when n < 2
return fib(n-1) + fib(n-2)
}
println(doubled.len())
println(f(10))
return 0
}
}
Parallel Loops
forall spreads iterations across every available CPU core. Order isn't
guaranteed; finish runs once all workers are done.
application Parallel {
def new(args string[]) int {
let values = [10, 20, 30, 40]
forall let (idx, v) in values {
println("worker got {v} at {idx}")
} finish {
println("all workers done")
}
return 0
}
}
Async & Channels
await composes with &&/|| over completion state:
|| resumes on the first to finish, && waits for both.
module work {
async def slow() int {
let acc = 0
for let i in 0..2000000 {
acc = i
}
return 7
}
async def fast() string {
return "ok"
}
}
application Concurrency {
use work
def new(args string[]) int {
let a = work.slow()
let b = work.fast()
// resume as soon as either finishes
let first = await (a || b)
println("string won") when first is string
// futures can be cancelled or given a deadline
let stuck = work.slow().timeout(50ms)
let dropped = work.slow().cancel()
// a channel between workers
let ch channel<int> = new(size=4)
ch <- 42
ch.close()
select {
case ch:
println(<-ch)
default:
println("nothing ready")
}
// or drain a channel in parallel across cores
forall let v in ch {
println(v)
} empty {
println("channel was empty")
}
return 0
}
}
Value Tags
Tags travel with a value's identity - through calls, array slots and channels - while copies start clean. A gate function refuses anything that was not marked as signed.
module demo.tags {
type Request {
id int
def new(v int) {
id = v
}
}
def deliver(message string) !none {
fail "unsigned message" unless message.tags().has("signed")
println("delivered: {message}")
return none
}
}
application TagsDemo {
use demo.tags::(*)
def new(args string[]) int {
let message = "hello"
message.tags().set("+signed")
deliver(message) onerror (err error) {
println("rejected: {err.message}")
recover none
}
let copy = message + "!" // a new value: no tags
deliver(copy) onerror (err error) {
println("rejected: {err.message}")
recover none
}
let request = new Request(7)
request.tags().add("Network/primary").add("pii")
let queue channel<Request> = new(size=1)
queue <- request
let got = <-queue
if got is Request {
for let tag in got.tags().list() {
println(tag) // Network/primary, then pii
}
}
return 0
}
}
Services
@commit() mutates service state and emits a typed event; @on subscribes
to it. @query() reads a snapshot.
module demo {
use std.services::(*)
service Storage {
description = "Counts opened files"
dependencies = Dependencies{hard: [], soft: []}
lifecycle = Lifecycle{start: StartMode.Manual, restart: RestartMode.Never}
permissions = []
opened int
def config<T>(val T) !none {
return none
}
async def start() !none {
return none
}
async def stop() !none {
return none
}
async def restart() !none {
return none
}
@query()
def openCount() int {
return opened
}
@commit()
def open(id int) int {
opened += 1
return id
}
}
@on(Storage.open)
def logOpen(ev Event<int>) {
println("opened {ev.result}, total {Storage.openCount()}")
}
}
Custom Annotations
meta def defines a new annotation. The first parameter's context type decides
what it can decorate - and a parameter annotation can rewrite the argument before the body
ever runs:
module demo {
use std.reflection as reflection
meta def setTo(ctx reflection.ParameterMetaContext, value int) {
ctx.set(value)
}
def id(@setTo(42) x int) int {
return x
}
def run() bool {
return id(1) == 42 // the argument was rewritten on the way in
}
}
Annotations are also discoverable at runtime, so a registry can find everything tagged with a given annotation without any string lookups or manual registration:
module demo {
use std.reflection
meta def mark(target reflection.DeclarationMetaContext, name string) {}
@mark("t")
type Thing {}
@mark("f")
def handler(x int) int {
return x
}
def run() int {
let hits = reflection.attrs(mark)
return hits.len() // 2 - the type and the function
}
}
And metadata attached by an annotation stays readable at runtime, which is how the JSON encoder finds its field names:
module demo {
use std.reflection as reflection
use std.data.encoding as encoding
type T {
@encoding.json(name="a", omitempty=true)
x int
}
def run() bool {
let t = T{x: 1}
let md = reflection.fieldMetadataOf(t, "x")
return md.load("encoder.json.name") == "a"
}
}
SIMD
vec4<T> and friends map to hardware SIMD where available - arithmetic runs
across all lanes at once, and comparisons return a mask for branchless selection.
application Simd {
def new(args string[]) int {
let data = float[1.0, 2.0, 3.0, 4.0]
let v vec4<float>
let a = v.load(data, 0)
let w vec4<float>
let b = w.splat(2.0)
let scaled = a * b // all four lanes at once
let big = a.gt(b) // mask4<float>
let picked = big.blend(a, b) // branchless select
scaled.store(data, 0)
return 0
}
}
C Interop
Annotate a module and every body-less def inside binds to that library - this is
how the standard library's OpenGL bindings work.
@link("GL")
module gl {
const GL_TRIANGLES uint32 = 0x00000004
def glClear(mask uint32);
def glViewport(x, y, width, height int);
}
@c("libm.so")
module libm {
def sin(x float64) float64;
def cos(x float64) float64;
}
Script Interop
Pricing rules written in JavaScript, called from ADM through typed declarations. The script
is built into the program; js.runtime() is the engine the declarations run in,
and a function set on its global object is one the script can call.
export function total(items) {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
export function discount(amount, code) {
if (code !== "SPRING") throw new Error("unknown code " + code)
return amount * 0.1
}
module shop {
use adm.interop.js::*
struct Item {
name string
price float
quantity int
}
@js("pricing.js")
def total(items Item[]) float;
// Fails with a js.ScriptError when the script throws.
@js("pricing.js")
def discount(amount float, code string) !float;
}
application Shop {
use (
shop
shop::(Item)
)
def new(args string[]) !int {
let cart = [Item{name: "notebook", price: 4.5, quantity: 3}, Item{name: "pen", price: 1.2, quantity: 10}]
let due = shop.total(cart)
println("total {"%.2f".format(due)}")
println("spring {"%.2f".format(try shop.discount(due, "SPRING"))} off")
shop.discount(due, "FREE") onerror (err error) {
println("free {err.message}")
recover 0.0
}
return 0
}
}
Databases
SQL through std.data.db: one API for every database, with the driver picked by the
URL's scheme. The drivers are libraries (adm.database.sqlite,
adm.database.postgres, adm.database.mysql); importing one is what
registers its scheme. Values are always bound, never pasted into the statement, and a
sql def turns a statement into a typed method.
module books {
use (
std.data.db
std.time
)
struct Book {
id int
title string
author string
// The column is called `published`.
@db.column("published")
year int
rating ?float
// Text in the row, a time in the program.
@db.column(stored = db.Stored.Text)
added time.Time
}
// The statements as methods; `{name}` is a bound argument.
type Shelf {
@db.connection()
conn db.Queryable
def new(conn db.Queryable) {
self.conn = conn
}
sql def find(id int) !?Book {
SELECT * FROM books WHERE id = {id}
}
sql def by(author string) !Book[] {
SELECT * FROM books WHERE author = {author} ORDER BY published
}
sql def rate(id int, rating float) !db.Result {
UPDATE books SET rating = {rating} WHERE id = {id}
}
sql def best() !(string, float) {
SELECT title, rating FROM books WHERE rating IS NOT NULL ORDER BY rating DESC LIMIT 1
}
}
}
application Bookshelf {
use (
std.data.db
std.time
adm.database.sqlite // makes the `sqlite:` scheme known
books::(Book, Shelf)
)
def new(args string[]) !int {
let conn = try db.connect("sqlite:books.db")
try conn.exec("CREATE TABLE IF NOT EXISTS books (id INTEGER PRIMARY KEY, title TEXT NOT NULL UNIQUE, author TEXT NOT NULL, published INTEGER NOT NULL, rating REAL, added TEXT NOT NULL)")
// Arguments are bound to the placeholders in order.
let res = try conn.exec("INSERT INTO books (title, author, published, added) VALUES (?, ?, ?, ?)", "Dune", "Frank Herbert", 1965, time.now())
println("inserted id {res.lastInsertId}")
// A struct's fields as named arguments.
let book = Book{id: 2, title: "Solaris", author: "Stanisław Lem", year: 1961, rating: 4.5, added: time.now()}
try conn.exec("INSERT INTO books (id, title, author, published, rating, added) VALUES (:id, :title, :author, :published, :rating, :added)", db.values(book))
// Rows into structs, by column name.
let all = try db.readAll<Book>(try conn.query("SELECT * FROM books ORDER BY id"))
println("{all.len()} books")
// A failure says what kind it is.
conn.exec("INSERT INTO books (title, author, published, added) VALUES (?, ?, ?, ?)", "Dune", "Someone Else", 2000, time.now()) onerror (err error) {
if err is db.DbError {
println("{err.kind.name}: {err.message}") // Constraint: sqlite: UNIQUE constraint failed: books.title
}
recover db.Result{affected: 0, lastInsertId: 0}
}
let shelf = new Shelf(conn)
try shelf.rate(1, 4.8)
let (title, rating) = try shelf.best()
println("best: {title} ({"%.1f".format(rating)})")
for let (_, b) in try shelf.by("Stanisław Lem") {
println("{b.year} {b.title}")
}
return 0
}
}
The Database service holds connections by name, each a pool shared between tasks.
A sql def written as a plain function runs on the service's default connection,
or on the one @db("name") names, so the code that uses it never passes a
connection around. PostgreSQL writes its placeholders $1 and SQLite
?; a sql def is the same text for both.
module shop {
use std.services.database::(Database, db)
// Outside a type a `sql def` runs on the service's default connection.
sql def place(customer string, total float) !none {
INSERT INTO orders (customer, total) VALUES ({customer}, {total})
}
sql def revenue() !float {
SELECT coalesce(sum(total), 0.0) FROM orders
}
// `@db` names another of the service's connections.
@db("audit")
sql def record(event string) !none {
INSERT INTO events (event) VALUES ({event})
}
}
application Shop {
use (
std.services.database::(Database)
adm.database.postgres
adm.database.sqlite
shop
)
def new(args string[]) !int {
try await Database.start()
// Each connection is a pool that tasks share.
try Database.add("postgres://app:secret@db.internal/shop?sslmode=verify-full")
try Database.add("audit", "sqlite:audit.db")
try shop.place("ana", 19.90)
try shop.record("order for ana")
println("revenue {"%.2f".format(try shop.revenue())}")
return 0
}
}
GPU Kernels
A cuda def is a CUDA kernel with ADM parameters; arrays are copied to the device and back around the launch, and the same program runs on the CPU when there is no GPU.
application Brighten {
use std.gpu
cuda def brighten(pixels byte[], amount int) !none {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < pixels_len) {
int v = pixels[i] + amount;
pixels[i] = v > 255 ? 255 : v;
}
}
def new(args string[]) int {
let image = byte[10, 120, 250]
brighten(image, 40) onerror recover none
println(image[0], image[1], image[2]) // 50 160 255
println("ran on {gpu.deviceName()}") when gpu.available()
return 0
}
}
Data reused across launches goes into a DeviceBuffer with gpu.upload; see GPU kernels in the reference.
Collections
std.data.collections has the usual structures as generic types; every one iterates with for.
application Words {
use std.data.collections::*
def new(args string[]) int {
let counts = new HashMap<string, int>()
for let word in "the cat and the hat".split(" ") {
counts.set(word, counts.get(word, 0) + 1)
}
let repeated = new Deque<string>()
for let entry in counts {
repeated.pushBack(entry.key) when entry.value > 1
}
println(repeated.popFront()) // the
let seen = new BloomFilter(1024)
seen.add("cat")
println(seen.mightContain("cat")) // true
return 0
}
}
Semantic Versions
std.data.semver parses versions, compares them by precedence and checks ranges in the usual ^, ~ and || notation.
application Versions {
use std.data.semver::*
def new(args string[]) int {
let v = parse("1.4.0-rc.1")
return 1 when v is error
println(v.major) // 1
println(v < "1.4.0") // true: a prerelease precedes the release
println(v.satisfies("^1.2.0")) // false: prereleases need an opt-in
println(v.satisfies(">=1.4.0-alpha")) // true
println(v.canonical()) // 1.4.0-rc.1
return 0
}
}
MCP Server
std.proto.mcp makes a program a Model Context Protocol server: mark functions as tools, resources and prompts, and mcp.serve offers them to an AI application over standard input and output. Names, descriptions and argument schemas come from the declarations and their doc comments. new mcp.Server(...) serves the same over HTTP, and mcp.connect is the client.
application Calculator {
use (
std.proto.mcp
tools.calculator as _
)
def new(args string[]) int {
// Serves every marked function over standard input and output.
mcp.serve("calculator", "1.0.0") onerror (err error) {
return 1
}
return 0
}
}
module tools.calculator {
use std.proto.mcp
// Adds two numbers.
// @param a the first number
// @param b the second number
@mcp.tool(readOnly = true)
def add(a int, b int) int {
return a + b
}
// A note by its number.
@mcp.resource("notes://\{id\}")
def note(id int) !string {
fail "there are nine notes" when id > 9
return "Note {id}"
}
// Asks for a review of some code.
// @param code the code to review
// @param focus what to look for
@mcp.prompt()
def review(code string, focus string = "bugs") string {
return "Review this code for {focus}:\n{code}"
}
}
Units of Measure
A number literal takes a unit in parentheses, and the type of the value is its dimension. The compiler checks the dimensions; at run time the values are plain floats.
application Trip {
use std.units::*
def eta(distance Length, speed Speed) Time {
return distance / speed
}
def new(args string[]) int {
let distance = 420(km)
let speed = 110(km/h)
let t = eta(distance, speed)
println(t.str(h, digits = 1)) // 3.8 h
let fuel = 6.2(L/100km)
let used Volume = distance * fuel
println(used.str(L)) // 26.04 L
let tank = parseUnit("45 L", L) onerror (err error) {
println(err)
return 1
}
println("{(tank / fuel).str(km, digits = 0)} on a full tank") // 726 km
let force = 75(kg) * 9.81(m/s²)
println(force) // 735.75 kg·m/s²
println(force.str(N)) // 735.75 N
let outside = 21(°C)
println(outside.str(K)) // 294.15 K
let link = 1(GB) / 8(s)
println(link.str(Mbit/s)) // 1000 Mbit/s
// let wrong = distance + t // error: cannot add Length and Time
let level = quantity(-6, dB) // a gain of 0.501
println(level.str(dB)) // -6 dB
return 0
}
}
The Units of measure page lists every unit in std.units and shows how to declare your own.
Media Player
std.media.player plays a playlist of local files, folders and internet radio streams through the default speaker, with crossfades, shuffle, repeat, ReplayGain and a chain of std.media.dsp effects. Playback runs on its own task, and the controls can be called from any task.
application Jukebox {
use (
std.media.player::*
std.media.dsp::*
)
def new(args string[]) int {
let player = new MediaPlayer()
let added = player.playlist.addFolder("~/Music/Albums") onerror (err error) {
println("cannot read the folder: {err}")
return 1
}
player.playlist.push("https://radio.example.org/stream.mp3")
println("{added} tracks and one radio station")
let eq = new Equalizer(EqLayout.Graphic10)
eq.apply(EqPreset.Rock)
player.effects.push(eq)
player.effects.push(new Limiter())
player.crossfade = 3s
player.repeat = RepeatMode.RepeatPlaylist
player.shuffle()
let done channel<bool> = new(size = 1)
player.onTrackChange = def (i int) {
return when i < 0
let track = player.playlist.tracks[i]
println("now playing: {track.artist} - {track.title}")
}
player.onStateChange = def (state PlayerState) {
done <- true when state == PlayerState.Stopped
}
player.play() onerror (err error) {
println("cannot play: {err}")
return 1
}
<-done
return 0
}
}
The player reads WAV, AIFF, FLAC, MP3, Ogg Vorbis, Opus, AAC and ALAC with decoders written in ADM, and plays MIDI files through the std.media.synth synthesizer. .m3u and .pls playlists load and save with playlist.load and playlist.save.
Plugins
A plugin exports part of what it uses and builds into a single .admplugin package; adm doc --plugin shows what is inside.
@manifest(version = "1.2.0")
plugin ImageTools {
use (
acme.imaging
acme.imaging.filters
)
export (
acme.imaging.filters
acme.imaging::(Image, decode)
)
}
adm build writes ImageTools.admplugin; the Plugins page covers the package layout and the shared runtime.
Datagrams
std.net.udp.dial opens a UDP socket connected to one peer: the system drops datagrams from other senders, and send and receive name no address. Two such sockets on one machine exchange a datagram each way.
application PingPong {
use std.net.udp
def new(args string[]) !int {
// Two sockets on this machine, each connected to the other: the system drops
// datagrams from anyone else, and a send names no address.
let left = try udp.dial("127.0.0.1:47002", local = "127.0.0.1:47001")
let right = try udp.dial("127.0.0.1:47001", local = "127.0.0.1:47002")
try left.send("ping".bytes(), 1s)
let buf byte[] = []
buf.len(64)
let n = try right.receive(buf, 1s)
println("right got {string(buf.slice(0, n))} from {right.remoteAddr()}")
try right.send("pong".bytes(), 1s)
let m = try left.receive(buf, 1s)
println("left got {string(buf.slice(0, m))}")
try left.close()
try right.close()
return 0
}
}
A udp.Peer is a net.DatagramTransport, which DTLS and the other datagram protocols of std.net run over. For many peers on one socket, net.Socket has writeTo and readFrom; Socket.connectDatagram(address) connects a socket you made yourself, after which send and receive work as above.
Memory-Mapped Files
os.mapFile maps a file into memory, and Mapping.pages gives its bytes as an ordinary byte[] over the mapping's own pages: nothing is copied, and the system reads each page when the loop first touches it, so the file may be larger than memory.
application Lines {
use (
std.os
std.services.storage::MapAccess
)
// Counts the lines of a file of any size: the pages are read as the loop
// touches them, and none of the file is copied.
def new(args string[]) !int {
let path = args.len() > 1 ?? (args[1], "/usr/share/dict/words")
let mapping = try os.mapFile(path, MapAccess.Copy)
defer mapping.close()
let text = try mapping.pages(0, mapping.length())
let lines = 0
for let b in text {
lines += 1 when b == 10
}
println("{path}: {lines} lines in {text.len()} bytes")
return 0
}
}
A MapAccess.Copy mapping keeps stores into the array in this process and works on a file you may only read; MapAccess.ReadWrite writes them to the file. The pages stay mapped while the array or a slice of it lives, even after close. Mapping.read(offset, length) copies a range instead, and mapping.reader() streams it.
Styles and Themes
A component's look comes from four places, later winning: its own style, the rules of the style sheets around it, its states' looks, and what the call that made it sets. This page uses all of them: a Card with states and named variants, a font set once on the page and inherited field by field, a house StyleSheet for every window with one rule by kind and one by role, and a sheet of its own for part of the page while the window is narrow.
module themed {
use (
std.math.geom::(Axes, Corners, Edges)
std.ui::(SizeClass, StyleSheet, kind, role, rule)
std.ui.components::*
std.ui.styles::*
std.units::*
)
// A card: a box with a background and a border, whose text follows its font.
component Card {
type {
@input(value = true)
title string = ""
}
view {
Heading(title)
children()
}
style {
Box
Layout
Decorated
Typography
Compositing
arrange = Arrange.Column
gap = new Axes<Extent>(8(px))
padding = new Edges<Extent>(16(px))
radius = new Corners<Extent>(8(px))
font = {size: 15(px)}
on = {
hover: {opacity: 0.95},
}
variants = {
quiet: {opacity: 0.7},
wide: {width: 480(px)},
}
}
}
// House style: every button square; anything with the button role inside a card bold.
let house = new StyleSheet([
rule(kind<Button>(), style = {radius: new Corners<Extent>(0(px))}),
rule(role(Role.Button).inside(kind<Card>()), style = {font: {weight: FontWeight.Bold}}),
])
// A sheet of its own for one part of the page.
let compact = new StyleSheet([
rule(kind<Card>(), style = {padding: new Edges<Extent>(6(px)), gap: new Axes<Extent>(4(px))}),
])
component Page {
view {
Card("Account", style = {variant: [wide]}) {
Label("Signed in")
Button("Sign out", style = {width: 140(px), on: {hover: {font: {italic: true}}}})
}
// Below `UIConfig.compactWidth` the second card takes the compact sheet.
if viewport.widthClass == SizeClass.Compact {
Styled(compact) {
Card("Recent", style = {variant: [quiet]}) {
Label("Nothing yet")
}
}
} else {
Card("Recent", style = {variant: [quiet]}) {
Label("Nothing yet")
}
}
}
style {
Box
Layout
Typography
arrange = Arrange.Column
gap = new Axes<Extent>(12(px))
padding = new Edges<Extent>(24(px))
// Inherited by every text and button on the page.
font = Font{family: "Inter", size: 14(px)}
}
}
}
application Themed {
use (
std.ui::(UI, UIConfig)
std.ui.renderer
std.units::*
themed::(Page, house)
)
async def new(args string[]) !int {
try UI.config(UIConfig{rendererName: "opengl", compactWidth: 520(px)})
try await UI.start()
UI.addStyles(house)
try await UI.run(Page(), new renderer.WindowConfig("Themed", 900, 640))
return 0
}
}
The card's font = {size: 15(px)} names one field: the family is still the page's Inter. A rule whose selector names no kind, role(Role.Button).inside(kind<Card>()), sets font on any style that has it, so it reaches a Button, a link or anything else with the button role inside a card. viewport.widthClass is Compact below UIConfig.compactWidth; the Components page has the full list of style properties, states and selectors.
Tests
check suites live next to the code they verify. They're type-checked with the rest
of the program and dropped from release builds.
module demo.paths {
use std.os::(base, extension)
use std.testing::(benchmark, test)
check "Path" {
@test()
def TestExtension() {
assert extension("/path/to/file.txt") == "txt"
assert extension("file") == ""
}
@benchmark()
def BenchBase() {
base("/path/to/file.txt")
}
}
}
Run them with adm test, or benchmarks with adm test --bench .