Vela. Library

The modules that come with the compiler, under std.

Using the library

Vela's library is a set of modules written in Vela that come with the compiler. A program brings one in the way it brings in a module of its own, with include and a path that starts with std:

include std/files {File}          ; File, by its own name
include std/files                 ; or through the module: files.File
include std/files => disk         ; or under a name of the file's choosing: disk.File

Nothing is read from the disk to find it: the library is part of the compiler, so the command, Vela Studio, and the web editor all have the same library, and a program needs nothing installed beside the compiler. Everything the module rules say holds for it too: priv keeps what a module keeps to itself, names brought in may be renamed with =>, and a file includes each module once.

std/collections

Lazy iterators and eager tasks over the items of a slice. The pattern Iterator<T> is one next() after another; a slice has its chain members (map, filter, take, skip, zip, enumerate, and the consumers collect, fold, find, all, any) in a file that includes the module, each step reading the one before it and gathering nothing until a consumer asks. zip gives Pair shapes (.first, .second) and enumerate gives Indexed (.index, .item); either side of zip may be a slice, read as its own iterator. The shapes Iterator, Pair, Indexed, and SliceIter are brought in by name; the chain members need no import, and each x in it goes through any iterator.

The eager tasks are written to be read in a pipe: each takes the slice first, then what it does with an item (a task value, or a one-line lambda, whose types the slice tells), then, for those that make a new slice, the region it goes in, last, so at r gives it. Brought in with include std/collections (map, filter), or named collections.map(…) after include std/collections.

include std/collections (map, filter, fold, take)

task main()
    region r
    fixed nums = new [int]([1, 2, 3, 4, 5]) at r
    show(nums |> filter(n -> n > 2) at r |> map(n -> n * 10) at r)   ; [30, 40, 50]
    show(nums |> fold(0, (sum, n) -> sum + n))                   ; 15
    show(nums |> take(2))                                          ; [1, 2], a view of nums
fi
Task What it gives
map(xs, f, r) -> [R]@r a new slice in r of what f gives for each item, in order
filter(xs, keep, r) -> [T]@r a new slice in r of the items keep holds for, in order
fold(xs, initial, step) -> A initial, then step of it and each item in turn: fold(xs, 0, (sum, n) -> sum + n) adds them up; makes no memory
zip(xs, ys, r) -> [(A, B)]@r a new slice in r of pairs of the items at the same place, as long as the shorter slice
enumerate(xs, r) -> [(int, T)]@r a new slice in r of each item with its place, from 0
take(xs, n) -> [T]@a the first n items, a view of the same slice; all of them when it has fewer, none when n is below 1
skip(xs, n) -> [T]@a the items after the first n, a view of the same slice; none when it has n or fewer
find(xs, test) -> maybe T the first item test holds for, or none
all(xs, test) -> bool whether test holds for every item; true for none
any(xs, test) -> bool whether test holds for some item; false for none

std/files

Files on the disk. A File is made from its path, and making one opens nothing: it is a value that names a file, which its methods ask about, open, read, write, rename, and delete. A file is opened in a region, which closes it when its block ends, as it frees its memory (a block … fi makes a block anywhere, for just that); close() closes it sooner. Everything that touches the disk can fail, with a FileError that a plan B handles.

include std/files {File, FileMode}

task main()
    region r
    flex notes = File { path: "notes.txt" }
    notes.open(FileMode.Write) at r { failure e
        show("could not open it")
    }
    notes.write("first line\n") { failure e
        show("could not write")
    }
    notes.close() { failure e
        show("could not close")
    }
    notes.open(FileMode.Read) at r { failure e
        show("could not open it")
    }
    loop notes.line() at r { none } is {line}
        show(line)                  ; first line
    fi
fi                              ; r's block ends, and closes the file

File

Its fields are read fields: any code reads them, and only File's own alters methods change them. path is given when a File is made; the others start at their own values, which no literal gives.

