This is intended to document the *.wit
format as it exists today. The goal is
to provide an overview to understand what features wit
files give you and how
they're structured. This isn't intended to be a formal grammar, although it's
expected that one day we'll have a formal grammar for *.wit
files.
If you're curious to give things a spin try out the online
demo of wit-bindgen
where
you can input *.wit
on the left and see output of generated bindings for
languages on the right. If you're looking to start you can try out the
"markdown" output mode which generates documentation for the input document on
the left.
The wit
format is a curly-braced-based format where whitespace is optional (but
recommended). It is intended to be easily human readable and supports features
like comments, multi-line comments, and custom identifiers. A wit
document
is parsed as a unicode string, and when stored in a file is expected to be
encoded as UTF-8.
Additionally, wit files must not contain any bidirectional override scalar values, control codes other than newline, carriage return, and horizontal tab, or codepoints that Unicode officially deprecates or strongly discourages.
The current structure of tokens are:
token ::= whitespace
| comment
| operator
| keyword
| identifier
Whitespace and comments are ignored when parsing structures defined elsewhere here.
A whitespace
token in *.wit
is a space, a newline, a carriage return, or a
tab character:
whitespace ::= ' ' | '\n' | '\r' | '\t'
A comment
token in *.wit
is either a line comment preceded with //
which
ends at the next newline (\n
) character or it's a block comment which starts
with /*
and ends with */
. Note that block comments are allowed to be nested
and their delimiters must be balanced
comment ::= '//' character-that-isnt-a-newline*
| '/*' any-unicode-character* '*/'
There is a special type of comment called documentation comment
. A
doc-comment
is either a line comment preceded with ///
whichends at the next
newline (\n
) character or it's a block comment which starts with /**
and ends
with */
. Note that block comments are allowed to be nested and their delimiters
must be balanced
doc-comment ::= '///' character-that-isnt-a-newline*
| '/**' any-unicode-character* '*/'
There are some common operators in the lexical structure of wit
used for
various constructs. Note that delimiters such as {
and (
must all be
balanced.
operator ::= '=' | ',' | ':' | ';' | '(' | ')' | '{' | '}' | '<' | '>' | '*' | '->'
Certain identifiers are reserved for use in wit
documents and cannot be used
bare as an identifier. These are used to help parse the format, and the list of
keywords is still in flux at this time but the current set is:
keyword ::= 'use'
| 'type'
| 'resource'
| 'func'
| 'u8' | 'u16' | 'u32' | 'u64'
| 's8' | 's16' | 's32' | 's64'
| 'float32' | 'float64'
| 'char'
| 'handle'
| 'record'
| 'enum'
| 'flags'
| 'variant'
| 'union'
| 'bool'
| 'string'
| 'option'
| 'list'
| 'expected'
| 'unit'
| 'as'
| 'from'
| 'static'
| 'interface'
| 'tuple'
| 'async'
| 'future'
| 'stream'
A wit
document is a sequence of items specified at the top level. These items
come one after another and it's recommended to separate them with newlines for
readability but this isn't required.
A use
statement enables importing type or resource definitions from other
wit documents. The structure of a use statement is:
use * from other-file
use { a, list, of, names } from another-file
use { name as other-name } from yet-another-file
Specifically the structure of this is:
use-item ::= 'use' use-names 'from' id
use-names ::= '*'
| '{' use-names-list '}'
use-names-list ::= use-names-item
| use-names-item ',' use-names-list?
use-names-item ::= id
| id 'as' id
Note: Here use-names-list?
means at least one use-name-list
term.
There are a number of methods of defining types in a wit
document, and all of
the types that can be defined in wit
are intended to map directly to types in
the interface types specification.
A type
statement declares a new named type in the wit
document. This name can
be later referred to when defining items using this type. This construct is
similar to a type alias in other languages
type my-awesome-u32 = u32
type my-complicated-tuple = tuple<u32, s32, string>
Specifically the structure of this is:
type-item ::= 'type' id '=' ty
A record
statement declares a new named structure with named fields. Records
are similar to a struct
in many languages. Instances of a record
always have
their fields defined.
record pair {
x: u32,
y: u32,
}
record person {
name: string,
age: u32,
has-lego-action-figure: bool,
}
Specifically the structure of this is:
record-item ::= 'record' id '{' record-fields '}'
record-fields ::= record-field
| record-field ',' record-fields?
record-field ::= id ':' ty
A flags
statement defines a new record
-like structure where all the fields
are booleans. The flags
type is distinct from record
in that it typically is
represented as a bit flags representation in the canonical ABI. For the purposes
of type-checking, however, it's simply syntactic sugar for a record-of-booleans.
flags properties {
lego,
marvel-superhero,
supervillan,
}
// type-wise equivalent to:
//
// record properties {
// lego: bool,
// marvel-superhero: bool,
// supervillan: bool,
// }
Specifically the structure of this is:
flags-items ::= 'flags' id '{' flags-fields '}'
flags-fields ::= id,
| id ',' flags-fields?
A variant
statement defines a new type where instances of the type match
exactly one of the variants listed for the type. This is similar to a "sum" type
in algebraic datatypes (or an enum
in Rust if you're familiar with it).
Variants can be thought of as tagged unions as well.
Each case of a variant can have an optional type associated with it which is present when values have that particular case's tag.
All variant
type must have at least one case specified.
variant filter {
all,
none,
some(list<string>),
}
Specifically the structure of this is:
variant-items ::= 'variant' id '{' variant-cases '}'
variant-cases ::= variant-case,
| variant-case ',' variant-cases?
variant-case ::= id
| id '(' ty ')'
An enum
statement defines a new type which is semantically equivalent to a
variant
where none of the cases have a payload type. This is special-cased,
however, to possibly have a different representation in the language ABIs or
have different bindings generated in for languages.
enum color {
red,
green,
blue,
yellow,
other,
}
// type-wise equivalent to:
//
// variant color {
// red,
// green,
// blue,
// yellow,
// other,
// }
Specifically the structure of this is:
enum-items ::= 'enum' id '{' enum-cases '}'
enum-cases ::= id,
| id ',' enum-cases?
A union
statement defines a new type which is semantically equivalent to a
variant
where all of the cases have a payload type and the case names are
numerical. This is special-cased, however, to possibly have a different
representation in the language ABIs or have different bindings generated in for
languages.
union configuration {
string,
list<string>,
}
// type-wise equivalent to:
//
// variant configuration {
// 0(string),
// 1(list<string>),
// }
Specifically the structure of this is:
union-items ::= 'union' id '{' union-cases '}'
union-cases ::= ty,
| ty ',' union-cases?
Functions can also be defined in a *.wit
document. Functions have a name,
parameters, and results. Functions can optionally also be declared as async
functions.
thunk: func()
fibonacci: func(n: u32) -> u32
sleep: async func(ms: u64)
Specifically functions have the structure:
func-item ::= id ':' 'async'? 'func' '(' func-args ')' func-ret
func-args ::= func-arg
| func-arg ',' func-args?
func-arg ::= id ':' ty
func-ret ::= nil
| '->' ty
Resources represent a value that has a hidden representation not known to the outside world. This means that the resource is operated on through a "handle" (a pointer of sorts). Resources also have ownership associated with them and languages will have to manage the lifetime of resources manually (they're similar to file descriptors).
Resources can also optionally have functions defined within them which adds an
implicit "self" argument as the first argument to each function of the same type
of the including resource, unless the function is flagged as static
.
resource file-descriptor
resource request {
static new: func() -> request
body: async func() -> list<u8>
headers: func() -> list<string>
}
Specifically resources have the structure:
resource-item ::= 'resource' id resource-contents
resource-contents ::= nil
| '{' resource-defs '}'
resource-defs ::= resource-def resource-defs?
resource-def ::= 'static'? func-item
As mentioned previously the intention of wit
is to allow defining types
corresponding to the interface types specification. Many of the top-level items
above are introducing new named types but "anonymous" types are also supported,
such as built-ins. For example:
type number = u32
type fallible-function-result = expected<u32, string>
type headers = list<string>
Specifically the following types are available:
ty ::= 'u8' | 'u16' | 'u32' | 'u64'
| 's8' | 's16' | 's32' | 's64'
| 'float32' | 'float64'
| 'char'
| 'bool'
| 'string'
| 'unit'
| tuple
| list
| option
| expected
| future
| stream
| id
tuple ::= 'tuple' '<' tuple-list '>'
tuple-list ::= ty
| ty ',' tuple-list?
list ::= 'list' '<' ty '>'
option ::= 'option' '<' ty '>'
expected ::= 'expected' '<' ty ',' ty '>'
future ::= 'future' '<' ty '>'
stream ::= 'stream' '<' ty ',' ty '>'
The tuple
type is semantically equivalent to a record
with numerical fields,
but it frequently can have language-specific meaning so it's provided as a
first-class type.
Similarly the option
and expected
types are semantically equivalent to the
variants:
variant option {
none,
some(ty),
}
variant expected {
ok(ok-ty)
err(err-ty),
}
These types are so frequently used and frequently have language-specific meanings though so they're also provided as first-class types.
Finally the last case of a ty
is simply an id
which is intended to refer to
another type or resource defined in the document. Note that definitions can come
through a use
statement or they can be defined locally.
Identifiers in wit
can be defined with two different forms. The first is a
lower-case stream-safe NFC kebab-case identifier where each part delimited
by '-'s starts with a XID_Start
scalar value with a zero Canonical Combining
Class:
foo: func(bar: u32)
red-green-blue: func(r: u32, g: u32, b: u32)
This form can't name identifiers which have the same name as wit keywords, so the second form is the same syntax with the same restrictions as the first, but prefixed with '%':
%foo: func(%bar: u32)
%red-green-blue: func(%r: u32, %g: u32, %b: u32)
// This form also supports identifiers that would otherwise be keywords.
%variant: func(%enum: s32)
A wit
document is resolved after parsing to ensure that all names resolve
correctly. For example this is not a valid wit
document:
type foo = bar // ERROR: name `bar` not defined
Type references primarily happen through the id
production of ty
.
Additionally names in a wit
document can only be defined once:
type foo = u32
type foo = u64 // ERROR: name `foo` already defined
Names do not need to be defined before they're used (unlike in C or C++), it's ok to define a type after it's used:
type foo = bar
record bar {
age: u32,
}
Types, however, cannot be recursive:
type foo = foo // ERROR: cannot refer to itself
record bar1 {
a: bar2,
}
record bar2 {
a: bar1, // ERROR: record cannot refer to itself
}
The intention of wit
is that it maps down to interface types, so the goal of
name resolution is to effectively create the type section of a wasm module using
interface types. The restrictions about self-referential types and such come
from how types can be defined in the interface types section. Additionally
definitions of named types such as record foo { ... }
are intended to map
roughly to declarations in the type section of new types.