Luce Base
LuceSourceLuciaOS

24. Examples

Complete programs, each written against the rules of this document. They are the shortest programs that use each part of the language, not the shortest programs possible. Every program is in a package with this manifest and is built and run as shown:

[package]
name = "examples"
luce build main.lucb -o main      # one file, native target of the host
./main
luce test                         # every `test` declaration in the package
luce build --lib                  # a library plus its C header

24.1 Arguments and output#

pub func main(arguments: str[]) -> i32:
    if arguments.length < 2:
        print("usage: greet NAME")
        return 1
    print(f"hello, {arguments[1]}")
    return 0

24.2 A struct with defaults, a custom initialiser, and a static constructor#

pub let out_of_range: ErrorCode = ErrorCode.package(1)

pub struct Style:
    pub let width: f64 = 1.0
    pub let dashed: bool = false
    pub let color: u32 = 0xFFFFFF

pub struct Percentage:
    let value: f64

    pub func init(value: f64) -> !:
        if value < 0.0 or value > 100.0:
            error(out_of_range, "percentage must be 0 through 100")
        self.value = value

    pub static func half() -> Percentage:
        return Percentage(value = 50.0) catch failure: trap("50 is in range")

pub func main(arguments: str[]) -> i32!:
    let thin = Style(width = 0.5)                    # dashed and color defaulted
    let opacity = try Percentage(75.0)
    let half = Percentage.half()
    print(f"{thin.width} {opacity.value} {half.value}")
    return 0

24.3 A ring buffer#

A struct that owns a span, allocated from the allocator that was current when it was made and released through the same one.

import memory
from memory import Allocator

pub let full: ErrorCode = ErrorCode.package(2)

pub struct Ring:
    var items: u32[]
    var head: usize
    var count: usize
    var allocator: Allocator

    pub static func create(capacity: usize) -> Ring!:
        return Ring(items = try new u32[capacity], head = 0, count = 0, allocator = memory.allocator)

    pub func push(value: u32) -> !:
        if self.count == self.items.length:
            error(full, "ring buffer is full")
        self.items[(self.head + self.count) % self.items.length] = value
        self.count += 1

    pub func pop() -> u32?:
        if self.count == 0: return none
        let value = self.items[self.head]
        self.head = (self.head + 1) % self.items.length
        self.count -= 1
        return value

    pub func destroy():
        free(self.items) in self.allocator

pub func main(arguments: str[]) -> i32!:
    var ring = try Ring.create(4)
    defer ring.destroy()
    for value: u32 in 1..=4:
        try ring.push(value)
    while let value = ring.pop():
        print(f"{value}")
    return 0

24.4 An arena allocator#

A type that implements Allocator, remembers its parent, and is used through with.

import c
import luce_std.files
import memory
from memory import Allocator

pub struct Arena: Allocator:
    var parent: Allocator
    var block: u8[]
    var used: usize

    pub static func over(parent: Allocator, capacity: usize) -> Arena!:
        with parent:
            return Arena(parent = parent, block = try memory.allocate(capacity, 16), used = 0)

    pub func allocate(size: usize, alignment: usize) -> u8[]?:
        let base = (usize)self.block.data
        let start = ((base + self.used + alignment - 1) & ~(alignment - 1)) - base
        let end = start +? size else return none
        if end > self.block.length: return none
        self.used = end
        return self.block[start..<end]

    pub func resize(block: u8[], size: usize) -> bool:
        return size <= block.length

    pub func release(block: u8[]):
        return                                   # an arena releases everything at once

    pub func reset():
        self.used = 0

    pub func destroy():
        free(self.block) in self.parent

func words(text: const u8[]) -> usize:
    var count: usize = 0
    var inside = false
    for byte in text:
        let space = byte == ' ' or byte == '\n'
        if not space and not inside: count += 1
        inside = not space
    return count

pub func main(arguments: c.str[]) -> i32!:
    var arena = try Arena.over(memory.allocator, 1 << 20)
    defer arena.destroy()
    with arena:
        let text = try files.read(arguments[1])   # the bytes in the arena, freed by destroy
        defer text.release()
        print(f"{words(text.value)}")
    return 0

24.5 An intrusive list#

Pointers, nullable pointers, memory.offset_of, and the two casts C uses for the same structure.

import memory

struct Link:
    var next: Link*?

struct Task:
    var name: str
    var priority: u32
    var link: Link

func task_of(link: Link*) -> Task*:
    return (Task*)((u8*)link - memory.offset_of(Task, link))

func push(head: Link*?*, task: Task*):
    task.link.next = *head
    *head = &task.link

func highest(head: Link*?) -> Task*?:
    var best: Task*? = none
    var current = head
    while let link = current:
        let task = task_of(link)
        if let leader = best:
            if task.priority > leader.priority: best = task
        else:
            best = task
        current = link.next
    return best

pub func main(arguments: str[]) -> i32:
    var tasks = [Task(name = "build", priority = 2, link = Link(next = none)),
                 Task(name = "test", priority = 5, link = Link(next = none)),
                 Task(name = "ship", priority = 1, link = Link(next = none))]
    var head: Link*? = none
    for task in &tasks:
        push(&head, task)
    if let task = highest(head):
        print(task.name)                          # test
    return 0

