Skip to content

Lexical structure

Line comments only — // to end of line. There is no block-comment form.

// this is a comment
identifier = ( letter | "_" ) { letter | digit | "_" } .
letter = "A" … "Z" | "a" … "z" .
digit = "0" … "9" .

A letter or underscore, followed by letters, digits, or underscores. Case-sensitive.

Initial case is significant by kind — it carries the value/type distinction so the grammar stays unambiguous (a bare uppercase name is always a type, so Foo { ... } and NS.Foo { ... } always read as construction, never as a value’s member-then-block):

  • Types are PascalCase. data, ref data, choice, ref choice, enum, interface, static func, and delegate func names must start uppercase. A lower-case type name is a hard error (ES2160) — it would collide with value/local syntax and could not be constructed with Name { ... }.
  • Free functions are camelCase. A receiverless free function must start lowercase (ES2161). Two exceptions may be PascalCase to match .NET convention: a method (first parameter is a data receiver, value or *T) and a static func member — neither is ever a bare name, so neither can be mistaken for a type.

Always reserved — never usable as identifiers:

GroupKeywords
Control flowif else return while for in match default defer select break continue
Declarationsnamespace data func const let var enum choice interface using derive ref pub readonly with
Concurrencyspawn chan await
Errors / boundarytry catch throw out params
Operator wordsand or not
Constantstrue false nil

Contextual keywords — matched by text in a specific position only; valid identifiers everywhere else (so let static = 1 is legal):

KeywordReserved position
static abstract open virtual task base returnsdeclaration position
delegatebefore func at member scope — mints a nominal delegate type
eventbefore name : in a ref data / interface body
raisebefore EventName( in statement position
newexpression position, immediately before an upper-case type name followed by {, (, or <
initmember position in a ref data body — a constructor
yieldstatement start inside an IAsyncEnumerable<T> function

new is the sharpest case: it is recognized only before Type{ / Type( / Type< in expression position (new Point { x: 1 }, new Vec2(3, 4)), and is an ordinary identifier everywhere else (let new = 1 compiles). There is no async keyword — await alone makes a function asynchronous (see Concurrency).

CategoryOperators
Arithmetic+ - * / %
Comparison== != < > <= >=
Logical&& || ! (or and or not)
Compound assignment+= -= *= /=
Error propagation? (postfix)
Ternary? :
Null-coalescing??
Null-conditional?.
Range..
Index-from-end^
Heap constructionnew (contextual keyword)
Address-of&
By-ref pass (call site)* / &
Return type-> or returns
Expression body= (after the signature)
Arrow lambda=>
Assignment=

The postfix ? (try-unwrap) is disambiguated from the ternary ? by lookahead: if the next token can start an expression, it is a ternary.

int_lit = digit { digit | "_" } .
float_lit = digit { digit | "_" } "." digit { digit | "_" } [ exponent ]
| digit { digit | "_" } exponent .
exponent = ( "e" | "E" ) [ "+" | "-" ] digit { digit } .
string_lit = `"` { string_char | interpolation } `"` .
char_lit = "'" ( char_char | escape ) "'" .
bool_lit = "true" | "false" .
nil_lit = "nil" .
TypeExamples
Integer42 · 0 · 1_000_000 (underscore separators)
Float3.14 · 0.5 · 1.0e10
String"hello" · "hello {name}" (interpolated)
Char'a' · '\n'
Booleantrue · false
Nullnil

A leading - is the unary negation operator, not part of the literal.

interpolation = "{" Expr "}" .

No prefix required. { expr } inside a string inserts the value of any expression — a variable, a member chain ({x.field}), an operator expression ({a + b}), a call ({f(x)}), a ternary ({x > 0 ? "+" : "-"}), an index ({xs[0]}). Each hole is handed to the expression parser and type-checked like any other expression; value types are boxed into the underlying string.Concat call. Braces nest, so object/collection literals inside a hole balance.

The hole rule: a { opens a hole only when the next character can start an expression — a letter, _, (, or !. A leading digit is excluded, so {0} and {0:d} stay literal and BCL format strings pass through to string.Format unchanged.

let msg = "user {u.name} has {u.count} items" // holes
let fmt = "progress: {0:p1}" // literal — passes to string.Format