Skip to content
hekla

Keywords and annotations

The closed sets. 32 hard keywords, 18 soft ones, and the six annotations.

Every list on this page is closed and derived from the compiler’s own tables, so it cannot drift from what the language accepts.

Hard keywords

Thirty-two words, reserved everywhere. None of these can be a field, parameter or local name.

Keyword
as binds the whole event in an effect arm or a projector handler
command a declaration
const a declaration
delete a projector write
effect a declaration
else with if
emit a command statement
entity inside a projector
enum a declaration, module or projector scoped
event a declaration
false a literal
fn a declaration, module or effect scoped
fold a read declaration
for iteration
guard a declaration, and its use site
if branching
in with for
invalid an outcome
invoke an effect calling a command
let the only binding form
none the absent optional
on an arm, and a fold’s slice
patch a projector write
projector a declaration
put a projector write
record a declaration
refusal a declaration
reject an outcome
return
test a declaration
true a literal
update a projector write

Soft keywords

Eighteen words claimed only positionally, so each stays usable as an ordinary name everywhere else. An entity column called given, a parameter called no and a field called timeout are all legal.

Keyword Claimed in
deliver a test action
erase an effect arm
erased a test setup
expect a test expectation
fail an effect arm
given a test log
http an effect arm
latest an effect arm’s delivery
live an effect arm’s delivery
log an effect arm
no a test expectation
nothing a test expectation
project a test action
respond a test setup
run a test action
secret a declaration, and a test setup
skipped a test expectation
timeout a test setup

There is a third group the tables above deliberately leave out: bare identifiers resolved by the callable dispatch rather than by the lexer or a positional check. reveal and now, plus the constructor roots Uuid, Json, Map, Timestamp, Money and Decimal, and the three rounding words HalfUp, HalfEven and Down. They are builtins, not keywords.

Annotations

Six, and the set is closed. An unrecognised one is unknown-annotation; a recognised one in the wrong place is bad-annotation.

Annotation Legal on
@subject(field) an event field only seals the value under a key scoped to that subject
@max(n) an event, entity or record field bounds a String or String? and nothing else
@no_index an event field only keeps the field out of the tag set, so it is not foldable
@key an entity field, or an effect arm’s trigger destructure the primary key, or the lane
@index an entity field only makes the column filterable on the scan route
@default an enum variant only the zero value, which is what patch materialises

Two forms behave like annotations without the @:

Form Where
index (a, b) entity level a composite index
= <literal> an entity field a default for a column a write may omit

An annotation name may never contain a ..

The subtle rules

@max applies to String and String? and nothing else. @max on an Int, a container or a record is an error naming the type, because a length there would have nothing to bound.

@subject on a record field is a dedicated error, not a generic one: a subject-bound value is recovered from its schema path, and a record reached through a container has no path to recover it from.

An entity may have at most one @key. A second is bad-annotation.

An effect arm’s @keys are ordered. A composite key is a sequence, so { @key a, @key b } and { @key b, @key a } are different lanes.

= none on an optional column is rejected, because an optional is already absent by default.

Symbols

{ } a block, a record literal, a JSON object
( ) grouping, parameters, arguments, a type scale
[ ] a list literal, a comprehension, a test row key
=> a fold arm’s result
-> a fn return type
? an optional type, and an optional secret
@ an event path, and an annotation
. member access, and a projector’s stored value
= binding, and a named argument
== != < <= > >= comparison, non-associative
+ - * / % arithmetic. % is Int only, and there is no + on strings
&& || ! boolean
, separator

There are no semicolons anywhere in the language, and statements are separated by newlines.

What none of this includes

while, break, continue, match, switch, var, import, module, class, struct, interface, async, await, try, catch, throw, null, nil, undefined.

None of them is a reserved word, because none of them is a concept the language has. See Containers and control flow for why the loop forms stop where they do.