Builtins

A builtin is a function the language already knows. You do not import it, declare it or define it — you call it. There are exactly one hundred, and the wcl CLI adds three more of wdoc's own, which is where the 103 in this chapter comes from.

The bulk of this chapter is a catalogue. Each entry carries one builtin's signature, its parameters, its return value, and an example whose printed answer came from a real run.

The five sections before the catalogue are the part you cannot get by looking one name up: how a call behaves, how to ask the language what it has, and which of the near-identical pairs to reach for.

§ 1One call, one answer

A builtin call is an ordinary call expression. Expressions and operators covers the syntax; four rules govern what a builtin does with it.

The one exception to fixed arity is format, which takes a template plus as many arguments as the template has {} slots.

Builtins are the last place the resolver looks

A name in call position is resolved against the document first — let bindings, fn items, fields — and only then against the builtin registry. So a fn len(xs: list<utf8>) -> u32 { … } of your own shadows the builtin inside its scope. The flip side is the error message: a name that resolves to nothing callable is reported as unknown built-in 'box', because the registry is where the search ended, not where it began.

§ 2A file that uses seven of them

Type this one and run it. It is a three-service fleet that reports on itself, and it calls map, sort, unique, max_by, sum, len and format to do it.

release.wclwcl
# release.wcl — one fleet, seven builtins.
@block("service")
type Service {
  @inline(0) name: utf8
  region:   utf8
  replicas: u32
}

@document
type Release {
  @children("service") services: list<Service>

  names:    list<utf8>
  regions:  list<utf8>
  busiest:  utf8
  replicas: u32
  summary:  utf8
}

names    = sort(map(services, fn(s: Service) -> utf8 { s.name }))
regions  = unique(map(services, fn(s: Service) -> utf8 { s.region }))
busiest  = { let s = max_by(services, fn(s: Service) -> u32 { s.replicas }); s.name }
replicas = sum(map(services, fn(s: Service) -> u32 { s.replicas }))
summary  = format("{} services over {} regions, {} replicas, busiest is {}",
                  len(services), len(regions), replicas, busiest)

service "web"   { region = "us-east-1"  replicas = 4u32 }
service "db"    { region = "us-east-1"  replicas = 2u32 }
service "cache" { region = "eu-west-1"  replicas = 1u32 }
text
$ wcl check release.wcl
OK
$ wcl get release.wcl names
["cache", "db", "web"]
$ wcl get release.wcl regions
["us-east-1", "eu-west-1"]
$ wcl get release.wcl busiest
"web"
$ wcl get release.wcl replicas
7u32
$ wcl get release.wcl summary
"3 services over 2 regions, 7 replicas, busiest is web"

Three things in that file are worth naming before the catalogue starts. A gather field is a list<Service>, so every list builtin works on it directly. unique keeps first-seen order, which is why us-east-1 leads. And busiest wraps its call in a block expression: max_by returns a record-shaped value, and reading a field straight off a call — max_by(…).name — fails with expected a reference, got call. Bind it with a let first. That rule bites hardest in Reflection, where almost every builtin answers a record.

§ 3Asking the language what it has

The catalogue below was generated from the language itself, and you can regenerate it. builtin_names() answers every registered name, sorted, and fn_signature(name) answers that builtin's documentation as a record.

catalogue.wclwcl
@document
type Catalogue {
  count: usize
  first: list<utf8>
  sig:   utf8
}

count = len(builtin_names())
first = take(builtin_names(), 3)
sig   = { let s = fn_signature("map"); s.signature }
text
$ wcl get catalogue.wcl count
102
$ wcl get catalogue.wcl first
["__wdoc_slot", "abs", "acos"]
$ wcl get catalogue.wcl sig
"fn(xs: [T], f: fn (T) -> U) -> [U]"

The count is 102 rather than 100, and the first name is one you did not write, because the environment a builtin lives in belongs to the host, not to the language. The wcl CLI is a wdoc-aware host: it registers wdoc's two builtins into every document it opens, whether or not the document imports wdoc. The bare language registers one hundred. wcl repl shows both, because a REPL started with a file opens it the way the CLI opens any document, and a REPL started without one evaluates against an empty document in the plain language environment:

text
$ echo 'len(builtin_names())' | wcl repl
100
$ echo 'len(builtin_names())' | wcl repl release.wcl
102

The hundred are registered by five modules of wcl_lang, and this chapter groups them by what they are for rather than by which module holds them.

ModuleRegistersCovered in
collections54Lists, Records, Strings, Tensors, Failing on purpose
math29Math
reflect12Reflection
paths3Paths and globs
units2Units
wdoc, via the host2Builtins the host adds

§ 4Choosing between the near-twins

Several builtins answer nearly the same question, and picking the wrong one of a pair is the most common way to be surprised. This is the table you cannot build by reading either entry on its own.

When you wantReach forNotBecause
A substring testcontains(s, needle)list_containscontains is strings only and fails on a list; list_contains accepts a list or a tensor and answers false for anything else, silently
The matching elementfind(xs, pred)index_of / list_containsfind answers the element or none, index_of answers a position or -1, list_contains answers a bool. Three questions, three shapes
A sub-list by positionslice(xs, a, b)take + dropslice clamps both bounds and also reads a string; take/drop are the one-sided forms and are lists only
The smallest of a listmin_by(xs, key)min(a, b)min compares exactly two numbers. min_by walks a list and answers an element, or none when it is empty
A sorted listsort(xs)sort_by(xs, key)sort orders numbers or strings directly and fails on a mixed list; sort_by is for everything else, and is stable
A string built from partsformat(t, …)concat / joinformat renders any value into a {} slot; concat and join demand strings. The interpolated $"…${x}…" literal renders the same way format does — see Values and primitives
To stop on a bad valueassert(cond, msg)error / panicassert is the guard form; error and panic abort unconditionally. All three fail the same way, and try/catch recovers all three
A byte size renderedformat_unit(v, type, unit)format_unit_valueformat_unit reads the factor from the type's @unit decorator, so it survives a change to the factor. format_unit_value takes the factor from you and checks nothing
To know if a path is coveredpath_contains(p, c)glob_match / glob_overlapspath_contains is a segment-aware prefix test between two concrete paths. glob_match tests one path against a pattern; glob_overlaps tests two patterns against each other

One pair deserves its own sentence. error and panic are the same function under two names — identical implementations, identical error: <msg> output, and both equally catchable by try/catch. Nothing about panic is more final than error. Choose between them for the reader, not for the runtime.

§ 5How to read an entry

Every entry below carries four things: the signature, a one-line description, a table of parameters and the return, and an example. The signature is the one the language reports for itself through fn_signature, which uses an informal notation — it is documentation, not a type you may write.

