Grammar and precedence
This page summarizes the concrete grammar rules that most often matter when reading or writing Khora. Individual reference pages show the complete examples for each construct.
Declaration starts
Section titled “Declaration starts”A source file is a sequence of declarations. Common forms are:
module Path ;import Path::{...} ;import Path::* ;
pub? const Pattern (: Type)? = Expr ;derive(...)? pub? type Name<TypeParams>? (= TypeDef)? ;pub? fn Name<TypeParams>? (Params) (-> Type)? EffectClause* (Block | ;)pub? effect Name<TypeParams>? { Field, ... }pub? context Name { name: Expr, ... }pub? trait Name<TypeParams>? (: Bounds)? { TraitItem* }impl<TypeParams>? Type (for Type)? { TraitItem* }
test "name" Blockbench "name" Blockextern fn name(Params) (-> Type)? EffectClause* ;Current public visibility is spelled pub.
Paths and fields
Section titled “Paths and fields”Compile-time path:
app::model::UserResult::OkRuntime projection:
user.nameresponse.status:: and . are intentionally separate tokens with separate roles.
Type grammar shapes
Section titled “Type grammar shapes”Named/generic type:
UserList<User>Tuple and unit:
()(Int, String)Record:
{ id: Int, name: String }Record update, in expression position — the base first, then the fields that replace its own:
{ ..base }{ ..base, id: 7 }{ ..base, id: 7, name: "Grace" }Open row:
{ db: Db | 'ef }Function type:
Request -> Response with { db: Db } raises DbError + HttpErrorExplicit polymorphism:
forall<A>. A -> AVariant type:
| None| Some(value: A)Generic parameter grammar
Section titled “Generic parameter grammar”Ordinary parameter:
<A>Bound:
<A: Eq + Show>Const parameter:
<const N: Int>Row parameter:
<'ef>Variance:
<+A><-A>These forms may be mixed in one parameter list.
Primary expression forms
Section titled “Primary expression forms”42"text"path::name{}(a, b)[a, b]{ let x = 1; x + 1 }fn x => x + 1if condition { a } else { b }match value { Pattern => Expr, }while condition { body; }for pattern in iterable { body; }loop { body; }raise errorhandler for Effect { operation: fn () => value }with Context { body; }breakcontinuereturn valuePostfix forms
Section titled “Postfix forms”Starting from an expression, postfix forms bind tightly and may chain:
f(a, b)value.fieldfallible()!fallible()! catch { Error::Case => recover(), }operation() with { service: handler }operation() with ProductionCalls, field projection, postfix failure propagation, catch, and postfix with bind more tightly than binary operators.
Lambda grammar
Section titled “Lambda grammar”Single parameter:
fn value => expressionIgnored parameter:
fn _ => expressionParameter list:
fn (left: Int, right: Int) => left + rightBlock body:
fn value => { let next = value + 1; next * 2}Pipeline grammar
Section titled “Pipeline grammar”First argument:
value |> f(a)Explicit insertion point:
value |> f(a, _, b)A stage may contain one _ placeholder.
Flow lambda:
||> normalize|> validate!|> persist!||> starts a unary lambda; following |> stages belong to that flow expression until grouping ends.
Operator precedence
Section titled “Operator precedence”From loosest to tightest:
=— assignment, right-associative|>— pipeline, left-associative||— boolean OR&&— boolean AND== != < > <= >=— comparisons+ -* / %- prefix
- ! - postfix call,
., failure!,catch, andwith
For example:
x = value + 1 |> doubleparses as assigning the result of the full pipeline to x.
Blocks versus record literals
Section titled “Blocks versus record literals”These braces are a record because they begin with fields:
{ id: 1, name: "Ada" }These braces are a block:
{ let id = 1; id + 1}An empty {} is an empty record literal. In control-flow forms such as match, if, while, for, and loop, the grammar already knows when braces introduce the body or arm list.
Match arms
Section titled “Match arms”Pattern (if Expr)? => Expr ,?Examples:
Result::Ok(value) => value,value if value > 0 => value,_ => 0,Pattern shapes
Section titled “Pattern shapes”_LiteralidentifierPathPath(Pattern, ...)Path { field, field: Pattern, ... }(Pattern, ...)See Patterns for binding and exhaustiveness rules.
Numeric literals
Section titled “Numeric literals”1_0000.256.02e2319.99dThe d suffix creates an exact Decimal; a fractional literal without it is Float.
Semicolons
Section titled “Semicolons”A semicolon terminates declarations such as const, type, imports, and signature-only functions, and turns an expression inside a block into a statement:
let value = compute();log(value);valueThe last value is the block result because it has no semicolon. A function definition ends with its closing } rather than };.
Khora 0.2.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource