Skip to content
CryoCryo home
LanguageFoundations

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_case for variables, functions, methods.
  • PascalCase for types (struct, class, enum, trait, alias).
  • SCREAMING_SNAKE_CASE for compile-time constants.

1.2 Keywords

Keywords are reserved identifiers. They cannot be used as variable, function, or type names.

Control flowDeclarationsModifiersOperator keywordsSpecial valuesReserved for future use
iffunctionfromconstnewtrueyield
elseclassasmutdeletefalseauto
switchstructimplementstaticsizeofnullunsigned
caseenumintrinsicpublicalignofthistuple
defaulttraitwhereprivatetypeofThisoptional
matchtypeexternprotectedinwith
whilenamespacevirtualas
formoduleoverrideawait
loopimportinline
doexportunsafe
breakstatic_assertmove
continueunionasync
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 a u64 operand. Hoist the literal into a const 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} formats expr through the Display trait; {expr:?} formats it through Debug. Any type implementing the relevant trait works, including Option, Result, and Array<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]}
    • align is < (left), > (right), or ^ (centre); the default is left. fill is any single character placed before the alignment ({n:*>6}****42).
    • width is a minimum field width counted in Unicode scalars; shorter values are padded with fill (space by default).
    • A leading 0 zero-pads numbers, keeping the sign and any radix prefix leftmost ({−5:06}-00005, {255:#06x}0x00ff).
    • .precision truncates a Display value to that many scalars ({"hello":.3}hel) and sets the fractional digits of a float ({pi:.2}3.14).
    • type selects an integer radix: x/X (hex), o (octal), b (binary); # adds the 0x/0o/0b prefix. 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 bare a ? 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 String the caller owns (and drops). The parser desugars the whole f-string to calls into std::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.