Member What it is
path: text The path the File names.
state: FileState What its methods left it: Idle until opened, then Open, Closed, or Gone once deleted.
mode: maybe FileMode How it was opened last; none before then.
alters open(mode) at r Opens the file as mode says, until close() or the end of r's block. Fails with AlreadyOpen on a file open already, and NotFound or NotAllowed when the system says so.
alters close() Closes the file. On one that isn't open, fails with NotOpen.
read() at r -> text@r The rest of the file, from where reading is.
line() at r -> maybe text@r The next line without its line break (a Windows \r\n as well); none at the end. A last line without a line break is still a line.
lines() at r -> [text@r]@r The rest of the file, line by line.
write(t) Writes t, of any region, where writing is.
ended() -> bool Whether nothing more can be read: at the end, or not open for reading.
size() -> int How many bytes the file has; an open one with what was written so far.
exists() -> bool Whether a file that can be read is at the path. A folder, or a file the program may not read, is not one, and neither is a path holding a zero byte.
alters rename(to) Gives the file another path, on the disk and in path.
alters delete() Deletes the file, closing it first when it is open; its state is then Gone.
str() at r -> text@r The path and the state: notes.txt (open).

Each of read, line, lines, write, size, rename, and delete can fail, and their results are ~ FileError. The methods marked alters change the File, so they are called on a flex variable (or through a pointer). The ones that take a region take it last, so it is written after the call: notes.read() at r, as any such call may.

FileMode

Variant Opens the file to
Read read it from its start; it must be there.
Write write it from its start, emptied first, and made when it isn't there.
Append write at its end, and made when it isn't there.
ReadWrite read and write it; it must be there. Reading and writing go on from the same place.

FileError

Variant When
NotFound There is no file at the path, or none that can be read.
NotAllowed The system doesn't let the program open it so.
AlreadyOpen Opened again before it was closed.
NotOpen Not open: never opened, closed, deleted, or closed by its region or by a copy.
WrongMode Read when opened only to write, or written when opened only to read.
Failed Anything else the system reports.

NotFound and NotAllowed are told apart where the system's C library says which; where it doesn't, a failure to open is Failed.

Regions and copies

The region a file is opened in owns it: when the region's block ends, the file is closed, even when a File naming it lives on outside the block. A File is a value, and a copy of it names the same open file, so closing either closes it for both. Neither can tell the File they didn't close, so its state still says Open until one of its own methods finds out: every method then finds the file closed, and fails with NotOpen, never reaching a file opened since (a close() that finds it so sets state to Closed). The state is what the File's methods left it, and what the file is, its methods tell.

region r
flex kept = File { path: "notes.txt" }
each pass in 0..1
    region round
    kept.open(FileMode.Read) at round { failure e
        show("could not open it")
    }
fi                                  ; round's block ends, and closes the file
show(kept.str() at r)                ; notes.txt (open): what open left it
show(kept.line() at r { failure e
    "closed"                        ; NotOpen, so the plan B runs
})                                  ; { "closed" }: line() gives a maybe text, none at the end

examples/files.vela is a whole program: a list written to a file, added to, read back line by line, and deleted.

std/gui

Foundational 2D software canvas rasterization, native X11 window presentation, styling themes, interactive widgets, and per-frame region discipline. Brought in with include std/gui {Window, Canvas, GuiEvent, Point, Size, Rect, Color, Style, Button, Label, TextBox, ListBox, CheckBox, ProgressBar, ImageView, MessageBox, InputBox, VBox, HBox, is_interactive} or include std/gui => gui. See also the comprehensive guide in docs/gui.html.

include std/gui {Window, Canvas, GuiEvent, Point, Rect, Color, Style, Button, ProgressBar}

