1Lexical Structure
This section describes the building blocks the lexer recognises before any syntactic or semantic meaning is assigned.
1.1 Identifiers
An identifier begins with a letter (a-z, A-Z) or underscore, followed by any sequence of letters, digits, and underscores.
identifier = letter { letter | digit | "_" }
The compiler does not enforce naming, but the standard library and ecosystem use the following conventions, which the bundled TextMate grammar and LSP also assume:
snake_casefor variables, functions, methods.PascalCasefor types (struct, class, enum, trait, alias).SCREAMING_SNAKE_CASEfor compile-time constants.
1.2 Keywords
Keywords are reserved identifiers. They cannot be used as variable, function, or type names.
| Control flow | Declarations | Modifiers | Operator keywords | Special values | Reserved for future use | |
|---|---|---|---|---|---|---|
if | function | from | const | new | true | yield |
else | class | as | mut | delete | false | auto |
switch | struct | implement | static | sizeof | null | unsigned |
case | enum | intrinsic | public | alignof | this | tuple |
default | trait | where | private | typeof | This | optional |
match | type | extern | protected | in | with | |
while | namespace | virtual | as | |||
for | module | override | await | |||
loop | import | inline | ||||
do | export | unsafe | ||||
break | static_assert | move | ||||
continue | union | async | ||||
return | ||||||
asm |
move marks a closure that captures its environment by move (see section 16.3).
async marks a function, method, or trait method whose body is compiled into a state machine and whose call returns a future; await suspends the enclosing async body until a future completes (see section 19).
Reserved-for-future-use keywords are recognised by the lexer; the parser may accept them in places that have no semantic implementation. See section 22.
1.3 Comments
Cryo recognises four comment styles. Documentation comments are semantically meaningful: they attach to the declaration that follows them and surface in LSP hovers and generated documentation.
// Line comment.
/* Block comment.
Spans multiple lines. */
/// Outer documentation comment (line form). Attaches to the next declaration.
/// Multiple consecutive /// lines are joined.
/** Outer documentation comment (block form). Attaches to the next declaration. */
///! Inner documentation comment. Attaches to the enclosing module/namespace.
1.4 Literals
Numeric Literals
Integer literals support four bases. Underscores are visual separators that the compiler ignores. A type suffix pins the literal to a width; without one, the type is inferred from context (defaulting to i32 for integers and f64 for floats).
42 // decimal
1_000_000 // separators: identical to 1000000
0xFF // hex
0b1010 // binary
0o755 // octal
42u64 // typed: unsigned 64-bit
42i8 // typed: signed 8-bit
3.14 // float (defaults to f64)
3.14f32 // explicit 32-bit float
1.0e10 // scientific notation
2.5e-3f64 // scientific with explicit type
Type suffixes: u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 usize isize f32 f64
Trap. Integer literals exceeding
i64::MAX(e.g.0xFFFF_FFFF_FFFF_FFFF) wrap to negative when used inline against au64operand. Hoist the literal into aconst u64 NAME = ...binding to compare correctly.
String and Character Literals
Strings are enclosed in double quotes; characters in single quotes. Both share the same set of escape sequences.
"Hello, world!"
"line one\nline two"
'A'
'\n'
'\x41' // hex byte: equivalent to 'A'
Escape sequences: \n \t \r \0 \\ \' \" \xHH (hex byte). Raw strings (r"...") and the additional C escapes \a \b \f \v are reserved but not yet implemented - see section 22.
f-strings (string interpolation)
An f-string, prefixed with f, builds an owned String by interpolating
expressions written inside {...}:
const x: i32 = 42;
const opt: Option<i32> = Option::Some(7);
const s: String = f"x = {x}, opt = {opt:?}"; // "x = 42, opt = Some(7)"
-
{expr}formatsexprthrough theDisplaytrait;{expr:?}formats it throughDebug. Any type implementing the relevant trait works, includingOption,Result, andArray<T>. -
The embedded expression is a full expression:
f"{a + b}",f"{p.x}",f"{m.get(k)}". -
A hole may carry a format spec after a colon, a subset of Rust's:
{expr:[[fill]align][#][0][width][.precision][type]}alignis<(left),>(right), or^(centre); the default is left.fillis any single character placed before the alignment ({n:*>6}→****42).widthis a minimum field width counted in Unicode scalars; shorter values are padded withfill(space by default).- A leading
0zero-pads numbers, keeping the sign and any radix prefix leftmost ({−5:06}→-00005,{255:#06x}→0x00ff). .precisiontruncates a Display value to that many scalars ({"hello":.3}→hel) and sets the fractional digits of a float ({pi:.2}→3.14).typeselects an integer radix:x/X(hex),o(octal),b(binary);#adds the0x/0o/0bprefix. A radix formats the value's own-width two's-complement bits, so{(-5i32):x}→fffffffb.- The spec separator is the first top-level
:. A hole that mixes a ternary with a spec must parenthesise the ternary (f"{(c ? a : b):>4}"); a barea ? b : c(spaces around:) is not mistaken for a spec.
-
{{and}}produce literal{and}. Standard escape sequences in the literal text are processed as in a normal string. -
The result is a heap-backed
Stringthe caller owns (and drops). The parser desugars the whole f-string to calls intostd::fmt::interp, which is auto-imported into any module that uses one.
For raw, untyped formatted output (C printf semantics, %d/%s
specifiers, not type-checked), use printf - an intrinsic that is
auto-imported into every module (no import needed). For typed,
Display-formatted output, build a String (usually with an f-string) and
pass it to print / println from std::fmt; both take a single
already-formatted argument (println(f"{x}")), not a printf-style format
string with trailing values.
Boolean and Null Literals
true
false
null // null pointer; valid in any pointer context
There is no implicit conversion between boolean and integers; if (1) is a type error.