WCL Reference · explanation
Introduction
WCL is a typed configuration and schema language. A .wcl file carries the data: the ports, the replica counts, the URLs. It also carries the shape that data has to fit. One command, wcl check, holds the shape against the data, and it does so before the program that needs the configuration has started.
This chapter is about why that matters and what it buys you. It sets WCL against the formats you already use, and it introduces wdoc, the document generator built on the same language. If you would rather start typing, Quick start installs the tool and walks a first document from four lines to a checked schema.
What WCL is for
Configuration is the part of a system with no compiler. The code around it is type-checked, reviewed and tested. The file that decides which database it talks to is a bag of strings that nothing looks at until the process boots. So a missing key is a crash at 3am, a replicas: 600 is an outage, and a debug: true that outlived its pull request is an incident report.
The usual answer is to describe the shape somewhere else: a JSON Schema file, a Kubernetes CRD, a struct with #[serde(deny_unknown_fields)] in the program that reads it. That works, and it puts the description of the data in a different file, a different language, and often a different repository from the data itself. The two drift.
WCL's answer is that the shape belongs in the language. A .wcl file declares its own types, and wcl check holds the data against them. It is an ordinary command, so you can run it in CI, in a pre-commit hook, or on your laptop before you push. Nothing has to consume the file for wcl check to judge it.
The type system carries more than the strings, numbers and booleans of a data format:
- Sized numbers and units. u32, f64, and literals like 512MiB and 90s that evaluate to a plain number in a declared base unit. See Values and primitives.
- Names that mean something. identifier for a name the rest of the document may point at, symbol for a tag, and a symbol_set for a closed vocabulary whose members are checked.
- Composite types. Records, lists, tensors, unions, interfaces and type aliases with @min / @max / @non_empty constraints. See Types.
- Expressions and functions. The right of an = is an expression, not just a literal: arithmetic, string interpolation, conditionals, let bindings, calls. See Expressions and operators and Functions.
- A document model. Blocks nest, a schema decides what may nest inside what, and gather fields collect every block of a kind into a list the host can walk. See Documents, fields and blocks.
How WCL differs from JSON, YAML and TOML
Take a real fragment. Here is a service deployed to two environments, written the way most projects write it today:
service: checkout
environments:
staging:
url: https://staging.example.com
replicas: 1
debug: yes
prod:
url: https://example.com
replicas: 6
debug: no
Nothing in that file says replicas holds a number. Nothing says 600 would be absurd. Nothing says environments may hold staging and prod and not stagng. Every one of those is the consuming program's problem, discovered at startup, in production, by whoever is on call. And debug: yes is not even reliably a boolean: YAML 1.1 parsers read yes and no as true and false, YAML 1.2 parsers read them as the strings "yes" and "no". Which one you get depends on a library you did not choose.
The same data in WCL, with the shape written down beside it:
@block("env")
type Env {
@inline(0) name: identifier
url: utf8
replicas: u32
debug: bool
}
@document
type AppConfig {
service: utf8
@children("env") envs: list<Env>
}
service = "checkout"
env staging {
url = "https://staging.example.com"
replicas = 1
debug = true
}
env prod {
url = "https://example.com"
replicas = 6
debug = false
}
It is longer, and the extra length is the whole point: the top half is the contract, and it is the reason wcl check can answer a question about the bottom half. There is no debug: yes ambiguity to resolve, because yes is not a boolean in WCL. It is a name, and a name that nothing declares fails to resolve.
| Question | JSON | YAML | TOML | WCL |
|---|---|---|---|---|
| Comments | No | Yes | Yes | Yes, and wcl fmt keeps them |
| Scalar types | String, number, bool, null | The same, guessed from the text | Plus dates and times | Sized integers, floats, units, bool, four string encodings, identifiers, symbols, none |
| Where the schema lives | Another file (JSON Schema) | Another file | Another file | This file, or one it imports |
| Computed values | No | Anchors and aliases reuse, never compute | No | Full expressions, let bindings and functions |
| Who validates | The consuming program | The consuming program | The consuming program | wcl check, before anything consumes it |
| Reusing a fragment | Copy it | &anchor / *alias | Copy it | import, let, functions, references |
The last row is the one that compounds. A configuration format without composition grows by copying, and a file that grew by copying is a file where two of the copies disagree. Namespaces and imports covers how to split a document across files. Expressions and operators covers how to compute one field from another.
WCL is not a general-purpose language
There is no I/O, no mutation, no loops that can fail to terminate. Every expression evaluates to a value, and evaluation is lazy and cached. See How a document evaluates. What WCL adds over a data format is types, composition and checking, not a runtime.
The other half: wdoc
wdoc is a document generator written in WCL. You declare pages and sites as blocks, and wcl wdoc renders them to a static website, a Markdown tree or a PDF. The book you are reading is a wdoc site. It is not a second language: import <wdoc.wcl> pulls in a library of type declarations shipped inside the binary, so every block it gives you is an ordinary @block type of the kind you can declare yourself. The same checker reads it, and the same errors come back.
Introduction to wdoc is the full account, and it opens Part II. Quick start builds a site in a dozen lines.
How to read this book
This is one reference for both halves, in three parts.
| Part | What it covers |
|---|---|
| Front matter | This chapter and Quick start: what WCL is for and how it differs from a data format, then the shortest path from an empty folder to a checked document. |
| The WCL language | Documents, values, collections, types, expressions, control flow, functions, namespaces, schemas, decorators, connections, evaluation, builtins, and the CLI. |
| wdoc | Its own introduction, then pages and sites, templates, themes, visibility and output targets. Then the block reference: text, code, callouts, tables, media, icons, math, data views, diagrams, charts, timelines, terminals, wireframes, and writing your own. |
Read Part I in order the first time. Its first five chapters build on one another: Documents, fields and blocks is the shape of a file, Values and primitives is what goes in it, Lists, tensors and records is what those compose into, Types is how you name a shape, and Expressions and operators is how you compute one. After those five the chapters are largely independent, and Schemas is the one to reach for when a wcl check message surprises you.
Part II assumes Part I and otherwise stands alone. Read Introduction to wdoc for what it is and how the part is laid out, then Documents, pages and sites for the shape of a build. After that, look up the block you need.
Three conventions run through every chapter:
- Snippets were run. A .wcl listing that carries a filename is a whole file, schema included, unless the surrounding text says it replaces part of one. A $ listing is real terminal output, copied rather than typed out. The failures are as real as the successes.
- Chapter links carry the chapter's name. Schemas goes to a chapter. How WCL differs goes to a heading in this one. The build checks every link in this book, so a link that renders is a link that resolves.
- Gaps are stated, not hidden. Where the tool does not check something it describes, a warning callout says so. Where a feature is deliberately unfinished, the chapter names it rather than leaving you to find out.
Where to go next
- Quick start. Install the tool and take one file from four lines to a checked schema. The next chapter.
- Documents, fields and blocks. Fields, blocks, labels, tables and let items in full. The start of Part I.
- Values and primitives. The seven ways to write a scalar, and which of them the checker judges.
- Schemas. @document, @block, @child, @children, @inline, @table, and exactly what wcl check checks.
- Documents, pages and sites. The start of Part II, and the wdoc equivalent of Documents, fields and blocks.