In a signatureMeansLegal as a field typeLegal as a parameter type
[T]A list of any one element typeNo — write list<T>, see Lists, tensors and recordsNo — write list<T>
T / U / K / A / BA type variable: whatever you pass, or whatever your callback returnsNo — WCL has no generic declarationsNo
numberAny numeric value, widened to f64 on the way inNo — write i64, u32, f64, …, see Values and primitivesYes
recordA record value, or a union variant with a record bodyNo — declare a type of your own, see Lists, tensors and recordsYes
&TA reference to a declaration — a type, field, block or variant named in an expressionYes — see TypesYes
anyAny value at allNoNo
neverThe call does not returnNoNo

Two consequences follow. Most of that notation is for reading, not for writing. A field declared record fails with unknown type 'record'. So the result of a builtin that answers a record either stays inside an expression, which is where most of them belong, or you copy it into a type you declared. A callback parameter is looser: it may be typed record, number or &T. That is why the reflection examples below write fn(f: record) -> utf8 { … }.

And [T] says nothing about the element type. sort and sum check theirs at run time and fail on a mixed list, because the signature could not.

The example line ends in a # → … comment carrying the answer wcl repl prints for it. That is the display form: abs(-7.5) is 7.5 and not 7, because every math builtin answers an f64.

§ 6Lists

Twenty-eight builtins over list<T>, the collection everything else turns into. A gather field is one, a table's rows are one, and split, range, keys and chars all answer one. Lists, tensors and records covers the value; this section covers what you can do to it.

They divide by job. Build one with range. Read one with len, at, head, tail, take, drop, slice, index_of and list_contains. Transform one with map, filter, fold, flatten, zip, enumerate, reverse and unique. Search one with any, all and find. Order one with sort, sort_by, sort_connected, min_by, max_by and group_by. And sum reduces a numeric one.

Nine of them also accept a tensor, reading its flat row-major data: at, fold, head, index_of, len, list_contains, map, sum and tail. Only map keeps the shape; the rest answer a plain value or a plain list. The other nineteen refuse a tensor, and filter says why — dropping elements would invalidate the shape.

Two accept a string: len, which counts characters, and slice. Everything else that reads a string lives under Strings.

§ 6.1all

text
fn(xs: [T], pred: fn (T) -> bool) -> bool

true when the predicate holds for every element (short-circuits; true for an empty list).

ParameterTypeMeaning
xs[T]The list to test.
predfn (T) -> boolPredicate applied to each element.
Returnsbooltrue if every element satisfies the predicate.
wcl
all([2, 4, 6], fn(x: i64) -> bool { x % 2 == 0 })   # → true

§ 6.2any

text
fn(xs: [T], pred: fn (T) -> bool) -> bool

true when the predicate holds for at least one element (short-circuits).

ParameterTypeMeaning
xs[T]The list to test.
predfn (T) -> boolPredicate applied to each element.
Returnsbooltrue if any element satisfies the predicate.
wcl
any([1, 2, 3], fn(x: i64) -> bool { x > 2 })   # → true

§ 6.3at

text
fn(xs: [T], i: i64) -> T

The element at a zero-based index; errors if out of bounds or negative.

ParameterTypeMeaning
xs[T]The list to index.
ii64The zero-based index.
ReturnsTThe element at i.
wcl
at([10, 20, 30], 1)   # → 20

Out of range is a failure, not a none: at([1, 2], 5) reports 'at': at: index 5 out of bounds. Reach for head or a guarded slice when the index may not be there. Also accepts a tensor, indexing its flat data.

§ 6.4drop

text
fn(xs: [T], n: i64) -> [T]

Every element of a list after the first n.

ParameterTypeMeaning
xs[T]The list to drop from.
ni64How many leading elements to skip.
Returns[T]The elements after the first n.
wcl
drop([1, 2, 3, 4], 2)   # → [3, 4]

§ 6.5enumerate

text
fn(xs: [T]) -> [[i64, T]]

Pair every element with its zero-based index, as [index, element] pairs.

ParameterTypeMeaning
xs[T]The list to enumerate.
Returns[[i64, T]][index, element] pairs.
wcl
enumerate(["a", "b"])   # → [[0, "a"], [1, "b"]]

The pairs are two-element lists, not records: read the index with at(pair, 0) and the element with at(pair, 1).

§ 6.6filter

text
fn(xs: [T], pred: fn (T) -> bool) -> [T]

Keep only the list elements for which the predicate returns true.

ParameterTypeMeaning
xs[T]The list to filter.
predfn (T) -> boolPredicate deciding whether to keep an element.
Returns[T]The elements for which the predicate returned true.
wcl
filter([1, 2, 3, 4], fn(x: i64) -> bool { x % 2 == 0 })   # → [2, 4]

Refuses a tensor outright — dropping elements would invalidate the shape. The message says so: filter: tensors are not supported (would invalidate shape); use map or convert to a list.

§ 6.7find

text
fn(xs: [T], pred: fn (T) -> bool) -> T

The first element for which the predicate returns true, or none.

ParameterTypeMeaning
xs[T]The list to search.
predfn (T) -> boolPredicate applied to each element.
ReturnsTThe first matching element, or none.
wcl
find([1, 2, 3], fn(x: i64) -> bool { x > 1 })   # → 2

§ 6.8flatten

text
fn(xss: [[T]]) -> [T]

Concatenate a list of lists into a single list, one level deep.

ParameterTypeMeaning
xss[[T]]A list whose elements are themselves lists.
Returns[T]The inner lists concatenated, one level deep.
wcl
flatten([[1, 2], [3]])   # → [1, 2, 3]

§ 6.9fold

text
fn(xs: [T], init: U, f: fn (U, T) -> U) -> U

Reduce a list or tensor to a single value by repeatedly combining the accumulator with each element.

ParameterTypeMeaning
xs[T]The list or tensor to reduce.
initUThe initial accumulator value.
ffn (U, T) -> UCombines the accumulator with the next element.
ReturnsUThe final accumulator value.
wcl
fold([1, 2, 3], 0, fn(acc: i64, x: i64) -> i64 { acc + x })   # → 6

Accepts a tensor, reading its flat row-major data.

§ 6.10group_by

text
fn(xs: [T], key: fn (T) -> K) -> [record]

Group elements by a key function into { key, items } records, in first-seen key order.

ParameterTypeMeaning
xs[T]The list to group.
keyfn (T) -> KMaps each element to its group key.
Returns[record]One { key, items } record per distinct key.
wcl
group_by([1, 2, 3, 4], fn(x: i64) -> i64 { x % 2 })   # → [ { items: [1, 3], key: 1 },  { items: [2, 4], key: 0 }]

Groups appear in first-seen key order, not sorted key order — grouping [1, 2, 3, 4] by x % 2 puts the odd group first, because 1 came first.

text
fn(xs: [T]) -> T

The first element of a list or tensor (none when empty).

ParameterTypeMeaning
xs[T]A list or tensor.
ReturnsTThe first element, or none if empty.
wcl
head([1, 2, 3])   # → 1

The one list reader that answers none instead of failing on an empty list. It also accepts a tensor, and reads the first element of its flat data.

§ 6.12index_of

text
fn(xs: [T], needle: T) -> i64

The index of the first element equal to needle, or -1 if absent.

ParameterTypeMeaning
xs[T]The list to search.
needleTThe value to look for.
Returnsi64The zero-based index, or -1 if not found.
wcl
index_of([10, 20, 30], 20)   # → 1

Accepts a list or a tensor, and answers -1 for anything else — including a string, which it never searches.

§ 6.13len

text
fn(xs: [T]) -> usize

The number of elements in a list or tensor, or characters in a string.

ParameterTypeMeaning
xs[T]A list, tensor, or string.
ReturnsusizeThe number of elements (or characters).
wcl
len([10, 20, 30])   # → 3

Three shapes, one question. A list answers with its element count, a tensor with the product of its dimensions, and a string with its character count — len("héllo") is 5, not the byte length.

§ 6.14list_contains

text
fn(xs: [T], needle: T) -> bool

Whether a list contains a value equal to needle.

ParameterTypeMeaning
xs[T]The list to search.
needleTThe value to look for.
Returnsbooltrue if an equal element is present.
wcl
list_contains([1, 2, 3], 2)   # → true

Accepts a list or a tensor. Anything else answers false rather than failing, so a mis-typed argument is silent — see Choosing between the near-twins.

§ 6.15map

text
fn(xs: [T], f: fn (T) -> U) -> [U]

Apply a function to every element of a list or tensor, returning the transformed collection.

ParameterTypeMeaning
xs[T]The list or tensor to transform.
ffn (T) -> UFunction applied to each element.
Returns[U]A new collection of the transformed elements.
wcl
map([1, 2, 3], fn(x: i64) -> i64 { x * 2 })   # → [2, 4, 6]

The only iterator that keeps a tensor's shape: map over a tensor[2x2] answers a tensor[2x2]. Every other transformer either refuses a tensor or flattens it.

§ 6.16max_by

text
fn(xs: [T], key: fn (T) -> K) -> T

The element with the largest key, or none for an empty list.

ParameterTypeMeaning
xs[T]The list to search.
keyfn (T) -> KMaps each element to its comparison key.
ReturnsTThe element with the largest key, or none.
wcl
max_by(["a", "abc", "ab"], fn(s: utf8) -> i64 { len(s) })   # → "abc"

§ 6.17min_by

text
fn(xs: [T], key: fn (T) -> K) -> T

The element with the smallest key, or none for an empty list.

ParameterTypeMeaning
xs[T]The list to search.
keyfn (T) -> KMaps each element to its comparison key.
ReturnsTThe element with the smallest key, or none.
wcl
min_by(["abc", "a", "ab"], fn(s: utf8) -> i64 { len(s) })   # → "a"

§ 6.18range

text
fn(start: i64, end: i64) -> [i64]

The half-open integer range [start, end) as a list.

ParameterTypeMeaning
starti64Inclusive lower bound.
endi64Exclusive upper bound; must be >= start.
Returns[i64]The integers from start up to (but excluding) end.
wcl
range(0, 4)   # → [0, 1, 2, 3]

§ 6.19reverse

text
fn(xs: [T]) -> [T]

Reverse the order of a list's elements.

ParameterTypeMeaning
xs[T]The list to reverse.
Returns[T]The list in reverse order.
wcl
reverse([1, 2, 3])   # → [3, 2, 1]

§ 6.20slice

text
fn(xs: utf8 | [T], start: i64, end: i64) -> utf8 | [T]

The half-open range [start, end) of a string's characters or a list's elements (bounds are clamped).

ParameterTypeMeaning
xsutf8 | [T]The string or list to slice.
starti64Inclusive start index (clamped to the length).
endi64Exclusive end index (clamped to the length).
Returnsutf8 | [T]The sub-string / sub-list.
wcl
slice([10, 20, 30, 40], 1, 3)   # → [20, 30]

The one slicer that reads a string as well as a list. Both bounds are clamped, so slice([1, 2, 3], 0, 99) is the whole list rather than an error.

§ 6.21sort

text
fn(xs: [T]) -> [T]

Sort a list — numerically for all-numeric lists, lexicographically for all-string lists.

ParameterTypeMeaning
xs[T]An all-numeric or all-string list.
Returns[T]The sorted list.
wcl
sort([3, 1, 2])   # → [1, 2, 3]

Mixed lists fail loudly rather than inventing an order: sort([1, "a"]) reports list must be all numeric or all strings, found mixed. Tensors are refused; sort tensor_data(t) instead.

§ 6.22sort_by

text
fn(xs: [T], key: fn (T) -> K) -> [T]

Sort a list by a key function (stable). Keys must be all numeric or all strings.

ParameterTypeMeaning
xs[T]The list to sort.
keyfn (T) -> KMaps each element to its sort key.
Returns[T]The elements ordered by ascending key.
wcl
sort_by(["abc", "a", "ab"], fn(s: utf8) -> i64 { len(s) })   # → ["a", "ab", "abc"]

§ 6.23sort_connected

text
fn(items: [T], edges: [{source, destination, ...}]) -> [T]

Reorder a list so that items joined by edges cluster together (recursing into children).

ParameterTypeMeaning
items[T]Items identified by an id field (possibly nested via children).
edges[{source, destination, ...}]Edge records linking item ids.
Returns[T]The reordered list, connected items adjacent.
wcl
sort_connected([{ id: "a" }, { id: "b" }, { id: "c" }],
               [{ source: "a", destination: "c" }])   # → [ { id: "a" },  { id: "c" },  { id: "b" }]

Written for diagram layout: it reads an id field off each item, recurses into a children field, and reorders so that edge-joined items sit together. Items no edge names keep their relative order.

§ 6.24sum

text
fn(xs: [number]) -> number

Add together every element of a non-empty homogeneous numeric list or tensor.

ParameterTypeMeaning
xs[number]A non-empty list or tensor of one numeric type.
ReturnsnumberThe total, in the element's numeric type.
wcl
sum([1, 2, 3, 4])   # → 10

An empty list is a failure — sum([]) reports 'sum': sum: empty list — because there is no zero to return without knowing the element type. Also accepts a tensor.

§ 6.25tail

text
fn(xs: [T]) -> [T]

Every element of a list or tensor except the first.