task main()
    flex win = Window.open("GUI Application", 340, 220) { failure _ <- }
    fixed btn = Button { rect: Rect { x: 30, y: 120, width: 90, height: 36 }, label: "Action", style: Style.primary() }
    fixed canvas = win.canvas()

    loop win.is_open
        region frame default
        fixed ev = win.poll_event()
        match ev
        case GuiEvent.Redraw
            canvas.clear(Color.hex(0x0F172A))
            btn.draw(canvas, Point { x: 0, y: 0 }, false)
            win.present()
        case GuiEvent.CloseRequested
            win.close()
            break
        case _
        fi
    fi
fi
Name Role
Point 2D coordinate: Point { x: int, y: int }
Size Dimensions: Size { width: int, height: int }
Rect 2D bounding box: Rect { x, y, width, height }, with .contains(point) and .intersects(other_rect)
Color RGBA color: Color { r, g, b, a }, Color.rgb(r, g, b), Color.rgba(r, g, b, a), Color.hex(0xRRGGBB), and built-in palettes (.black(), .white(), .red(), .green(), .blue(), .gray(), .transparent())
Style Unified styling theme shape: presets Style.default(), .primary(), .secondary(), .success(), .danger(), .dark(), .light(), and fluent modifiers .with_bg(c), .with_fg(c), .with_border(c, width), .with_padding(p)
Window Window context: Window.open(title, width, height), .canvas(), .poll_event(), .push_event(type, a1, a2, a3), .present(), .close(), .is_open
Canvas Software rasterization drawing context: .clear(color), .draw_pixel(x, y, color), .pixel_at(x, y), .fill_rect(rect, color), .draw_rect(rect, color), .draw_line(x1, y1, x2, y2, color), .draw_text(msg, pos, color), .export_ppm(r)
GuiEvent Event kind: MouseDown(x, y, btn), MouseUp(x, y, btn), MouseMove(x, y), KeyDown(key, mods), KeyUp(key, mods), Resize(w, h), CloseRequested, Redraw, None
Button Interactive widget: Button { rect, label, style }, .draw(canvas, mouse_pos, is_down), .clicked(pos)
Label Text widget: Label { pos, text, color, bg }, .draw(canvas)
TextBox Single-line input widget: TextBox { rect, text, placeholder, style }, .set_text(t), .clear(), .handle_click(pos), .handle_key(key, mods), .draw(canvas)
ListBox Selectable list: ListBox { rect, item_height, style }, .add_item(t), .clear(), .select(idx), .selected_item(), .handle_click(pos), .draw(canvas, mouse_pos)
CheckBox Toggle box: CheckBox { rect, label, checked, style }, .toggle(), .handle_click(pos), .clicked(pos), .draw(canvas, mouse_pos)
ProgressBar Progress indicator: ProgressBar { rect, value, fill_color, style }, .set_value(v) (0..100), .draw(canvas)
ImageView Graphic frame: ImageView { rect, border_color }, .draw_checkerboard(canvas, cell_size), .draw_frame(canvas, bg)
MessageBox Modal alert overlay: MessageBox { style }, .show_modal(title, message), .dismiss(), .handle_click(pos), .draw(canvas, win_w, win_h, mouse_pos, is_down)
InputBox Modal prompt overlay: InputBox { style }, .show_modal(title, prompt, default), .dismiss(), .value(), .handle_click(pos), .handle_key(key, mods), .draw(...)
VBox, HBox Linear layout flow calculators: VBox.new(x, y, width, spacing), HBox.new(x, y, height, spacing), .next(size), .reset()

std/int512

512-bit signed integers in two's complement, Int512. Brought in with include std/int512 {Int512, int512, zero512}. A value is held across eight 64-bit unsigned limbs, lowest limb first. Every arithmetic operation strictly follows Vela's checked-arithmetic safety: any overflow past 512 bits or division by zero immediately stops the program with an informative runtime trap. Int512 follows the module's Numeric<T> pattern, so it also answers the operators the pattern names: a + b, a * b, -a, and a < b read as the methods plus, times, neg, and cmp, both operands Int512.

include std/int512 {Int512, int512, int512_parse}