24.6 A tagged union#

An enum with payloads, heap-allocated cases with errdefer, and an exhaustive match expression.

enum Expr:
    number(value: f64)
    add(left: Expr*, right: Expr*)
    multiply(left: Expr*, right: Expr*)
    negate(operand: Expr*)

func evaluate(expression: const Expr*) -> f64:
    return match *expression:
        .number(value) => value
        .add(left, right) => evaluate(left) + evaluate(right)
        .multiply(left, right) => evaluate(left) * evaluate(right)
        .negate(operand) => -evaluate(operand)

func release(expression: Expr*):
    match *expression:
        .number(_): return
        .add(left, right), .multiply(left, right):
            release(left)
            release(right)
        .negate(operand): release(operand)
    free(expression)

pub func main(arguments: str[]) -> i32!:
    let two = try new Expr.number(value = 2.0)
    errdefer release(two)
    let three = try new Expr.number(value = 3.0)
    errdefer release(three)
    let sum = try new Expr.add(left = two, right = three)
    errdefer free(sum)
    let negated = try new Expr.negate(operand = sum)
    defer release(negated)
    print(f"{evaluate(negated)}")                # -5.0
    return 0

24.7 Flags#

enum Access as u32:
    empty = 0
    read = 1
    write = 2
    execute = 4

func describe(mode: Access) -> str:
    return match mode:
        .empty => "no access"
        .read => "read-only"
        _ if (mode & Access.write) != Access.empty => "writable"
        _ => "other"

pub func main(arguments: str[]) -> i32:
    let mode = Access.read | Access.write
    print(f"{describe(mode)} {(u32)mode}")       # writable 3
    return 0

24.8 Calling C#

A binding written by hand: an opaque handle, a nullable result, C text, and cleanup with defer.

import c

extern type Window
extern func SDL_Init(flags: u32) -> i32
extern func SDL_GetError() -> c.str
extern func SDL_CreateWindow(title: c.str, x: i32, y: i32, w: i32, h: i32, flags: u32) -> Window?
extern func SDL_DestroyWindow(window: Window)
extern blocking func SDL_Delay(ms: u32)
extern func SDL_Quit()

pub let sdl_failed: ErrorCode = ErrorCode.package(3)

func last_error() -> str:
    return str(SDL_GetError()) catch failure:
        recover "SDL reported invalid text"

pub func main(arguments: str[]) -> i32!:
    if SDL_Init(0x20) != 0:
        error(sdl_failed, last_error())
    defer SDL_Quit()
    let window = SDL_CreateWindow("Luce Base", 100, 100, 800, 600, 0) else error(sdl_failed, last_error())
    defer SDL_DestroyWindow(window)
    SDL_Delay(2000)
    return 0

24.9 Exporting to C#

A Base function with a C-representable signature, and the header the compiler writes for it.

export func checksum(data: const u8[]) -> u32:
    var sum: u32 = 0
    for byte in data:
        sum = sum *% 31 +% byte                  # byte widens to u32 implicitly
    return sum
#include <stdint.h>
#include <stddef.h>

uint32_t checksum(const uint8_t *data, size_t data_length);

24.10 A spinlock and a counter#

import thread

struct SpinLock:
    var locked: @bool

    func acquire():
        while self.locked.swap(true, .acquire):
            thread.pause()

    func release():
        self.locked.store(false, .release)

struct Shared:
    var lock: SpinLock
    var total: u64

func bump(context: void*):
    let shared = (Shared*)context
    for _ in 0..<100_000:
        shared.lock.acquire()
        shared.total += 1
        shared.lock.release()

pub func main(arguments: str[]) -> i32!:
    var shared: Shared
    var handles: thread.Handle[4]
    for index in 0..<4:
        handles[index] = try thread.spawn(bump, &shared)
    for handle in handles:
        try handle.join()
    print(f"{shared.total}")                     # 400000
    return 0

24.11 A generic over a span#

from luce import Comparable

func largest[T: Comparable](values: const T[]) -> T?:
    if values.length == 0: return none
    var best = values[0]
    for value in values:
        if value.compare(best) > 0: best = value
    return best

pub func main(arguments: str[]) -> i32:
    let numbers = [3, 9, 4]
    let top = largest(numbers) else trap("nonempty")
    print(f"{top}")                              # 9
    return 0

24.12 Text without allocation#

Formatted output into a caller's buffer, a str that views it, and a fmt parameter that forwards a formatted string.

import io
import luce
from io import Location
import strings

func describe(bytes: usize, buffer: u8[]) -> str!:
    if bytes >= 1 << 20:
        return try strings.format(buffer, f"{bytes >> 20} MiB")
    if bytes >= 1 << 10:
        return try strings.format(buffer, f"{bytes >> 10} KiB")
    return try strings.format(buffer, f"{bytes} B")