ParameterTypeMeaning
xs[T]A list or tensor.
Returns[T]The elements after the first.
wcl
tail([1, 2, 3])   # → [2, 3]

Accepts a tensor, and drops the shape: the answer is always a plain list.

§ 6.26take

text
fn(xs: [T], n: i64) -> [T]

The first n elements of a list (fewer if the list is shorter).

ParameterTypeMeaning
xs[T]The list to take from.
ni64How many leading elements to keep.
Returns[T]The first n elements.
wcl
take([1, 2, 3, 4], 2)   # → [1, 2]

§ 6.27unique

text
fn(xs: [T]) -> [T]

Remove duplicate elements from a list, keeping first-seen order.

ParameterTypeMeaning
xs[T]The list to deduplicate.
Returns[T]The list with duplicates removed.
wcl
unique([1, 2, 2, 3, 1])   # → [1, 2, 3]

§ 6.28zip

text
fn(a: [A], b: [B]) -> [(A, B)]

Pair up elements of two lists by index, stopping at the shorter length.

ParameterTypeMeaning
a[A]The first list.
b[B]The second list.
Returns[(A, B)]Index-paired [a, b] lists, up to the shorter length.
wcl
zip([1, 2, 3], ["a", "b"])   # → [[1, "a"], [2, "b"]]

The pairs are two-element lists, exactly as enumerate produces.

§ 7Records

Four builtins over the record — the { name: value, … } literal covered in Lists, tensors and records. A record has no declared type and no field order, which is why keys sorts.

§ 7.1keys

text
fn(r: record) -> [utf8]

The field names of a record, in deterministic (sorted) order.

ParameterTypeMeaning
rrecordA record value (or a union variant with a record body).
Returns[utf8]The field names.
wcl
keys({ name: "Rex", age: 4 })   # → ["age", "name"]

Sorted, always — a record's field order is not preserved, so keys gives you a deterministic one instead.

§ 7.2map_values

text
fn(r: record, f: fn (T) -> U) -> record

Apply a function to every field value of a record, keeping the keys.

ParameterTypeMeaning
rrecordThe record to transform.
ffn (T) -> UFunction applied to each field value.
ReturnsrecordA record with the same keys and transformed values.
wcl
map_values({ low: 1, high: 9 }, fn(x: i64) -> i64 { x * 2 })   # →  { high: 18, low: 2 }

§ 7.3merge

text
fn(a: record, b: record) -> record

Combine two records into one; fields of b win on a name clash.

ParameterTypeMeaning
arecordThe base record.
brecordThe overriding record.
ReturnsrecordA record with the union of both field sets.
wcl
merge({ host: "localhost", port: 80 }, { port: 8080 })   # →  { host: "localhost", port: 8080 }

§ 7.4values

text
fn(r: record) -> [T]

The field values of a record, in the same order as keys.

ParameterTypeMeaning
rrecordA record value (or a union variant with a record body).
Returns[T]The field values.
wcl
values({ name: "Rex", age: 4 })   # → [4, "Rex"]

In the same order as keys, which is sorted by field name. values({ name: "Rex", age: 4 }) is [4, "Rex"], not ["Rex", 4].

§ 8Strings

Fifteen builtins over utf8. Every one of them counts in characters, not bytes: chars, pad_start and pad_end all treat a multi-byte character as one unit.

len and slice also read strings, but they are list builtins that happen to accept one, so they are catalogued under Lists. format_unit and format_unit_value build strings too, and are under Units because that is what they are about.

§ 8.1chars

text
fn(s: utf8) -> [utf8]

The characters of a string as a list of one-character strings.

ParameterTypeMeaning
sutf8The string to split into characters.
Returns[utf8]One string per character.
wcl
chars("abc")   # → ["a", "b", "c"]

Characters, not bytes: chars("héllo") is five one-character strings.

§ 8.2concat

text
fn(a: utf8, b: utf8) -> utf8

Concatenate two strings into one.

ParameterTypeMeaning
autf8The left-hand string.
butf8The string appended after a.
Returnsutf8The two strings joined together.
wcl
concat("foo", "bar")   # → "foobar"

§ 8.3contains

text
fn(s: utf8, needle: utf8) -> bool

Whether a string contains a substring.

ParameterTypeMeaning
sutf8The string to search.
needleutf8The substring to look for.
Returnsbooltrue if the substring is present.
wcl
contains("hello", "ell")   # → true

Strings only. Its list-shaped twin is list_contains, and passing a list here fails with 'contains': expected utf8 string, found list.

§ 8.4ends_with

text
fn(s: utf8, suffix: utf8) -> bool

Whether a string ends with a suffix.

ParameterTypeMeaning
sutf8The string to test.
suffixutf8The suffix to look for.
Returnsbooltrue if the string ends with the suffix.
wcl
ends_with("hello", "lo")   # → true

§ 8.5format

text
fn (utf8, ...args) -> utf8

Substitute trailing arguments into a template's {} placeholders ({{/}} are literal braces).

ParameterTypeMeaning
templateutf8Template string with {} placeholders.
Returnsutf8The template with placeholders substituted.
wcl
format("{} = {}", "x", 42)   # → "x = 42"

The one variadic builtin. {} takes the next argument in order; {{ and }} are literal braces. A slot renders the value the way a person reads it — a u32 loses its suffix — which is the same rendering an interpolated $"…${x}…" string performs. See Values and primitives.

§ 8.6join

text
fn(parts: [utf8], sep: utf8) -> utf8

Join a list of strings into one, inserting a separator between each.

ParameterTypeMeaning
parts[utf8]The strings to join.
seputf8The separator inserted between parts.
Returnsutf8The joined string.
wcl
join(["a", "b", "c"], "-")   # → "a-b-c"

§ 8.7pad_end

text
fn(s: utf8, width: i64, pad: utf8) -> utf8

Right-pad a string with a fill pattern until it is width characters long.

ParameterTypeMeaning
sutf8The string to pad.
widthi64The target character count.
padutf8The fill pattern (repeated / truncated as needed).
Returnsutf8The padded string (unchanged if already wide enough).
wcl
pad_end("42", 5, "0")   # → "42000"

Same fill rule as pad_start, applied on the right.

§ 8.8pad_start

text
fn(s: utf8, width: i64, pad: utf8) -> utf8

Left-pad a string with a fill pattern until it is width characters long.

ParameterTypeMeaning
sutf8The string to pad.
widthi64The target character count.
padutf8The fill pattern (repeated / truncated as needed).
Returnsutf8The padded string (unchanged if already wide enough).
wcl
pad_start("42", 5, "0")   # → "00042"

The pad pattern is repeated and truncated to fill the gap, so a multi-character pattern may end mid-pattern. A string already width wide comes back unchanged; nothing is ever cut.