task main()
    region r
    fixed a = int512(1000)
    fixed b = int512_parse("98765432101234567890") { Int512.zero() }
    fixed c = a.times(b)
    show(c.str(r))                          ; 98765432101234567890000
    show("{a} / 3 = {a.div(int512(3))}" at r)
fi
Operation / Member What it does
plus(other), minus(other), times(other) addition, subtraction, and multiplication; traps on signed overflow
div(other), rem(other) quotient (truncated toward zero) and remainder; traps on division by zero or MIN / -1
neg(), abs() negation and absolute value; traps on MIN
cmp(other), less(other) compares values: cmp gives -1, 0, or 1; less gives bool
to_int64(), to_uint64() converts to primitive 64-bit integer; traps if the value does not fit
str(r: region) formats the integer as decimal text into region r
Int512.zero(), Int512.one() shared constants for 0 and 1
Int512.min_value(), Int512.max_value() the smallest (−2511) and largest (2511 − 1) 512-bit signed integers
Int512.from_int(n), int512(n) creates an Int512 from an int64
int512_from_uint64(n) creates an Int512 from a uint64
int512_parse(s), Int512.parse(s) parses text (decimal, or base prefix 0x, 0o, 0b); gives Int512 ~ ParseError

std/json

JSON parsing, serialization, and tree manipulation. Brought in with include std/json {Json, JsonError, parse, stringify}.

include std/json {Json, parse, stringify}

task main()
    region r default
    fixed raw = "\{"name": "Vela", "stars": 100, "active": true\}"
    fixed doc = parse(raw, r) { Json.Null }
    fixed name = doc.get("name") { Json.Null }.as_text() { "" }
    fixed stars = doc.get("stars") { Json.Null }.as_int() { 0 }
    show(name, " has ", stars, " stars")
    show("json: ", doc.str(r))
fi
Name Role
Json Kind representing JSON values: Null, Bool(b), Int(n), Float(f), Str(s), Array(items), Object(props)
Json.is_null() Returns true if the value is Null
Json.as_bool(), as_int(), as_float(), as_text() Unwraps primitive values into maybe T
Json.get(key) Looks up a property in an object, returning maybe Json@this
Json.item(idx) Retrieves an element from an array by index, returning maybe Json@this
Json.str(r) Serializes the JSON tree to text in region r
parse(raw, r) Parses a JSON string into Json@r ~ JsonError@r, strictly: an unknown escape such as \q fails with InvalidEscape, and a control character written raw in a string with UnexpectedToken; \uXXXX escapes, pairs included, are decoded
stringify(val, r) Serializes a Json@p value into JSON formatted text

std/loops

