Introduction
This specification defines the syntax and semantics of the Larol programming language. Although it may be used as a reference, this document is not intended to serve as an introduction to Larol.
Scope
This document specifies only the Larol language and its runtime. It does not specify adjacent utilities such as language servers, build systems, or frameworks.
Compliance
All behavior and rules described in this document are normative and are required of a compliant implementation, except where explicitly stated otherwise.
- Italicized text is informative and does not impose requirements on an implementation.
- Any behavior or rule explicitly described as optional may be omitted by an implementation.
- Any behavior or rule explicitly described as implementation-defined may vary between implementations. An implementation shall document its choice.
Notation
The grammar in this specification is written in Extended Backus–Naur Form (EBNF), based on ISO/IEC 14977.
Conventions
Every Assertions list is enforced at compile time; a violation of any assertion produces a compile-time error.
A fatal error is a compile-time error after which compilation cannot continue.
Lexical Structure
This page describes the conversion from Larol source ASCII characters into tokens.
Larol source ASCII characters are converted into lexical tokens through lexical analysis. Tokens are constructed by matching consecutive input characters against the token productions shown below. Each source character belongs to at most one token, and each token consists of one or more consecutive source characters.
Discarded Characters
Whitespace and comments have no semantic meaning and are only required where they are necessary to separate adjacent tokens, which would otherwise be greedily lexed.
Whitespace
The following ASCII characters are considered whitespace and do not directly produce tokens.
- Character Tabulation,
- Line Feed,
- Line Tabulation,
- Form Feed,
- Carriage Return,
- Space.
Comments
Characters following, and including // are ignored until the next 'Line Feed' or 'Carriage Return' character.
Characters following, and including /* are ignored until after the occurrence of */.
Disambiguations
In cases where multiple different valid tokens match, disambiguation rules are used:
- tokens with a longer total length (in characters) are given higher precedence;
- all other tokens are given higher precedence than the
identifiertoken.
Additional disambiguation rules should not be required for any inputs.
Unexpected Tokens
Any source character which:
- is not discarded, and
- does not belong to a token matched by the lexical token productions,
causes a lexical error.
A compliant implementation may recover from a lexical error and continue lexical analysis, producing additional tokens; alternatively, such an error could be fatal.
Syntax
Lexical Helper Productions
The following productions are helpers used in the token productions; they are not lexed into individual tokens.
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G"
| "H" | "I" | "J" | "K" | "L" | "M" | "N"
| "O" | "P" | "Q" | "R" | "S" | "T" | "U"
| "V" | "W" | "X" | "Y" | "Z"
| "a" | "b" | "c" | "d" | "e" | "f" | "g"
| "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u"
| "v" | "w" | "x" | "y" | "z" ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
hex_digit = digit | "A" | "B" | "C" | "D" | "E" | "F"
| "a" | "b" | "c" | "d" | "e" | "f" ;
character = ? any ASCII source character ? ;
quotation_mark = '"' ;
apostrophe = "'" ;
reverse_solidus = "\" ;
escape_sequence = reverse_solidus, ( "n" | "r" | "t" | reverse_solidus | quotation_mark
| "x", hex_digit, hex_digit ) ;
char_escape_sequence = reverse_solidus, ( "n" | "r" | "t" | reverse_solidus | apostrophe
| "x", hex_digit, hex_digit ) ;
string_character = character - ( quotation_mark | reverse_solidus | ? line feed ? | ? carriage return ? ) ;
char_character = character - ( apostrophe | reverse_solidus | ? line feed ? | ? carriage return ? ) ;
exponent_part = "e", [ "-" ], digit, { [ "_" ], digit } ;
Lexical Token Productions
Each of the following productions represents an individual token.
identifier = ( letter | "_" ), { letter | digit | "_" } ;
(* Literals *)
integer_literal = digit, { [ "_" ], digit }
| "0x", hex_digit, { [ "_" ], hex_digit } ;
float_literal = digit, { [ "_" ], digit }, ".", digit, { [ "_" ], digit }, [ exponent_part ] ;
boolean_literal = true | false ;
string_literal = quotation_mark, { string_character | escape_sequence }, quotation_mark ;
char_literal = apostrophe, ( char_character | char_escape_sequence ), apostrophe ;
(* Reserved Keywords *)
alloc = "alloc" ;
collect = "collect" ;
i8 = "i8" ;
i16 = "i16" ;
i32 = "i32" ;
i64 = "i64" ;
u8 = "u8" ;
u16 = "u16" ;
u32 = "u32" ;
u64 = "u64" ;
f32 = "f32" ;
f64 = "f64" ;
bool = "bool" ;
function = "function" ;
enum = "enum" ;
namespace = "namespace" ;
over = "over" ;
structure = "structure" ;
define = "define" ;
let = "let" ;
match = "match" ;
false = "false" ;
true = "true" ;
type = "type" ;
(* Symbols *)
ampersand = "&" ;
ampersand_ampersand = "&&" ;
bang = "!" ;
bang_equal = "!=" ;
colon = ":" ;
colon_colon = "::" ;
comma = "," ;
dot = "." ;
equal = "=" ;
equal_equal = "==" ;
greater_than = ">" ;
greater_than_equal = ">=" ;
left_brace = "{" ;
left_bracket = "[" ;
left_paren = "(" ;
less_than = "<" ;
less_than_equal = "<=" ;
minus = "-" ;
number_sign = "#" ;
percent = "%" ;
plus = "+" ;
question_mark = "?" ;
right_brace = "}" ;
right_bracket = "]" ;
right_paren = ")" ;
semicolon = ";" ;
solidus = "/" ;
star = "*" ;
vertical_bar_vertical_bar = "||" ;
Types
Every type is a value: a type reference is an expression whose value is of the type type. Types are not a separate syntactic category; the type forms described below are expression forms, and every position which requires a type takes an expression. Compile-time assertions ensure that expressions are of the type type when required.
Type values are produced by the expression forms:
- the built-in types: boolean, numeric, type, and namespace,
- the function type form,
- the handle type form,
- the slice type form,
- a tuple expression whose elements are all type values,
- a reference to a structure or enumeration declaration,
- a reference to a define which is bound to a type value, and
- a reference to a scope binding which is bound to a type value.
Runtime Representation
A type is runtime representable when values of that type can exist at runtime. A value whose type is not runtime representable exists only at compile time and cannot exist at runtime.
The namespace and type types have no runtime representation, while the numeric and boolean types do. The function, structure, enumeration, and tuple types are runtime representable when all their component types are; the handle and slice types require that their operand type value is runtime representable, and are themselves runtime representable.
Namespace Type
Like all type expressions, this expression produces a value of the type type.
Namespace-typed values contain other named symbols which can be accessed by name using the static access expression.
Compile Time
Values of the namespace type are compile-time values with no runtime representation; they cannot exist at runtime.
Syntax
namespace_type = namespace ;
Type
Like all type expressions, this expression produces a value of the type type.
Also known as 'metatype.'
A type-typed value represents a type, which may have an associated runtime representation.
Every value of the type type is of the type type.
Compile Time
Values of the type type are compile-time values with no runtime representation; they cannot exist at runtime.
Syntax
type_type = type ;
Enumeration Type
Like all type expressions, this expression produces a value of the type type.
Enumeration-typed values are one of a finite set of variants determined by the specific enumeration type. A variant may carry a payload value.
Each enumeration declaration introduces a new enumeration type. Each variant declared by an enumeration is a compile-time entity of that enumeration's type: a variant cannot be evaluated as a runtime value, and may only be used to construct a value or as a match pattern.
Assertions
- An enumeration declares at most 256 variants.
Representation
Values of an enumeration type are represented as:
- a
u8tag distinguishing the variant; and - a union of the possible payloads for the specific enumeration type.
Lookup Scope
Each enumeration type's variants are resolved as members of the enumeration type value itself: the static access operation on a value of the type type which denotes an enumeration type resolves its provided identifier to that enumeration's variant of the same name.
Syntax
Produced by the reference expression when the referenced symbol is an enumeration type.
Function Type
Like all type expressions, this expression produces a value of the type type.
Also known as a 'function pointer.'
Function-typed values are any declared function with a matching return type and parameters.
Assertions
- Each parameter's type expression is a value of the type type.
- The return type's expression is a value of the type type.
Representation
If its return type and parameters' types are runtime representable, a function-typed value is represented as a pointer-sized type on the target platform. Otherwise, the function-typed value is a compile-time value with no runtime representation; it cannot exist at runtime.
Syntax
function_type = function, left_paren, [ expression, { comma, expression } ], right_paren, colon, expression ;
Handle Type
Like all type expressions, this expression produces a value of the type type.
Handle-typed values are wrappers around region-allocated values of the operand's type value.
Assertions
- The operand is a value of the type type which is runtime representable.
Representation
A pointer type on the target platform, which points to the handle's region-allocated value.
Syntax
A handle type is produced by the & unary prefix applied to an expression; see the expression grammar.
Boolean Type
Like all type expressions, this expression produces a value of the type type.
Boolean-typed values represent one of two possible states: true or false.
Representation
Values of the boolean type are represented as exactly 8 bits. The state false is encoded as 0, and the state true is encoded as 1.
Syntax
boolean_type = bool ;
Numeric Types
Like all type expressions, this expression produces a value of the type type.
Numeric-typed values are either integer or floating-point numbers, with a specific representation, precision, and range of representable values dependent on the type itself.
Representation
i8,i16,i32, andi64are signed binary integers in two's complement representation, exactly 8, 16, 32, and 64 bits in size respectively.u8,u16,u32, andu64are unsigned binary integers, exactly 8, 16, 32, and 64 bits in size respectively.f32andf64are IEEE 754 single-precision and double-precision binary floating-point numbers respectively.
Syntax
numeric_type = i8 | i16 | i32 | i64
| u8 | u16 | u32 | u64
| f32 | f64 ;
Slice Type
Like all type expressions, this expression produces a value of the type type.
Also known as a 'slice handle.'
Slice-typed values contain a sequence of values, where each value's type is of the operand's type value.
Assertions
- The operand is a value of the type type which is runtime representable.
Representation
Values of any slice type are represented as:
- a pointer type on the target platform, which points to the slice's first element, and
- a pointer-sized unsigned integer on the target platform, which holds the slice's length.
A slice's elements are contiguously allocated.
Syntax
A slice type is produced by the [] unary prefix applied to an expression; see the expression grammar.
Structure Type
Like all type expressions, this expression produces a value of the type type.
Structure-typed values contain a value for each field declared by the structure type, where each field's type is determined by its declaration.
Each structure declaration introduces a new structure type.
Representation
A value of a structure type consists of a value for each field declared by the structure.
Syntax
Produced by the reference expression when the referenced symbol is a structure type.
Tuple Type
Like all type expressions, this expression produces a value of the type type.
Tuple-typed values contain a value for each element of the tuple type, where each element's type is determined by its corresponding element type.
Representation
A value of a tuple type consists of a value for each element of the tuple type.
Syntax
Produced by a tuple expression when every element of the expression is a value of the type type; such a tuple expression has the type type itself.
Global Declarations
A Larol program is composed of global declarations, each of which is either a
- function,
- structure,
- enumeration,
- namespace, or
- define.
A program is a sequence of top-level global declarations; see the complete grammar.
Names
Each global declaration has a namespace-qualified name, which must be unique; in cases where qualified names are not unique, a fatal error is produced.
Names must be unique between all global declarations, not only declarations of the same kind (function, structure, etc).
Global declarations' names are composed of the unqualified identifier token they were declared with, qualified with the qualified name of any directly enclosing namespace. The identifier token used for a declaration's name is the first identifier token in the declaration, unless otherwise stated.
See namespaces.
Declaration Resolution Order
Global declarations' lexical order is semantically meaningless.
Some global declarations depend on other global declarations: a dependency edge exists from a global declaration D to a global declaration R when resolving D requires R to be fully resolved. A dependency edge exists when:
D's definition contains a raw reference to the type declared byR. A raw reference is any use of a type which requires its layout: structure fields, enumeration variant payloads, function parameter and return types, construction operands, and type positions within a function or define's expression, other than as a handle or slice pointee.D's definition referencesRby value;Ris a define, structure, or enumeration declaration. A reference which resolves to a function value creates no edge, whetherRis a function declaration or a define bound to a function; see the first exception below.
A dependency edge does not exist when:
D's definition referencesRas a function value, whetherRis a function declaration or a define whose value is a function; a reference to a function value requires only its signature, never its completeness.D's definition referencesRonly as a handle or slice pointee; a pointer's layout is independent of its pointee, and only that the pointee is runtime representable is required.D's definition referencesRonly as a namespace or a namespace path element; names are available lexically.
In particular, references to function values, whether to a function declaration or a define bound to a function, and references to handle/slice pointees never create dependency edges, so recursion through function references and through handles or slices is not cyclic.
Global declarations are arranged in a directed acyclic graph (DAG) where vertices represent global declarations and edges point to dependent global declarations. If declarations do not conform to such a graph, a fatal error is produced.
Dependency cycles between global declarations cause a fatal error; compilation cannot continue as the graph must be topologically orderable. For example, a structure directly embedding itself causes a cycle, and must be embedded indirectly through a handle or slice instead.
The global declaration directed acyclic graph is topologically ordered; dependencies are resolved before dependent global declarations.
Symbol Resolution
All global declarations' names resolve to a value whose type is dependent on the declaration kind, when referenced by a reference expression.
Each global declaration's page in this document describes what the declared name will resolve to when referenced (under the 'resultant symbol' subheading).
Syntax
global_declaration = function_declaration | structure_declaration | enumeration_declaration | namespace_declaration | define_declaration ;
Define Declarations
Defines are named global declarations, subject to the behavior described here.
Resultant Symbol
References to define declarations resolve to the exact value of the right side's expression: a value of any kind, including a function value, a type value, a namespace value, and the value of another define.
Syntax
define_declaration = define, identifier, equal, expression ;
Enumeration Declarations
Enumerations are named global declarations, subject to the behavior described here.
Resultant Symbol
References to enumeration declarations resolve to the declared enumeration type value, with the declared variants.
Assertions
- Each variant's payload type expression is a value of the type type.
- Each variant's payload type is runtime representable.
- Each variant's name is unique within the enumeration.
Syntax
enumeration_declaration_variant = identifier, [ colon, expression ], semicolon ;
enumeration_declaration = enum, identifier, left_brace, { enumeration_declaration_variant }, right_brace ;
Function Declarations
Functions are named global declarations, subject to the behavior described here.
Resultant Symbol
References to function declarations resolve to a function value.
Assertions
- Each parameter's type expression is a value of the type type.
- The return type's expression is a value of the type type.
- Each parameter's name is unique within the function.
Syntax
function_declaration_parameter = identifier, colon, expression ;
function_declaration_parameter_list = left_paren, [ function_declaration_parameter, { comma, function_declaration_parameter } ], right_paren ;
function_declaration = function, identifier, function_declaration_parameter_list, colon, expression, expression ;
Namespace Declarations
Namespaces are named global declarations, subject to the behavior described here.
Resultant Symbol
References to namespace declarations resolve to a namespace value: a value of the namespace type, containing the names of all global declarations lexically within the declaration. A namespace value is used as the operand of the static access operator.
Syntax
namespace_declaration = namespace, identifier, left_brace, { global_declaration }, right_brace ;
Structure Declarations
Structures are named global declarations, subject to the behavior described here.
Resultant Symbol
References to structure declarations resolve to the declared structure type value, with the declared fields.
Assertions
- Each field's type expression is a value of the type type.
- Each field's type is runtime representable.
- Each field's name is unique within the structure.
Syntax
structure_declaration_field = identifier, colon, expression, semicolon ;
structure_declaration = structure, identifier, left_brace, { structure_declaration_field }, right_brace ;
Expressions
Expressions specify values, computations, and control flow.
Forms
- Primary
- Binary
- Prefix
- Postfix
- Alloc
- Alloc Slice
- Collect
- Match
- Reference
- Scope
- Conditional Expression
- Tuple
Semantics
Implicit Conversions
Wherever any rule of this specification requires an expression to have some type, an expression whose type differs is accepted when a conversion from its type to the required type is specified on this page. The conversion is applied implicitly; conversions require no notation, and the rules that rely on them need not mention them.
Widening
An expression of type T implicitly converts to U when T widens to U within the same family:
- signed integers:
i8→i16→i32→i64 - unsigned integers:
u8→u16→u32→u64 - floating-point:
f32→f64
Conversions between families are not allowed; in particular, signed and unsigned integers, or integers and floating-point types, cannot be implicitly converted.
Handle Dereference
An expression of handle type &T implicitly converts to T by dereferencing the handle. Conversions compose: an expression of type &&T converts first to &T, and then to T.
Common Type
Two or more types have a common type when they are all identical, or when they are all numeric types within the same family; in the latter case, the common type is the widest of them.
When determining a common type, a candidate type may be replaced by any type it converts to; the replacement is repeated until the types are identical or all numeric within one family, and the resulting type is the common type. Thus, for example, &T and T have the common type T.
Precedence and Associativity
This page defines the precedence and associativity rules that determine how expressions are grouped and evaluated.
Precedence
Expressions bind, tightest first, as follows:
- postfix operations,
- unary prefix operators and the type-forming unary prefixes (
&T,[]T), - multiplicative operators,
- additive operators,
- comparison operators,
- logical conjunction (
&&), - logical disjunction (
||), and - conditional expressions.
Associativity
Binary operators are left-associative: repeated operators at the same precedence level group from left to right.
Conditional expressions are right-associative: nested conditional expressions group from right to left.
Syntax
This section restates the precedence-relevant productions; each production is defined canonically on its own page, and the complete grammar collects them in one place.
conditional_expression = logical_or_expression, [ question_mark, conditional_expression, colon, conditional_expression ] ;
logical_or_expression = logical_and_expression, { logical_disjunction_operator, logical_and_expression } ;
logical_and_expression = comparison_expression, { logical_conjunction_operator, comparison_expression } ;
comparison_expression = additive_expression, { comparison_operator, additive_expression } ;
additive_expression = multiplicative_expression, { additive_operator, multiplicative_expression } ;
multiplicative_expression = unary_expression, { multiplicative_operator, unary_expression } ;
unary_expression = { unary_operator | ampersand | left_bracket, right_bracket }, postfix_expression ;
unary_operator = negation_operator | logical_not_operator | length_operator ;
postfix_expression = primary_expression, { postfix_operation } ;
postfix_operation = call_operation | static_access_operation | dynamic_access_operation | subscript_operation | subslice_operation | construction_operation ;
Binary Expressions
Binary expressions combine two operands with an infix operator.
Forms
Arithmetic Operators
The arithmetic operators compute a numeric value from two operands.
+addition-subtraction*multiplication/division%remainder
Process
At runtime:
- the left operand is evaluated;
- the right operand is evaluated; and
- the operator is applied to both values, and its result is the expression's value.
For integer types, / truncates towards zero and % yields the remainder of truncating division, which has the sign of the dividend. Integer arithmetic wraps on overflow: signed integer types wrap in two's complement, and unsigned integer types wrap modulo 2^N, where N is the type's width. For floating-point types, all operators follow IEEE 754 semantics.
Integer division by zero, or integer remainder by zero, produces a runtime error; see the runtime error model.
Assertions
- Both operands are numeric types within the same family: signed integer, unsigned integer, or floating-point.
- The remainder operator
%requires integer operands. - The operands have a common type, and the result has that type.
- When the surrounding context requires a type within the operator's applicable family, each operand's expected type is that required type.
Syntax
additive_operator = plus | minus ;
multiplicative_operator = star | solidus | percent ;
Comparison Operators
The comparison operators compare two values and yield a bool.
==equality!=inequality<less than<=less than or equal>greater than>=greater than or equal
Process
At runtime:
- the left operand is evaluated;
- the right operand is evaluated; and
- the operator is applied to both values, and its result is the expression's value.
The ordering operators <, <=, >, and >= compare their operands numerically; for floating-point operands, comparisons follow IEEE 754 semantics.
The equality operators == and != apply equality: two values are equal when they have the same type and
- for numeric types, their values are numerically equal; for integer types this means the same bit pattern, and for floating-point types comparisons follow IEEE 754 semantics;
- for
bool, they are the same state; - for handles, their handle values are equal: both point to the same region-allocated storage;
- for structures, their fields are equal in order;
- for tuples, their elements are equal in order;
- for enumerations, they denote the same variant and their payloads are equal; and
- for slices, their representations are equal: the same length and the same address of the first element.
Slice equality is identity-based: a slice's representation (length and first-element address) is compared, not its elements.
Assertions
- The operands have a common type, to which each operand is implicitly converted for comparison.
- The ordering operators
<,<=,>, and>=require numeric operands within the same family: signed integer, unsigned integer, or floating-point. - The equality operators
==and!=require that, once converted to their common type, the operands are numeric types within the same family, or identical types amongbool, handle, structure, tuple, enumeration, and slice.
Syntax
comparison_operator = equal_equal | bang_equal | less_than | less_than_equal
| greater_than | greater_than_equal ;
Logical Operators
The logical operators combine two bool values into a bool.
&&logical conjunction||logical disjunction
Process
At runtime:
- the left operand is evaluated;
- if its value already determines the result, that value is the expression's value, and nothing further is evaluated; and
- otherwise, the right operand is evaluated, and its value is the expression's value.
Assertions
- Both operands have type
bool, and the result has typebool.
Syntax
logical_conjunction_operator = ampersand_ampersand ;
logical_disjunction_operator = vertical_bar_vertical_bar ;
Prefix Expressions
Prefix expressions apply an operator before their operand.
Forms
Negation
The negation operator denotes the arithmetic negation of its operand.
Process
At runtime:
- the operand is evaluated; and
- its arithmetic negation is computed, and is the negation's value.
For signed integer types, negation is two's complement negation; negating the minimum value of a signed integer type wraps to itself. For floating-point types, negation follows IEEE 754 semantics.
Negative literal values are formed by applying this operator to a literal; see integer literals.
Assertions
- The operand's type is a signed integer type or a floating-point type, and the negation's result has that type.
- When an expected type exists, the operand's expected type is that type.
Syntax
negation_operator = minus ;
Logical Not
The logical not operator denotes the logical negation of its operand.
Process
At runtime:
- the operand is evaluated; and
- the state
truemaps tofalse, the statefalsemaps totrue, and the resulting state is the not's value.
Assertions
- The operand's type is
bool, and the not's result has typebool.
Syntax
logical_not_operator = bang ;
Length
The length operator denotes the number of elements in its slice operand.
Process
At runtime:
- the operand is evaluated; and
- the number of elements in the slice is computed, and is the length's value.
Assertions
- The operand's type is a slice type, and the length's result has type
u64.
Syntax
length_operator = number_sign ;
Postfix Expressions
Postfix expressions apply an operator after their operand.
Forms
Call
A call expression applies a function value to its arguments.
Process
At runtime:
- the callee is evaluated;
- the arguments are evaluated, left to right; and
- the callee is applied to the arguments' values, and its result is the call's value.
Assertions
- The callee has a function type.
- The number of arguments equals the number of parameters.
- Each argument has its parameter's type, and its expected type is the parameter's type.
A call whose callee has no runtime representation occurs at compile time.
Syntax
call_operation = left_paren, [ expression, { comma, expression } ], right_paren ;
Construction
The construction postfix operation constructs a new value of a structure or enumeration type from its arguments.
Process
At runtime:
- the arguments are evaluated, left to right; and
- a value of the operand's structure type, or of the enumeration type of the referenced enumeration variant, is constructed from them.
Assertions
- The operand is either:
- a value of the type type denoting a structure type; or
- a reference to an enumeration variant, qualified through the enumeration's lookup scope.
- Structure construction arguments are positional: there is exactly one argument per declared field, in declared order.
- An enumeration construction has exactly one argument, the referenced variant's declared payload value; a variant with no declared payload has zero arguments.
- Each argument has the type of its corresponding field or payload, and its expected type is that type.
Syntax
construction_operation = left_brace, [ expression, { comma, expression } ], right_brace ;
Dynamic Access
The dynamic access postfix operation accesses a named field within a structure.
Process
At runtime:
- the operand is evaluated; and
- the provided identifier is resolved as a member of the operand; and
- the result is the resolved member.
Enumeration variant payloads are not accessed dynamically; they are extracted by match expressions.
Assertions
- The operand is of a structure type.
- The accessed name is a field declared by that structure type.
Syntax
dynamic_access_operation = dot, identifier ;
Static Access
The static access postfix operation accesses a named symbol within a namespace, or a variant within an enumeration.
Process
At compile time:
- the operand is resolved;
- the provided identifier is resolved as a member of the operand's namespace, or as a variant of the operand's enumeration; and
- the result is the resolved member.
Assertions
- The operand is a value of the
namespacetype, or a value of thetypetype which denotes an enumeration type. - The provided identifier matches a member of that namespace, or a variant of that enumeration.
Syntax
static_access_operation = colon_colon, identifier ;
Subscript
The subscript postfix operation indexes into a slice or tuple value, returning the resulting value.
Process
At runtime:
- the operand is evaluated;
- the index expression is evaluated; and
- the element at the index is the subscript's result.
For slices, an index outside the slice's range is a runtime error; the compiler emits runtime logic to detect it, unless it can be proven that the index is within range. A negative index value is out of range.
A compile-time constant is an expression composed of integer literals, parenthesized expressions, and the arithmetic and negation operators, which the compiler evaluates at compile time. Arithmetic within a compile-time constant wraps as it would at runtime; a compile-time constant whose evaluation divides or takes a remainder by zero is a compile-time error.
See the runtime error model on the architecture page.
Assertions
- The operand's type is a slice type or a tuple type.
- The index expression has an integer type, signed or unsigned.
- For tuple operands, the index is a compile-time constant within the tuple's arity, and the subscript has the indexed element's declared type; a constant outside the tuple's arity is a compile-time error.
- For slice operands, the subscript has the slice's element type.
Syntax
subscript_operation = left_bracket, expression, right_bracket ;
Subslice
The subslice postfix operation indexes a range of a slice value, returning a new slice spanning that range. The start index is inclusive; the end index is exclusive.
Process
At runtime:
- the operand is evaluated;
- the start index expression is evaluated;
- the end index expression is evaluated; and
- the slice of the operand's elements from the start index, inclusive, up to the end index, exclusive, is the subslice's result.
For a start or end index outside the slice's range, or a start index greater than the end index, is a runtime error; the compiler emits runtime logic to detect it, unless it can be proven that the indices are within range and the start index does not exceed the end index. A negative index value is always out of range.
See the runtime error model on the architecture page.
Assertions
- The operand's type is a slice type.
- Each index expression has an integer type, signed or unsigned.
- The subslice has the operand's slice type.
Syntax
subslice_operation = left_bracket, expression, comma, expression, right_bracket ;
Alloc Expression
An alloc expression allocates storage for a value and yields a handle to it.
An alloc expression has the handle type &T, where T is the initializer expression's type.
Process
At runtime:
- enough space for a value of the initializer expression's type is allocated, see region allocation;
- the initializer expression is evaluated;
- its result is stored in the allocated space; and
- a handle to the newly allocated storage is returned.
Assertions
- The initializer expression's type is runtime representable.
Syntax
alloc_handle_expression = alloc, unary_expression ;
Alloc Slice Expression
An alloc slice expression allocates storage for a specified number of values and yields a slice to them.
An alloc slice expression has the slice type whose element type is the initializer function's return type.
Process
At runtime:
- the first expression is evaluated to determine the number of values to allocate;
- enough space for that number of values of the initializer function's return type is allocated, see region allocation;
- the second expression (the initializer function) is evaluated once for each element of the allocation, in order;
- if the initializer function accepts a
u64parameter, the zero-based index of the element is provided as its argument; - each resulting value is stored in its corresponding element of the allocated space; and
- a slice to the allocated storage is returned.
Assertions
- The first expression has type
u64, and its expected type is that type. - The second expression has function type
function(): Torfunction(u64): Tfor some typeT. - The initializer function's return type is runtime representable.
Syntax
alloc_slice_expression = alloc, left_bracket, expression, right_bracket, unary_expression ;
Collect Expression
A collect expression builds a slice by repeatedly applying a function to a state and appending each resulting value to the slice.
A collect expression has the slice type whose element type is the first element of the function's return type.
Process
At runtime:
- the initial state expression—the expression following
over—is evaluated; - the function operand—the first expression—is evaluated;
- the function is applied to the current state;
- the resulting three-element tuple is decomposed into a result value, a next state, and a
bool; - the result value is appended to the slice;
- if the
boolisfalse, the iteration ends and the slice is returned; and - otherwise, the next state becomes the current state, and the process repeats.
The slice is allocated one element at a time, with each element constructed in place immediately following the preceding element (allocated contiguously); see region allocation.
Assertions
- The function operand has a function type with a single parameter whose type is the initial state's type.
- The function's return type is a three-element tuple type.
- The second element of the function's return type is the initial state's type.
- The third element of the function's return type is
bool. - The first element of the function's return type is runtime representable.
- The initial state expression's type is runtime representable.
Syntax
collect_expression = collect, unary_expression, over, unary_expression ;
Match Expression
A match expression dispatches on a value of enumeration type: the value selects an arm, and the selected arm's function is applied on the variant's payload to produce a result.
Process
At runtime:
- the scrutinee expression is evaluated;
- the variant denoted by its value selects the arm whose pattern references that variant; and
- the selected arm's function is called with the variant's payload as its argument, and its result is the match's value.
Assertions
- The scrutinee is a value of an enumeration type.
- Each arm's pattern references a distinct variant of that enumeration type.
- Every variant of that enumeration type is referenced by exactly one arm.
- Each arm's body is a value of a function type whose parameters match the referenced variant's payload: a payload-less variant requires a function with no parameters; otherwise, a single parameter of the variant's payload type is required.
- The arms' functions' return types have a common type, the selected arm's return value is implicitly converted to that type, and the match expression has that type.
Syntax
match_pattern = symbol_reference, { static_access_operation } ;
match_arm = match_pattern, colon, expression, semicolon ;
match_expression = match, left_paren, expression, right_paren, left_brace, { match_arm }, right_brace ;
Primary Expressions
Primary expressions are the base of the expression grammar; all other expression forms are constructed from them.
Integer Literals
Integer literals are non-negative decimal or hexadecimal values. Hexadecimal literals use the 0x prefix. Underscores may separate digit groups.
A surrounding rule which requires a specific type of an expression gives that type as the expression's expected type. Each expression form's page states any expected type it gives to its sub-expressions.
An integer type admits a literal if its range contains the literal's value. A floating-point type admits an integer literal if it exactly represents its value.
The literal's type is determined as follows:
- If the literal has an expected type which admits it, it has that type.
- If the literal is immediately (possibly through parentheses) the operand of a negation, it is typed by its negated value. The negated expression has the negation's expected type if one exists which admits the negated value; otherwise, it has the smallest signed integer type which admits it.
- If the literal is an operand of an arithmetic or comparison expression whose other operand is a non-literal numeric value, it has the smallest type in that operand's family which admits it. For signed integers, the types are
i8,i16,i32,i64; for unsigned integers, the types areu8,u16,u32,u64; for floating-point values, the literal has the other operand's type when that type admits it. - Otherwise, the literal has type
i64.
A compile-time error occurs if no applicable type admits the literal's value.
Rule 2 allows the minimum value of a signed type to be written: -2147483648 is admitted by i32, and negating i32's minimum wraps to the minimum. Rule 1 also applies through the negation's expected type, so -5 is admitted when its expected type is i64.
Float Literals
Float literals are non-negative decimal values consisting of a fraction with an optional exponent. Underscores may separate digit groups.
A float literal has type f64, or f32 when f32 is expected. A float literal that is an operand of an arithmetic or comparison expression whose other operand is a non-literal f32 value has type f32 when f32 admits it. When an expected type exists and is neither f32 nor f64, a compile-time error is produced.
Boolean Literals
The keywords true and false denote the corresponding values of bool.
String Literals
String literals are byte sequences enclosed in quotation marks. Escape sequences represent individual bytes:
\n— line feed\r— carriage return\t— character tabulation\\— reverse solidus\"— quotation mark\xHH— byte with hexadecimal valueHH
A string literal has type []u8 and denotes a slice over statically allocated storage; no allocation is required.
Whether two string literals with identical contents denote the same static storage is implementation-defined; consequently, equality of two string-literal slices is implementation-defined.
Character Literals
Character literals are single bytes enclosed in apostrophes. Their escape sequences are the same as those of string literals, with \' denoting an apostrophe:
\n— line feed\r— carriage return\t— character tabulation\\— reverse solidus\'— apostrophe\xHH— byte with hexadecimal valueHH
A character literal has type u8.
Parenthesized Expressions
A parenthesized expression is an expression enclosed in parentheses. It has the same type and value as the enclosed expression.
Syntax
primary_expression = integer_literal
| float_literal
| boolean_literal
| string_literal
| char_literal
| symbol_reference
| parenthesized_expression
| tuple_expression
| numeric_type
| boolean_type
| namespace_type
| type_type
| function_type
| match_expression
| scope_expression
| alloc_expression
| collect_expression ;
parenthesized_expression = left_paren, expression, right_paren ;
alloc_expression = alloc_handle_expression | alloc_slice_expression ;
Symbol References
A symbol reference names a binding or a global declaration by its name.
Name Resolution
The name of a symbol reference resolves as follows:
- the bindings visible in the innermost enclosing scope expression are searched first, then those of each enclosing scope expression in turn, and then the parameters of the enclosing function, if any;
- the lexically enclosing namespaces are searched in turn, innermost first;
- if still no declaration is found, the global scope is searched;
- if no declaration is found, a compile-time error is produced.
Qualified Names
A reference may continue with further segments, each a static access operation; the resolution of such chains is specified on that page.
The final segment's symbol must be usable in the position of the reference: a value in expression positions, a value of the type type in type positions or as the operand of a construction operation when it denotes a structure type, or a variant in construction operations and match patterns.
Structure fields are not resolved by reference lookup; a field is accessed dynamically, on a structure value; see dynamic access.
Syntax
symbol_reference = identifier ;
Scope Expression
A scope expression is a brace-enclosed sequence of let bindings followed by a final expression; the final expression's value is the scope's value.
Process
At runtime:
- the
letbindings are evaluated in order, each binding its name to its expression's value; and - the final expression is evaluated, and its value is the scope's value.
Bindings
Each binding has the type of its expression, and is visible from its point of definition through the end of the enclosing scope, shadowing bindings of enclosing scopes as well as namespace and global declarations; see the reference resolution order. A binding may hold a value of any type, including a type or namespace value, which may then be used in type positions or as the operand of static access.
In expression position a leading left_brace is unambiguous: a construction requires a preceding operand.
Assertions
- No name is bound twice within a single scope.
- Every binding's expression, the final expression, and every reference to a binding resolve.
No sub-expression of a scope expression has an expected type.
Syntax
let_binding = let, identifier, equal, expression, semicolon ;
scope_expression = left_brace, { let_binding }, expression, right_brace ;
Conditional Expression
A conditional expression selects between two branch expressions based on a condition.
Process
At runtime:
- the condition is evaluated;
- if it yields
true, the then-expression is evaluated, and its value is the conditional's value; and - otherwise, the else-expression is evaluated, and its value is the conditional's value.
Only the selected branch is evaluated.
Assertions
- The condition's type is
bool. - The branches have a common type; the conditional expression has that type.
- When an expected type exists, each branch's expected type is that type.
Syntax
conditional_expression = logical_or_expression, [ question_mark, conditional_expression, colon, conditional_expression ] ;
Tuple Expression
A tuple expression constructs a tuple value from element expressions.
A tuple expression contains at least two element expressions. An expression enclosed in parentheses without element separators is a parenthesized expression, which has the same value and type as its enclosed expression; the two forms are unambiguous, since a comma separates tuple elements only at the outermost level of the enclosing parentheses.
Process
At runtime:
- the element expressions are evaluated, left to right; and
- a tuple value of their values, in order, is constructed.
Assertions
- Each element has a type, and the tuple's type is the tuple type whose element types are those types.
- When an expected tuple type exists, each element's expected type is the corresponding element of the expected tuple type.
- A tuple expression whose element values are all of the type type has the
typetype itself: its value is the tuple type of those element types.
Syntax
tuple_expression = left_paren, expression, comma, expression, { comma, expression }, right_paren ;
Complete Grammar
This page collects the complete grammar of the language in one place. Every production below is defined canonically on the page indicated; those pages remain authoritative, and this page is a duplicate reference. The lexical productions are defined on the lexical structure page.
Lexical Helper Productions
See lexical structure.
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G"
| "H" | "I" | "J" | "K" | "L" | "M" | "N"
| "O" | "P" | "Q" | "R" | "S" | "T" | "U"
| "V" | "W" | "X" | "Y" | "Z"
| "a" | "b" | "c" | "d" | "e" | "f" | "g"
| "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u"
| "v" | "w" | "x" | "y" | "z" ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
hex_digit = digit | "A" | "B" | "C" | "D" | "E" | "F"
| "a" | "b" | "c" | "d" | "e" | "f" ;
character = ? any ASCII source character ? ;
quotation_mark = '"' ;
apostrophe = "'" ;
reverse_solidus = "\" ;
escape_sequence = reverse_solidus, ( "n" | "r" | "t" | reverse_solidus | quotation_mark
| "x", hex_digit, hex_digit ) ;
char_escape_sequence = reverse_solidus, ( "n" | "r" | "t" | reverse_solidus | apostrophe
| "x", hex_digit, hex_digit ) ;
string_character = character - ( quotation_mark | reverse_solidus | ? line feed ? | ? carriage return ? ) ;
char_character = character - ( apostrophe | reverse_solidus | ? line feed ? | ? carriage return ? ) ;
exponent_part = "e", [ "-" ], digit, { [ "_" ], digit } ;
Lexical Token Productions
See lexical structure.
identifier = ( letter | "_" ), { letter | digit | "_" } ;
(* Literals *)
integer_literal = digit, { [ "_" ], digit }
| "0x", hex_digit, { [ "_" ], hex_digit } ;
float_literal = digit, { [ "_" ], digit }, ".", digit, { [ "_" ], digit }, [ exponent_part ] ;
boolean_literal = true | false ;
string_literal = quotation_mark, { string_character | escape_sequence }, quotation_mark ;
char_literal = apostrophe, ( char_character | char_escape_sequence ), apostrophe ;
(* Reserved Keywords *)
alloc = "alloc" ;
collect = "collect" ;
i8 = "i8" ;
i16 = "i16" ;
i32 = "i32" ;
i64 = "i64" ;
u8 = "u8" ;
u16 = "u16" ;
u32 = "u32" ;
u64 = "u64" ;
f32 = "f32" ;
f64 = "f64" ;
bool = "bool" ;
function = "function" ;
enum = "enum" ;
namespace = "namespace" ;
over = "over" ;
structure = "structure" ;
define = "define" ;
let = "let" ;
match = "match" ;
false = "false" ;
true = "true" ;
type = "type" ;
(* Symbols *)
ampersand = "&" ;
ampersand_ampersand = "&&" ;
bang = "!" ;
bang_equal = "!=" ;
colon = ":" ;
colon_colon = "::" ;
comma = "," ;
dot = "." ;
equal = "=" ;
equal_equal = "==" ;
greater_than = ">" ;
greater_than_equal = ">=" ;
left_brace = "{" ;
left_bracket = "[" ;
left_paren = "(" ;
less_than = "<" ;
less_than_equal = "<=" ;
minus = "-" ;
number_sign = "#" ;
percent = "%" ;
plus = "+" ;
question_mark = "?" ;
right_brace = "}" ;
right_bracket = "]" ;
right_paren = ")" ;
semicolon = ";" ;
solidus = "/" ;
star = "*" ;
vertical_bar_vertical_bar = "||" ;
Type Grammar
See types and the pages defining each type expression form: type, namespace, boolean, numeric, function, tuple, structure, and enumeration. A tuple type is a tuple expression, the names of structure and enumeration declarations are symbol references, and handle and slice types are formed by the & and [] unary prefixes in the expression grammar.
type_type = type ;
namespace_type = namespace ;
boolean_type = bool ;
numeric_type = i8 | i16 | i32 | i64
| u8 | u16 | u32 | u64
| f32 | f64 ;
function_type = function, left_paren, [ expression, { comma, expression } ], right_paren, colon, expression ;
Global Declaration Grammar
See global declarations and the pages defining each declaration kind (functions, structures, enumerations, namespaces, defines).
program = { global_declaration } ;
global_declaration = function_declaration | structure_declaration | enumeration_declaration | namespace_declaration | define_declaration ;
function_declaration_parameter = identifier, colon, expression ;
function_declaration_parameter_list = left_paren, [ function_declaration_parameter, { comma, function_declaration_parameter } ], right_paren ;
function_declaration = function, identifier, function_declaration_parameter_list, colon, expression, expression ;
structure_declaration_field = identifier, colon, expression, semicolon ;
structure_declaration = structure, identifier, left_brace, { structure_declaration_field }, right_brace ;
enumeration_declaration_variant = identifier, [ colon, expression ], semicolon ;
enumeration_declaration = enum, identifier, left_brace, { enumeration_declaration_variant }, right_brace ;
namespace_declaration = namespace, identifier, left_brace, { global_declaration }, right_brace ;
define_declaration = define, identifier, equal, expression ;
Expression Grammar
The operators are defined on the arithmetic, comparison, and logical pages, the prefix forms on the negation, logical not, length, handle, and slice pages, the primary forms on the primary expressions page and the pages linked there, and the postfix operations on their pages (call, static access, dynamic access, subscript, subslice, construction).
expression = conditional_expression ;
conditional_expression = logical_or_expression, [ question_mark, conditional_expression, colon, conditional_expression ] ;
logical_or_expression = logical_and_expression, { logical_disjunction_operator, logical_and_expression } ;
logical_and_expression = comparison_expression, { logical_conjunction_operator, comparison_expression } ;
comparison_expression = additive_expression, { comparison_operator, additive_expression } ;
additive_expression = multiplicative_expression, { additive_operator, multiplicative_expression } ;
multiplicative_expression = unary_expression, { multiplicative_operator, unary_expression } ;
unary_expression = { unary_operator | ampersand | left_bracket, right_bracket }, postfix_expression ;
unary_operator = negation_operator | logical_not_operator | length_operator ;
negation_operator = minus ;
logical_not_operator = bang ;
length_operator = number_sign ;
postfix_expression = primary_expression, { postfix_operation } ;
postfix_operation = call_operation | static_access_operation | dynamic_access_operation | subscript_operation | subslice_operation | construction_operation ;
call_operation = left_paren, [ expression, { comma, expression } ], right_paren ;
static_access_operation = colon_colon, identifier ;
dynamic_access_operation = dot, identifier ;
subscript_operation = left_bracket, expression, right_bracket ;
subslice_operation = left_bracket, expression, comma, expression, right_bracket ;
construction_operation = left_brace, [ expression, { comma, expression } ], right_brace ;
primary_expression = integer_literal | float_literal | boolean_literal | string_literal | char_literal | symbol_reference | parenthesized_expression | tuple_expression | numeric_type | boolean_type | namespace_type | type_type | function_type | match_expression | scope_expression | alloc_expression | collect_expression ;
parenthesized_expression = left_paren, expression, right_paren ;
tuple_expression = left_paren, expression, comma, expression, { comma, expression }, right_paren ;
symbol_reference = identifier ;
match_pattern = symbol_reference, { static_access_operation } ;
match_arm = match_pattern, colon, expression, semicolon ;
match_expression = match, left_paren, expression, right_paren, left_brace, { match_arm }, right_brace ;
let_binding = let, identifier, equal, expression, semicolon ;
scope_expression = left_brace, { let_binding }, expression, right_brace ;
alloc_expression = alloc_handle_expression | alloc_slice_expression ;
alloc_handle_expression = alloc, unary_expression ;
alloc_slice_expression = alloc, left_bracket, expression, right_bracket, unary_expression ;
collect_expression = collect, unary_expression, over, unary_expression ;
additive_operator = plus | minus ;
multiplicative_operator = star | solidus | percent ;
comparison_operator = equal_equal | bang_equal | less_than | less_than_equal
| greater_than | greater_than_equal ;
logical_conjunction_operator = ampersand_ampersand ;
logical_disjunction_operator = vertical_bar_vertical_bar ;
Architecture
This section describes language and runtime behaviors that affect program execution.
Topics
Runtime Errors
This page specifies the runtime error model of the language.
A runtime error terminates evaluation of the program with an implementation-defined diagnosis. No further evaluation of the program occurs.
The runtime error conditions defined by the language are:
- integer division or remainder by zero,
- an out-of-range slice subscript, and
- an out-of-range or inverted slice subslice.
The compiler inserts a runtime check at every expression which may produce a runtime error, unless it can prove that the error cannot occur.
Region Allocation
Allocation is implementation-defined: the mechanism by which memory is obtained and managed is left to the implementation. The placement guarantees stated elsewhere in this specification (for example, that a slice's elements are contiguously allocated) remain normative.
Entrypoint
How a program begins executing is implementation-defined; an implementation may define one or more entrypoints, and may impose additional requirements on them (for example, that an entrypoint is a function with a specific signature).
This specification does not constrain the entrypoint.