§ 8.9repeat

text
fn(s: utf8, n: i64) -> utf8

A string repeated n times (empty for n <= 0).

ParameterTypeMeaning
sutf8The string to repeat.
ni64How many copies to concatenate.
Returnsutf8n copies of s.
wcl
repeat("ab", 3)   # → "ababab"

n <= 0 answers the empty string rather than failing.

§ 8.10replace

text
fn(s: utf8, old: utf8, new: utf8) -> utf8

Replace every occurrence of a substring with another.

ParameterTypeMeaning
sutf8The string to search.
oldutf8The substring to find.
newutf8The replacement substring.
Returnsutf8The string with every match replaced.
wcl
replace("hello world", "world", "there")   # → "hello there"

§ 8.11split

text
fn(s: utf8, sep: utf8) -> [utf8]

Split a string on every occurrence of a separator into a list of pieces.

ParameterTypeMeaning
sutf8The string to split.
seputf8The separator to split on.
Returns[utf8]The pieces between separators.
wcl
split("a,b,c", ",")   # → ["a", "b", "c"]

§ 8.12starts_with

text
fn(s: utf8, prefix: utf8) -> bool

Whether a string begins with a prefix.

ParameterTypeMeaning
sutf8The string to test.
prefixutf8The prefix to look for.
Returnsbooltrue if the string starts with the prefix.
wcl
starts_with("hello", "he")   # → true

§ 8.13to_lower

text
fn(s: utf8) -> utf8

Lowercase every character of a string.

ParameterTypeMeaning
sutf8The string to lowercase.
Returnsutf8The lowercased string.
wcl
to_lower("AbC")   # → "abc"

§ 8.14to_upper

text
fn(s: utf8) -> utf8

Uppercase every character of a string.

ParameterTypeMeaning
sutf8The string to uppercase.
Returnsutf8The uppercased string.
wcl
to_upper("abc")   # → "ABC"

§ 8.15trim

text
fn(s: utf8) -> utf8

Remove leading and trailing whitespace from a string.

ParameterTypeMeaning
sutf8The string to trim.
Returnsutf8The string without leading/trailing whitespace.
wcl
trim("  hi  ")   # → "hi"

§ 9Tensors

One constructor and three accessors. A tensor is a flat, row-major buffer plus a shape — see Lists, tensors and records. These four are the only builtins that know about the shape. The nine list builtins that accept a tensor all read its flat data, and only map gives a tensor back.

§ 9.1tensor

text
fn(data: [number], shape: [usize]) -> tensor<T>

Build a tensor from flat row-major data and a shape; the data length must equal the product of the dimensions.

ParameterTypeMeaning
data[number]Flat, row-major element data.
shape[usize]The dimension sizes.
Returnstensor<T>The constructed tensor.
wcl
tensor([1, 2, 3, 4], [2, 2])   # → tensor[2x2](1, 2, 3, 4)

The data length must equal the product of the dimensions. tensor([1, 2, 3], [2, 2]) reports tensor: data length 3 does not match shape product 4.

§ 9.2tensor_data

text
fn(t: tensor<T>) -> [T]

The flat row-major element data of a tensor as a list.

ParameterTypeMeaning
ttensor<T>The tensor to read.
Returns[T]The tensor's flat, row-major element data.
wcl
tensor_data(tensor([1, 2, 3, 4], [2, 2]))   # → [1, 2, 3, 4]

§ 9.3tensor_reshape

text
fn(t: tensor<T>, shape: [usize]) -> tensor<T>

Reinterpret a tensor's data under a new shape; the element count must be unchanged.

ParameterTypeMeaning
ttensor<T>The tensor to reshape.
shape[usize]The new dimension sizes.
Returnstensor<T>The same data under the new shape.
wcl
tensor_reshape(tensor([1, 2, 3, 4], [2, 2]), [4])   # → tensor[4](1, 2, 3, 4)

The element count must be unchanged; the data is not copied or reordered, only re-read.

§ 9.4tensor_shape

text
fn(t: tensor<T>) -> [usize]

The dimension sizes of a tensor as a list.

ParameterTypeMeaning
ttensor<T>The tensor to read.
Returns[usize]The tensor's dimension sizes.
wcl
tensor_shape(tensor([1, 2, 3, 4], [2, 2]))   # → [2, 2]

§ 10Failing on purpose

Three ways to stop. All three are implemented as the same abort: evaluation of the field that forced them ends, and the message is printed after error:. All three are recoverable by try body catch e => fallback — see Control flow.

The difference is the shape of the call, not the severity. assert takes a condition and does nothing when it holds. error and panic take no condition and always abort. They are literally the same function registered twice.

§ 10.1assert

text
fn(cond: bool, msg: utf8) -> none

Return none when cond is true, otherwise abort with msg.

ParameterTypeMeaning
condboolThe condition that must hold.
msgutf8The error message reported when cond is false.
Returnsnonenone when the assertion holds (otherwise aborts).
wcl
assert(1 + 1 == 2, "math is broken")   # → none

The return value is none, which is rarely what you want on the right of an =. Use it as a guard inside a block expression: { let _ = assert(self.port > 1024u32, "port must be above 1024"); 3u32 }.

§ 10.2error

text
fn(msg: utf8) -> never

Abort evaluation with an error message.

ParameterTypeMeaning
msgutf8The error message to report.
ReturnsneverNever returns — aborts evaluation.
guard.wclwcl
@block("service")
type Service {
  @inline(0) name: utf8
  port:    u32
  checked: u32
}

@document
type Cfg {
  @children("service") services: list<Service>
}

service "web" {
  port    = 80u32
  checked = if self.port > 1024u32 { self.port } else { error("port must be above 1024") }
}
text
$ wcl get guard.wcl services.web.checked
wcl::eval::user_error

  × error: port must be above 1024

The failure aborts the field that called it, and the message is printed verbatim after error:. try … catch e => … recovers it — see Control flow.

§ 10.3panic

text
fn(msg: utf8) -> never

Abort evaluation with an unrecoverable failure message.

ParameterTypeMeaning
msgutf8The failure message to report.
ReturnsneverNever returns — aborts evaluation.
wcl
panic("unreachable: two roots")   # → aborts: error: unreachable: two roots

Identical to error in every observable way: same abort, same error: <msg> output, and equally catchable by try/catch. The two names differ in intent only.

§ 11Math

Twenty-nine builtins, and one rule that covers all of them: arguments widen to f64 and results are f64. sqrt(144) answers 12.0, not 12. That is why the signatures say number going in and f64 coming out — an i64, a u32 and an f64 are all accepted, and none of them survives the call as itself.

