Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

  1. Italicized text is informative and does not impose requirements on an implementation.
  2. Any behavior or rule explicitly described as optional may be omitted by an implementation.
  3. 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.

  1. Character Tabulation,
  2. Line Feed,
  3. Line Tabulation,
  4. Form Feed,
  5. Carriage Return,
  6. 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:

  1. tokens with a longer total length (in characters) are given higher precedence;
  2. all other tokens are given higher precedence than the identifier token.

Additional disambiguation rules should not be required for any inputs.

Unexpected Tokens

Any source character which:

  1. is not discarded, and
  2. 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:

  1. the built-in types: boolean, numeric, type, and namespace,
  2. the function type form,
  3. the handle type form,
  4. the slice type form,
  5. a tuple expression whose elements are all type values,
  6. a reference to a structure or enumeration declaration,
  7. a reference to a define which is bound to a type value, and
  8. 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

  1. An enumeration declares at most 256 variants.

Representation

Values of an enumeration type are represented as:

  1. a u8 tag distinguishing the variant; and
  2. 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

  1. Each parameter's type expression is a value of the type type.
  2. 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

  1. 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

  1. i8, i16, i32, and i64 are signed binary integers in two's complement representation, exactly 8, 16, 32, and 64 bits in size respectively.
  2. u8, u16, u32, and u64 are unsigned binary integers, exactly 8, 16, 32, and 64 bits in size respectively.
  3. f32 and f64 are 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

  1. The operand is a value of the type type which is runtime representable.

Representation

Values of any slice type are represented as:

  1. a pointer type on the target platform, which points to the slice's first element, and
  2. 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

  1. function,
  2. structure,
  3. enumeration,
  4. namespace, or
  5. 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:

  1. D's definition contains a raw reference to the type declared by R. 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.
  2. D's definition references R by value; R is a define, structure, or enumeration declaration. A reference which resolves to a function value creates no edge, whether R is a function declaration or a define bound to a function; see the first exception below.

A dependency edge does not exist when:

  1. D's definition references R as a function value, whether R is a function declaration or a define whose value is a function; a reference to a function value requires only its signature, never its completeness.
  2. D's definition references R only 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.
  3. D's definition references R only 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

  1. Each variant's payload type expression is a value of the type type.
  2. Each variant's payload type is runtime representable.
  3. 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

  1. Each parameter's type expression is a value of the type type.
  2. The return type's expression is a value of the type type.
  3. 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

  1. Each field's type expression is a value of the type type.
  2. Each field's type is runtime representable.
  3. 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

  1. Primary
  2. Binary
  3. Prefix
  4. Postfix
  5. Alloc
  6. Alloc Slice
  7. Collect
  8. Match
  9. Reference
  10. Scope
  11. Conditional Expression
  12. Tuple

Semantics

  1. Implicit Conversions
  2. Precedence and Associativity

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:

  1. postfix operations,
  2. unary prefix operators and the type-forming unary prefixes (&T, []T),
  3. multiplicative operators,
  4. additive operators,
  5. comparison operators,
  6. logical conjunction (&&),
  7. logical disjunction (||), and
  8. 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

  1. Arithmetic
  2. Comparison
  3. Logical

Arithmetic Operators

The arithmetic operators compute a numeric value from two operands.

  • + addition
  • - subtraction
  • * multiplication
  • / division
  • % remainder

Process

At runtime:

  1. the left operand is evaluated;
  2. the right operand is evaluated; and
  3. 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

  1. Both operands are numeric types within the same family: signed integer, unsigned integer, or floating-point.
  2. The remainder operator % requires integer operands.
  3. The operands have a common type, and the result has that type.
  4. 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:

  1. the left operand is evaluated;
  2. the right operand is evaluated; and
  3. 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

  1. The operands have a common type, to which each operand is implicitly converted for comparison.
  2. The ordering operators <, <=, >, and >= require numeric operands within the same family: signed integer, unsigned integer, or floating-point.
  3. The equality operators == and != require that, once converted to their common type, the operands are numeric types within the same family, or identical types among bool, 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:

  1. the left operand is evaluated;
  2. if its value already determines the result, that value is the expression's value, and nothing further is evaluated; and
  3. otherwise, the right operand is evaluated, and its value is the expression's value.

Assertions

  1. Both operands have type bool, and the result has type bool.

Syntax

logical_conjunction_operator = ampersand_ampersand ;

logical_disjunction_operator = vertical_bar_vertical_bar ;

Prefix Expressions

Prefix expressions apply an operator before their operand.

Forms

  1. Negation
  2. Logical Not
  3. Length

Negation

The negation operator denotes the arithmetic negation of its operand.

Process

At runtime:

  1. the operand is evaluated; and
  2. 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

  1. The operand's type is a signed integer type or a floating-point type, and the negation's result has that type.
  2. 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:

  1. the operand is evaluated; and
  2. the state true maps to false, the state false maps to true, and the resulting state is the not's value.

Assertions

  1. The operand's type is bool, and the not's result has type bool.

Syntax

logical_not_operator = bang ;

Length

The length operator denotes the number of elements in its slice operand.

Process

At runtime:

  1. the operand is evaluated; and
  2. the number of elements in the slice is computed, and is the length's value.

