Identifiers

Identifiers are the names used throughout WCL — for fields, types, block kinds, block labels, variants, symbols, let bindings, and imported items. The lexical rule is the same everywhere, with one convenience exception for block labels.

Lexical rule

An identifier starts with an ASCII letter (a-z, A-Z) or an underscore, and continues with letters, digits, or underscores. No Unicode, no dashes, no spaces.

name           // ok
my_field       // ok
_internal      // ok
v2             // ok
HTTPStatus     // ok

2nd_attempt    // NOT ok — must not start with a digit
kebab-case     // NOT ok as a field/type name — dashes aren't identifier chars

Block labels: kebab-case and paths

Block labels (the name after a block kind) are the one place a bare identifier may contain - and / connectors — so kebab-case class names and path-like page names need no quoting. The connector sits directly between name parts (no surrounding spaces).

class dgm-box {}              // kebab-case, bare
class wdoc-series-1 {}        // trailing number is fine
page reference/intro {}       // path-like
page api/v1/users {}

class "dgm-box" {}            // quoting still works

Reserved words

A handful of words are reserved by the lexer and cannot be used as identifiers in any position:

ReservedUsed for
true, falseBoolean literals
noneThe none literal
if, else, matchControl flow

Other words that look special — type, interface, union, symbol_set, let, import, connection, fn, extends, as — are recognised only in declaration positions, so they may also appear as ordinary identifiers. It reads more clearly to keep them for their declaration use.

Naming conventions

The standard library follows Rust-like conventions, but the language does not enforce them. Pick a style and stay consistent.

ConventionUsed for
snake_caseFields, let bindings, block kinds, symbols
PascalCaseTypes, interfaces, unions, variants, symbol sets
SCREAMINGRarely; reserve for visibly-constant globals

References

A &T field accepts any value that structurally satisfies interface T. It is how a record or function declares give-me-anything-that-looks-like-this without committing to a specific concrete type.

Declaring a reference field

Use &InterfaceName as the field type. The accepted values are exactly those that satisfy the interface — every required field, with compatible types.

interface Drawable {
  x: f64
  y: f64
}

type Scene {
  focus: &Drawable     // any value with x and y of type f64
}

Why a reference?

A reference field lets a schema be open-ended without being untyped. A renderer that consumes &Drawable can accept any future shape that satisfies the contract — Circle, Image, Container, a user's Custom — without knowing those concrete types.

type Circle { x: f64  y: f64  radius: f64 }
type Square { x: f64  y: f64  side:   f64 }

s1 = Scene { focus: Circle { x: 0.0,  y: 0.0,  radius: 5.0 } }
s2 = Scene { focus: Square { x: 10.0, y: 10.0, side:   8.0 } }

Structural, not nominal

A type need not explicitly extend or implement the interface. If it has all the required fields with compatible types, a &T field accepts it.