Nothing here is a constraint check. sqrt(-1), ln(0) and 0.0 / 0.0 produce the IEEE answers (NaN, -inf) rather than errors, exactly as the floating-point arithmetic in Expressions and operators does.

The trigonometric functions work in radians. radians and degrees convert; pi, tau and e are the three nullary constants.

§ 11.1abs

text
fn(x: number) -> f64

Absolute value.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
abs(-7.5)   # → 7.5

§ 11.2acos

text
fn(x: number) -> f64

Arccosine, in radians, of a value in [-1, 1].

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
acos(1.0)   # → 0.0

§ 11.3asin

text
fn(x: number) -> f64

Arcsine, in radians, of a value in [-1, 1].

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
asin(1.0)   # → 1.5707963267948966

§ 11.4atan

text
fn(x: number) -> f64

Arctangent, in radians.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
atan(1.0)   # → 0.7853981633974483

§ 11.5atan2

text
fn(a: number, b: number) -> f64

Arctangent of a/b in radians, using the signs of both to pick the quadrant.

ParameterTypeMeaning
anumberThe first operand.
bnumberThe second operand.
Returnsf64The result, as an f64.
wcl
atan2(1.0, 1.0)   # → 0.7853981633974483

§ 11.6cbrt

text
fn(x: number) -> f64

Cube root.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
cbrt(27)   # → 3.0

§ 11.7ceil

text
fn(x: number) -> f64

Round up to the nearest integer.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
ceil(3.1)   # → 4.0

§ 11.8clamp

text
fn(x: number, lo: number, hi: number) -> f64

Constrain x to the range [lo, hi].

ParameterTypeMeaning
xnumberThe value to clamp.
lonumberThe lower bound.
hinumberThe upper bound.
Returnsf64x limited to [lo, hi], as an f64.
wcl
clamp(12.0, 0.0, 10.0)   # → 10.0

No check that lo <= hi. When they are the wrong way round the answer is lo.

§ 11.9cos

text
fn(x: number) -> f64

Cosine of an angle in radians.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
cos(0.0)   # → 1.0

§ 11.10degrees

text
fn(x: number) -> f64

Convert an angle from radians to degrees.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
degrees(pi())   # → 180.0

§ 11.11e

text
fn() -> f64

Euler's number e (≈ 2.71828).

ParameterTypeMeaning
Returnsf64The value of e.
wcl
e()   # → 2.718281828459045

§ 11.12exp

text
fn(x: number) -> f64

e raised to the power x.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
exp(0.0)   # → 1.0

§ 11.13floor

text
fn(x: number) -> f64

Round down to the nearest integer.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
floor(3.9)   # → 3.0

§ 11.14hypot

text
fn(a: number, b: number) -> f64

Length of the hypotenuse sqrt(a² + b²).

ParameterTypeMeaning
anumberThe first operand.
bnumberThe second operand.
Returnsf64The result, as an f64.
wcl
hypot(3, 4)   # → 5.0

§ 11.15ln

text
fn(x: number) -> f64

Natural (base-e) logarithm.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
ln(1.0)   # → 0.0

§ 11.16log10

text
fn(x: number) -> f64

Base-10 logarithm.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
log10(1000.0)   # → 3.0

§ 11.17log2

text
fn(x: number) -> f64

Base-2 logarithm.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
log2(8.0)   # → 3.0

§ 11.18max

text
fn(a: number, b: number) -> f64

The larger of two numbers.

ParameterTypeMeaning
anumberThe first operand.
bnumberThe second operand.
Returnsf64The result, as an f64.
wcl
max(3, 7.5)   # → 7.5

Two numbers, not a list. The list form is max_by.

§ 11.19min

text
fn(a: number, b: number) -> f64

The smaller of two numbers.

ParameterTypeMeaning
anumberThe first operand.
bnumberThe second operand.
Returnsf64The result, as an f64.
wcl
min(3, 7.5)   # → 3.0

Two numbers, not a list. The list form is min_by.

§ 11.20pi

text
fn() -> f64

The constant π (≈ 3.14159).

ParameterTypeMeaning
Returnsf64The value of π.
wcl
pi()   # → 3.141592653589793

§ 11.21pow

text
fn(a: number, b: number) -> f64

Raise a to the power b.

ParameterTypeMeaning
anumberThe first operand.
bnumberThe second operand.
Returnsf64The result, as an f64.
wcl
pow(2, 10)   # → 1024.0

§ 11.22radians

text
fn(x: number) -> f64

Convert an angle from degrees to radians.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
radians(180.0)   # → 3.141592653589793

§ 11.23round

text
fn(x: number) -> f64

Round to the nearest integer (ties away from zero).

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
round(2.5)   # → 3.0

Ties go away from zero: round(2.5) is 3.0 and round(-2.5) is -3.0.

§ 11.24sign

text
fn(x: number) -> f64

The sign of x: 1, -1, or 0.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
sign(-3.0)   # → -1.0

Zero answers 0.0, so the result is one of exactly three values.

§ 11.25sin

text
fn(x: number) -> f64

Sine of an angle in radians.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
sin(0.0)   # → 0.0

§ 11.26sqrt

text
fn(x: number) -> f64

Square root.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
sqrt(144)   # → 12.0

§ 11.27tan

text
fn(x: number) -> f64

Tangent of an angle in radians.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
tan(0.0)   # → 0.0

§ 11.28tau

text
fn() -> f64

The constant τ = 2π (≈ 6.28319).

ParameterTypeMeaning
Returnsf64The value of τ (2π).
wcl
tau()   # → 6.283185307179586

§ 11.29trunc

text
fn(x: number) -> f64

Discard the fractional part, rounding toward zero.

ParameterTypeMeaning
xnumberThe input value (any number, widened to f64).
Returnsf64The result, as an f64.
wcl
trunc(3.9)   # → 3.0

§ 12Reflection

Twelve builtins that read the document rather than its data: what a type declares, what decorates it, what a namespace holds, what the source said. They exist so a document can generate its own reference — which is how the catalogue in this chapter was produced.

Most of them take a reference — the declaration's bare name, written in expression position; the rest take a name as a string. Every example below is a field of one small file, so you can see what each answers against the same schema:

schema.wclwcl
# schema.wcl — a tiny schema that documents itself.
namespace app

# The edge service: terminates TLS and forwards to the app tier.
# Owned by the platform team.
@block("service")
type Service {
  @inline(0) name: identifier
  port: u32
  @child("limits") limits: Limits?
}

@block("limits")
type Limits {
  cpu: f64
}

@document
type Inventory {
  @children("service") services: list<Service>

  decorators:   list<utf8>
  block_kind:   utf8
  fields:       list<utf8>
  children:     list<utf8>
  declarations: list<utf8>
  legal_on:     usize
  kind:         utf8
  doc:          utf8
  source:       utf8
  map_sig:      utf8
  builtins:     usize
  doubled:      i64
}