Assertions

  1. 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

  1. Call
  2. Construction
  3. Dynamic Access
  4. Static Access
  5. Subscript
  6. Subslice

Call

A call expression applies a function value to its arguments.

Process

At runtime:

  1. the callee is evaluated;
  2. the arguments are evaluated, left to right; and
  3. the callee is applied to the arguments' values, and its result is the call's value.

Assertions

  1. The callee has a function type.
  2. The number of arguments equals the number of parameters.
  3. 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:

  1. the arguments are evaluated, left to right; and
  2. a value of the operand's structure type, or of the enumeration type of the referenced enumeration variant, is constructed from them.

Assertions

  1. The operand is either:
  2. Structure construction arguments are positional: there is exactly one argument per declared field, in declared order.
  3. An enumeration construction has exactly one argument, the referenced variant's declared payload value; a variant with no declared payload has zero arguments.
  4. 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:

  1. the operand is evaluated; and
  2. the provided identifier is resolved as a member of the operand; and
  3. the result is the resolved member.

Enumeration variant payloads are not accessed dynamically; they are extracted by match expressions.

Assertions

  1. The operand is of a structure type.
  2. 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:

  1. the operand is resolved;
  2. the provided identifier is resolved as a member of the operand's namespace, or as a variant of the operand's enumeration; and
  3. the result is the resolved member.

Assertions

  1. The operand is a value of the namespace type, or a value of the type type which denotes an enumeration type.
  2. 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:

  1. the operand is evaluated;
  2. the index expression is evaluated; and
  3. 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

  1. The operand's type is a slice type or a tuple type.
  2. The index expression has an integer type, signed or unsigned.
  3. 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.
  4. 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:

  1. the operand is evaluated;
  2. the start index expression is evaluated;
  3. the end index expression is evaluated; and
  4. 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

  1. The operand's type is a slice type.
  2. Each index expression has an integer type, signed or unsigned.
  3. 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:

  1. enough space for a value of the initializer expression's type is allocated, see region allocation;
  2. the initializer expression is evaluated;
  3. its result is stored in the allocated space; and
  4. a handle to the newly allocated storage is returned.

Assertions

  1. 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:

  1. the first expression is evaluated to determine the number of values to allocate;
  2. enough space for that number of values of the initializer function's return type is allocated, see region allocation;
  3. the second expression (the initializer function) is evaluated once for each element of the allocation, in order;
  4. if the initializer function accepts a u64 parameter, the zero-based index of the element is provided as its argument;
  5. each resulting value is stored in its corresponding element of the allocated space; and
  6. a slice to the allocated storage is returned.

Assertions

  1. The first expression has type u64, and its expected type is that type.
  2. The second expression has function type function(): T or function(u64): T for some type T.
  3. 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:

  1. the initial state expression—the expression following over—is evaluated;
  2. the function operand—the first expression—is evaluated;
  3. the function is applied to the current state;
  4. the resulting three-element tuple is decomposed into a result value, a next state, and a bool;
  5. the result value is appended to the slice;
  6. if the bool is false, the iteration ends and the slice is returned; and
  7. 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

  1. The function operand has a function type with a single parameter whose type is the initial state's type.
  2. The function's return type is a three-element tuple type.
  3. The second element of the function's return type is the initial state's type.
  4. The third element of the function's return type is bool.
  5. The first element of the function's return type is runtime representable.
  6. 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:

  1. the scrutinee expression is evaluated;
  2. the variant denoted by its value selects the arm whose pattern references that variant; and
  3. the selected arm's function is called with the variant's payload as its argument, and its result is the match's value.

Assertions

  1. The scrutinee is a value of an enumeration type.
  2. Each arm's pattern references a distinct variant of that enumeration type.
  3. Every variant of that enumeration type is referenced by exactly one arm.
  4. 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.
  5. 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:

  1. If the literal has an expected type which admits it, it has that type.
  2. 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.
  3. 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 are u8, u16, u32, u64; for floating-point values, the literal has the other operand's type when that type admits it.
  4. 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 value HH

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 value HH

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:

  1. 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;
  2. the lexically enclosing namespaces are searched in turn, innermost first;
  3. if still no declaration is found, the global scope is searched;
  4. 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:

  1. the let bindings are evaluated in order, each binding its name to its expression's value; and
  2. 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

  1. No name is bound twice within a single scope.
  2. 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:

  1. the condition is evaluated;
  2. if it yields true, the then-expression is evaluated, and its value is the conditional's value; and
  3. otherwise, the else-expression is evaluated, and its value is the conditional's value.

Only the selected branch is evaluated.

Assertions

  1. The condition's type is bool.
  2. The branches have a common type; the conditional expression has that type.
  3. 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:

  1. the element expressions are evaluated, left to right; and
  2. a tuple value of their values, in order, is constructed.

Assertions

  1. Each element has a type, and the tuple's type is the tuple type whose element types are those types.
  2. When an expected tuple type exists, each element's expected type is the corresponding element of the expected tuple type.
  3. A tuple expression whose element values are all of the type type has the type type 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

  1. Runtime Errors
  2. Region Allocation
  3. Entrypoint

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:

  1. integer division or remainder by zero,
  2. an out-of-range slice subscript, and
  3. 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.