func log(message: fmt, at: Location = luce.location):
    _ = io.stderr().write(f"{at.file}:{at.line}: {message}\n") catch failure: recover 0

pub func main(arguments: str[]) -> i32!:
    var buffer: u8[32]
    let size = try describe(3_145_728, buffer)
    print(size)                                  # 3 MiB
    log(f"described {size}")
    return 0

24.13 A string builder#

A Writer over memory it owns, growing with resize where the allocator allows and reallocating otherwise.

import memory
from io import Writer
from memory import Allocator

pub struct Builder: Writer:
    var bytes: u8[]
    var length: usize
    var allocator: Allocator

    pub static func create(capacity: usize) -> Builder!:
        return Builder(bytes = try new u8[capacity] ---, length = 0, allocator = memory.allocator)

    pub func write(data: const u8[]) -> usize!:
        let needed = self.length +? data.length else error(memory.exhausted, "builder overflow")
        if needed > self.bytes.length:
            let grown = try new u8[needed * 2] --- in self.allocator
            memory.copy(grown, self.bytes, self.length)
            free(self.bytes) in self.allocator
            self.bytes = grown
        memory.copy(self.bytes[self.length..], data, data.length)
        self.length = needed
        return data.length

    pub func text() -> str:
        return (str)self.bytes[..<self.length]

    pub func destroy():
        free(self.bytes) in self.allocator

pub func main(arguments: str[]) -> i32!:
    var builder = try Builder.create(16)
    defer builder.destroy()
    for index in 0..<3:
        try builder.write(f"line {index}\n")
    print(builder.text())
    return 0

24.14 A hash map#

A generic open-addressing map keyed by any hashable, equatable type.

import memory
from luce import Equatable, Hashable
from memory import Allocator

pub struct Map[K: Hashable & Equatable, V]:
    var keys: K[]
    var values: V[]
    var used: bool[]
    var count: usize
    var allocator: Allocator

    pub static func create(capacity: usize) -> Map[K, V]!:
        return Map(keys = try new K[capacity] ---, values = try new V[capacity] ---,
                   used = try new bool[capacity], count = 0, allocator = memory.allocator)

    func slot(key: K) -> usize:
        var index = (usize)(key.hash() % self.keys.length)
        while self.used[index] and not (self.keys[index] == key):
            index = (index + 1) % self.keys.length
        return index

    pub func insert(key: K, value: V) -> !:
        if self.count == self.used.length:
            error(memory.exhausted, "map is full")
        let index = self.slot(key)
        if not self.used[index]:
            self.used[index] = true
            self.keys[index] = key
            self.count += 1
        self.values[index] = value

    pub func find(key: K) -> V?:
        let index = self.slot(key)
        if not self.used[index]: return none
        return self.values[index]

    pub func destroy():
        free(self.keys) in self.allocator
        free(self.values) in self.allocator
        free(self.used) in self.allocator

pub func main(arguments: str[]) -> i32!:
    var ages = try Map[str, u32].create(16)
    defer ages.destroy()
    try ages.insert("ada", 36)
    try ages.insert("alan", 41)
    let age = ages.find("alan") else trap("inserted")
    print(f"{age}")                              # 41
    return 0

24.15 Error handling end to end#

A fallible function calling another, one catch that recovers, one that adds context through a caller's buffer, and main reporting.

import c
import luce_std.files
import strings

pub let missing_field: ErrorCode = ErrorCode.package(4)

struct Config:
    var name: str
    var threads: u32

func field(text: str, key: str) -> str?:
    let bytes = text.bytes
    var start: usize = 0
    while start < bytes.length:
        var end = start
        while end < bytes.length and bytes[end] != '\n':
            end += 1
        let line = bytes[start..<end]
        if line.length > key.length and (str)line[..<key.length] == key:
            var value = line[key.length..]
            while value.length > 0 and (value[0] == ' ' or value[0] == '='):
                value = value[1..]
            return (str)value
        start = end + 1
    return none

func number(text: str) -> u32!:
    if text.length == 0:
        error(missing_field, "empty number")
    var value: u32 = 0
    for byte in text.bytes:
        if byte < '0' or byte > '9':
            error(missing_field, "not a digit")
        value = value * 10 + u32(byte - '0')
    return value

func parse(text: str, scratch: u8[]) -> Config!:
    let name = field(text, "name") else error(missing_field, "name")
    let threads = field(text, "threads") else error(missing_field, "threads")
    let count = number(threads) catch failure:
        error(failure.code, try strings.format(scratch, f"threads: {failure.message}"))
    return Config(name = name, threads = count)

func load(path: c.str, scratch: u8[]) -> Config!:
    let bytes = files.read(path) catch failure:
        if failure.code == files.missing:
            return try parse("name = default\nthreads = 4", scratch)
        error(failure.code, failure.message)
    defer bytes.release()
    return try parse(try str(bytes.value), scratch)

pub func main(arguments: c.str[]) -> i32!:
    var scratch: u8[256]
    let config = try load(arguments[1], scratch)
    print(f"{config.name} on {config.threads} threads")
    return 0