decorators   = decorator_names(Service)
block_kind   = decorator_arg(Service, "block", "name")
fields       = map(type_fields(Service), fn(f: record) -> utf8 { f.name })
children     = map(child_types(Inventory), fn(t: &Service) -> utf8 { { let d = decl_info(t); d.name } })
declarations = map(namespace_decls("app"), fn(t: &Service) -> utf8 { { let d = decl_info(t); d.name } })
legal_on     = len(decorators_for_kind("service"))
kind         = { let d = decl_info(Service); d.kind }
doc          = doc_comment(Service)
source       = ast_string(Limits)
map_sig      = { let s = fn_signature("map"); s.signature }
builtins     = len(builtin_names())
doubled      = eval("2 * 21")

service web { port = 8080u32 }

Two shapes recur. A builtin returning [&T] answers a list of further references, so the usual move is to map over it and reflect each one — that is what children and declarations do above. And a builtin returning a record must be bound before a field is read off it: decl_info(Service).kind fails with expected a reference, got call, while { let d = decl_info(Service); d.kind } answers "type". Note that only wcl get and a build catch it — the field is lazy, so wcl check alone reports OK.

§ 12.1ast_string

text
fn(target: &T) -> utf8

Pretty-print the canonical source behind a reference (type/interface/union/symbol_set/block/field) or a function value.

ParameterTypeMeaning
target&TA dataref to a declaration, or a function value.
Returnsutf8The canonical (pretty-printed) source text.
wcl
source = ast_string(Limits)
text
$ wcl get schema.wcl source
"@block(\"limits\")\ntype Limits {\n  cpu: f64\n}"

§ 12.2builtin_names

text
fn() -> [utf8]

The names of every registered built-in function, sorted. Pair with fn_signature to introspect each one.

ParameterTypeMeaning
Returns[utf8]Every built-in's name, sorted alphabetically.
wcl
builtins = len(builtin_names())
text
$ wcl get schema.wcl builtins
103

§ 12.3child_types

text
fn(target: &T) -> [&T]

Reflect a type into references to the element types of its @child / @children block slots (own slots first, then inherited via extends). Pair with type_table / type_fields to auto-document the blocks a @document declares.

ParameterTypeMeaning
target&TA reference to a type or interface declaration.
Returns[&T]One type reference per block slot. Slots that accept a union or interface resolve to that type's name; scalar (non-block) fields are skipped.
wcl
children = map(child_types(Inventory), fn(t: &Service) -> utf8 { { let d = decl_info(t); d.name } })
text
$ wcl get schema.wcl children
["Service"]

§ 12.4decl_info

text
fn(target: &T) -> record

Describe a top-level declaration: its name, kind, doc comment, and schema classification (block / table / decorator / document).

ParameterTypeMeaning
target&TA reference to a type, interface, union, or symbol_set declaration.
Returnsrecord{ name, full_name, kind, doc, is_imported, is_document, block_kind, table_kind, decorator_name, extends }. The classification fields are none when the decorator is absent.
wcl
kind = { let d = decl_info(Service); d.kind }
text
$ wcl get schema.wcl kind
"type"

The kind of a @document type is still "type"; the is_document flag is what tells you it is the document schema.

§ 12.5decorator_arg

text
fn(target: &T, decorator: utf8, slot: utf8) -> any

Read one named argument of a decorator on a referenced declaration (none if absent).

ParameterTypeMeaning
target&TA reference to a type, field, block, or variant.
decoratorutf8The decorator name, e.g. "doc".
slotutf8The argument (slot) name to read.
ReturnsanyThe argument's value, or none if absent.
wcl
block_kind = decorator_arg(Service, "block", "name")
text
$ wcl get schema.wcl block_kind
"service"

§ 12.6decorator_names

text
fn(target: &T) -> [utf8]

List the names of the decorators attached to a referenced declaration.

ParameterTypeMeaning
target&TA reference to a type, field, block, or variant.
Returns[utf8]The decorator names, in source order.
wcl
decorators = decorator_names(Service)
text
$ wcl get schema.wcl decorators
["block"]

§ 12.7decorators_for_kind

text
fn(kind: utf8) -> [&T]

List references to decorator schemas applicable to a block kind. Pair with decl_info, doc_comment, and type_fields to render them.

ParameterTypeMeaning
kindutf8The block kind as a string; instance-derived kinds are supported.
Returns[&T]One reference per applicable decorator-schema declaration.
wcl
legal_on = len(decorators_for_kind("service"))
text
$ wcl get schema.wcl legal_on
22

Answers the decorator schemas that are legal on the kind, which includes every built-in one — 22 of them for an ordinary block, before the document declares any of its own.

§ 12.8doc_comment

text
fn(target: &T) -> utf8

The doc comment — the contiguous run of # / // lines immediately above a declaration — attached to a reference, or "" when there is none. Complements decorator_arg(x, "doc", …) for @doc("…") metadata.

ParameterTypeMeaning
target&TA reference to a type, interface, union, variant, symbol_set, or field.
Returnsutf8The joined comment text, or "" when absent.
wcl
doc = doc_comment(Service)
text
$ wcl get schema.wcl doc
"The edge service: terminates TLS and forwards to the app tier.\nOwned by the platform team."

The run must be unbroken: a blank line ends it, and anything above the blank line belongs to nothing. See Documents, fields and blocks.

§ 12.9eval

text
fn(src: utf8) -> any

Parse a string as a WCL expression and evaluate it in the current scope.

ParameterTypeMeaning
srcutf8WCL expression source to parse and evaluate.
ReturnsanyThe value the expression evaluates to.
wcl
doubled = eval("2 * 21")
text
$ wcl get schema.wcl doubled
42

The expression is parsed and evaluated in the calling scope, so it can see self, sibling fields and let bindings: a field written doubled = eval("self.port * 2u32") inside a service whose port is 80u32 answers 160u32.

§ 12.10fn_signature

text
fn(f: any) -> record

Describe a function's parameters and return type. Pass a function value, or a built-in's name as a string.

ParameterTypeMeaning
fanyA function value, or the name of a built-in as a utf8 string.
ReturnsrecordA record { doc, params: [{name, type, doc}], return_type, return_doc, signature, is_builtin }.
wcl
map_sig = { let s = fn_signature("map"); s.signature }
text
$ wcl get schema.wcl map_sig
"fn(xs: [T], f: fn (T) -> U) -> [U]"

Takes a function value, or a builtin's name as a string. The record it returns has to be bound before you can read a field off it — fn_signature("map").signature fails with expected a reference, got call, while { let s = fn_signature("map"); s.signature } does not.