What a loop that gives a value fails with, LoopResult<B, M>: a kind that takes types, written in Vela. A loop gives a value in any file, with or without this module; a file that writes LoopResult itself, in a type or a variant, brings it in by name (unknown type 'LoopResult'; it is in Vela's library, and 'include std/loops {LoopResult}' brings it in). A case Broke b on a loop's failure names no kind, and needs no include.

include std/loops {LoopResult}

task main()
    flex last = 0
    fixed found: int ~ LoopResult = each x in [2, 7, 4]
        when x > 5
            break = x
        fi
        last = x
    finally
        when last == 4 fail LoopResult.Manual("four") else last fi
    fi
    show(found { 0 })                  ; 0: a break stopped it at 7
fi
Variant Why the loop has no value
Skipped its body never ran: the condition failed at once, or there was nothing to go through
Broke(value: maybe B) a break stopped it, with what break = v gave, or none
EndedByContinue its final pass ended with continue, before the last line
Manual(value: M) the last line of its final pass failed with fail LoopResult.Manual(m)

B is what the loop's breaks give and M what its last lines fail with, each taken from the place the value goes where the loop gives none, and bool where no place names it either. On a loop's own variable, a bare LoopResult takes the loop's types, as found does above; anywhere else they are written, LoopResult<int, text>.

std/math

Numbers beyond arithmetic: the macros P (pi) and e, and tasks for powers, trigonometry, exponentials, and logarithms. Every task takes and gives floats, so an int is written as a float (math.sin(1.0)) or turned into one (math.sin(float(n))). Angles are in radians.

include std/math (P, e, sin)

task main()
    show(sin(P / 2.0))              ; 1
    show(math.cos(P))               ; -1
    show(e ** 2.0)                  ; 7.38906, computed as math.exp(2.0)
    show(math.log(1000.0))          ; 3, base 10 when none is given
    show(math.log(8.0, 2.0))        ; 3
    show(math.log(e, e))            ; 1, the natural logarithm
fi
Name What it is
P pi, 3.141592653589793, a macro
e Euler's number, 2.718281828459045, a macro; math.e ** x is computed as math.exp(x), which C works out more closely than a power
pow(x, y) x raised to the power y, as x ** y is for floats
sin(x), cos(x), tan(x) the sine, cosine, and tangent of an angle
asin(x), acos(x), atan(x) the angle whose sine, cosine, or tangent is x
sinh(x), cosh(x), tanh(x) the hyperbolic sine, cosine, and tangent
exp(x) e raised to the power x
log(x, base = 10.0) the power base is raised to for x, base 10 when the call leaves it out (see optional arguments); with e it is the natural logarithm. Bases 2, 10, and e are C's own log2, log10, and log, which are exact where a quotient of logarithms may not be.

A value outside what a task takes gives what C's gives, as float arithmetic does: math.log(-1.0) and math.asin(2.0) are nan, and math.log(0.0) is -inf. Nothing stops the program. math.P and math.e are macros, so nothing can change them ('P' is a macro, a value that never changes, not a variable), and a file that brings in e by name can't name a variable e; one that writes math.e can.

std/web

Non-blocking networking, HTTP/1.1 request/response shapes, pattern-matching routers, decorator-based route registration, and composable call-time middleware. Brought in with include std/web {Socket, Request, Response, Method, WebError, get, post, serve} or include std/web => web. See also the architectural guide in docs/web.html.

include std/web {Socket, Request, Response, WebError, get, post, serve}

task auth(next: (Request@r, @r) -> Response@r ~ WebError@r, req: Request@r, r: region default) -> Response@r ~ WebError@r
    when req.header("authorization") is {token}
        when token == "Bearer secret"
            <- next(req, r)
        fi
    fi
    <- fail WebError.Unauthorized("invalid token")
fi

$get("/greeting/\{name\}")
task greet(req: Request@r, r: region default) -> Response@r ~ WebError@r
    fixed name = req.param("name") { "friend" }
    <- Response.text("Hello, {name}!\n" at r, r)
fi

$get("/secret")
$(auth)
task secret_panel(req: Request@r, r: region default) -> Response@r ~ WebError@r
    <- Response.text("Confidential data\n", r)
fi

task main()
    serve("127.0.0.1:8080") { failure err
        show("server stopped: ", err)
    }
fi
Name Role
Socket Non-blocking TCP socket handle (open, bind_to, listen, accept, connect, read, write, close, poll)
Request@r HTTP request: .method, .path, .raw_query, .headers, .body_data, .header(name), .query(name, r), .param(name), .body(); .path is decoded from %XX escapes, and .query decodes them and + in its key and value
Response@r HTTP response: Response.text(body, r), Response.html(body, r), Response.json(body, r), Response.with_status(code, r), Response.redirect(to, r), .header(k, v), .status_code(code), .set_body(v)
WebError Failure kind: ListenFailed, BadRequest(why), PayloadTooLarge, RouteNotFound(where), MethodNotAllowed(method, path), Unauthorized(why), ServerFailed(why), BadResponse(why) (a reply that isn't HTTP)
$get, $post, $put, $patch, $delete, $route Startup decorators that register handlers to the default router before main
$on_error, on_error(handler) Custom error handler decorator for DefaultRouter or programmatic router.on_error(handler) / server.on_error(handler) accepting (err: WebError@r, req: Request@r, r: region default) -> Response@r
Router Programmatic router shape: Router.new(), .get(pattern, h), .post(pattern, h), .on_error(h), .enroll(method_code, pattern, h), .route(req, r)
Server Configurable server shape: Server.new(addr), .timeouts(read_ms, idle_ms), .body_limit(bytes), .keep_alive(bool), .on_error(h), .serve(handler), Server.stop()
Poll Descriptor readiness table: Poll.clear(), Poll.watch(sock, events), Poll.unwatch(sock), Poll.wait(timeout_ms), Poll.is_readable(sock), Poll.is_writable(sock)
serve(addr, handler) Listens on TCP address and drives the non-blocking event loop dispatching incoming requests
Client Configurable HTTP client: Client.new(timeout_ms), .timeout(ms), .get(url, r), .post(url, body, ct, r), .post_json(url, body, r)
fetch(url, r) Performs an HTTP GET request and parses response into Response@r ~ WebError@r
fetch_post(url, body, ct, r), fetch_json(url, body, r) Performs an HTTP POST request and parses response into Response@r ~ WebError@r
FileServer Configurable static file server shape: FileServer.new(folder, prefix), .serve(req, r)
serve_files(pattern, folder, prefix), files(folder, req, prefix, r) Serves static files with path confinement discipline (rejecting .., backslashes, colons, control chars, and empty segments) and MIME mapping
file_response(path, r) Reads a single file into Response@r with automatic MIME detection; the path is relative, with no .. part, so it can't leave the folder the program runs in, and an absolute one fails with BadRequest
parse_response(raw, r) Parses an HTTP reply into Response@r ~ WebError@r, failing with BadResponse when the status line is missing, doesn't start with HTTP/, or has no status from 100 to 599, or the headers never end
Response.stream(chunks, r), Response.sse(chunks, r) Constructs HTTP/1.1 chunked transfer responses or Server-Sent Events (SSE)
sse_event(data, event, id, r), sse_msg(event, data, r) Formats compliant SSE stream messages (event: ...\ndata: ...\n\n)
req.body_lines(r) Parses request body line-by-line into a region-scoped slice [text]@r
WebSocket RFC 6455 WebSocket handle: .send_text(msg, r), .send_binary(data, r), .ping(data, r), .pong(data, r), .close(code, reason, r), .recv(into), .is_open()
WebSocketMessage Typed message kind: Text(payload), Binary(payload), Close(code, reason), Ping(payload), Pong(payload), None
ws_upgrade(client_key, r), ws_accept(req, client, r) Performs RFC 6455 WebSocket handshake and produces HTTP 101 Switching Protocols response
ws_connect(url, r) Connects to a WebSocket server URL (ws://host:port/path) and completes client handshake
router.ws(pattern, handler), ws(pattern, handler) Enrolls a persistent WebSocket route handler (ws: WebSocket@r, req: Request@r, r: region default) -> nothing

How the library is made

The library is Vela source in the repository's std/ folder, which the compiler holds as it is built. Most of it is Vela like any program's. What Vela itself can't do, such as asking the system about a file, is a native task: its header is Vela, and its body is a function of the runtime that the generated C includes.

; std/files.vela
priv native task path_exists(path: text@p) -> bool

A native task has no body and no fi, and the compiler writes its body as a call of the runtime function src/library.rs names for it. Only the library has them: in a program, native task is refused ('native' marks a task whose body the compiler writes in C, which only Vela's library has), so the library is not a way to call any C a program likes. Like read, native is a word only there, so a program may still name anything native. The runtime's functions use standard C11 alone, so a program that uses the library builds wherever any other does. std/math's tasks are native ones whose bodies are C's own functions of math.h, and its P and e are macros like any program's.