§ 12.11namespace_decls

text
fn(ns: utf8) -> [&T]

List references to every top-level declaration (type / interface / union / symbol_set) in a namespace, for schema-documentation generators. Pair with decl_info, doc_comment, type_fields, and ast_string to render each. Imported (library) declarations are included — filter on decl_info(d).is_imported to drop them.

ParameterTypeMeaning
nsutf8The namespace, dotted (e.g. "wdoc"); "" for the root namespace.
Returns[&T]One reference per declaration: types first, then interfaces, unions, symbol sets, in source order.
wcl
declarations = map(namespace_decls("app"), fn(t: &Service) -> utf8 { { let d = decl_info(t); d.name } })
text
$ wcl get schema.wcl declarations
["Service", "Limits", "Inventory"]

Imported declarations are included. Filter on decl_info(d).is_imported to keep only the ones the document declares itself.

§ 12.12type_fields

text
fn(target: &T) -> [record]

Reflect a type or interface into a list of field-description records (own fields first, then inherited via extends).

ParameterTypeMeaning
target&TA reference to a type or interface declaration.
Returns[record]One record per field: { name, type, is_function, optional, has_default, default, is_block, repeated, accepts, decorators }.
wcl
fields = map(type_fields(Service), fn(f: record) -> utf8 { f.name })
text
$ wcl get schema.wcl fields
["name", "port", "limits"]

§ 13Paths and globs

Three pure string functions. Nothing here touches the disk: a path is a /-separated sequence of segments, empty and . segments are dropped, and a pattern with a trailing / owns its whole subtree. src, src/ and ./src are the same path to all three.

Glob syntax is shared by glob_match and glob_overlaps: * matches within one segment, ** spans whole segments, ? matches one character, and [abc] / [a-z] / [!x] are character classes.

§ 13.1glob_match

text
fn(pattern: utf8, path: utf8) -> bool

Match one concrete path against a glob. * stays within a segment, ** spans segments, ? matches one character, [a-z] / [!x] are character classes. A trailing / on the pattern matches the whole subtree.

ParameterTypeMeaning
patternutf8The glob pattern.
pathutf8The concrete path to test.
Returnsbooltrue if the path matches the pattern.
wcl
glob_match("src/*.rs", "src/main.rs")   # → true

§ 13.2glob_overlaps

text
fn(a: utf8, b: utf8) -> bool

Whether two glob patterns can match a common path. Concrete paths are patterns too, so this subsumes glob_match for overlap gates. Trailing / means the whole subtree. Conservative: exotic negated-class pairings may report true when no shared path exists, never false when one does.

ParameterTypeMeaning
autf8The first glob pattern (or concrete path).
butf8The second glob pattern (or concrete path).
Returnsbooltrue if some path is matched by both patterns.
wcl
glob_overlaps("src/", "src/*.rs")   # → true

Conservative in one direction only: an exotic pairing of negated classes may answer true when no shared path exists, but never false when one does.

§ 13.3path_contains

text
fn(parent: utf8, child: utf8) -> bool

Segment-aware path prefix test: whether child is parent itself or lives under it. Splits on /, so src/ does not contain src2/x. A path contains itself.

ParameterTypeMeaning
parentutf8The containing path (trailing slash optional).
childutf8The path to test.
Returnsbooltrue if child equals parent or is nested beneath it.
wcl
path_contains("src/", "src/core/mod.rs")   # → true

Segment-aware, so src does not contain src2/x. A path contains itself.

§ 14Units

Two builtins, both going the same direction: from a stored base-unit number back to a labelled string. Writing 512MiB in a document is the other direction, and the evaluator does that one — see Values and primitives.

§ 14.1format_unit

text
fn(value: i64, type: utf8, unit: utf8) -> utf8

Render a base-unit value in a chosen unit, looking the factor up from a unit type by name: format_unit(size, "std.ByteSize", "MiB")"5 MiB". The inverse of literal-unit resolution, so it stays correct if the type's @unit factor changes.

ParameterTypeMeaning
valuei64The stored value, in the type's base unit.
typeutf8The unit type's dotted name, e.g. "std.ByteSize".
unitutf8The unit to render in, e.g. "MiB".
Returnsutf8The value divided by the unit's factor, suffixed with the unit (e.g. "5 MiB").
wcl
format_unit(5242880, "std.ByteSize", "MiB")   # → "5 MiB"

Reads the factor off the named type's @unit decorator rather than hard-coding one, so it stays correct if the factor changes. The exact inverse of writing 5MiB — see Values and primitives.

§ 14.2format_unit_value

text
fn(value: i64, factor: i64, unit: utf8) -> utf8

Render a number in a unit given its factor explicitly: format_unit_value(5242880, 1048576, "MiB")"5 MiB". The primitive behind format_unit for callers that already hold the factor.

ParameterTypeMeaning
valuei64The stored value, in the type's base unit.
factori64The unit's multiplier (base units per one unit).
unitutf8The unit label to append.
Returnsutf8value / factor followed by the unit label.
wcl
format_unit_value(5242880, 1048576, "MiB")   # → "5 MiB"

The primitive behind format_unit, for a caller that already holds the factor. Nothing checks that the factor matches any declared unit.

§ 15Builtins the host adds

The two the wcl CLI registers on wdoc's behalf. They are not part of the language, they are not in wcl_lang, and a host embedding WCL without wdoc will not have them — but builtin_names() reports them under the CLI, so they are catalogued here rather than left unexplained.

Nothing stops you adding your own. A host registers a Rust closure against a name and it becomes callable from every document that host opens. See the wcl_lang crate docs for Environment::add_builtin and from_fn.

§ 15.1page_metadata

text
fn(ctx: TemplateCtx) -> PageMetadata

Return memoised reading order, neighbours and active TOC path plus authored heading metadata for the current template page. Site metadata is indexed once and no other page body is evaluated.

ParameterTypeMeaning
ctxTemplateCtxThe current template context.
ReturnsPageMetadataMetadata for the current page.
wcl
{ let m = page_metadata(c); m.next }   # → the next TOC entry, or none

wdoc's, not the language's. It reads the current template context and answers the page's reading order, neighbours, active TOC path and heading outline. The site index is memoised and no page body is lowered. See Templates and layouts.

§ 15.2__wdoc_slot

text
(no signature registered)

Internal. No documentation is registered for it.

ParameterTypeMeaning
ReturnsNothing documented.
wcl
__wdoc_slot(c.slots, name, :blocks)   # → the blocks filling the named slot

wdoc's, and internal. It backs the template slot machinery and is the one builtin the CLI registers with no documented signature, which is why fn_signature("__wdoc_slot") answers a record whose every field is empty. Treat it as private to the standard library.

§ 16Where to go next