# FlexLang > A language for declaring the data that system components exchange and the protocols that govern those exchanges. Flex is open and non-proprietary, and is maintained independently of any vendor implementation. ## Grammar The concrete syntax of Flex, in EBNF. ```ebnf Root = ModuleDecl ImportDecl* Decl* ModuleDecl = 'module' IDENT* ImportDecl = 'import' IDENT* (ImportAlias | ImportElementList) Decl = AnnotationDecl | ConstDecl | EnumDecl | FunctionDecl | TransformDecl | ContractDecl | StructDecl | NewtypeDecl | VariantDecl | TypeAliasDecl | ComponentDecl | ConnectionDecl | GroupDecl | SystemDecl | GlobalProtocolDecl | LocalProtocolDecl ImportAlias = 'as' Name ImportElementList = '(' ImportElement* ')' Name = IDENT ImportElement = IDENT ('=>' Name)? AnnotationDecl = AnnotationUse* 'annotation' Name AnnotationParamList? AnnotationScopeList? ConstDecl = AnnotationUse* 'const' Name ':' Type '=' Expr ';' EnumDecl = AnnotationUse* 'enum' Name PrimitiveType EnumMember* FunctionDecl = AnnotationUse* 'function' Name FunctionParam* '->' Type (Expr | BlockExpr) TransformDecl = AnnotationUse* 'transform' Name TransformParam* '->' Type ContextParam* (Expr | BlockExpr) ContractDecl = AnnotationUse* 'contract' Name ContractParam* ContractBody StructDecl = AnnotationUse* 'abstract'? 'extensible'? 'message'? 'struct' Name ('extends' NamedType)? StructField* StructAssertion* NewtypeDecl = AnnotationUse* 'newtype' Name NewtypeField StructAssertion* VariantDecl = AnnotationUse* 'message'? 'variant' Name VariantConstructor* TypeAliasDecl = AnnotationUse* 'type' Name '=' Type ';' ComponentDecl = AnnotationUse* 'component' Name ';' ConnectionDecl = AnnotationUse* 'connection' Name 'from' Path 'to' Path ';' GroupDecl = AnnotationUse* 'group' Name '{' GroupMember* '}' SystemDecl = AnnotationUse* 'system' Name SystemProtocolRef* GlobalProtocolDecl = AnnotationUse* 'global' 'protocol' Name GlobalStmt* LocalProtocolDecl = AnnotationUse* 'local' 'protocol' Name 'in' Path LocalStmt* AnnotationUse = '@' AnnotationArgList? AnnotationArgList = '(' AnnotationArg* ')' AnnotationArg = AnnotationConstRef? AnnotationConstRef = IDENT* AnnotationParamList = '(' AnnotationParam* ')' AnnotationScopeList = '|' Name* '|' AnnotationParam = Name ':' Type Type = PrimitiveType | TupleType | OptionalType | ArrayType | NamedType Expr = BitLiteral | StringLiteral | IntegerLiteral | FloatLiteral | RefExpr | ContextParamRefExpr | ParenExpr | BinaryExpr | UnaryExpr | TypeAscriptionExpr | MemberAccessExpr | ArrayAccessExpr | CallExpr | StructLitExpr | IfExpr | MatchExpr | SomeExpr | NoneExpr | TryExpr | ValueCastExpr | FoldExpr | MapExpr | FilterExpr | FlatmapExpr | ArrayComprehensionExpr | ArrayLitExpr | BlockExpr PrimitiveType = 'bit' | 'string' | 'float32' | 'float64' | INT_TYPE | UINT_TYPE EnumMember = AnnotationUse* Name '=' ';' FunctionParam = Name ':' Type BlockExpr = '{' Stmt* Expr? '}' TransformParam = Name ':' Type ContextParam = '?' Name ':' Type ContractParam = Name ':' Type ContractBody = '{' Stmt* '}' Stmt = LetStatement | AssertStatement | IfStatement NamedType = Path StructField = AnnotationUse* Name ':' Type ';' StructAssertion = 'assert' (Name '=')? Expr ';' NewtypeField = Name ':' Type ';' VariantConstructor = AnnotationUse* Name Type* ';' Path = IDENT* SystemProtocolRef = Path ';' GroupMember = Path ';' TupleType = '(' Type* ')' OptionalType = 'Optional' '<' Type '>' ArrayType = Type '[' ']' BitLiteral = 'true' | 'false' StringLiteral = STRING IntegerLiteral = INTEGER FloatLiteral = FLOAT RefExpr = Path ContextParamRefExpr = '?' IDENT ParenExpr = '(' Expr TupleElement* ')' BinaryExpr = Expr ('+' | '-' | '*' | '/' | '%' | '++' | '&&' | '&' | '||' | '|' | '^' | '==' | '!=' | '<' | '>' | '<=' | '>=' | '<<') Expr UnaryExpr = ('!' | '~' | '-') Expr TypeAscriptionExpr = Expr ':' Type MemberAccessExpr = Expr '.' IDENT ArrayAccessExpr = Expr '[' Expr ('..' Expr)? ']' CallExpr = Expr CallArgList GivingArgList? StructLitExpr = Expr StructFieldBinding* NewtypeLitValue? StructUpdateBody? IfExpr = 'if' Expr 'then'? Expr 'else' Expr MatchExpr = 'match' '(' Expr ')' MatchArm* SomeExpr = 'some' '(' Expr ')' NoneExpr = 'none' TryExpr = 'try' '(' Expr ')' ValueCastExpr = 'value_cast' '?'? '<' Type '>' '(' Expr ')' FoldExpr = 'fold' FoldAccumulator LetPattern 'in' Expr BlockExpr MapExpr = 'map' '(' LetPattern 'in' Expr ')' BlockExpr FilterExpr = 'filter' '(' LetPattern 'in' Expr ')' BlockExpr FlatmapExpr = 'flatmap' '(' LetPattern 'in' Expr ')' BlockExpr ArrayComprehensionExpr = '[' Expr CompClause* ']' ArrayLitExpr = '[' Expr* ']' TupleElement = Expr CallArgList = '(' Expr* ')' GivingArgList = 'giving' '(' Expr* ')' StructFieldBinding = IDENT '=' Expr ';' NewtypeLitValue = Expr StructUpdateBody = Expr 'with' StructFieldBinding* MatchArm = MatchPattern '=>' Expr ';'? MatchPattern = DiscardPattern | LiteralPattern | SomePattern | NonePattern | QualifiedPattern | TupleMatchPattern | ArrayMatchPattern | IdentPattern | StructMatchPattern | TypeMatchPattern FoldAccumulator = LetPattern (':' Type)? '=' Expr LetPattern = IdentBinding | DiscardBinding | TuplePattern | TypedBinding CompClause = ForClause | FilterClause | ScanClause | LetClause | ZipClause ForClause = 'for' LetPattern 'in' Expr FilterClause = 'if' Expr ScanClause = 'scan' LetPattern (':' Type)? '=' Expr 'in' Expr LetClause = 'let' LetPattern '=' Expr ZipClause = 'zip' 'for' LetPattern 'in' Expr LetStatement = 'let' LetPattern (':' Type)? '=' Expr ';' AssertStatement = 'assert' Expr ';' IfStatement = 'if' Expr 'then'? StmtList 'else' StmtList StmtList = '{' Stmt* '}' | Stmt IdentBinding = Name DiscardBinding = _ TuplePattern = '(' LetPattern* ')' TypedBinding = LetPattern ':' Type DiscardPattern = _ LiteralPattern = INTEGER | FLOAT | STRING | 'true' | 'false' SomePattern = 'some' '(' MatchPattern ')' NonePattern = 'none' QualifiedPattern = Path MatchPattern* TupleMatchPattern = '(' MatchPattern* ')' ArrayMatchPattern = '[' MatchPattern* ']' IdentPattern = Name StructMatchPattern = MatchPattern 'as' Type TypeMatchPattern = MatchPattern ':' Type LocalStmt = ProtocolLetStatement | VarStatement | SetStatement | SendStatement | RecvStatement | BranchBlock | ListenBlock | LoopBlock | BreakStatement | DoStatement | StateAnnotationStatement ProtocolLetStatement = 'let' IdentBinding (':' Type)? '=' Expr ';' VarStatement = 'var' IdentBinding (':' Type)? '=' Expr ';' SetStatement = 'set' Path '=' Expr ';' SendStatement = 'send' SendWhat ConnectionClause AnnotationUse* ';' RecvStatement = 'recv' RecvWhat ConnectionClause AnnotationUse* ';' BranchBlock = 'branch' BranchArm* 'end' ListenBlock = 'listen' ListenArm* 'end' LoopBlock = 'loop' Name? ('invariant' Expr)? LocalStmt* BreakStatement = 'break' Name? ';' DoStatement = 'do' 'tail'? Path ';' StateAnnotationStatement = AnnotationUse SendWhat = SendExpression | SendAny | SendAnyBind | SendLet ConnectionClause = 'from'? 'to'? 'on'? Path* RecvWhat = RecvVar | RecvDiscard | RecvAnyBind | RecvLet SendExpression = Expr SendAny = 'any' Type SendAnyBind = 'any' Name ':' Type 'where' Expr SendLet = 'let' LetPattern ':' Type ('where' Expr)? RecvVar = Path ('assuming' Expr)? RecvDiscard = _ (':' Type)? RecvAnyBind = 'any' Name ':' Type 'assuming' Expr RecvLet = 'let' LetPattern ':' Type ('assuming' Expr)? BranchArm = '|' ('else' | Expr) '=>' LocalStmt* ListenArm = '|' 'recv' RecvWhat ConnectionClause AnnotationUse* '=>' LocalStmt* GlobalStmt = ExchStatement | ChoiceBlock | LoopBlock | BreakStatement | ParallelBlock | InBlock | DoStatement | StateAnnotationStatement ExchStatement = 'exch' SendWhat ('into' RecvWhat)? ConnectionClause AnnotationUse* ';' ChoiceBlock = 'choice' 'in' ChoiceComponent* SyncWhereClause? ChoiceArm* 'end' ParallelBlock = 'parallel' GlobalStmt* ParallelWithBranch* InBlock = 'in' Path LocalStmt* ChoiceComponent = Path SyncWhereClause = 'where' SyncVarDef* ChoiceArm = '|' ('else' | Expr) '=>' GlobalStmt* SyncVarDef = Name '=' SyncExpr* SyncExpr = 'in' Path '{' Expr '}' ParallelWithBranch = 'with' GlobalStmt* ``` --- ## Reserved Words Every word Flex reserves. None can be used as an identifier. ```text abstract annotation any as assert assuming bit branch break choice component connection const contract do else end enum exch extends extensible false filter flatmap float32 float64 fold for from function given giving global group if import in into invariant let listen local loop map match message module newtype none on Optional parallel protocol recv scan send set some string struct system tail then to transform true try type value_cast var variant where with zip ``` --- ## What goes where A Flex file is its `module` header, then its imports, then a run of global declarations. A position is a place in that file where the grammar expects a declaration, a statement, or a clause, and most constructs stand in exactly one. Each table below answers one question — which positions accept a given construct. ```flex module radar::sensors.tracking component Radar; component CommandPost; message struct TrackReport { track_id: uint32; bearing_deg: float32; } struct SearchSector { start_deg: uint16; end_deg: uint16; assert end_deg > start_deg; } function span_deg(sector: SearchSector) -> uint16 { let span: uint16 = sector.end_deg - sector.start_deg; span; } const sweep_widths_deg: uint16[] = [w * 2 for w in [10: uint16, 20: uint16]]; local protocol Sweep in Radar { var sent: uint32 = 0; send any TrackReport to CommandPost; set sent = sent + 1; } global protocol Report { exch any TrackReport from Radar to CommandPost; in CommandPost { var seen: uint32 = 0; set seen = seen + 1; } } ``` ### The positions | Position | Where it is | |---|---| | top level | a file's body, after the [header](https://flexlang.org/flex/modules/module.md) and the imports | | block | a braced statement list: a [`function`](https://flexlang.org/flex/functions/function.md) or [`transform`](https://flexlang.org/flex/functions/transform.md) body, and a branch of a statement [`if`](https://flexlang.org/flex/expressions/if.md) | | contract body | inside a [`contract`](https://flexlang.org/flex/metadata/contract.md) | | struct body | inside a [`struct`](https://flexlang.org/flex/data-types/struct.md) or a [`newtype`](https://flexlang.org/flex/data-types/newtype.md) | | local body | inside a [`local protocol`](https://flexlang.org/flex/protocols/local-protocol.md) | | global body | inside a [`global protocol`](https://flexlang.org/flex/protocols/global-protocol.md) | | comprehension | after the first element of a [`[…]` comprehension](https://flexlang.org/flex/iteration/comprehension.md) | - A [`branch`](https://flexlang.org/flex/protocols/branch.md) arm, a [`listen`](https://flexlang.org/flex/protocols/listen.md) arm, a [`loop`](https://flexlang.org/flex/protocols/loop.md) body in a local protocol, and an [`in` block](https://flexlang.org/flex/protocols/in-block.md) are all the local body position. - A [`choice`](https://flexlang.org/flex/protocols/choice.md) arm, a [`parallel`](https://flexlang.org/flex/protocols/parallel.md) branch, and a `loop` body in a global protocol are all the global body position. - An `in` block is therefore the local body position sitting inside a global protocol. ### Declarations The top level is a sequence of global declarations, and no other position accepts any of them: - [`annotation`](https://flexlang.org/flex/metadata/annotation.md) - [`const`](https://flexlang.org/flex/data-types/const.md) - [`enum`](https://flexlang.org/flex/data-types/enum.md) - [`function`](https://flexlang.org/flex/functions/function.md) - [`transform`](https://flexlang.org/flex/functions/transform.md) - [`contract`](https://flexlang.org/flex/metadata/contract.md) - [`struct`](https://flexlang.org/flex/data-types/struct.md) - [`newtype`](https://flexlang.org/flex/data-types/newtype.md) - [`variant`](https://flexlang.org/flex/data-types/variant.md) - [`type`](https://flexlang.org/flex/data-types/type-alias.md) - [`component`](https://flexlang.org/flex/protocols/component.md) - [`connection`](https://flexlang.org/flex/protocols/connection.md) - [`group`](https://flexlang.org/flex/protocols/group.md) - [`system`](https://flexlang.org/flex/protocols/system.md) - [`global protocol`](https://flexlang.org/flex/protocols/global-protocol.md) - [`local protocol`](https://flexlang.org/flex/protocols/local-protocol.md) Most may be preceded by a run of annotation uses. A `struct` or `variant` declaration may also carry leading modifier keywords. The header and the imports precede all of them — [module](https://flexlang.org/flex/modules/module.md) has the shape of the file itself. ### Statements | Statement | block | contract body | struct body | local body | global body | |---|---|---|---|---|---| | [`let`](https://flexlang.org/flex/expressions/block-let-assert.md) | ✓ | ✓ | — | ✓ | — | | [`assert`](https://flexlang.org/flex/expressions/block-let-assert.md) | ✓ | ✓ | ✓ | — | — | | [`if`](https://flexlang.org/flex/expressions/if.md) | ✓ | ✓ | — | — | — | | [`var`](https://flexlang.org/flex/protocols/local-protocol.md) | — | — | — | ✓ | — | | [`set`](https://flexlang.org/flex/protocols/local-protocol.md) | — | — | — | ✓ | — | | [`send`](https://flexlang.org/flex/protocols/send.md) | — | — | — | ✓ | — | | [`recv`](https://flexlang.org/flex/protocols/recv.md) | — | — | — | ✓ | — | | [`branch`](https://flexlang.org/flex/protocols/branch.md) | — | — | — | ✓ | — | | [`listen`](https://flexlang.org/flex/protocols/listen.md) | — | — | — | ✓ | — | | [`exch`](https://flexlang.org/flex/protocols/exch.md) | — | — | — | — | ✓ | | [`choice`](https://flexlang.org/flex/protocols/choice.md) | — | — | — | — | ✓ | | [`parallel`](https://flexlang.org/flex/protocols/parallel.md) | — | — | — | — | ✓ | | [`in`](https://flexlang.org/flex/protocols/in-block.md) | — | — | — | — | ✓ | | [`loop`](https://flexlang.org/flex/protocols/loop.md) | — | — | — | ✓ | ✓ | | [`break`](https://flexlang.org/flex/protocols/loop.md) | — | — | — | ✓ | ✓ | | [`do`](https://flexlang.org/flex/protocols/do.md) | — | — | — | ✓ | ✓ | | [a standalone annotation](https://flexlang.org/flex/metadata/annotation.md) | — | — | — | ✓ | ✓ | - No statement stands at the top level, and no declaration stands in any of these positions. - A block's `let` and a local protocol's `let` are different statements, and they take different patterns. - A struct body takes its assertions after all of its fields. - `var` and `set` belong to a local protocol, so an `in` block is where a global protocol keeps state. - A `break` may name a `loop` that encloses the `in` block it stands in. - An `in` block holds local statements, so it does not accept another `in`. - Every annotation use other than the standalone form attaches to something. [`annotation`](https://flexlang.org/flex/metadata/annotation.md) holds the set of targets. ### Clauses | Clause | What takes it | |---|---| | `extends` | a [`struct`](https://flexlang.org/flex/data-types/struct.md) declaration | | `given` | a [`transform`](https://flexlang.org/flex/functions/transform.md) declaration | | `giving` | a call to a transform that declares a `given` clause | | `into let` | [`exch`](https://flexlang.org/flex/protocols/exch.md) | | `where`, a predicate | a [`send`](https://flexlang.org/flex/protocols/send.md) or `exch` payload that binds a name | | `assuming` | [`recv`](https://flexlang.org/flex/protocols/recv.md), and `exch … into` | | `where`, a variable list | a [`choice`](https://flexlang.org/flex/protocols/choice.md#synchronized) naming more than one component | | `invariant` | a [`loop`](https://flexlang.org/flex/protocols/loop.md#invariants) in a local protocol, or in an `in` block | | `for`, `if`, `let`, `scan`, `zip for` | a [comprehension](https://flexlang.org/flex/iteration/comprehension.md) | - A `loop` in a global protocol takes no `invariant`. - The comprehension clauses spelled `if` and `let` are clauses, not the statements above. ### Expressions An expression stands in all of these slots: - a `const` initializer - a struct or newtype assertion - a block's statements and its tail value - a contract's `assert` - a `let`, `var`, or `set` value - a `send` or `recv` predicate - a `branch` or `choice` guard - a `loop` invariant - every clause of a comprehension Some slots take less than a whole expression: - An annotation argument is a literal or a reference to a `const`. An argument built with an operator is a parse error. - An enum member's value is a literal. - A `loop` invariant begins with a reference to a bound variable or a `let` binding. - A guard of a `choice` naming more than one component reads only the variables its `where` clause defines. - `?name` stands only in the body of the transform whose `given` clause declares it. ### Types A type stands in all of these slots: - a struct or newtype field - a `function` or `transform` parameter and return type - an `annotation` parameter - a `const` declaration - a `let` or `var` annotation - an ascription, `expr: Type` - the right side of a `type` alias - an `Optional` argument, an array's element, a tuple's element - the payload of `send`, `recv`, and `exch` - a `value_cast` target - the type a `match` pattern narrows to Some slots take only certain types: - The payload of `send`, `recv`, and `exch` is a non-abstract `message` struct or a `message` variant. - A transform's parameter types and return type are non-abstract `message` structs. - An enum's backing type is a primitive. ### Patterns | Slot | Forms it takes | |---|---| | a block [`let`](https://flexlang.org/flex/expressions/block-let-assert.md) | a name, `_`, a tuple, or `pat: Type` | | a protocol [`let` or `var`](https://flexlang.org/flex/protocols/local-protocol.md) | one name | | a [`recv`](https://flexlang.org/flex/protocols/recv.md) binding | a name, `_`, a tuple, or `pat: Type` | | a [comprehension](https://flexlang.org/flex/iteration/comprehension.md) clause's binding | a name, `_`, a tuple, or `pat: Type` | | a [`fold`, `map`, `filter`, or `flatmap`](https://flexlang.org/flex/iteration/fold-map-filter-flatmap.md) binding | a name, `_`, a tuple, or `pat: Type` | | a [`match`](https://flexlang.org/flex/expressions/match.md) arm | `_`, a literal, `some`, `none`, a constructor, a tuple, an array, a name, `pat as Type`, `pat : Type` | - A match arm is the only slot that takes the match patterns. `pat as Type` outside one is a parse error. ### References | Slot | What the path names | |---|---| | `from` and `to`, on `send`, `recv`, and `exch` | a component | | `on`, on `send`, `recv`, and `exch` | a connection | | `from` and `to`, on a [`connection`](https://flexlang.org/flex/protocols/connection.md) declaration | a component | | `local protocol P in C` | a component | | [`in C { … }`](https://flexlang.org/flex/protocols/in-block.md) | a component | | [`choice in C`](https://flexlang.org/flex/protocols/choice.md) | a component | | [`do`](https://flexlang.org/flex/protocols/do.md) in a local protocol | a local protocol of the same component | | `do` in a global protocol | a global protocol | | a [`system`](https://flexlang.org/flex/protocols/system.md) member | a local protocol | | a [`group`](https://flexlang.org/flex/protocols/group.md) member | a component or a group | | `extends` | an [extensible struct](https://flexlang.org/flex/data-types/struct.md) | | an annotation use | an [`annotation`](https://flexlang.org/flex/metadata/annotation.md) declaration | - A `send`, `recv`, or `exch` needs a connection clause. A statement carrying none is a parse error. - An `exch` needs a `from` component, written or implied by an `on` connection. ### Errors #### A declaration inside a protocol ```flex invalid component Radar; local protocol Sweep in Radar { const max_range_m: int32 = 40000; // error: unexpected } ``` #### A block statement in a global protocol ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Report { let latest: uint32 = 1; // error: unexpected exch any TrackReport from Radar to CommandPost; } ``` #### A field after an assertion ```flex invalid struct SearchSector { start_deg: uint16; assert start_deg < 360; end_deg: uint16; // error: unexpected } ``` #### A `break` with no enclosing loop ```flex invalid component Radar; local protocol Sweep in Radar { break; // error: break-outside-loop } ``` --- ## array A homogeneous, ordered, variable-length collection, written by suffixing `[]` to the element type. ```flex const bearings: float32[] = [87.5, 91.0, 274.25]; const grid: int32[][] = [[1], [2, 3]]; const pending: string[] = []; ``` ### Elements The `[]` suffix repeats, so `int32[][]` is an array of arrays of `int32`. The element type may be any type, including the struct being declared, which is how a recursive type holds its children. - Every element of an array has one type. - Two array types are assignable when their element types are. - An empty `[]` takes its element type from context: a declared type, a parameter, or an ascription. - `length` reports the count, always as a `uint32`. ```flex struct Waypoint { name: string; legs: Waypoint[]; } ``` ### Indexing and slicing `xs[i]` selects one element and `xs[a..b]` selects a range. Both count from zero, and the slice is inclusive at both ends. - An index is an integer type, and an index past the end is a runtime failure that `try` catches. - A slice never fails. Bounds are clamped into the array, and a slice whose clamped bounds cross is the empty array. - `last` reports the final element and fails on an empty array. ```flex eval const bearings: float32[] = [87.5, 91.0, 274.25]; length(bearings) // => 3 bearings[(0: uint32)] // => 87.5 bearings[(1: uint32)..(9: uint32)] // => [91, 274.25] try(bearings[(7: uint32)]) // => none last(bearings) // => 274.25 ``` **Inclusive Slices** `xs[a..b]` includes both ends, unlike `substring`, which takes the half-open range `[start, end)`. A slice clamps out-of-range bounds and never fails, where `substring` reports a range past the end as a runtime failure. ### Concatenation `++` joins two arrays of the same element type and yields that type. ```flex eval const head: uint8[] = [1, 2]; const rest: uint8[] = [3]; head ++ rest // => [1, 2, 3] length(head ++ rest) // => 3 ``` ### Errors #### An index that is not an integer ```flex invalid const bearings: float32[] = [87.5, 91.0]; const first: float32 = bearings[true]; // error: array-index-type ``` #### Elements of more than one type ```flex invalid const mixed: string[] = ["air", 2]; // error: incompatible-branch-types ``` #### An empty literal with no element type An empty array carries no element to infer from, so the type has to come from somewhere else. ```flex invalid function count(pending: string[]) -> uint32 = { let empty = []; // error: ambiguous-inferred-type length(empty); }; ``` #### Concatenating arrays of different element types ```flex invalid const bearings: float32[] = [87.5]; const names: string[] = ["air"]; const both: float32[] = bearings ++ names; // error: invalid-operand-types ``` ### Related - [tuple](https://flexlang.org/flex/data-types/tuple.md) — a fixed-length group whose elements may differ in type. - [primitives](https://flexlang.org/flex/data-types/primitives.md) — the element types an array is usually built over. - [comprehension](https://flexlang.org/flex/iteration/comprehension.md) — building one from another. --- ## const A named, immutable value declared at module scope. ```flex const max_tracks: uint16 = 512; const home_station: string = "GroundStation-1"; ``` ### Declaration A const is a name, a type, and an initializer, and both the type and the initializer are mandatory. A value bound inside a block is a `let` instead. - Omitting the type or the initializer is a parse error, so neither `const max_tracks = 512;` nor `const max_tracks: uint16;` parses. - The name is unique among the file's declarations. - The initializer must be assignable to the declared type, and a numeric literal in it pins to that type. - A const is a global declaration, so another module imports it the way it imports a type. - Annotations precede the declaration. ```flex annotation Units(name: string) @Units("meters") const max_range_m: float32 = 250000.0; ``` ### Initializers The initializer is an expression, not only a literal: arithmetic, a struct literal, an enum member, a call to a function the file declares. - A const may reference another const. Declaration order does not matter, so the reference may name a const declared further down the file. - A reference carries the const's declared type, so arithmetic on it is that type's arithmetic, wrapping included. - A const of a struct type is constructed the way any other value of that type is, so the struct's assertions run and a failing one is catchable with `try`. - A const is in scope wherever a value expression is: a function body, a struct assertion, a `send` predicate. ```flex eval struct SearchSector { start_deg: uint16; end_deg: uint16; assert ordered = start_deg < end_deg; } enum TrackKind int32 { Air = 0; Surface = 1; } const max_tracks: uint16 = 512; const half_tracks: uint16 = max_tracks / 2; const default_kind: TrackKind = TrackKind.Air; const forward: SearchSector = SearchSector { start_deg = 350; end_deg = 360; }; const reversed: SearchSector = SearchSector { start_deg = 90; end_deg = 10; }; half_tracks // => 256 max_tracks * 128 // => 0 default_kind // => TrackKind.Air forward.end_deg // => 360 try(reversed) // => none ``` ### Errors #### An initializer that does not match the declared type ```flex invalid const tracking_enabled: bit = "true"; // error: type-mismatch ``` #### A const that references itself ```flex invalid const max_tracks: uint16 = max_tracks + 1; // error: const-self-reference ``` #### Consts that reference each other A cycle is rejected however many consts it runs through, and every const on it is reported. ```flex invalid const max_tracks: uint16 = max_sectors; // error: cyclic-const const max_sectors: uint16 = max_tracks; // error: cyclic-const ``` #### Naming a const where a type is expected A const binds a value and never introduces a type. ```flex invalid const max_tracks: uint16 = 512; struct TrackTable { entries: max_tracks; // error: not-a-type } ``` ### Related - [struct](https://flexlang.org/flex/data-types/struct.md) — declaring a type a const can hold. --- ## enum A named type whose values are listed in the declaration, each fixed to a literal of a primitive backing type. ```flex enum TrackKind int32 { Air = 0; Surface = 1; Subsurface = 2; } ``` ### Members Each member is a name, `=`, a literal, and a semicolon. The backing type follows the enum's name and is a primitive: `bit`, `string`, `float32`, `float64`, `intN`, or `uintN`. - An enum declares at least one member. - Member names are unique within the enum. - Each member's value must be assignable to the backing type. - Two members may carry the same value. - The value is a literal, so a negative member value does not parse. - A backing type that is not a primitive does not parse either. ```flex enum SensorMode string { Search = "search"; Track = "track"; } enum Toggle bit { Off = false; On = true; } enum Priority uint8 { Low = 0; Default = 0; High = 2; } ``` ### Values A member is reached through the enum, as `TrackKind.Air`. Its type is the enum, never the backing type — the backing type says what the members are written as, not what they convert to. - An enum value is not assignable to its backing type, and no cast converts one: `value_cast` requires a numeric source. - Equality compares members. Two members sharing one backing value are not equal. - A `match` over an enum covers it member by member. ```flex eval enum TrackKind int32 { Air = 0; Surface = 1; } enum Priority uint8 { Low = 0; Default = 0; } function label(kind: TrackKind) -> string = match (kind) { TrackKind.Air => "air"; TrackKind.Surface => "surface"; }; TrackKind.Air // => TrackKind.Air label(TrackKind.Surface) // => "surface" Priority.Low == Priority.Default // => false ``` ### Errors #### An enum with no members ```flex invalid enum TrackKind int32 {} // error: empty-enum ``` #### Two members with one name ```flex invalid enum TrackKind int32 { Air = 0; Air = 1; // error: duplicate-enum-member } ``` #### A member value the backing type does not accept ```flex invalid enum TrackKind int32 { Air = "air"; // error: enum-backing-type-mismatch } ``` #### Using an enum value as its backing type An enum is a distinct type, so its members are not the integers they are written as. ```flex invalid enum TrackKind int32 { Air = 0; Surface = 1; } const kind_code: int32 = TrackKind.Air; // error: type-mismatch ``` ### Related - [variant](https://flexlang.org/flex/data-types/variant.md) — a closed set of constructors that carry payloads. - [const](https://flexlang.org/flex/data-types/const.md) — naming one member for the rest of the module to use. --- ## newtype A nominal single-field wrapper, distinct from the type it wraps: no conversion runs in either direction, and two newtypes over one underlying type are not interchangeable. ```flex newtype Altitude { value: int32; } const cruise: Altitude = Altitude{ 1200 }; const raw: int32 = cruise.value; ``` ### Declaration A newtype body declares exactly one field, and may close with a run of `assert` invariants over that field. - A newtype declares one field, no more and no fewer. - The body admits that field and then assertions, so a second field does not parse. - The field type may be any type. - Assertions follow the field and are checked at construction, under a struct's rules. - The name is a global declaration, unique among the file's declarations. ```flex newtype Altitude { value: int32; assert above_ground = value >= 0; } newtype TrackIds { values: uint32[]; } ``` ### Values Construction is `Path { expr }` — the value alone, with no field name and no `=`. Unwrapping is a member access on the field name. - The expression must be assignable to the field type. - A newtype value is not assignable to the underlying type, and the underlying type is not assignable to the newtype. `Altitude` and `int32` are unrelated types. - Two newtypes over one underlying type are not interchangeable either, which is what makes a newtype worth declaring over a type alias. - Equality is defined between values of the same newtype, comparing what they wrap. ```flex eval newtype Altitude { value: int32; assert above_ground = value >= 0; } const cruise: Altitude = Altitude{ 1200 }; cruise.value // => 1200 cruise == Altitude{ 1200 } // => true try(Altitude{ -5 }) // => none ``` ### Errors #### Field bindings in place of a value A struct literal names its fields; a newtype literal holds the wrapped value alone. ```flex invalid newtype Altitude { value: int32; } const cruise: Altitude = Altitude{ value = 1200; }; // error: newtype-value-syntax ``` #### A wrapped value of the wrong type ```flex invalid newtype Altitude { value: int32; } const cruise: Altitude = Altitude{ "high" }; // error: newtype-value-type ``` #### The underlying type in place of the newtype ```flex invalid newtype Altitude { value: int32; } const cruise: Altitude = 1200; // error: type-mismatch const raw: int32 = Altitude{ 1200 }; // error: type-mismatch ``` #### A literal whose target is neither a struct nor a newtype An alias is not a wrapper, so it has neither fields to bind nor a value to hold. ```flex invalid type Altitude = int32; const cruise: Altitude = Altitude{ value = 1200; }; // error: not-a-struct-or-newtype ``` ### Related - [type-alias](https://flexlang.org/flex/data-types/type-alias.md) — the transparent alternative, which introduces no new type. - [struct](https://flexlang.org/flex/data-types/struct.md) — the multi-field record, and the source of the assertion rules. --- ## optional A value that is either present or absent: `Optional` holds `some(v)` or `none`. ```flex const callsign: Optional = some("Viper-1"); const no_track: Optional = none; ``` ### Values `Optional<` and `>` are fixed syntactic wrappers rather than a general type constructor. - `some(expr)` has type `Optional` for an `expr` of type `T`. - `none` takes its `T` from context: a declared type, a parameter type, or an ascription. A `none` with none of those is ambiguous. - Optionals nest, and the closing `>` of an inner one is its own token, so `Optional>` closes correctly. - Two optional types are assignable when their inner types are. ```flex const nested: Optional> = some(some(true)); function safe_divide(a: float64, b: float64) -> Optional = if b == 0.0 then none else some(a / b); ``` ### Reading the value back An optional is opened by naming both cases. Nothing implicitly unwraps one. - A `match` covers an optional with a `some(x)` pattern and a `none` pattern. - `getOrElse(x, fallback)` returns the contained value or the fallback, whose type must be the inner type. - `try(expr)` produces `Optional` from an expression that may fail at runtime, so a caught failure arrives as `none`. - `map` accepts an optional as its source as well as an array. ```flex eval const callsign: Optional = some("Viper-1"); const no_track: Optional = none; getOrElse(callsign, "unknown") // => "Viper-1" getOrElse(no_track, 0) // => 0 match (callsign) { some(name) => name; none => "unknown"; } // => "Viper-1" try((100: int32) / 0) // => none ``` ### Errors #### A payload of the wrong type ```flex invalid const callsign: Optional = some(7); // error: type-mismatch ``` #### An absent value with no type to be absent of A `const` always carries a type, so a context-less `none` surfaces on an inferred binding instead. A binding the declared return type reaches is pinned by it; this one is not. ```flex invalid function callsign(id: uint32) -> string = { let missing = none; // error: ambiguous-inferred-type "unknown"; }; ``` #### A fallback that is not the inner type ```flex invalid const no_track: Optional = none; const track: uint32 = getOrElse(no_track, "none"); // error: builtin-call-args ``` #### An optional where the inner type is expected ```flex invalid const callsign: Optional = some("Viper-1"); const name: string = callsign; // error: type-mismatch ``` ### Related - [match](https://flexlang.org/flex/expressions/match.md) — the form that covers both cases. - [primitives](https://flexlang.org/flex/data-types/primitives.md) — the inner types an optional usually wraps. --- ## primitives The built-in types every other type is composed from: `bit`, `string`, `float32`, `float64`, and the arbitrary-width integers `intN` and `uintN`. ```flex const tracking_enabled: bit = true; const home_station: string = "GroundStation-1"; const bearing_deg: float32 = 87.5; const earth_radius_m: float64 = 6378137.0; const max_tracks: uint16 = 512; const altitude_m: int32 = -120; ``` ### bit The boolean primitive. It holds `true` or `false`, and it is not a numeric type. - `bit` is assignable only to `bit`. No integer converts to it, and no cast produces one. - `&&`, `||`, `^`, and `!` are defined on it, as logical operators. - A `match` over a `bit` covers it by listing `true` and `false`. ### string The character-sequence primitive. A literal is `"`-delimited and must be terminated on the line it opens. - The escapes are `\"`, `\\`, `\n`, `\r`, and `\t`. - `++` concatenates two strings. - `substring` takes a half-open character range and fails when the range runs past the end. ### Floats `float32` and `float64` are IEEE-754 single and double precision, and they are distinct types. - Neither is assignable to the other. `value_cast` widens; `float64to32` narrows, and it is a built-in function rather than a cast because it loses precision. - Overflow yields `inf` and an invalid operation yields `NaN`, so float arithmetic never fails. - `NaN` is unordered, so every comparison is `false` against it and `!(a < b)` is not `a >= b`. ### Integers `intN` is signed and `uintN` is unsigned, for any width `N` of one bit or more: `int8`, `uint16`, `int37`. - The width is written directly after the prefix, with no space and no leading zeros. - Two integer types are assignable only when identical. There is no widening and no signed/unsigned coercion, so a conversion is a `value_cast`. - Arithmetic is fixed-width and wraps on overflow, at the width the type declares. - Division truncates toward zero, and `%` takes the sign of its left operand. - Division by zero is a runtime failure, so `try` turns it into `none`. ```flex eval const limit: uint8 = 250; const ceiling: int8 = 127; limit + 10 // => 4 ceiling + 1 // => -128 (-7: int32) / 2 // => -3 try(limit / 0) // => none ``` ### Assignability A value is assignable to a target when the two types are the same type. These rules say what "the same" means, and nothing outside them converts on its own. - Primitives, enums, and named types match by name. - Arrays and optionals match on their inner types; tuples match position-wise and must have equal arity. - A numeric literal has no width of its own, so it is assignable to any type of its category: an integer literal to any `intN` or `uintN`, a decimal literal to either float. - A struct is assignable wherever one of its ancestors is expected. ### Errors #### A value of another primitive type A value's type has to be the target's type exactly: there is no widening between integer widths, no conversion between the float types, and no coercion between `bit` and a number. ```flex invalid const short: int8 = 42; const long: int16 = short; // error: type-mismatch const narrow: float32 = 0.5; const wide: float64 = narrow; // error: type-mismatch const tracking_enabled: bit = 1; // error: type-mismatch ``` ### Related - [value-cast](https://flexlang.org/flex/expressions/value-cast.md) — the conversions between numeric types. - [operators](https://flexlang.org/flex/expressions/operators.md) — what each primitive supports. - [literals](https://flexlang.org/flex/expressions/literals.md) — how a value of each type is written. --- ## struct A named record of zero or more typed fields. Structs form single-inheritance hierarchies with `extends`, and modifiers control whether one may be extended, built, or sent between components. ```flex struct TrackReport { track_id: uint32; bearing_deg: float32; range_m: float32; } ``` ### Fields Each field is a name, a colon, a type, and a semicolon. Field order is the declaration order, and it is the order a struct literal and the wire form both use. - A struct may declare zero fields. - A field may be typed with the struct being declared, so a tree is a struct that holds an array of itself. - Field names are unique within a struct, and unique against every inherited field. ```flex struct Empty {} struct Waypoint { name: string; legs: Waypoint[]; } ``` **Structs Have No Methods** A struct declares fields and assertions and nothing else — no method, no constructor body, no visibility modifier. Behavior lives in a `function`, a `transform`, or a protocol, so a struct is only the data. ### Modifiers These modifiers may precede `struct`, in this order: `abstract`, `extensible`, `message`. - `extensible` lets other structs name this one after `extends`. - `abstract` withholds construction: the type may be extended but never built by a struct literal. It only means something alongside `extensible`. - `message` marks a struct as something components send each other in a protocol. ```flex extensible struct Contact { contact_id: uint32; } abstract extensible struct Sensor { sensor_id: uint32; } message struct SlewCommand { sensor_id: uint32; bearing_deg: float32; } ``` **message Marks the Interface** `message` picks out the types that cross a component boundary: a protocol sends these, a `transform` maps between them, and a plain struct may still be a field inside one. Choosing what to mark is an ontological question: which types are things the system is made of, and which are only how a component does its work. That answer belongs in the declaration, not in whatever the protocols happen to send. ### Inheritance `extends` names one parent, so the hierarchy is a tree rather than a lattice. A struct holds every field its ancestors declare, ahead of its own. - The parent must be a struct, and it must be declared `extensible`. - Inherited fields cannot be redeclared, retyped, or reordered. - A value of a struct is accepted anywhere one of its ancestors is expected. The reverse is not true: a `Contact` is not a `Radar`. ```flex extensible struct Contact { contact_id: uint32; } struct Radar extends Contact { scan_period_ms: uint32; } const fixed: Contact = Radar { contact_id = 4; scan_period_ms = 2000; }; ``` ### Assertions A run of `assert` statements may close a struct body. They state what has to be true of a value of this type, and they are checked every time one is constructed. ```flex struct SearchSector { start_deg: uint16; end_deg: uint16; assert ordered = start_deg < end_deg; assert end_deg <= 360; } ``` - Assertions come after every field. A field written below an assertion is a parse error. - The name in `assert ordered = …` is a label, reported when the invariant fails. `assert a = b` labels the assertion `a`; `assert a == b` compares `a` and `b`. - Every own field is in scope, whatever the declaration order. Inherited fields are not. - Each condition has type `bit`. - A failing assertion is a catchable failure, so `try` turns it into `none`. - A struct-update re-checks the invariants after applying the overrides, so an update cannot reach a value the equivalent literal is rejected for. ```flex eval struct SearchSector { start_deg: uint16; end_deg: uint16; assert ordered = start_deg < end_deg; } const forward: SearchSector = SearchSector { start_deg = 350; end_deg = 360; }; try(SearchSector { start_deg = 90; end_deg = 10; }) // => none try(SearchSector { forward with end_deg = 10; }) // => none forward.end_deg // => 360 ``` ### Errors #### Extending a struct that is not extensible A parent has to opt in with `extensible` before anything may extend it. ```flex invalid struct Contact { contact_id: uint32; } struct Radar extends Contact { // error: extends-non-extensible scan_period_ms: uint32; } ``` #### Extending something that is not a struct `extends` names a struct. An alias, a newtype, or an enum in that position has no fields to inherit. ```flex invalid type ContactId = uint32; struct Radar extends ContactId { // error: extends-non-struct scan_period_ms: uint32; } ``` #### Marking a struct abstract without extensible An `abstract` struct cannot be constructed, so unless it is also `extensible` no value of it can ever exist. ```flex invalid abstract struct Sensor { // error: abstract-requires-extensible sensor_id: uint32; } ``` #### Declaring the same field twice ```flex invalid struct TrackReport { track_id: uint32; track_id: uint32; // error: duplicate-field-name } ``` #### Redeclaring an inherited field A child holds its ancestors' fields already, so naming one again would give the struct two fields with one name. ```flex invalid extensible struct Contact { contact_id: uint32; } struct Radar extends Contact { contact_id: uint32; // error: inherited-field-collision } ``` #### Inheritance that forms a cycle ```flex invalid extensible struct Contact extends Radar { // error: cyclic-inheritance contact_id: uint32; } extensible struct Radar extends Contact { // error: cyclic-inheritance scan_period_ms: uint32; } ``` #### Constructing an abstract struct An `abstract` struct is a place to put shared fields. Construct one of its descendants instead. ```flex invalid abstract extensible struct Sensor { sensor_id: uint32; } const s: Sensor = Sensor { sensor_id = 1; }; // error: abstract-struct-instantiation ``` #### An assertion whose condition is not bit ```flex invalid struct SearchSector { start_deg: uint16; assert start_deg; // error: assert-condition-type } ``` #### Naming an inherited field in an assertion An assertion sees only the fields its own declaration lists. ```flex invalid extensible struct Contact { contact_id: uint32; } struct Radar extends Contact { scan_period_ms: uint32; assert contact_id > 0; // error: unresolved-reference } ``` ### Related - [tuple](https://flexlang.org/flex/data-types/tuple.md) — the same grouping without names on the parts. - [newtype](https://flexlang.org/flex/data-types/newtype.md) — a single field and its assertions, as a distinct type. - [literals](https://flexlang.org/flex/expressions/literals.md) — writing a struct value, and updating one. --- ## tuple An ordered, fixed-length group of two or more values, each with its own type. ```flex const fix: (float64, float64) = (42.3601, -71.0589); const labeled: (string, uint32) = ("track", 7); ``` ### Elements A tuple type is a parenthesized list of two or more types, and a tuple value is a parenthesized list of that many expressions. - A tuple has at least two elements. A single parenthesized expression is a grouping, not a one-element tuple. - An element may be any type, a tuple included. - Two tuple types are assignable when they have equal arity and each element is assignable position-wise. - Equality compares element by element. ```flex const nested: ((string, uint32), bit) = (("track", 7), true); const sectors: (float32, float32)[] = [(0.0, 90.0), (90.0, 180.0)]; ``` ### Access `._N` selects element `N`, counting from zero. `N` is written as a literal, so the arity is known and the selection is checked. - `._N` is valid for `N` inside the tuple's arity and nowhere else. - A `let` tuple pattern destructures instead, binding one name per element. - A `match` tuple pattern does the same, and covers a tuple discriminee when each element pattern is itself irrefutable. ```flex eval const fix: (float64, float64) = (42.3601, -71.0589); const nested: ((string, uint32), bit) = (("track", 7), true); const repeated: (float64, float64) = (42.3601, -71.0589); fix._0 // => 42.3601 nested._0._1 // => 7 fix == repeated // => true ``` ### Errors #### An element index the tuple does not have ```flex invalid const fix: (float64, float64) = (42.3601, -71.0589); const altitude: float64 = fix._2; // error: invalid-member-access ``` #### A pattern that binds the wrong number of elements ```flex invalid function latitude(fix: (float64, float64)) -> float64 = { let (lat, lon, alt) = fix; // error: tuple-pattern-arity lat; }; ``` #### A tuple of a different arity ```flex invalid const fix: (float64, float64, float64) = (42.3601, -71.0589, 120.0); const flat: (float64, float64) = fix; // error: type-mismatch ``` ### Related - [array](https://flexlang.org/flex/data-types/array.md) — a variable-length collection of one element type. - [struct](https://flexlang.org/flex/data-types/struct.md) — the same grouping with names on the parts. --- ## type alias A structurally transparent name for an existing type, declared with `type`. Wherever the alias appears in a type position it means its right-hand side. ```flex type ContactId = uint32; type Coordinate = (float64, float64); type BearingSeries = float32[]; type MaybeCallsign = Optional; ``` ### Declaration An alias is `type`, a name, `=`, a type, and a semicolon. - The name is a global declaration, unique among the file's declarations. - The right-hand side may be any type, and it resolves in the declaring file's scope. - A value of the aliased type is assignable where the alias is expected, and the reverse holds. An alias introduces no type of its own; `newtype` is what does that. - An alias may name another alias, so long as the chain ends somewhere. ```flex eval type ContactId = uint32; type Coordinate = (float64, float64); const radar_id: ContactId = 7; const station: Coordinate = (42.3601, -71.0589); radar_id + (1: uint32) // => 8 station._0 // => 42.3601 ``` ### Errors #### An alias defined in terms of itself A cycle is rejected however many aliases it runs through. ```flex invalid type ContactId = TrackId; // error: cyclic-type-alias type TrackId = ContactId; // error: cyclic-type-alias ``` #### An alias naming something that is not a type ```flex invalid const max_tracks: uint16 = 512; type Capacity = max_tracks; // error: not-a-type ``` ### Related - [newtype](https://flexlang.org/flex/data-types/newtype.md) — the nominal wrapper, which is a distinct type. - [primitives](https://flexlang.org/flex/data-types/primitives.md) — the types an alias usually renames. --- ## variant A discriminated union: a named set of constructors, each carrying zero or more payload types. A value of the type is one constructor and its payloads. ```flex variant Angle { DMS(int32, int32, int32); Radians(float64); } ``` ### Constructors Each constructor is a name, an optional payload list, and a semicolon. The names are scoped to the variant and reached through it, as `Angle.Radians`. - A variant declares at least one constructor. - Constructor names are unique within the variant. Arity is not part of the name, so `Firm;` and `Firm(string);` collide. - A constructor may carry no payload. The list may be omitted or written empty, and `Firm;` and `Firm();` declare the same nullary constructor. - A payload type may be the variant being declared, so a route is a variant that holds the rest of itself. - `message` may precede `variant`, marking the type as something components send each other in a protocol. ```flex variant TrackQuality { Firm; Tentative(); Dropped(string); } variant Route { Stop(string); Leg(string, Route); } message variant SensorReport { Bearing(float32); Silent; } ``` ### Values A constructor with payloads is applied the way a function is called. A nullary constructor is used bare or with empty parens, and both forms give the same value. - The application supplies exactly as many arguments as the constructor has payload types. - Each argument must be assignable to its payload type. - Payloads have no names, so matching is what reads them back out. ```flex eval variant TrackQuality { Firm; Dropped(string); } const firm: TrackQuality = TrackQuality.Firm; const also_firm: TrackQuality = TrackQuality.Firm(); firm == also_firm // => true TrackQuality.Dropped("lost") // => TrackQuality.Dropped("lost") ``` ### Matching A pattern names a constructor and binds its payloads positionally. Each binding is in scope in that arm's body. - A pattern binds exactly as many sub-patterns as the constructor has payload types. - A bare `TrackQuality.Firm` pattern binds none, which is correct for a nullary constructor only. ```flex eval variant Angle { DMS(int32, int32, int32); Radians(float64); } function whole_degrees(a: Angle) -> int32 = match (a) { Angle.DMS(deg, min, sec) => deg; Angle.Radians(rad) => 0; }; whole_degrees(Angle.DMS(45, 30, 0)) // => 45 whole_degrees(Angle.Radians(1.57)) // => 0 ``` ### Errors #### A variant with no constructors ```flex invalid variant TrackQuality {} // error: empty-variant, expected ``` #### Two constructors with one name The payload list is not part of the name, so a second `Dropped` collides whatever it carries. ```flex invalid variant TrackQuality { Dropped(string); Dropped(uint32); // error: duplicate-variant-constructor } ``` #### Applying a constructor with the wrong number of arguments A bare reference counts as zero arguments, so it is an argument short of a constructor that carries a payload. ```flex invalid variant TrackQuality { Firm; Dropped(string); } const A: TrackQuality = TrackQuality.Dropped; // error: call-arg-count const B: TrackQuality = TrackQuality.Firm("lost"); // error: call-arg-count ``` #### A payload argument of the wrong type ```flex invalid variant TrackQuality { Dropped(string); } const A: TrackQuality = TrackQuality.Dropped(7); // error: call-arg-type ``` #### A pattern that binds the wrong number of payloads ```flex invalid variant TrackQuality { Firm; Dropped(string); } function label(q: TrackQuality) -> string = match (q) { TrackQuality.Dropped(reason, code) => reason; // error: incompatible-pattern _ => "firm"; }; ``` ### Related - [enum](https://flexlang.org/flex/data-types/enum.md) — a closed set of values over a primitive, with no payloads. - [struct](https://flexlang.org/flex/data-types/struct.md) — the record type a payload usually holds. --- ## block, let, assert A braced sequence of statements ending in one tail expression. The tail expression's type is the block's type, so a block is an expression and goes wherever an expression goes. ```flex function sweep_area(diameter: float64) -> float64 = { let radius: float64 = diameter / 2.0; assert radius > 0.0; 3.14159 * radius * radius; }; ``` ### Blocks A block holds zero or more statements and then one expression, semicolon-terminated. A statement is a `let`, an `assert`, or a statement [`if`](https://flexlang.org/flex/expressions/if.md). - The smallest block is `{ expr; }`. There is no empty block. - Blocks nest, and a nested block is an ordinary expression — a `let` initializer may be one. - A declaration cannot appear in a block. Structs, functions, and consts are top-level only. - A name a block binds is gone at the closing brace. ```flex function offset_range(raw_range_m: float64) -> float64 = { let corrected: float64 = { let bias_m: float64 = 12.5; raw_range_m - bias_m; }; corrected * 2.0; }; ``` ### let `let` binds an expression to a name, to `_`, or to a tuple pattern. The type annotation is optional. - The name is in scope for the statements that follow it and for the tail expression. It is not in scope in its own initializer. - Bindings are immutable, and nothing reassigns one. - A name already in scope — a parameter, an earlier `let`, an enclosing block's `let` — cannot be bound again. - With an annotation, the initializer must be assignable to it. - Without an annotation, the initializer's type must be fixed at every position. An unpinned numeric literal, a bare `none`, or an empty array anywhere inside it leaves nothing to infer from. - A tuple pattern needs one element per element of the tuple. `_` discards without binding and may appear more than once. - A tuple element may carry its own `: Type`. The annotation after the pattern belongs to the `let` itself. ```flex function correct(fix: (float64, float64), raw_bearing_deg: float64) -> float64 = { let (lat_deg, _) = fix; let declination_deg = 7.5: float64; let bearing_deg: float64 = raw_bearing_deg + declination_deg; bearing_deg + lat_deg; }; ``` **let Is Not a Top-Level Declaration** A `let` binds a name inside a block, whereas the form that binds at module scope is a [`const`](https://flexlang.org/flex/data-types/const.md). ### assert `assert` evaluates a condition and stops the enclosing block when it is false. It is a statement, not an expression, and produces no value. - The condition has type `bit`. - A failed assertion is a catchable failure, so [`try`](https://flexlang.org/flex/expressions/try.md) turns it into `none`. - An assertion is also the last statement of a [`contract`](https://flexlang.org/flex/metadata/contract.md) body, where it states the property being verified rather than guarding a computation. ```flex eval function halve(d: float64) -> float64 = { assert d > 0.0; d / 2.0; }; halve((10.0: float64)) // => 5 try(halve((10.0: float64))) // => some(5) try(halve((-1.0: float64))) // => none ``` **Assertions Always Run** An `assert` is part of what the program means, not a debug check a build mode removes. A failed one is a value the program produces — `try` turns it into `none` — so dropping it would change the result. ### Errors #### Binding a name that is already in scope ```flex invalid function report(track_id: uint32) -> uint32 = { let track_id: uint32 = 7; // error: illegal-shadowing track_id; }; ``` #### An initializer with no type of its own ```flex invalid function ready(armed: bit) -> bit = { let threshold = 5; // error: ambiguous-inferred-type armed; }; ``` #### A tuple pattern of the wrong length ```flex invalid function latitude(fix: (float64, float64, float64)) -> float64 = { let (lat_deg, lon_deg) = fix; // error: tuple-pattern-arity lat_deg; }; ``` #### An assertion whose condition is not bit ```flex invalid function halve(d: float64) -> float64 = { assert "positive"; // error: assert-condition-type d / 2.0; }; ``` #### Naming a binding outside the block that made it ```flex invalid function offset_range(raw_range_m: float64) -> float64 = { let corrected: float64 = { let bias_m: float64 = 12.5; raw_range_m - bias_m; }; corrected - bias_m; // error: unresolved-reference }; ``` ### Related - [if](https://flexlang.org/flex/expressions/if.md) — the statement that also belongs in a block, and the expression that has a value. - [try](https://flexlang.org/flex/expressions/try.md) — what a failed assertion becomes. - [literals](https://flexlang.org/flex/expressions/literals.md) — the ascription that gives an initializer a type. --- ## if A choice between two branches on a `bit` condition. As an expression it has a value and the `else` is mandatory; as a statement it has no value and each branch is a list of statements. ```flex const raw_bearing_deg: int32 = 400; const bearing_deg: int32 = if raw_bearing_deg > 360 then raw_bearing_deg - 360 else raw_bearing_deg; ``` ### Branches The condition has type `bit`, only the taken branch evaluates, and the whole `if` has the type the two branches share. - The `else` is required. An `if` without one is a parse error. - Both branches must have a common type, which is the type of the expression. - Two structs in the same hierarchy have their nearest common ancestor as that type. - Two different numeric widths have no common type. `int32` and `int64` branches are rejected. - There is no `else if`. The else branch is another `if`, which is why the chain reads as one. ```flex extensible struct Contact { contact_id: uint32; } struct Radar extends Contact { scan_period_ms: uint32; } struct Sonar extends Contact { depth_m: uint32; } const from_air: bit = true; const primary: Contact = if from_air then Radar { contact_id = 1; scan_period_ms = 2000; } else Sonar { contact_id = 2; depth_m = 40; }; const range_m: int32 = 8000; const band: int32 = if range_m > 10000 then 3 else if range_m > 5000 then 2 else 1; ``` ### Spellings `then` is optional, parentheses around the condition are just a parenthesized expression, and a braced branch is just a [block](https://flexlang.org/flex/expressions/block-let-assert.md). None of them is a separate form, so they mix freely and all of these are the same `if`. ```flex eval const armed: bit = true; if armed then "on" else "off" // => "on" if armed "on" else "off" // => "on" if (armed) then "on" else "off" // => "on" if armed { "on"; } else { "off"; } // => "on" if armed then { "on"; } else "off" // => "on" ``` A `{` after the condition always opens the then-branch, so a condition that ends in a struct literal has to be parenthesized. ```flex struct Threshold { limit_deg: uint16; } const over: bit = if (Threshold { limit_deg = 90; }).limit_deg > 0 { true; } else { false; }; ``` ### Statement form Written with a bare then-branch, `if` is a statement: it produces no value and each branch is a list of statements. It belongs in a block before the tail expression, or in a contract body. - The then-branch decides which form this is. Bare makes it a statement; braced makes it an expression, and both branches then need a tail value. - Given a bare then-branch, the `else` may be one bare statement or a braced list of them. The `else` is still mandatory. - Each branch is its own scope. A `let` in a branch is invisible to the other branch and to everything after the `if`, where it would otherwise exist on only one execution path. - A contract body has no tail expression, so both branches may be braced statement lists there. ```flex function bounded(raw_range_m: int32) -> int32 = { if raw_range_m > 0 then assert raw_range_m < 100000; else assert raw_range_m > -100000; raw_range_m; }; contract InSector(bearing_deg: int32) { if bearing_deg > 180 { assert bearing_deg <= 360; } else { assert bearing_deg >= 0; } assert true; } ``` ### Errors #### A condition that is not bit ```flex invalid const armed: bit = if (1: int32) then true else false; // error: type-mismatch ``` #### Branches with no common type ```flex invalid const label: string = if true then "inbound" else 42; // error: incompatible-branch-types ``` #### Branches of two different numeric widths ```flex invalid const range_m: int64 = if true then (1: int32) else (2: int64); // error: incompatible-branch-types ``` ### Related - [block, let, assert](https://flexlang.org/flex/expressions/block-let-assert.md) — the statements a branch is made of, and the block a braced branch is. - [match](https://flexlang.org/flex/expressions/match.md) — the choice over more than two cases, which also destructures. - [operators](https://flexlang.org/flex/expressions/operators.md) — the comparisons a condition is usually built from. --- ## literals The forms that write a value out directly: the primitive literals, and the composite forms for a struct, a tuple, and an array. ```flex const tracking_enabled: bit = true; const home_station: string = "GroundStation-1"; const max_tracks: uint16 = 512; const earth_radius_m: float64 = 6378137.0; ``` ### Primitive literals One form per primitive type. A numeric literal carries no width of its own — see [numeric type inference](#numeric-type-inference) for what pins it. - `true` and `false` are the `bit` literals. - A string literal is `"`-delimited, terminates on the line it opens, and recognizes the escapes `\"`, `\\`, `\n`, `\r`, and `\t`. - An integer literal is decimal, `0x` hexadecimal, or `0b` binary, both prefixes lowercase. - A decimal literal needs an integer part, a `.`, and a fractional part, so `0.5` parses and `.5` does not. - `_` separates decimal digits. A literal may not open or close with one, hold two in a row, or place one before the exponent marker. - An `e`/`E` exponent is a positive integer. The hexadecimal and binary forms accept neither `_` nor an exponent. ```flex const label: string = "she said \"track\""; const count: uint32 = 1_000_000; const mask: uint8 = 0b10100000; const signature: uint32 = 0x99aaBB; const scale: float64 = 8.02838E16; ``` ### Ascription `expr : Type` pins an expression's type. It constrains inference and performs no conversion. - The ascribed type becomes the expression's type, and an inner type it does not accept is a mismatch. - Ascription binds below every operator, so an un-grouped ascription reaches as far left as it can: `1 + 2: int32` ascribes the sum. - Parentheses are what ascribe one operand: `(1: int32) + 2`. ```flex eval (42: int16) // => 42 (1 + 2: int32) // => 3 (0.5: float32) // => 0.5 ``` ### Numeric type inference A numeric literal stands for a width still to be decided, and unification with the constraints around it decides one. Other forms open the same hole: an empty `[]` and a context-less `none`. - Constraints come from an ascription, a typed binding, and a declared type — a `const`'s annotation, a return type, a parameter type, a struct field type — as well as from the operators applied. - Enforcement recurses through structure, so a declared `int32[]` return type pins the literals in a comprehension over `[1, 2, 3]`. - Inference is solved one declaration at a time. No constraint crosses a declaration boundary, so a use of a declaration reads its declared type and never its initializer's holes. - A width still unconstrained once its declaration is solved is reported. No width is guessed. ```flex function scaled(factor: int32) -> int32[] = map(x in [1, 2, 3]) { x * factor; }; ``` ### Struct literals `TypeRef { field = expr; … }` — one binding per field, each closed by a semicolon. The literal's type is the referenced declaration. - Every field gets exactly one binding, fields inherited through `extends` included. - Bindings may appear in any order, and each must be assignable to the field's declared type. - A zero-field struct is written `Empty{}`. - `TypeRef { base with field = expr; }` copies `base` and overrides the fields it names, so an update binds only what it changes. ```flex struct TrackReport { track_id: uint32; bearing_deg: float32; } const initial: TrackReport = TrackReport { track_id = 1; bearing_deg = 87.5; }; const turned: TrackReport = TrackReport { initial with bearing_deg = 91.0; }; ``` ### Tuple literals A parenthesized list of two or more expressions, typed by its elements in order. - A single parenthesized expression is a grouping, not a one-element tuple. - Each element's type is determined independently. - An element may carry its own ascription, or the whole tuple may be ascribed. ```flex eval const fix: (float64, float64) = (42.3601, -71.0589); (1: int32, 2: int32) // => (1, 2) ((1, 2): (int32, int32)) // => (1, 2) fix._1 // => -71.0589 ``` ### Array literals A bracketed list of expressions of one type. A `for` after the first element makes it a comprehension instead of a list. - All elements share one type. - An empty `[]` takes its element type from context. - `++` concatenates two arrays of one element type. ```flex eval const bearings: float32[] = [87.5, 91.0]; bearings ++ [(274.25: float32)] // => [87.5, 91, 274.25] length(bearings) // => 2 ``` ### Errors #### A numeric literal nothing pins The width has to come from somewhere. An unconstrained literal is reported rather than defaulted. ```flex invalid function total(count: uint32) -> uint32 = { let scaled = 2 * 8; // error: ambiguous-inferred-type count; }; ``` #### A field the struct does not declare ```flex invalid struct TrackReport { track_id: uint32; } const initial: TrackReport = TrackReport { track_id = 1; bearing_deg = 87.5; }; // error: invalid-member-access ``` #### A field bound twice ```flex invalid struct TrackReport { track_id: uint32; bearing_deg: float32; } const initial: TrackReport = TrackReport { track_id = 1; track_id = 2; bearing_deg = 87.5; }; // error: duplicate-field-binding ``` #### A field left unbound A literal states the whole value, so every field needs a binding. An update is the form that changes part of one. ```flex invalid struct TrackReport { track_id: uint32; bearing_deg: float32; } const initial: TrackReport = TrackReport { track_id = 1; }; // error: missing-field ``` ### Related - [operators](https://flexlang.org/flex/expressions/operators.md) — what may be applied to a literal, and at what precedence. - [struct](https://flexlang.org/flex/data-types/struct.md) — the declaration a struct literal names. - [array](https://flexlang.org/flex/data-types/array.md) — indexing, slicing, and the array type itself. --- ## match A test of one value against ordered arms, where the first pattern that matches decides the result. It branches and destructures in one form. ```flex variant Angle { DMS(int32, int32, int32); Radians(float64); } function whole_degrees(a: Angle) -> int32 = match (a) { Angle.DMS(deg, min, sec) => deg; Angle.Radians(rad) => 0; }; ``` ### Arms An arm is a pattern, `=>`, and a body. The body is either an expression closed by a semicolon or a bare block, which needs no semicolon after it. - A `match` has at least one arm, and arms are tried in order. - Every arm body has the same type, and that type is the match's type. - No arm matching at runtime is a runtime failure, so `try` turns it into `none`. ```flex eval variant Angle { DMS(int32, int32, int32); Radians(float64); } function whole_degrees(a: Angle) -> int32 = match (a) { Angle.DMS(deg, min, sec) => deg; Angle.Radians(rad) => 0; }; function unwrap_or(v: Optional, fallback: int32) -> int32 = match (v) { some(x) => x; none => fallback; }; whole_degrees(Angle.DMS(45, 30, 0)) // => 45 unwrap_or(some((7: int32)), 0) // => 7 unwrap_or(none, 0) // => 0 match (true) { true => "on"; false => "off"; } // => "on" ``` ### Patterns A pattern either tests the value's shape or binds it, and the two compose. - `_` matches anything and binds nothing. It may appear more than once. - A literal matches that value. - `some(p)` and `none` match an optional. - A path with an optional argument list matches a variant constructor or an enum member, qualified as `Angle.Radians(r)` or bare as `Radians(r)`. - A parenthesized list matches a tuple position-wise, and a bracketed list matches an array. - A bare name binds whatever it is tested against. - `pat as Type` and `pat : Type` both ascribe a full type, acting as a downcast that matches only values of that type. - A pattern's bindings are in scope in that arm's body and nowhere else, and they may not shadow a name from an enclosing scope. ```flex extensible struct Contact { contact_id: uint32; } struct Radar extends Contact { scan_period_ms: uint32; } function describe(c: Contact) -> string = match (c) { r as Radar => "radar"; _ as Contact => "other"; }; ``` **Arms Match Shape, Not a Condition** A pattern says what the value looks like and nothing more, so `some(x) if x > 0 =>` is not something Flex accepts. To narrow past the shape, match the shape and then test the condition with an [`if`](https://flexlang.org/flex/expressions/if.md) in the arm's body. ### Exhaustiveness A `match` that does not cover its discriminee is reported as a warning. The program still checks, and the checker does not reject it. - `bit` is covered by `true` and `false`, an enum by all its members, a variant by all its constructors, and `Optional` by `some(_)` and `none`. - An `extensible` struct needs a wildcard arm, because a subtype may be declared in another file. - Numbers, strings, arrays, tuples, and non-extensible structs need a catch-all, since enumerating their values is impractical. - A tuple is also covered by a tuple pattern whose every element pattern is itself irrefutable, as `(a, b)` is. ### Errors #### Arms whose bodies have different types ```flex invalid function label(kind: uint8) -> string = match (kind) { 0 => "air"; _ => kind; // error: incompatible-branch-types }; ``` #### A pattern the discriminee's type cannot match A pattern's shape has to be one the value could have: a tuple pattern needs the right arity, a variant pattern the right constructor, a struct ascription a type in the discriminee's hierarchy. ```flex invalid function latitude(fix: (float64, float64)) -> float64 = match (fix) { (lat, lon, alt) => lat; // error: incompatible-pattern _ => 0.0; }; ``` #### A discriminee the arms do not cover ```flex invalid variant Angle { DMS(int32, int32, int32); Radians(float64); } function whole_degrees(a: Angle) -> int32 = match (a) { // warning: non-exhaustive-match Angle.DMS(deg, min, sec) => deg; }; ``` ### Related - [variant](https://flexlang.org/flex/data-types/variant.md) — the constructors a variant pattern names. - [optional](https://flexlang.org/flex/data-types/optional.md) — the `some`/`none` pair a match opens. - [enum](https://flexlang.org/flex/data-types/enum.md) — the members an enum match covers. - [if](https://flexlang.org/flex/expressions/if.md) — the choice between two branches, with no pattern. --- ## operators The arithmetic, concatenation, comparison, equality, logical, bitwise, and shift operators. Every binary operator requires its two operands to have the same type. ```flex eval const sum: int32 = (42: int32) + 8; sum // => 50 "hello" ++ " world" // => "hello world" (40: int32) < 42 // => true ``` ### Precedence Binary operators are left-associative and the unary prefixes are right-associative. The levels, from the one that binds loosest to the one that binds tightest: - `||` and `|` - `^` - `&&` and `&` - `==` `!=` `<` `<=` `>` `>=` `<<` `>>` - `++` - `+` `-` - `*` `/` `%` - unary `!` `~` `-` - postfix `.` `[]` `()` Some of those placements differ from a C-style layout. `^` sits on its own level between AND and OR. Equality, comparison, and the shifts all share one level. And `++` binds tighter than `+` and `-`, so `a ++ b + c` groups as `a ++ (b + c)`. Ascription binds below every operator, looser than `||`. ### Arithmetic `+ - * / %` are defined over every numeric type, and the result is the operand type. - A non-numeric operand is rejected; `++` is the operator for strings and arrays. - Unary `-` is defined on signed numerics only, so it does not apply to a `uintN`. - Division truncates toward zero, and `%` takes the sign of its left operand. - Division and modulo by zero are runtime failures, so `try` turns one into `none`. ```flex eval (-7: int32) / 2 // => -3 (-7: int32) % 2 // => -1 try((7: int32) / 0) // => none ``` ### Concatenation `++` joins two strings, or two arrays of one element type, and yields that same type. Mixing a string with a non-string, or arrays of different element types, is rejected. ### Comparison `< <= > >=` are defined over the numeric types and yield `bit`. - Comparison is total: it always yields a `bit` and never fails. - A `NaN` operand is unordered, so all of them yield `false` when either side is `NaN`. - One consequence is worth stating rather than tripping over: `!(a < b)` is not `a >= b` when either operand may be `NaN`, because both are then `false`. ```flex eval sqrt((-1.0: float64)) < (1.0: float64) // => false sqrt((-1.0: float64)) >= (1.0: float64) // => false ``` ### Equality `==` and `!=` are defined over any one type shared by both operands, and yield `bit`. `x != y` is `!(x == y)`. - Equality is structural: primitives by value, tuples and arrays element-wise, structs field-wise with field order irrelevant, variants by constructor and payload. - Arrays must match in length as well as contents. - `NaN == NaN` is `false`, following IEEE-754. ```flex eval const head: uint8[] = [1, 2]; const longer: uint8[] = [1, 2, 3]; (true, "air") == (true, "air") // => true head == longer // => false ``` ### Logical and bitwise `&&` and `||` are dual. On `bit` they are short-circuiting logical AND and OR, so the right operand goes unevaluated once the left decides the result; on `intN`/`uintN` they are bitwise AND and OR, and both operands are always evaluated. - `&` and `|` are synonyms for `&&` and `||`, at the same precedence and with the same meaning. - `^` is XOR — bitwise on integers, logical on `bit`. - Unary `!` and its synonym `~` are dual the same way: logical negation on a `bit`, bitwise complement on an integer, yielding the operand's own type. - None of these are defined on the float types. ```flex eval true && false // => false (5: uint8) || 3 // => 7 (5: uint8) ^ 3 // => 6 !(5: uint32) // => 4294967290 ``` ### Shifts `<<` and `>>` are defined over `intN`/`uintN`, and the result is the left operand's type. `>>` is lexed as two adjacent `>` tokens, which is why `Optional>` closes correctly. - Both operands are the same integer type. - The shift amount is in `[0, N)` for an `N`-bit operand. A literal amount at or above the width is rejected at check time; a runtime amount out of range yields zero. - `<<` zero-fills the low bits. `>>` zero-fills on a `uintN` and sign-extends on an `intN`. ```flex eval (0b01000000: uint8) << 1 // => 128 (0b10000000: uint8) >> 1 // => 64 (-8: int64) >> 2 // => -2 ``` ### Overflow Integer arithmetic is fixed-width and wraps: a `uintN` result reduces modulo `2^N`, and an `intN` result wraps two's-complement. Floats follow IEEE-754, where overflow yields `inf` and an invalid operation yields `NaN`. - Wrapping happens at the width inference pinned, so the same literals wrap differently under different annotations. - A `value_cast` is not subject to the wrapping rule. It widens losslessly or reports absence. - Evaluation never panics on a numeric operation. ```flex eval const limit: uint8 = 250; const ceiling: int8 = 127; limit + 10 // => 4 ceiling + 1 // => -128 ``` ### Operand types The type each operator group accepts, and what it yields: - `+ - * / %` — matching numerics, yielding the operand type. - `++` — two strings or two arrays of one element type, yielding that type. - `< <= > >=` — matching numerics, yielding `bit`. - `== !=` — any one type, yielding `bit`. - `&& || & | ^` — both `bit` or matching integers, yielding the operand type. - `<< >>` — matching integers, yielding the left operand's type. - unary `! ~` — a `bit` or an integer, yielding the operand type. - unary `-` — a signed numeric, yielding the operand type. ### Errors #### Operands the operator does not accept Both operands must have the same type, and each operator accepts only some types — there is no promotion between widths and no coercion between categories. ```flex invalid const mixed: int32 = (42: int32) + (43: int16); // error: invalid-operand-types const crossed: bit = (42: int32) == (42.0: float64); // error: invalid-operand-types const negated: uint8 = -(5: uint8); // error: invalid-operand-types const flipped: float32 = !(3.14: float32); // error: invalid-operand-types const shifted: uint8 = (1: uint8) << 8; // error: invalid-operand-types ``` ### Related - [primitives](https://flexlang.org/flex/data-types/primitives.md) — the types these operators are defined over. - [value-cast](https://flexlang.org/flex/expressions/value-cast.md) — the conversion between two numeric types. - [literals](https://flexlang.org/flex/expressions/literals.md) — how an operand's width gets pinned. --- ## try An expression whose failure becomes an absent value. `try(expr)` gives `some(v)` when `expr` succeeds. It gives `none` where `expr` would otherwise stop the program. ```flex const range_m: int32 = 8000; const sweeps: int32 = 0; const per_sweep: Optional = try(range_m / sweeps); ``` ### Failures `try` takes exactly one argument, and for an argument of type `T` the result is `Optional`. - Applying it to an expression that cannot fail is legal, and always gives `some`. - It catches these failures: division and modulo by zero, an out-of-bounds index, `last` on an empty array, out-of-range `substring` indices, a `range` step of `0`, and a failed `assert`. - It catches nothing the checker reports. A type error is not a failure a running program can reach. ```flex eval const bearings_deg: int32[] = [10, 45, 90]; try((100: int32) / (0: int32)) // => none try((100: int32) % (0: int32)) // => none try(bearings_deg[(9: uint32)]) // => none try(last([]: int32[])) // => none try(substring("inbound", 0, 9)) // => none try(bearings_deg[(1: uint32)]) // => some(45) ``` **There Are No Exceptions** Flex has no `throw`, no handler, and no error value: a failure is either something the checker rejects or one of the runtime failures this page lists. `try` is the only form that observes one, and an absent value is the only outcome it produces. ### What is not a failure Some things that look like failures are ordinary results, so `try` returns `some` of them. - Out-of-domain float math gives the IEEE-754 `NaN` value. `sqrt` of a negative number, and `asin` or `acos` outside `[-1.0, 1.0]`, are `some(NaN)`. - An out-of-bounds slice clamps to the array and can only come back empty, never absent. Only a plain index fails. ```flex eval const bearings_deg: int32[] = [10, 45, 90]; try(sqrt((-1.0: float64))) // => some(NaN) try(asin((2.0: float64))) // => some(NaN) try(bearings_deg[(1: uint32)..(9: uint32)]) // => some([45, 90]) try(bearings_deg[(8: uint32)..(9: uint32)]) // => some([]) ``` ### Errors #### Using the result where the plain value is expected `try` changes the type of the expression it wraps, so something has to open the optional. ```flex invalid const per_sweep: int32 = try((100: int32) / (2: int32)); // error: type-mismatch ``` ### Related - [optional](https://flexlang.org/flex/data-types/optional.md) — the type `try` produces, and how to read it back. - [match](https://flexlang.org/flex/expressions/match.md) — the form that handles both outcomes. - [block, let, assert](https://flexlang.org/flex/expressions/block-let-assert.md) — the assertion `try` turns into `none`. --- ## value_cast An explicit conversion between numeric types. `value_cast(expr)` returns `T`. `value_cast?(expr)` returns `Optional`, and is `none` when the value does not fit. ```flex const narrow: int16 = 42; const wide: int32 = value_cast(narrow); const checked: Optional = value_cast?(narrow); ``` ### Casting Flex has no implicit conversion between numeric types, which is what makes the cast the only route. - Source and target must both be numeric: an `intN`, a `uintN`, a `float32`, or a `float64`. - `value_cast` requires a widening pair — `uintN` to `uintM` for M at least N, `intN` to `intM` for M at least N, `uintN` to `intM` for M greater than N, and `float32` to `float64`. Every other pair needs `value_cast?`. - `value_cast?` returns `Optional` and is the form for a pair `value_cast` will not take. - `float64` to `float32` goes through the `float64to32` built-in, which is a function rather than a cast because it loses precision. - A cast is not subject to the wrapping rule that governs arithmetic. It either widens losslessly or reports absence. ```flex eval const narrow: int16 = 42; value_cast(narrow) // => 42 value_cast((0.5: float32)) // => 0.5 value_cast?((127: int16)) // => some(127) value_cast?((128: int16)) // => none getOrElse(value_cast?((42: int8)), 0) // => 42 float64to32((0.5: float64)) // => 0.5 ``` ### Errors #### A source that is not numeric ```flex invalid const flag: bit = true; const code: int32 = value_cast(flag); // error: value-cast-source-type ``` #### A target that is not numeric ```flex invalid const label: string = value_cast((42: int32)); // error: value-cast-target-type ``` #### A narrowing conversion asked for infallibly `value_cast` promises a value of the target type, which it cannot do for a pair that may not fit. ```flex invalid const narrow: int8 = value_cast((128: int16)); // error: value-cast-widening ``` #### A `float64` narrowed to a `float32` `float64to32` is the supported narrowing, and it is a built-in function rather than a cast. ```flex invalid const ratio: float32 = value_cast((0.5: float64)); // error: value-cast-target-type, value-cast-widening ``` ### Related - [primitives](https://flexlang.org/flex/data-types/primitives.md) — the numeric types and their assignability rule. - [optional](https://flexlang.org/flex/data-types/optional.md) — what the fallible form returns. - [operators](https://flexlang.org/flex/expressions/operators.md) — the wrapping arithmetic a cast is exempt from. - [builtin functions](https://flexlang.org/flex/functions/builtin-functions.md) — `float64to32`, and the other conversions that are functions. --- ## builtin functions The fixed library of functions every module already has. There is no import and no backing declaration; they are called like any other function. ```flex const bearings_deg: int32[] = [10, 45, 90]; const count: uint32 = length(bearings_deg); const widest_deg: int32 = last(bearings_deg); ``` ### Names These names are built in: `length`, `last`, `range`, `zip`, `enumerate`, `float64to32`, `floor`, `ceil`, `abs`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sqrt`, `pow`, `getOrElse`, `serialize`, and `substring`. - A declaration cannot take one of these names. - Arity and argument types are checked per function, and a mismatch is one diagnostic whatever the function. - A few are polymorphic over an element type. That polymorphism belongs to the built-ins only, and Flex has no generics of its own. **There Is No Standard Library** These names are the whole library, and nothing extends them: an `import` reaches only modules of your own project. ### Collections - `length(xs: T[]) -> uint32` — the element count, always `uint32` whatever `T` is. - `last(xs: T[]) -> T` — the final element. An empty array is a runtime failure. - `range(end)`, `range(start, end)`, `range(start, end, step)` — an integer array. Every argument is the same integer type, and the result is an array of it. - `zip(as: A[], bs: B[]) -> (A, B)[]` — element-wise pairing, as long as the shorter input. - `enumerate(xs: T[]) -> (uint32, T)[]` — each element paired with its index, from `0`. `range` treats `end` as exclusive. The one-argument form starts at `0` and steps by `1`, the two-argument form steps by `1`, and a step that never reaches `end` gives an empty array. A step of `0` is a runtime failure. ```flex eval const callsigns: string[] = ["Viper-1", "Hawk-2"]; const bearings_deg: int32[] = [10, 45, 90]; length(callsigns) // => 2 last(bearings_deg) // => 90 range((4: uint8)) // => [0, 1, 2, 3] range((0: int32), (360: int32), (90: int32)) // => [0, 90, 180, 270] zip(bearings_deg, callsigns) // => [(10, "Viper-1"), (45, "Hawk-2")] enumerate(callsigns) // => [(0, "Viper-1"), (1, "Hawk-2")] try(last([]: int32[])) // => none ``` ### Numbers - `abs(x: T) -> T` — absolute value for any numeric `T`. On an unsigned type it is the identity. - `floor(x: float64) -> Optional` — the largest integer at or below `x`, absent when that does not fit in an `int64`. - `ceil(x: float64) -> Optional` — the smallest integer at or above `x`, absent on the same terms. - `float64to32(x: float64) -> float32` — the narrowing to the nearest `float32`. It is the only supported route between the float types. - `sqrt(x: float64) -> float64` — the square root. A negative argument gives `NaN` rather than failing. - `pow(x: float64, y: float64) -> float64` — `x` raised to `y`. - `serialize(x: T) -> uint8[]` — the big-endian bytes of a numeric value. `serialize` needs a type that has a byte width: an `intN` or `uintN` whose width is a multiple of 8, a `float32`, or a `float64`. An `intN`/`uintN` gives `N/8` bytes most-significant first, and a float gives the bytes of its IEEE-754 pattern. Anything else is rejected at check time, because both the type and the width are already known. ```flex eval abs((-120: int32)) // => 120 abs((120: uint32)) // => 120 floor((87.6: float64)) // => some(87) floor((-2.3: float64)) // => some(-3) ceil((87.2: float64)) // => some(88) float64to32((87.5: float64)) // => 87.5 sqrt((16.0: float64)) // => 4 pow((2.0: float64), (8.0: float64)) // => 256 serialize((0x0102: uint16)) // => [1, 2] serialize((-1: int8)) // => [255] serialize((87.5: float32)) // => [66, 175, 0, 0] ``` ### Trigonometry - `sin(x: float64) -> float64`, `cos(x: float64) -> float64`, `tan(x: float64) -> float64` — the argument is in radians. - `asin(x: float64) -> float64`, `acos(x: float64) -> float64`, `atan(x: float64) -> float64` — the result is in radians. - `atan2(y: float64, x: float64) -> float64` — the polar angle of `(x, y)`, in radians over `(-π, π]`. `asin` and `acos` outside `[-1.0, 1.0]` give `NaN`, which is a value and not a failure. `atan2` takes the ordinate first and the abscissa second. ```flex eval cos((0.0: float64)) // => 1 asin((1.0: float64)) // => 1.5707963267948966 atan2((1.0: float64), (1.0: float64)) // => 0.7853981633974483 try(asin((2.0: float64))) // => some(NaN) ``` ### Optionals - `getOrElse(x: Optional, fallback: T) -> T` — the contained value, or the fallback when there is none. The first argument must be an `Optional`, and the fallback must be its inner type. ```flex eval getOrElse(some((512: uint16)), 0) // => 512 getOrElse((none: Optional), (0: uint16)) // => 0 ``` ### Strings - `substring(s: string, start: uint32, end: uint32) -> string` — the characters over the half-open range `[start, end)`. Both indices are integer types. A `start` past `end`, or an `end` past the length of the string, is a runtime failure. ```flex eval substring("GroundStation-1", 0, 6) // => "Ground" substring("Viper-1", 6, 7) // => "1" try(substring("Viper-1", 0, 99)) // => none ``` ### Errors #### Taking a built-in name for a declaration ```flex invalid const length: uint32 = 3; // error: reserved-builtin-name ``` #### An argument of the wrong type ```flex invalid const count: uint32 = length((42: int32)); // error: builtin-call-args ``` #### The wrong number of arguments ```flex invalid const count: uint32 = length([(1: int32), 2], [(3: int32)]); // error: builtin-call-args ``` #### Serializing something with no byte width ```flex invalid const bytes: uint8[] = serialize("Viper-1"); // error: builtin-call-args ``` #### Serializing a width that is not a whole number of bytes ```flex invalid const bytes: uint8[] = serialize((1: uint7)); // error: builtin-call-args ``` ### Related - [function](https://flexlang.org/flex/functions/function.md) — the declaration a built-in name cannot take, and the call form these share. - [value_cast](https://flexlang.org/flex/expressions/value-cast.md) — the numeric conversions that are forms rather than functions. - [array](https://flexlang.org/flex/data-types/array.md) — the collections most of these take. - [optional](https://flexlang.org/flex/data-types/optional.md) — what `floor`, `ceil`, and `getOrElse` deal in. --- ## function A top-level declaration taking at least one parameter and returning a declared type. The body is either an expression after `=` or a block whose tail expression is the return value. ```flex function slant_range_m(ground_range_m: float64, altitude_m: float64) -> float64 = sqrt(ground_range_m * ground_range_m + altitude_m * altitude_m); function classify(range_m: int32) -> string { let near: bit = range_m < 5000; if near then "near" else "far"; } ``` ### Declarations A function may be imported by another module. Parameter types and the return type may be any type. - An empty parameter list is a parse error. - Parameter names are unique within the declaration. - The function's name is unique among the file's declarations. - The body must be assignable to the declared return type. - Parameters are in scope in the body and nowhere else. - The function's own name is in scope in its body, so it may call itself. A recursive call types at the declared return type. The body forms differ only in punctuation. An expression body is `= expr;`. A block body is a braced block with no `=` and no closing semicolon. A block is itself an expression, so `= { … };` is the same body written the other way. ```flex function bearing_delta_deg(from_deg: int32, to_deg: int32) -> int32 = (to_deg - from_deg) % 360; function countdown(remaining: int32) -> int32 = if remaining <= 0 then 0 else countdown(remaining - 1); function sweep_area_m2(diameter_m: float64) -> float64 = { let radius_m: float64 = diameter_m / 2.0; 3.14159 * radius_m * radius_m; }; ``` ### Calls `callee(args)` is a postfix form. The result type is the callee's declared return type. - The argument count must match the declared parameter count. - Arguments match by position, and each must be assignable to its parameter's type. - A numeric literal argument pins to the parameter's type, so it needs no ascription. - A call reached through a module alias is written `alias.name(args)`. - A call result takes further postfix operators, so `make_point(38.9, -77.0).lat_deg` is one expression. ```flex eval struct GeoPoint { lat_deg: float64; lon_deg: float64; } function make_point(lat_deg: float64, lon_deg: float64) -> GeoPoint = GeoPoint { lat_deg = lat_deg; lon_deg = lon_deg; }; function add(a: int32, b: int32) -> int32 = a + b; add(40, 2) // => 42 make_point(38.9, -77.0).lat_deg // => 38.9 ``` **Functions Are Not Values** There is no function type: a function cannot be passed as an argument, returned, or held in a `let`. A call resolves to a declaration, which is why `map` and `fold` take a block body rather than a function to apply. ### Errors #### Declaring the same parameter twice ```flex invalid function bearing_delta_deg(from_deg: int32, from_deg: int32) -> int32 = // error: duplicate-param from_deg % 360; ``` #### A body that is not the return type ```flex invalid function classify(range_m: int32) -> string = range_m + 1; // error: type-mismatch ``` #### Calling with the wrong number of arguments ```flex invalid function add(a: int32, b: int32) -> int32 = a + b; const total: int32 = add(40); // error: call-arg-count ``` #### An argument of the wrong type ```flex invalid function add(a: int32, b: int32) -> int32 = a + b; const total: int32 = add("Viper-1", 2); // error: call-arg-type ``` ### Related - [transform](https://flexlang.org/flex/functions/transform.md) — the same shape restricted to message types, with contextual parameters. - [contract](https://flexlang.org/flex/metadata/contract.md) — the other named body, and the one nothing calls. - [builtin functions](https://flexlang.org/flex/functions/builtin-functions.md) — the functions already in scope with no declaration. - [block, let, assert](https://flexlang.org/flex/expressions/block-let-assert.md) — the statements a block body holds. --- ## transform A named translation between message types. Every parameter type and the return type must resolve to a non-abstract `message` struct. A `given` clause may add parameters that the call site supplies by type. ```flex message struct Celsius { temp_c: float64; } message struct Fahrenheit { temp_f: float64; } transform CelsiusToFahrenheit(reading: Celsius) -> Fahrenheit = Fahrenheit { temp_f = reading.temp_c * 9.0 / 5.0 + 32.0; }; ``` ### Declarations A transform is declared and called like a [`function`](https://flexlang.org/flex/functions/function.md), and the result type is the declared return type. - Each parameter type must be a `message` struct that is not `abstract`. - The return type must be a `message` struct that is not `abstract`. - A transform declares at least one parameter. An empty `()` parses, and the checker rejects it. - Parameter names are unique, contextual parameters included. - The body must be assignable to the return type. ```flex eval message struct Celsius { temp_c: float64; } message struct Fahrenheit { temp_f: float64; } transform CelsiusToFahrenheit(reading: Celsius) -> Fahrenheit = Fahrenheit { temp_f = reading.temp_c * 9.0 / 5.0 + 32.0; }; CelsiusToFahrenheit(Celsius { temp_c = 100.0; }) // => Fahrenheit { temp_f: 212 } ``` ### Contextual parameters A `given` clause declares parameters that travel with the call rather than in its argument list. Each is `?name: Type`, and the body refers to one as `?name`. - A `given` clause holds at least one contextual parameter. - Their types are pairwise distinct within the clause. - `?name` is valid only inside a transform that declares it. - A call supplies them explicitly with a `giving (…)` postfix clause, one argument per contextual parameter, matched by position. - With `giving` omitted, each contextual parameter of the callee is matched by type against the enclosing transform's own `given` clause. Distinct types make that match unambiguous. - An explicit `giving` clause bypasses the implicit match. - A contextual transform called with neither an explicit clause nor a matching enclosing one is an error. ```flex newtype SensorId { value: uint32; } message struct GroundFix { lat_deg: float64; } message struct TrackReport { lat_deg: float64; sensor_id: uint32; } message struct TrackBatch { lat_deg: float64; sensor_id: uint32; } transform FixToTrack(fix: GroundFix) -> TrackReport given (?sensor: SensorId) = TrackReport { lat_deg = fix.lat_deg; sensor_id = ?sensor.value; }; transform FixToBatch(fix: GroundFix) -> TrackBatch given (?sensor: SensorId) = { let report: TrackReport = FixToTrack(fix); TrackBatch { lat_deg = report.lat_deg; sensor_id = report.sensor_id; }; }; const fix: GroundFix = GroundFix { lat_deg = 38.9; }; const radar: SensorId = SensorId { 7 }; const report: TrackReport = FixToTrack(fix) giving (radar); ``` `FixToTrack(fix)` inside `FixToBatch` names no context of its own. Its `?sensor` is filled from `FixToBatch`'s `given` clause, which declares the same type. ### Errors #### A transform with no parameters ```flex invalid message struct Fahrenheit { temp_f: float64; } transform Freezing() -> Fahrenheit = Fahrenheit { temp_f = 32.0; }; // error: transform-min-params ``` #### A parameter that is not a message struct ```flex invalid struct Celsius { temp_c: float64; } message struct Fahrenheit { temp_f: float64; } transform CelsiusToFahrenheit(reading: Celsius) -> Fahrenheit = // error: transform-param-type Fahrenheit { temp_f = 32.0; }; ``` #### A return type that is not a message struct ```flex invalid message struct Celsius { temp_c: float64; } transform CelsiusToKelvin(reading: Celsius) -> float64 = // error: transform-return-type reading.temp_c + 273.15; ``` #### Two contextual parameters of one type Implicit passing matches by type, so a `given` clause cannot hold the same type twice. ```flex invalid newtype SensorId { value: uint32; } message struct GroundFix { lat_deg: float64; } message struct TrackReport { lat_deg: float64; sensor_id: uint32; } transform FixToTrack(fix: GroundFix) -> TrackReport given (?primary: SensorId, ?backup: SensorId) = // error: context-param-duplicate-type TrackReport { lat_deg = fix.lat_deg; sensor_id = ?primary.value; }; ``` #### A contextual reference outside a transform ```flex invalid newtype SensorId { value: uint32; } const sensor_id: uint32 = ?sensor.value; // error: unresolved-reference, context-param-ref-scope ``` #### `giving` on a call that takes no context ```flex invalid newtype SensorId { value: uint32; } function add(a: int32, b: int32) -> int32 = a + b; const radar: SensorId = SensorId { 7 }; const total: int32 = add(40, 2) giving (radar); // error: giving-target ``` #### The wrong number of contextual arguments ```flex invalid newtype SensorId { value: uint32; } message struct GroundFix { lat_deg: float64; } message struct TrackReport { lat_deg: float64; sensor_id: uint32; } transform FixToTrack(fix: GroundFix) -> TrackReport given (?sensor: SensorId) = TrackReport { lat_deg = fix.lat_deg; sensor_id = ?sensor.value; }; const fix: GroundFix = GroundFix { lat_deg = 38.9; }; const radar: SensorId = SensorId { 7 }; const report: TrackReport = FixToTrack(fix) giving (radar, radar); // error: giving-arg-count ``` #### A contextual argument of the wrong type ```flex invalid newtype SensorId { value: uint32; } message struct GroundFix { lat_deg: float64; } message struct TrackReport { lat_deg: float64; sensor_id: uint32; } transform FixToTrack(fix: GroundFix) -> TrackReport given (?sensor: SensorId) = TrackReport { lat_deg = fix.lat_deg; sensor_id = ?sensor.value; }; const fix: GroundFix = GroundFix { lat_deg = 38.9; }; const report: TrackReport = FixToTrack(fix) giving ("Viper-1"); // error: giving-arg-type ``` #### A contextual transform called with no context available ```flex invalid newtype SensorId { value: uint32; } message struct GroundFix { lat_deg: float64; } message struct TrackReport { lat_deg: float64; sensor_id: uint32; } transform FixToTrack(fix: GroundFix) -> TrackReport given (?sensor: SensorId) = TrackReport { lat_deg = fix.lat_deg; sensor_id = ?sensor.value; }; const fix: GroundFix = GroundFix { lat_deg = 38.9; }; const report: TrackReport = FixToTrack(fix); // error: giving-required ``` ### Related - [function](https://flexlang.org/flex/functions/function.md) — the same declaration without the message-type restriction. - [struct](https://flexlang.org/flex/data-types/struct.md) — the `message` and `abstract` modifiers a parameter type answers to. - [newtype](https://flexlang.org/flex/data-types/newtype.md) — the distinct types that make a `given` clause resolvable. --- ## comprehension An array whose elements are computed. The clauses describe an iteration and `expr` runs once per step. An `expr` of type `T` gives `T[]`. ```flex const bearings_deg: int32[] = [10, 45, 90]; const reciprocals_deg: int32[] = [b + 180 for b in bearings_deg]; ``` ### Clauses A comprehension is the element expression followed by a flat list of clauses. The `for` keyword after the first element is what distinguishes it from a plain [array literal](https://flexlang.org/flex/data-types/array.md). - The clauses are `for`, `if`, `let`, `scan`, and `zip for`. - The list is freely ordered and each clause may repeat. There is no required `for … if … scan` shape. - Clauses are processed left to right. Every binding is visible to the clauses after it and to the element expression, and not to itself. - Each pattern is the same pattern a [`let`](https://flexlang.org/flex/expressions/block-let-assert.md) takes: a name, a name with a type, a tuple to destructure, or `_`. ```flex eval const bearings_deg: int32[] = [10, 45, 90]; [b + 1 for b in bearings_deg] // => [11, 46, 91] [b for b in bearings_deg if b > 20] // => [45, 90] [d for b in bearings_deg let d = b * 2] // => [20, 90, 180] [s for b in bearings_deg scan s: int32 = 0 in s + b] // => [10, 55, 145] ``` **There Is No Loop Statement** Flex has no `for` or `while` in an expression: a comprehension and `fold`, `map`, `filter`, and `flatmap` are the whole of iteration, and `loop` belongs to a protocol. Every one of them produces a value, so nothing iterates for its effects. ### for `for pattern in source` iterates the source. A second `for` is a nested loop, not a lockstep one: the clauses form a cartesian product, and the leftmost `for` varies slowest. A later source may name a binding an earlier clause introduced. ```flex eval const sectors: int32[] = [1, 2]; const sweeps: int32[] = [10, 20]; [s + w for s in sectors for w in sweeps] // => [11, 21, 12, 22] ``` ### if `if predicate` drops the iterations where the predicate is false. The predicate has type `bit`. ### let `let pattern = expr` binds a value once per iteration, for the clauses after it and the element expression. It is the place to name a subexpression that would otherwise be repeated. ### scan `scan pattern = init in fold` carries an accumulator across iterations. The type annotation is optional and is inferred from the initializer when it is left out. - The accumulator is visible in its own fold expression, in every later clause, and in the element expression. It is not visible in its own initializer. - The element expression sees the accumulator for the current iteration — the value the fold expression just produced, not the one it started from. - Several `scan` clauses may be threaded through one comprehension. ```flex eval const legs_m: int32[] = [100, 250, 400]; [total for leg in legs_m scan total: int32 = 0 in total + leg] // => [100, 350, 750] [total for leg in legs_m scan total = (0: int32) in total + leg] // => [100, 350, 750] ``` ### zip for `zip for pattern in source` advances its source in lockstep with the frames already established instead of crossing with them. The result is as long as the shortest source. - `zip` cannot appear with `scan` in one comprehension. - `zip` cannot appear with `if` in one comprehension. ```flex eval const sectors: int32[] = [1, 2, 3]; const sweeps: int32[] = [10, 20]; [s + w for s in sectors zip for w in sweeps] // => [11, 22] [(s, w) for s in sectors zip for w in sweeps] // => [(1, 10), (2, 20)] ``` ### Errors #### A filter that is not bit ```flex invalid const bearings_deg: int32[] = [10, 45, 90]; const wrong: int32[] = [b for b in bearings_deg if b]; // error: comprehension-filter-type ``` #### `zip` together with `scan` ```flex invalid const sectors: int32[] = [1, 2, 3]; const sweeps: int32[] = [10, 20]; const wrong: int32[] = [t for s in sectors zip for w in sweeps scan t: int32 = 0 in t + s]; // error: comprehension-zip-scan-exclusive ``` #### `zip` together with a filter ```flex invalid const sectors: int32[] = [1, 2, 3]; const sweeps: int32[] = [10, 20]; const wrong: int32[] = [s + w for s in sectors zip for w in sweeps if s > 1]; // error: comprehension-zip-filter-exclusive ``` ### Related - [array](https://flexlang.org/flex/data-types/array.md) — the plain literal, and the type a comprehension produces. - [fold, map, filter, flatmap](https://flexlang.org/flex/iteration/fold-map-filter-flatmap.md) — the same iteration as expressions with block bodies. - [block, let, assert](https://flexlang.org/flex/expressions/block-let-assert.md) — the patterns a clause binds with. --- ## fold, map, filter, flatmap Looping expressions over a collection. Each takes a source, binds one element at a time, and runs a block body whose tail expression is that iteration's result. ```flex function total_range_m(ranges_m: int32[]) -> int32 = fold(total: int32 = 0; r in ranges_m) { total + r; }; ``` ### Sources and bindings `fold`, `map`, `filter`, and `flatmap` are all expressions rather than statements. - `fold` takes an array. `map`, `filter`, and `flatmap` take an array or an `Optional`. - Over an `Optional`, the body runs only when the source is `some`, and the result is an `Optional`. - The iteration variable is bound in the body and nowhere else. - The variable takes the same patterns a [`let`](https://flexlang.org/flex/expressions/block-let-assert.md) does, so a tuple element can be destructured in the header. - Ascribing the variable a type pins the source's element type, which is how a collection of otherwise unpinned literals becomes usable. ### fold `fold(acc = init; x in source)` reduces an array left to right. Each body tail becomes the next accumulator value, and the last one is the result. - The accumulator's type annotation is optional, and is inferred from the initializer when omitted. - The initializer must be assignable to the accumulator type, and so must the body tail. - The result type is the accumulator type. - A tuple accumulator carries several running values, and then the body must produce a tuple. ```flex eval const legs_m: int32[] = [100, 250, 400]; fold(total: int32 = 0; leg in legs_m) { total + leg; } // => 750 fold(total = (0: int32); leg in legs_m) { total + leg; } // => 750 fold((lo, hi) = ((0: int32), (0: int32)); leg in legs_m) { (lo + leg, hi + (1: int32)); } // => (750, 3) ``` ### map `map(x in source)` applies the body to every contained value. An array of `T` becomes an array of the body's type, and an `Optional` becomes an `Optional` of it. The body may have any type. It is the only one of these forms that changes the element type freely. ```flex eval const bearings_deg: int32[] = [10, 45, 90]; map(b in bearings_deg) { b * 2; } // => [20, 90, 180] map(b in some((45: int32))) { b * 2; } // => some(90) map(b in (none: Optional)) { b * 2; } // => none ``` ### filter `filter(x in source)` keeps the values whose body is `true`. The result has the source's own type. The body must have type `bit`. Filtering an `Optional` either keeps the `some` or turns it into `none`. ```flex eval const bearings_deg: int32[] = [10, 45, 90]; filter(b in bearings_deg) { b > 20; } // => [45, 90] filter(b in some((10: int32))) { b > 20; } // => none ``` ### flatmap `flatmap(x in source)` maps each value to a collection and flattens one level. Arrays are concatenated in iteration order; a nested optional collapses to a single one. - The body must produce the same kind of collection as the source. An array source needs an array body, and an optional source an optional body. - The body's element type need not relate to the source's. - An empty array body contributes nothing, which is how one pass both filters and maps. ```flex eval const bearings_deg: int32[] = [10, -45, 90]; flatmap(b in bearings_deg) { [b, b]; } // => [10, 10, -45, -45, 90, 90] flatmap(b in bearings_deg) { if b > 0 then [b] else []; } // => [10, 90] flatmap(b in some((45: int32))) { some(b * 2); } // => some(90) ``` ### Errors #### A fold over something that is not an array ```flex invalid function total(range_m: int32) -> int32 = fold(total: int32 = 0; r in range_m) { // error: fold-source-type total + r; }; ``` #### A fold initializer of the wrong type ```flex invalid function total(ranges_m: int32[]) -> int32 = fold(total: int32 = "none"; r in ranges_m) { // error: fold-init-type total + r; }; ``` #### A fold body that is not the accumulator's type ```flex invalid function total(ranges_m: int32[]) -> int32 = fold(total: int32 = 0; r in ranges_m) { // error: fold-body-type "counted"; }; ``` #### A map over something that is neither an array nor an optional ```flex invalid function doubled(range_m: int32) -> int32[] = map(r in range_m) { // error: map-source-type r * 2; }; ``` #### A filter over something that is neither an array nor an optional ```flex invalid function nearby(range_m: int32) -> int32 = filter(r in range_m) { // error: filter-source-type r < 500; }; ``` #### A filter body that is not bit ```flex invalid function nearby(ranges_m: int32[]) -> int32[] = filter(r in ranges_m) { // error: filter-body-type r * 2; }; ``` #### A flatmap over something that is neither an array nor an optional ```flex invalid function paired(range_m: int32) -> int32[] = flatmap(r in range_m) { // error: flatmap-source-type [r, r]; }; ``` #### A flatmap body that is not a collection ```flex invalid function paired(ranges_m: int32[]) -> int32[] = // error: type-mismatch flatmap(r in ranges_m) { // error: flatmap-body-type r; }; ``` ### Related - [comprehension](https://flexlang.org/flex/iteration/comprehension.md) — the same iteration in bracket form, with clauses instead of a body. - [array](https://flexlang.org/flex/data-types/array.md) — the source all of these are usually written over. - [optional](https://flexlang.org/flex/data-types/optional.md) — the other source, and what a `map` over one returns. --- ## comments and whitespace Text the language ignores. Both separate tokens and mean nothing beyond that, so either may go anywhere a token boundary may go, the file header included. ```flex // A line comment ends with the line. /* A block comment spans as many lines as it needs. */ const max_range_m: int32 = 40000; // and may follow code ``` ### Comments - A line comment runs from `//` to the end of the line. - A block comment runs from `/*` to the first `*/`, across as many lines as that takes. - Block comments do not nest. An inner `/*` is part of the comment, and the first `*/` ends it. - `///` is a line comment. Nothing reads it as documentation. - A comment may sit inside an expression, between any two tokens. ```flex files /* A comment may precede the header. */ module radar::sensors.tracking const max_range_m: int32 = /* and may interrupt an expression */ 40000; ``` ### Whitespace There is no layout rule and no significant newline. A declaration may be written across as many lines as it needs, or on one. - Any run of spaces, tabs, and newlines is one separator. - A separator is needed only where running two tokens together would make a different token. `const x` is two tokens; `constx` is one name. ```flex const max_range_m:int32=40000; const bearing_deg: float32 = 41.5; ``` ### Errors #### A block comment that is never closed ```flex invalid /* the rest of the file is inside this comment // error: unterminated-block-comment const max_range_m: int32 = 40000; ``` #### A block comment closed by an inner one The first `*/` ends the comment, so what the author meant as the end of it is code. ```flex invalid /* outer /* inner */ still meant to be a comment */ // error: unexpected const max_range_m: int32 = 40000; ``` ### Related - [identifiers](https://flexlang.org/flex/lexical/identifiers.md) — what the tokens a separator keeps apart may be named. - [module](https://flexlang.org/flex/modules/module.md) — the header a comment may precede. --- ## identifiers A name a declaration or a binding carries. It begins with a letter or an underscore, continues with letters, digits, and underscores, and its letters are ASCII. ```flex const structure: int32 = 1; const bit8: int32 = 8; const uint8_new: int32 = 9; const int032: int32 = 32; ``` ### Spelling The longest match wins, so a name that merely begins with a keyword, or with a width-suffixed type name, is an ordinary name. - `structure`, `bit8`, `float321`, and `uint8_new` are names. Only the exact spelling is taken as the keyword or the type, and a declaration cannot use it. - `int032` is a name rather than a type, because a width does not carry a leading zero. - A lone `_` is the discard token, not a name. A declaration cannot be called `_`. - Names are case-sensitive. ### Related - [comments and whitespace](https://flexlang.org/flex/lexical/comments-and-whitespace.md) — the text between names that means nothing. - [module](https://flexlang.org/flex/modules/module.md) — the names in a file header, which bind nothing inside the file. - [literals](https://flexlang.org/flex/expressions/literals.md) — the number, string, and `bit` forms a value is written in. --- ## annotation A named piece of metadata with optional parameters and optional scope restrictions. It is a global declaration, importable like a struct or a const, and it leaves the element it decorates unchanged. ```flex annotation Classification(level: string) annotation Deprecated @Deprecated @Classification("unclassified") struct TrackReport { track_id: uint32; bearing_deg: float32; } ``` ### Parameters Parameters follow the name in parentheses, each a name, a colon, and a type. - Parameters are optional. A parameterless annotation takes no parentheses, and an empty `()` is a parse error. - Parameter names are unique within the declaration. - A parameter's type may be any type. ```flex annotation Note(id: int32, msg: string) annotation Critical annotation Offset(x: int32, y: int32) ``` ### Scopes A scope list between `|` bars restricts where the annotation may be applied. With no list, it applies to any target below. | Keyword | Target | |---|---| | `Struct` / `Field` | a struct declaration / one of its fields | | `Variant` / `VariantConstructor` | a variant declaration / one of its constructors | | `Enum` / `EnumValue` | an enum declaration / one of its members | | `Newtype` | a newtype declaration (`NewType` is accepted as well) | | `TypeAlias` | a `type` alias | | `Const` | a `const` declaration | | `Function` / `Transform` / `Contract` | the corresponding declaration | | `Component` / `Connection` / `System` | the corresponding declaration | | `LocalProtocol` / `GlobalProtocol` | a `local protocol` / `global protocol` declaration | | `DeclAnnotation` | an `annotation` declaration itself | | `ProtocolStmt` / `ProtocolState` | an annotatable protocol statement: `send`, `recv`, `exch`, a `listen` arm, or a standalone use | | `Unique` / `Requirement` | not a target — see below | - Each name in the list is one of the keywords above. - Several keywords may be listed, separated by whitespace, and a use matching any one of them is accepted. - `Unique` and `Requirement` name no target. Listed alone, they leave the annotation applicable anywhere; listed beside a target scope, that scope still restricts. ```flex annotation Deprecated | Struct Variant VariantConstructor | annotation FieldTag(tag: uint8) | Field | annotation Topic(name: string) | ProtocolStmt | ``` ### Unique `Unique` limits an annotation to one use per set of argument values per package. - Two uses carrying the same argument values within one package are an error, and every use after the first is flagged. - Uses whose arguments differ are unrelated. - The rule is package-scoped, so the same values may repeat in a different package, and two colliding uses may sit in different modules of one package. - An argument-less annotation carries an empty argument tuple, which equals itself, so a bare `| Unique |` marker appears at most once per package. - Arguments compare as values, not as spellings. `@TrackId(42)`, `@TrackId(0x2A)`, and `@TrackId(4_2)` are one value, as are `1.0`, `1.00`, and `1e0`. - A constant reference is not evaluated. `@TrackId(max_tracks)` is compared by the declaration `max_tracks` resolves to, so it never collides with `@TrackId(42)` even when `max_tracks` is `42`. Two references to the same constant do collide. ```flex annotation TrackId(id: int32) | Unique | const max_tracks: int32 = 42; @TrackId(42) struct TrackReport { track_id: uint32; } @TrackId(max_tracks) struct TrackUpdate { track_id: uint32; } ``` ### Requirement `Requirement` marks the annotation as carrying requirement identity. The language records the mark and reads nothing from it; requirement tracing outside the language is what reads it. A use is checked exactly as any other use is. Like `Unique`, it names no target, so a target scope listed beside it is what decides where the annotation may be applied. ```flex enum GpsRequirement int32 { SysReq1234 = 1234; SysReq1235 = 1235; } annotation GpsClaim(req: GpsRequirement) | Requirement LocalProtocol | ``` ### Uses A use is `@`, the annotation's path, and arguments in parentheses when the declaration takes parameters. It stands immediately before the element it decorates, on its own line or on that element's line, and several uses stack in order. - The Target column above is the whole set of positions a use may occupy. An `import`, a `module` header, a `let`, and a newtype's single field take none. - The path must resolve to an annotation declaration. An annotation imported under a module alias is used as `@alias.Deprecated`. ```flex annotation Deprecated | Struct Variant VariantConstructor Field EnumValue | annotation FieldTag(tag: uint8) | Field | @Deprecated struct Contact { @FieldTag(1) contact_id: uint32; @Deprecated @FieldTag(2) bearing_deg: float32; } variant TrackKind { Air(uint32); @Deprecated Surface(uint32); } enum SensorMode string { Search = "search"; @Deprecated Sweep = "sweep"; } ``` ### Arguments An argument is a literal or a path naming a `const` or an enum member, either one optionally preceded by a unary `-`. No other expression is an argument. - Arguments match parameters positionally, and the count must match exactly. - Each argument's type must be assignable to its parameter's type. A numeric literal pins to the parameter's width. - A path argument carries the type of the declaration it resolves to. - Unary `-` applies to a numeric literal or a numeric constant, and never to an unsigned-integer parameter. ```flex annotation Offset(x: int32, y: int32) annotation Label(name: string, active: bit) annotation Mode(mode: SensorMode) enum SensorMode string { Search = "search"; Track = "track"; } const default_offset: int32 = 25; @Offset(-10, default_offset) @Label("header", true) @Mode(SensorMode.Search) struct DisplayHint { hint_id: uint32; } ``` ### Protocol statements Annotations on a protocol carry transport metadata — a topic, a URL, a timeout — and do not affect what the protocol does. They appear in two positions. - **Attached**: a trailing run on a `send`, `recv`, or `exch` statement, or on a `listen` arm, ahead of the terminator. These belong to that statement. - **Standalone**: a bare use as a statement of its own, anywhere a protocol statement is allowed. Each use is one statement, so two uses written ahead of another statement are two statements rather than a run attached to it. A standalone use is scope-checked as a protocol statement, so `ProtocolStmt` and `ProtocolState` both admit it. ```flex message struct TrackReport { track_id: uint32; } component Radar; component CommandPost; annotation Topic(name: string) | ProtocolStmt | annotation RecvWithinMs(ms: int32) | ProtocolStmt | global protocol ReportTrack { @Topic("tracks") exch any TrackReport from Radar to CommandPost @RecvWithinMs(1000); } ``` ### Errors #### Two parameters with one name ```flex invalid annotation Note(id: int32, id: string) // error: duplicate-annotation-param ``` #### A scope the language does not define The scope list is a fixed vocabulary. ```flex invalid annotation Deprecated | Gadget | // error: invalid-annotation-scope ``` #### The same Unique values twice in one package ```flex invalid annotation TrackId(id: int32) | Unique | @TrackId(42) struct TrackReport { track_id: uint32; } @TrackId(42) // error: duplicate-unique-annotation struct TrackUpdate { track_id: uint32; } ``` #### Using something that is not an annotation ```flex invalid struct Contact { contact_id: uint32; } @Contact // error: not-an-annotation struct Radar { scan_period_ms: uint32; } ``` #### The wrong number of arguments ```flex invalid annotation Note(id: int32, msg: string) @Note(42) // error: annotation-arg-count struct TrackReport { track_id: uint32; } ``` #### An argument the parameter's type does not accept ```flex invalid annotation Note(id: int32, msg: string) annotation FieldTag(tag: uint8) | Field | @Note("42", "late") // error: annotation-arg-type struct TrackReport { @FieldTag(-1) track_id: uint32; // error: annotation-arg-type } ``` #### Applying an annotation outside its scopes ```flex invalid annotation Deprecated | Struct | @Deprecated // error: annotation-scope-mismatch variant TrackKind { Air(uint32); Surface(uint32); } ``` ### Related - [const](https://flexlang.org/flex/data-types/const.md) — a constant an argument may name. - [enum](https://flexlang.org/flex/data-types/enum.md) — a member an argument may name. --- ## contract A named block whose last statement is an `assert`. The assertion states a property that should hold. Nothing calls a contract, and no interface includes one. ```flex contract bearing_wraps { let bearing_deg: int32 = 350; let turn_deg: int32 = 20; assert (bearing_deg + turn_deg) % 360 == 10; } ``` ### Bodies A contract's name is unique among the file's declarations, and no other module can name it as an element of an import. - The body is a block whose last statement is an `assert`. A body that ends any other way is a parse error. - Intermediate `assert` statements are allowed. - The final assert's condition has type `bit`. - The body type-checks as an ordinary block, so it may hold `let` and statement `if`. - The body may reference any in-scope declaration, including a `function` and a `const`. ```flex struct SearchSector { start_deg: uint16; end_deg: uint16; } function span_deg(sector: SearchSector) -> uint16 = sector.end_deg - sector.start_deg; const forward: SearchSector = SearchSector { start_deg = 350; end_deg = 360; }; contract forward_span_is_ten { let span: uint16 = span_deg(forward); assert span == 10; } ``` ### Parameters A parameterized contract states its property for all values of its parameters. An unparameterized one states it for the single computation its body spells out. - Parameters are optional, and a contract with none is written with no parentheses at all. An empty `()` is a parse error. - Parameter names are unique. - A parameter type may be any type. - Parameters are in scope in the body and nowhere else. ```flex contract average_within_bounds(low_deg: int32, high_deg: int32) { let mid_deg: int32 = (low_deg + high_deg) / 2; assert (low_deg <= mid_deg && mid_deg <= high_deg) || (high_deg <= mid_deg && mid_deg <= low_deg); } ``` ### Errors #### Declaring the same parameter twice ```flex invalid contract span_is_positive(start_deg: int32, start_deg: int32) { // error: duplicate-contract-param assert start_deg > 0; } ``` #### An assert condition that is not bit ```flex invalid contract callsign_is_set { assert "Viper-1"; // error: assert-condition-type } ``` ### Related - [block, let, assert](https://flexlang.org/flex/expressions/block-let-assert.md) — the statements a contract body holds, and what `assert` does inside a function. - [function](https://flexlang.org/flex/functions/function.md) — the named body that is called and returns a value. - [struct](https://flexlang.org/flex/data-types/struct.md) — the assertions that guard a value instead of stating a property. --- ## import A declaration bringing another module's top-level declarations into this file's scope, either behind an alias or one element at a time. It names the target by the module's full name, package included. An import needs a module to name, so every example here is two files. The line between them is where one file ends and the next begins. ```flex files module radar::sensors.contacts extensible struct Contact { contact_id: uint32; } // ──────────────────────────── module radar::display.tracks import radar::sensors.contacts as c const watched: c.Contact = c.Contact { contact_id = 4; }; ``` ### Forms An alias import is `import M as a`. An elements import is `import M (Foo, Bar)`, and an element may be renamed for this file with `=>`. - An alias binds one name. Every top-level declaration of the target is reached through it as `a.Name`, and an enum member or variant constructor as `a.Name.Member`. - An elements import binds each named declaration under its own name, and its members with it. - `Foo => Bar` binds the declaration as `Bar` here. Its members come with it under the new name. - A module may be imported more than once — under several aliases, and by alias and by element at once. - An imported declaration keeps the qualified name of the module that declares it. - A `contract` is not an importable element. ```flex files module radar::sensors.contacts enum Mode uint8 { Search = 0; Track = 1; } const max_range_m: int32 = 40000; function clamp_range(v: int32) -> int32 = if v > max_range_m then max_range_m else v; // ──────────────────────────── module radar::display.tracks import radar::sensors.contacts as c import radar::sensors.contacts (Mode => SensorMode, clamp_range) const mode: SensorMode = SensorMode.Track; const other: c.Mode = c.Mode.Search; const held_m: int32 = clamp_range(50000); ``` **Wildcard Imports** Flex has no wildcard import: every element an import brings in is named, either behind an alias or in the element list. The declaration behind an identifier is then always findable from the file that uses it. ### Scope An import injects its names before any reference is resolved, so a declaration may use an imported name written above or below it. - A name an import binds may not collide with one this file declares, or with one another import binds. - A declaration reached through an import brings only itself. What its own body uses resolves in the module that declares it, so `clamp_range` above reads `max_range_m` without the caller importing it. - A module that is not part of the project resolves to nothing, and so does an element the target does not declare. ### Errors #### Importing a module the project does not hold ```flex files invalid module radar::display.tracks import radar::sensors.contacts as c // error: unresolved-reference ``` #### Naming an element the module does not declare ```flex files invalid module radar::sensors.contacts struct Contact { contact_id: uint32; } // ──────────────────────────── module radar::display.tracks import radar::sensors.contacts (TrackReport) // error: unresolved-reference ``` #### An imported name this file already declares ```flex files invalid module radar::sensors.contacts struct Contact { contact_id: uint32; } // ──────────────────────────── module radar::display.tracks import radar::sensors.contacts (Contact) // error: import-conflict struct Contact { contact_id: uint32; } ``` #### Importing a contract ```flex files invalid module radar::sensors.contacts const max_range_m: int32 = 40000; contract range_is_positive { assert max_range_m > 0; } // ──────────────────────────── module radar::display.tracks import radar::sensors.contacts (range_is_positive) // error: unresolved-reference ``` #### The same element twice in one import ```flex files invalid module radar::sensors.contacts struct Contact { contact_id: uint32; } // ──────────────────────────── module radar::display.tracks import radar::sensors.contacts (Contact, Contact) // error: import-conflict, duplicate-import-element ``` ### Related - [module](https://flexlang.org/flex/modules/module.md) — the header an import names, and the one it has to follow. - [identifiers](https://flexlang.org/flex/lexical/identifiers.md) — what the names on either side of `=>` may be. --- ## module A file header naming the file. That name is mandatory and unique across the project, it is the namespace holding the file's declarations, and another file's `import` reaches them through it. ```flex files module radar::sensors.tracking message struct TrackReport { track_id: uint32; bearing_deg: float32; } ``` ### Headers A header is `module` and then the file's full name: a package, a `.`, then the module and any levels above it, separated by `.`. | In `module radar::sensors.tracking.reports` | What it is | |---|---| | `radar::sensors` | the package — names joined by `::` | | `tracking` | a level above the module; there may be any number of these, or none | | `reports` | the module — this file, the way a filename names a file | - The `::` separators are optional, so a single-name package is a header like `module tracking.reports`. - The `.` after the package is not optional. - A file carries exactly one header. - The full name is unique across the project. - A declaration's name is unique within the module. - The file's name on disk need not match any part of it. - The names in the header bind nothing inside the file, so a declaration may reuse one — a file headed `module radar::sensors.tracking` may declare `tracking`. ### File shape A file is its header, then its imports, then its [global declarations](https://flexlang.org/flex/appendices/positions.md). Only whitespace and comments may precede the header. ### Errors #### A header that stops at the package The `.` and the name after it are part of the header, so a package on its own is not one. ```flex files invalid module radar::sensors // error: expected, expected ``` #### A declaration above the header ```flex files invalid const max_range_m: int32 = 40000; // error: expected module radar::sensors.tracking // error: unexpected ``` #### Two files naming the same module Both files are reported, because neither is the one that is wrong. ```flex files invalid module radar::sensors.tracking // error: duplicate-module // ──────────────────────────── module radar::sensors.tracking // error: duplicate-module ``` #### Two declarations with one name ```flex invalid const max_range_m: int32 = 40000; const max_range_m: int32 = 50000; // error: duplicate-declaration-name ``` ### Related - [import](https://flexlang.org/flex/modules/import.md) — what may follow the header, and how another module's declarations get in. - [identifiers](https://flexlang.org/flex/lexical/identifiers.md) — what the names in a header may be. - [comments and whitespace](https://flexlang.org/flex/lexical/comments-and-whitespace.md) — what may precede the header. --- ## branch A decision a component makes inside a local protocol by evaluating guards. It runs the arm whose guard holds. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; bearing_deg: float32; } message struct Alert { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Screen in CommandPost { var alerts: int32 = 0; recv let report: TrackReport from Radar; branch | report.bearing_deg > 180.0 => set alerts = alerts + 1; send any Alert to Radar; | report.track_id == 0 => send any Ack to Radar; | else => end } ``` ### Arms Each arm is a `|`, a guard, `=>`, and the statements to run. The block closes with `end`, not a brace. - A guard has type `bit`. - Exactly one arm runs. - `| else =>` is optional and may appear only as the last arm. Without one, a decision where no guard holds has no arm to take. The guards are not retried, so the component is stuck at the block, and so is every component waiting on a message an arm would have sent. - Arms are not tried in order. Where two guards hold, either one may run. - An arm's statements are the local protocol's own, so an arm may hold a `send`, a nested `branch`, a `break`, or nothing at all. ### Errors #### A guard that is not bit ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Screen in CommandPost { recv let report: TrackReport from Radar; branch | report.track_id => send any Ack to Radar; // error: branch-guard-type | else => end } ``` #### An else arm that is not last ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Screen in CommandPost { recv let report: TrackReport from Radar; branch | else => // error: branch-else-not-last | report.track_id > 0 => send any Ack to Radar; end } ``` ### Related - [listen](https://flexlang.org/flex/protocols/listen.md) — the same shape, with each arm guarded by an arriving message. - [choice](https://flexlang.org/flex/protocols/choice.md) — the same decision in a global protocol, where the other components learn the outcome from the messages they receive. - [if](https://flexlang.org/flex/expressions/if.md) — the same decision between two expressions. - [loop, break](https://flexlang.org/flex/protocols/loop.md) — the repetition a branch usually decides to leave. --- ## choice A decision about which arm of a global protocol runs, made by the components named after `in`. ```flex component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; bearing_deg: float32; } message struct EngageOrder { track_id: uint32; } message struct Standby { track_id: uint32; } message struct Abort { track_id: uint32; } global protocol Engage { exch any TrackReport into let latest: TrackReport from Radar to CommandPost; choice in CommandPost | latest.bearing_deg > 180.0 => exch any EngageOrder from CommandPost to Launcher; | latest.bearing_deg < 90.0 => exch any Standby from CommandPost to Launcher; | else => exch any Abort from CommandPost to Launcher; end } ``` Each arm sends `Launcher` a different message, so `Launcher` can tell which one ran. ### Arms Each arm is a `|`, a guard, `=>`, and the statements to run. The block closes with `end`, not a brace. - The component after `in` is the one that decides, and it names a component. - A guard has type `bit`. It may name what an earlier `exch` bound, and what an earlier [`in` block](https://flexlang.org/flex/protocols/in-block.md) on the deciding component declared — the state of another component is not something this one can read. - Exactly one arm runs. - `| else =>` is optional and may appear only as the last arm. Without one, a decision where no guard holds has no arm to take. The guards are not retried, so the deciding component is stuck at the block, and so is every component waiting on a message an arm would have sent. - Arms are not tried in order. Where two guards hold, either one may run. - An arm's statements are the global protocol's own, so an arm may hold an `exch`, a `loop`, a nested `choice`, or nothing at all. - Naming more than one component after `in` makes it a [synchronized choice](#synchronized), which restricts the guards further. ### Synchronized Name more than one component after `in` and each of them decides for itself, with no message between them, so all of them have to reach the same answer. A `where` clause is what makes that possible: it defines the variables the guards may read, and gives each component its own expression for each one. - A definition is a name, `=`, and one `in C { … }` expression for every component named after `choice in`, comma separated. A second definition follows the first with nothing between them. - Every expression of one definition has the same type. - An expression is evaluated by its own component, and reads only what that component can see. - Only these variables may appear in the arm guards. A guard naming anything else is an error, even a binding that is in scope for one of the components. - A guard has type `bit`. - The arms cannot be nondeterministic: the components would not agree on which one to run. ```flex component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; bearing_deg: float32; } message struct EngageOrder { track_id: uint32; } message struct Ack { track_id: uint32; } global protocol Engage { exch any TrackReport into let at_post: TrackReport from Radar to CommandPost; exch any TrackReport into let at_launcher: TrackReport from Radar to Launcher; choice in CommandPost, Launcher where engaging = in CommandPost { at_post.bearing_deg > 180.0 }, in Launcher { at_launcher.bearing_deg > 180.0 } reporting = in CommandPost { at_post.track_id > 0 }, in Launcher { at_launcher.track_id > 0 } | engaging && reporting => exch any EngageOrder from CommandPost to Launcher; | reporting => exch any Ack from Launcher to CommandPost; | else => end } ``` ### Errors #### Deciding in something that is not a component A [group](https://flexlang.org/flex/protocols/group.md) holds components, but it is not one, so it cannot decide. ```flex invalid component Radar; component CommandPost; component Launcher; message struct EngageOrder { track_id: uint32; } group Command { CommandPost; Launcher; } global protocol Engage { choice in Command // error: choice-component-not-component | true => exch any EngageOrder from CommandPost to Launcher; | else => end } ``` #### A guard naming another component's state `sweeps` belongs to `Radar`, so a `choice in Launcher` cannot read it. ```flex invalid component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } message struct EngageOrder { track_id: uint32; } global protocol Engage { exch any TrackReport from Radar to CommandPost; in Radar { var sweeps: int32 = 0; } choice in Launcher | sweeps > 0 => exch any EngageOrder from Launcher to CommandPost; // error: unresolved-reference | else => end } ``` #### A synchronized guard naming something other than a synchronized variable ```flex invalid component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } message struct EngageOrder { track_id: uint32; } global protocol Engage { exch any TrackReport into let at_post: TrackReport from Radar to CommandPost; exch any TrackReport into let at_launcher: TrackReport from Radar to Launcher; choice in CommandPost, Launcher where engaging = in CommandPost { at_post.track_id > 0 }, in Launcher { at_launcher.track_id > 0 } | at_post.track_id > 0 => exch any EngageOrder from CommandPost to Launcher; // error: sync-choice-guard | else => end } ``` #### A synchronized guard that is not a condition ```flex invalid component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } message struct EngageOrder { track_id: uint32; } global protocol Engage { exch any TrackReport into let at_post: TrackReport from Radar to CommandPost; exch any TrackReport into let at_launcher: TrackReport from Radar to Launcher; choice in CommandPost, Launcher where engaging = in CommandPost { at_post.track_id > 0 }, in Launcher { at_launcher.track_id > 0 } | 1 => exch any EngageOrder from CommandPost to Launcher; // error: sync-choice-guard | else => end } ``` ### Related - [branch](https://flexlang.org/flex/protocols/branch.md) — the same decision inside one component's local protocol. - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the statement sequence an arm holds. - [exch](https://flexlang.org/flex/protocols/exch.md) — the exchanges that give each component something to decide on. - [in block](https://flexlang.org/flex/protocols/in-block.md) — where a deciding component's own state comes from. --- ## component A named participant in a protocol: something that sends and receives messages. The declaration is the name and nothing else, and protocol statements refer to it. ```flex component Radar; component CommandPost; ``` ### Names A component name is a global declaration — unique within its file, and importable by another module. - A component carries no fields, no state, and no behavior. What it does is written in the protocols that name it. - Every position that names a participant has to resolve to a `component`: a `connection` endpoint, the `in C` of a local protocol, the `from`/`to` of a connection clause, the `in C` of a `choice` or an `in` block. - Nothing declares a participant implicitly. A component that appears only in a `send` is an unresolved name, not a component brought into being by the statement. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } connection TrackFeed from Radar to CommandPost; local protocol Sweep in Radar { send any TrackReport to CommandPost on TrackFeed; } ``` ### Errors #### Naming a component that was never declared ```flex invalid component Radar; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to GroundStation; // error: unresolved-reference } ``` #### Declaring the same component twice ```flex invalid component Radar; component Radar; // error: duplicate-declaration-name ``` ### Related - [connection](https://flexlang.org/flex/protocols/connection.md) — the channel two components exchange messages over. - [local protocol](https://flexlang.org/flex/protocols/local-protocol.md) — the behavior one component runs. - [system](https://flexlang.org/flex/protocols/system.md) — one protocol per component, grouped for checking. - [group](https://flexlang.org/flex/protocols/group.md) — a named boundary around components, and around other groups. --- ## connection A unidirectional channel between two components: one always sends, the other always receives. Traffic in both directions is two connections. ```flex component Radar; component CommandPost; connection TrackFeed from Radar to CommandPost; ``` ### Declarations A named connection is declared at top level. An unnamed connection already exists for every ordered pair of components, and a statement that names no connection travels on that one. - The name is a global declaration — unique within its file, and importable. - `from` and `to` each name a component, and both are written. There is no bidirectional form. - Several connections may join the same pair of components. A statement picks one by name. ```flex component Radar; component CommandPost; connection TrackFeed from Radar to CommandPost; connection SlewLink from CommandPost to Radar; ``` ### Clauses `send`, `recv`, and `exch` all end with a connection clause, which says which components the message travels between and which connection carries it: `from A`, `to B`, `on Link`, or a combination. - At least one part is present. `from` and `to` name components, `on` names a connection. - An omitted endpoint is derived from the `on` connection, whose declaration fixes both ends. A written endpoint is never overridden. - In a `send` the sender is the component the statement is written in, and in a `recv` the receiver is. Writing that component out is allowed and says nothing new; writing a different one is an error. - Derivation works the same for `send`, `recv`, and `exch`, and for an imported connection as for a local one. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } connection TrackFeed from Radar to CommandPost; local protocol Sweep in Radar { send any TrackReport to CommandPost; send any TrackReport from Radar to CommandPost; send any TrackReport to CommandPost on TrackFeed; send any TrackReport on TrackFeed; } ``` The four statements send the same message to the same component. The first travels on the unnamed connection; the last takes both endpoints from `TrackFeed`. ### Errors #### An endpoint that is not a component ```flex invalid component CommandPost; message struct TrackReport { track_id: uint32; } connection TrackFeed from TrackReport to CommandPost; // error: connection-target-not-component ``` #### A clause part that names the wrong kind of declaration ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to CommandPost on TrackReport; // error: connection-clause-target } ``` #### A statement that names another component in its own role ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport from CommandPost to Radar; // error: connection-directionality } ``` #### A clause that runs against the connection it names ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } connection TrackFeed from Radar to CommandPost; local protocol Sweep in Radar { send any TrackReport to Radar on TrackFeed; // error: connection-directionality } ``` ### Related - [component](https://flexlang.org/flex/protocols/component.md) — the participants a connection joins. - [send](https://flexlang.org/flex/protocols/send.md) — the statement that puts a message on a connection. - [recv](https://flexlang.org/flex/protocols/recv.md) — the statement that takes one off. - [exch](https://flexlang.org/flex/protocols/exch.md) — both halves as one global statement. --- ## do A call to another protocol. `do P` runs `P` and comes back; `do tail P` runs `P` and never comes back. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Report in Radar { send any TrackReport to CommandPost; recv _: Ack from CommandPost; } local protocol Sweep in Radar { do Report; send any TrackReport to CommandPost; } ``` ### Targets A `do` in a local protocol names a local protocol on the same component. A `do` in a global protocol names a global protocol. - Execution continues with the statement after a plain `do`. - A protocol may be the target of several `do` statements, which is how a sequence written once is reused. - Only `do tail` may recurse, directly or through another protocol. A plain `do` that leads back to itself would have to return to a caller that is still waiting. ### tail `do tail P` hands control over for good, so it may appear only where nothing follows it: - the last statement of a protocol body, or - the last statement of a `choice`, `branch`, or `listen` arm whose own block is in tail position. That admits the shape a protocol is usually written in — a request and its answer, where one arm repeats and the other stops. It rules out a statement after the `do tail`, and it rules out a `do tail` inside a `loop` body, since a loop repeats on its own rather than by tail call. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } message struct Reject { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to CommandPost; listen | recv _: Ack from CommandPost => do tail Sweep; | recv _: Reject from CommandPost => end } ``` `Sweep` reports, and repeats itself as long as the report is acknowledged. The `do tail` is the last statement of an arm, and the `listen` is the last statement of the body. ### Errors #### Calling a protocol that runs on another component A local `do` continues the caller's own behavior, so the target has to be a protocol of the same component. ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Collect in CommandPost { recv _: TrackReport from Radar; } local protocol Sweep in Radar { send any TrackReport to CommandPost; do Collect; // error: do-component-mismatch } ``` #### A do tail with something after it ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { do tail Sweep; // error: do-tail-position send any TrackReport to CommandPost; } ``` ### Related - [local protocol](https://flexlang.org/flex/protocols/local-protocol.md) — the protocols a local `do` may name. - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the ones a global `do` may name. - [loop, break](https://flexlang.org/flex/protocols/loop.md) — repetition written as a block instead of a tail call. - [listen](https://flexlang.org/flex/protocols/listen.md) — the arms a `do tail` usually sits at the end of. --- ## exch One interaction between two components: the send and the matching receive as a single step of a global protocol. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Report { exch any TrackReport from Radar to CommandPost; } ``` ### Payloads The sending half takes the same forms as [`send`](https://flexlang.org/flex/protocols/send.md#payloads), and an optional `into` describes the receiving half with the forms of [`recv`](https://flexlang.org/flex/protocols/recv.md#payloads). Omit the `into` and the receiver is read off the sending half. - `into` is what says where the message lands and what the receiver assumes. Write it when the exchange binds the message for later statements, or when the receiver's `assuming` differs from the sender's `where`. - `into let name: T` introduces a binding visible to later statements. A bare `into name` names a binding that already exists. - Both endpoints have to be fixed, though not written: an omitted `from` or `to` comes from the `on` connection, so `exch any TrackReport on TrackFeed;` fixes both. - The message type is message data on both halves, and the sending half's type is assignable to the `into` target's type. Assignable, not identical — a receiver may name an ancestor of what is sent. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; bearing_deg: float32; } message struct EngageOrder { track_id: uint32; } connection TrackFeed from Radar to CommandPost; global protocol Report { exch any TrackReport on TrackFeed; exch any TrackReport into let latest: TrackReport from Radar to CommandPost; exch any report: TrackReport where report.bearing_deg >= 0.0 into any accepted: TrackReport assuming accepted.bearing_deg >= 0.0 from Radar to CommandPost; exch EngageOrder { track_id = latest.track_id; } from CommandPost to Radar; } ``` ### Compatibility The sender's `where` and the receiver's `assuming` are the two halves of one claim about the value on the wire. The exchange is compatible when everything the sender may produce is something the receiver accepts, given the statements before it. - An exchange with neither predicate makes no claim, and is compatible. - A `where` that is weaker than the `assuming` is not: the sender may produce a value the receiver treats as a safety error. - Which endpoints an exchange connects also decides which earlier [`in` block](https://flexlang.org/flex/protocols/in-block.md) bindings it can name. An endpoint derived from a connection involves its component exactly as a written one does. ### Errors #### An exchange that fixes only one endpoint Neither a written endpoint nor an `on` connection supplies the receiver here. ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Report { exch any TrackReport from Radar; // error: exch-from-to-required } ``` #### An into target that is not a binding ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Report { exch any TrackReport into TrackReport from Radar to CommandPost; // error: exch-into-target-not-var } ``` #### Two halves that disagree about the message ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } global protocol Report { exch any TrackReport into let ack: Ack from Radar to CommandPost; // error: exch-type-mismatch } ``` ### Related - [send](https://flexlang.org/flex/protocols/send.md) — the sending half on its own, in a local protocol. - [recv](https://flexlang.org/flex/protocols/recv.md) — the receiving half on its own. - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the statement sequence an `exch` belongs to. - [connection](https://flexlang.org/flex/protocols/connection.md) — the clause that fixes the endpoints. --- ## global protocol A whole conversation between components, written as one ordered sequence of exchanges. ```flex component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } message struct EngageOrder { track_id: uint32; } global protocol Engage { exch any TrackReport into let latest: TrackReport from Radar to CommandPost; choice in CommandPost | latest.track_id > 0 => exch any EngageOrder from CommandPost to Launcher; | else => end } ``` ### Statements The name is a global declaration. The body is a sequence of statements, executed in order: - `exch` — one interaction between two components, a send and its matching receive as one step. - `choice` — one component picking an arm for the whole protocol. - `loop` and `break`. - `parallel` — branches whose statements interleave. - `in C { … }` — local statements executed in one component. - `do` — running another global protocol. - A standalone annotation. `let`, `var`, and `set` are not statements here. A value the protocol needs later is bound by `exch … into let`, and state that belongs to one component lives in an `in` block. Every statement names the components it involves, so the protocol says who talks to whom in the order it happens. What each component does on its own — the behavior a local protocol spells out — appears only where an `in` block puts it. ### Errors #### Binding a variable directly in a global protocol ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Engage { var count: int32 = 0; // error: unexpected exch any TrackReport from Radar to CommandPost; } ``` ### Related - [local protocol](https://flexlang.org/flex/protocols/local-protocol.md) — one component's view of the same conversation. - [exch](https://flexlang.org/flex/protocols/exch.md) — the interaction statement and its payload forms. - [choice](https://flexlang.org/flex/protocols/choice.md) — branching a global protocol. - [in block](https://flexlang.org/flex/protocols/in-block.md) — local statements and state inside a global protocol. - [parallel](https://flexlang.org/flex/protocols/parallel.md) — interleaving branches. --- ## group A named set of components and other groups: an architectural boundary drawn around runnable software, not a participant in it. A group never sends, receives, or runs a protocol. ```flex component Radar; component Sonar; component TrackFusion; component CommandPost; group Sensors { Radar; Sonar; } group CombatSystem { Sensors; TrackFusion; CommandPost; } ``` ### Members The body is a sequence of members, each a path and a `;`. - The name is a global declaration, unique in its file and importable by another module. - A group may have zero members. - Each member names a component or a group. - Membership is by declaration, not by spelling. Two import aliases of one component name one member. - A group is not a component. It cannot stand where a component is required: a `connection` endpoint, a connection clause, or the `in C` of a local protocol, a `choice`, or an `in` block. - A `system` names local protocols, so a group cannot be a `system` member either. ```flex files module fleet::patrol.sensors component Radar; component Sonar; // ──── module fleet::patrol.architecture import fleet::patrol.sensors as s group Sensors { s.Radar; s.Sonar; } ``` ### Nesting A group inside another group draws a boundary within a boundary. - A member belongs to at most one group. This holds across modules: two groups in two files cannot both claim one component. - Membership is acyclic. No group contains itself, directly or through other groups. - Groups therefore form a forest: every component and group has at most one parent group, and no cycles. - A group need not have a parent, and a component need not belong to any group. ```flex component Radar; component Sonar; component Launcher; component GroundStation; group Sensors { Radar; Sonar; } group Weapons { Launcher; } group Ship { Sensors; Weapons; } ``` `Ship` contains `Sensors` and `Weapons`, each of which has one parent. `GroundStation` belongs to no group. ### Errors #### A member that is not a component or a group ```flex invalid component Radar; message struct TrackReport { track_id: uint32; } group Sensors { Radar; TrackReport; // error: group-member-target } ``` #### A component in two groups Every membership after the first is the error, ordered by file and then by position in the file. ```flex invalid component Radar; group Sensors { Radar; } group Surveillance { Radar; // error: duplicate-group-membership } ``` #### The same member twice in one group ```flex invalid component Radar; group Sensors { Radar; Radar; // error: duplicate-group-membership } ``` #### A group that contains itself Every member entry on the cycle is an error, in whichever group it appears. ```flex invalid group Sensors { Surveillance; // error: cyclic-group-membership } group Surveillance { Sensors; // error: cyclic-group-membership } ``` #### Using a group where a component is required ```flex invalid component Radar; component CommandPost; group Sensors { Radar; } connection TrackFeed from Sensors to CommandPost; // error: connection-target-not-component ``` ### Related - [component](https://flexlang.org/flex/protocols/component.md) — the participants a group draws a boundary around. - [system](https://flexlang.org/flex/protocols/system.md) — a named set of local protocols, the behavioral counterpart to a group. --- ## in block Local statements inside a global protocol, executed by one component. It is where a global protocol says what a single component does between exchanges. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } global protocol Report { exch any TrackReport into let latest: TrackReport from Radar to CommandPost; in CommandPost { var alerts: int32 = 0; branch | latest.track_id > 0 => set alerts = alerts + 1; | else => end } exch any Ack from CommandPost to Radar; } ``` ### Statements `in C { … }` holds local-protocol statements, and `C` names a component. The enclosed statements follow the local-protocol rules, so a `send` inside the block sends from `C`: - `let`, `var`, and `set` — the state `C` keeps between exchanges, and what most `in` blocks are for. - `branch` and `listen` — a decision `C` makes privately. No other component's behavior forks with it, which is what separates this from a [`choice`](https://flexlang.org/flex/protocols/choice.md). - `loop` and `break`. - `do` — running another local protocol of `C`. - `send` and `recv` — legal, and seldom what a global protocol wants. An interaction belongs in an [`exch`](https://flexlang.org/flex/protocols/exch.md), which states both halves at once; a lone `send` here has no matching receive anywhere, so nothing ever takes the message. - A standalone annotation. ### Bindings - A `let` or `var` declared in the block is component-scoped: it is visible to later global statements that involve `C`, and to nothing else. A statement involves a component by naming it as a connection endpoint, deriving that endpoint from a connection, or choosing in it. - Visibility runs forward only. A statement above the block cannot name what it binds. - Binding names are one namespace across every `in` block of the protocol, whichever component each block names. ```flex component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } message struct EngageOrder { track_id: uint32; } global protocol Engage { exch any TrackReport from Radar to CommandPost; in CommandPost { var engagements: int32 = 0; set engagements = engagements + 1; } choice in CommandPost | engagements > 0 => exch any EngageOrder from CommandPost to Launcher; | else => end } ``` ### Errors #### An in block for something that is not a component An `in` block's statements run as one component, and a [group](https://flexlang.org/flex/protocols/group.md) is not one. ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } group Sensors { Radar; } global protocol Report { exch any TrackReport from Radar to CommandPost; in Sensors { // error: in-block-component-not-component var reports: int32 = 0; } } ``` #### One name in two in blocks ```flex invalid component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } global protocol Engage { exch any TrackReport from Radar to CommandPost; in CommandPost { var count: int32 = 0; } in Launcher { var count: int32 = 0; // error: duplicate-in-block-binding } } ``` #### Reading a binding from a component that is not involved ```flex invalid component Radar; component CommandPost; component Launcher; message struct TrackReport { track_id: uint32; } message struct EngageOrder { track_id: uint32; } global protocol Engage { in CommandPost { var engagements: int32 = 0; } choice in Launcher | engagements > 0 => exch any EngageOrder from Launcher to CommandPost; // error: unresolved-reference | else => end } ``` ### Related - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the statements an `in` block sits among. - [local protocol](https://flexlang.org/flex/protocols/local-protocol.md) — the statements it holds, and the `let`/`var`/`set` rules. - [choice](https://flexlang.org/flex/protocols/choice.md) — the statement that reads an `in` block's state most often. --- ## listen A decision made inside a local protocol by the message that arrives. Each arm is guarded by a `recv`. ```flex component Radar; component CommandPost; message struct Ack { track_id: uint32; } message struct Reject { track_id: uint32; } message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to CommandPost; listen | recv _: Ack from CommandPost => | recv _: Reject from CommandPost => end } ``` ### Arms Each arm is a `|`, a `recv` without its semicolon, `=>`, and the statements to run. The block closes with `end`. - An arm's `recv` obeys the [`recv`](https://flexlang.org/flex/protocols/recv.md) rules: the message type is message data, an `assuming` predicate is `bit`, and the connection clause receives at this component. - A `let` binding in an arm's guard is in scope in that arm and nowhere else. - Arms are distinguished by the message they receive. Two arms receiving the same type from the same component are not a choice the sender can direct. - An arm's statements are local-protocol statements, and an arm may run nothing. ```flex component Radar; component CommandPost; message struct Ack { track_id: uint32; } message struct Reject { track_id: uint32; reason: string; } message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { var rejected: int32 = 0; send any TrackReport to CommandPost; listen | recv let ack: Ack from CommandPost => send any TrackReport to CommandPost; | recv let reject: Reject assuming reject.track_id > 0 from CommandPost => set rejected = rejected + 1; end } ``` ### Errors #### An arm receiving a type that has no wire form ```flex invalid component Radar; component CommandPost; struct Ack { track_id: uint32; } message struct Reject { track_id: uint32; } local protocol Sweep in Radar { listen | recv _: Ack from CommandPost => // error: message-data-type | recv _: Reject from CommandPost => end } ``` #### An arm receiving from the wrong end ```flex invalid component Radar; component CommandPost; message struct Ack { track_id: uint32; } local protocol Sweep in Radar { listen | recv _: Ack from Radar to CommandPost => // error: connection-directionality end } ``` ### Related - [recv](https://flexlang.org/flex/protocols/recv.md) — the statement each arm is guarded by. - [branch](https://flexlang.org/flex/protocols/branch.md) — the same shape, with guards the component evaluates itself. - [choice](https://flexlang.org/flex/protocols/choice.md) — the global protocol's branching statement. --- ## local protocol The behavior of one component, written as one ordered sequence of statements. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to CommandPost; recv _: Ack from CommandPost; } ``` ### Statements `local protocol P in C` says that `P` runs in component `C`. The name is a global declaration, and `C` names a component. The body is a sequence of statements, executed in order: - `send` and `recv` — a message leaving, and a message arriving. - `branch` and `listen` — a choice the component makes itself, and a choice the arriving message makes for it. - `loop` and `break`. - `do` — running another protocol on the same component. - `let`, `var`, and `set` — the state the protocol keeps. - A standalone annotation. An empty body is legal: a protocol may declare a component's behavior to be nothing. **A Protocol Is Not an Implementation** A local protocol says which messages cross and in what order, never how the component computes one — `send any TrackReport` names the type and no value. The implementation lives outside Flex, and the protocol is what it is checked against. ### Variables `let` binds a value once, `var` binds one that can be reassigned, and `set` reassigns a `var`. All three are local-protocol statements; a global protocol binds with `exch … into let` or inside an [`in` block](https://flexlang.org/flex/protocols/in-block.md). - A protocol `let` or `var` binds one identifier. There is no tuple pattern here, unlike the `let` in an expression block. - The type annotation is optional. Without one the initializer's type has to be fully determined, so a bare numeric literal needs an annotation or an ascription. - With an annotation, the initializer is assignable to it. - `set` targets a `var` in scope, and its right side is assignable to that variable's type. - A binding may not shadow a variable or parameter already in scope. - A binding is visible to every later statement of the same block. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { var count: int32 = 0; let limit: int32 = 10; let active = true; send any TrackReport to CommandPost; set count = count + 1; } ``` ### Errors #### Running a protocol in something that is not a component ```flex invalid component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in TrackReport { // error: local-protocol-component-not-component send any TrackReport to CommandPost; } ``` #### Reassigning a let binding `set` writes to a `var`. A `let` is bound once. ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { let limit: int32 = 10; set limit = 20; // error: set-target-not-var send any TrackReport to CommandPost; } ``` ### Related - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the same conversation written once, for every component. - [send](https://flexlang.org/flex/protocols/send.md) — the message-sending statement and its four payload forms. - [recv](https://flexlang.org/flex/protocols/recv.md) — the message-receiving statement. - [do](https://flexlang.org/flex/protocols/do.md) — handing control to another protocol on this component. - [system](https://flexlang.org/flex/protocols/system.md) — the set of local protocols that make up one deployment. --- ## loop, break A block that repeats until something inside it breaks out. Both a local and a global protocol may hold one. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Sweep in Radar { loop { send any TrackReport to CommandPost; recv let ack: Ack from CommandPost; branch | ack.track_id == 0 => break; | else => end } } ``` ### Bodies - An empty body is legal, and repeats nothing forever. - The optional name after `loop` is a label — `loop reporting { … }`. It exists so a [`break`](#break) can say which loop it leaves, which only matters once loops nest. - A loop with no reachable `break` repeats forever, which is a legitimate description of a component that runs until it is switched off. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Stream { in CommandPost { var latest: TrackReport = TrackReport { track_id = 1; }; } loop reporting { exch any TrackReport into latest from Radar to CommandPost; choice in CommandPost | latest.track_id == 0 => break reporting; | else => end } } ``` ### Invariants An `invariant` is an assertion about the loop, not a condition for leaving it: a claim that something is true each time the loop begins an iteration. Leaving is [`break`](#break)'s job. - An `invariant` may appear on a loop in a local protocol, or on a loop inside an [`in` block](https://flexlang.org/flex/protocols/in-block.md). - A loop in a global protocol takes no `invariant`. - The expression has type `bit`. - It begins with a reference to a `var` or `let` in scope. `invariant 5 == 5` is rejected — it holds, and says nothing about the loop. - An invariant over state the body never changes is accepted, and says nothing about the loop either. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { var count: int32 = 0; loop invariant count < 10 { send any TrackReport to CommandPost; set count = count + 1; branch | count >= 10 => break; | else => end } } ``` ### break `break` leaves a loop. A bare `break` leaves the innermost one; `break L` leaves the loop labeled `L`. - A `break` sits inside the loop it leaves. - A labeled `break` names a loop that encloses it, which is how an inner loop leaves an outer one. - Execution continues after the loop it left. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Sweep in Radar { var count: int32 = 0; loop reporting { loop { send any TrackReport to CommandPost; set count = count + 1; branch | count >= 10 => break reporting; | else => break; end } recv _: Ack from CommandPost; } } ``` ### Errors #### A break outside every loop ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to CommandPost; break; // error: break-outside-loop } ``` #### A break naming a loop that does not enclose it ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { loop { send any TrackReport to CommandPost; break reporting; // error: break-outside-loop } } ``` #### An invariant that does not begin with a bound variable ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { loop invariant 5 == 5 { // error: loop-invariant-expression send any TrackReport to CommandPost; } } ``` ### Related - [branch](https://flexlang.org/flex/protocols/branch.md) — how a loop in a local protocol usually decides to leave. - [choice](https://flexlang.org/flex/protocols/choice.md) — how a loop in a global protocol does. - [do](https://flexlang.org/flex/protocols/do.md) — repetition by tail call instead of by loop. - [local protocol](https://flexlang.org/flex/protocols/local-protocol.md) — the `var` an invariant reads. --- ## parallel Branches inside a global protocol that run at the same time. Nothing orders one branch's statements against another's, and the protocol continues past the block once every branch has finished. ```flex component Radar; component GroundStation; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Heartbeat { uptime_s: uint32; } global protocol Watch { parallel { exch any TrackReport from Radar to CommandPost; } with { exch any Heartbeat from GroundStation to CommandPost; } } ``` ### Branches `parallel { … }` opens the first branch, and each `with { … }` after it adds another. - Statements inside one branch keep their order. Statements in different branches have none relative to each other. - A branch may be empty, and a `parallel` with no `with` is one branch running by itself. - A branch holds any global statement, so a branch may exchange messages, loop, or choose. - Nothing in a branch is in tail position, since the other branches continue after it. - Branches that share a component describe that component doing two things at once, which is why the ordinary case is branches over disjoint components. ```flex component Radar; component GroundStation; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Heartbeat { uptime_s: uint32; } message struct Ack { track_id: uint32; } global protocol Watch { parallel { exch any TrackReport from Radar to CommandPost; exch any Ack from CommandPost to Radar; } with { exch any Heartbeat from GroundStation to CommandPost; } exch any Ack from CommandPost to GroundStation; } ``` The `Ack` to `Radar` follows the `TrackReport`. Neither is ordered against the `Heartbeat`, and the final `Ack` follows all three. ### Related - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the statement sequence a branch holds. - [exch](https://flexlang.org/flex/protocols/exch.md) — the interaction a branch is usually made of. - [choice](https://flexlang.org/flex/protocols/choice.md) — branches where one runs instead of all. --- ## recv In a local protocol, a message arriving at the component the statement is written in. The component waits until it arrives. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Collect in CommandPost { recv let latest: TrackReport from Radar; } ``` ### Payloads The payload takes one of the following forms, followed by the [connection clause](https://flexlang.org/flex/protocols/connection.md#clauses) and a semicolon. - A name receives into a variable already in scope, which is a `var` whose type is message data. - `_` discards what arrives, and `_: T` discards a message of a stated type. - `any name: T assuming pred` binds the message under a stated assumption. - `let pattern: T` binds what arrives for later statements, and takes an `assuming` too. The message type is message data: a non-abstract `message` struct, or a `message` variant. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; bearing_deg: float32; } message struct Ack { track_id: uint32; } local protocol Collect in CommandPost { var latest: TrackReport = TrackReport { track_id = 0; bearing_deg = 0.0; }; recv latest from Radar; recv _: Ack from Radar; recv any report: TrackReport assuming report.bearing_deg >= 0.0 from Radar; recv let first: TrackReport from Radar; send any Ack to Radar; } ``` ### Assumptions An `assuming` predicate is what the receiver assumes about the message, checked when the message arrives. Its type is `bit`. - A value that violates the assumption is a safety error at that receipt. - The assumption is not a filter and not a guard. It does not wait for a value that satisfies it, and it does not discard one that fails: the receive fires either way. - Two arms of a [`listen`](https://flexlang.org/flex/protocols/listen.md) receiving the same type and differing only in their `assuming` are therefore indistinguishable, so an assumption cannot direct a choice by value. - The assumption is the counterpart of the sender's [`where`](https://flexlang.org/flex/protocols/send.md#predicates). - Neither type checking nor evaluation is affected by an assumption. ### Errors #### An assuming predicate that is not bit ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Collect in CommandPost { recv any report: TrackReport assuming report.track_id from Radar; // error: recv-assumption-type } ``` #### Receiving into a variable that has no wire form ```flex invalid component Radar; component CommandPost; local protocol Collect in CommandPost { var count: int32 = 0; recv count from Radar; // error: message-data-type } ``` ### Related - [send](https://flexlang.org/flex/protocols/send.md) — the matching statement at the other end. - [listen](https://flexlang.org/flex/protocols/listen.md) — arms guarded by a `recv`, where the message decides which one runs. - [exch](https://flexlang.org/flex/protocols/exch.md) — both halves written as one global statement. - [connection](https://flexlang.org/flex/protocols/connection.md) — the `from`/`to`/`on` clause every message statement carries. --- ## send In a local protocol, a message leaving the component the statement is written in. The statement names what is sent and where it goes. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; bearing_deg: float32; } local protocol Sweep in Radar { send any TrackReport to CommandPost; } ``` ### Payloads The payload takes one of the following forms, followed by the [connection clause](https://flexlang.org/flex/protocols/connection.md#clauses) and a semicolon. - An expression sends that value. - `any T` sends some value of `T`, without saying which. - `any name: T where pred` sends some value of `T` that satisfies `pred`. - `let pattern: T` binds what is sent, so later statements can refer to it. It also takes a `where`. The message type is message data: a non-abstract `message` struct, or a `message` variant. In the expression form, the expression's type is what has to be message data. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; bearing_deg: float32; } message struct Ack { track_id: uint32; } local protocol Sweep in Radar { send TrackReport { track_id = 7; bearing_deg = 91.5; } to CommandPost; send any TrackReport to CommandPost; send any report: TrackReport where report.bearing_deg >= 0.0 to CommandPost; send let sent: TrackReport to CommandPost; recv any ack: Ack assuming ack.track_id == sent.track_id from CommandPost; } ``` ### Predicates A `where` predicate is what the sender guarantees about the value it puts on the wire, and its type is `bit`. - The predicate is a promise, not a filter. `any report: TrackReport where pred` may produce any value of the type satisfying `pred`, so whatever consumes it has to be correct for all of them and not for one convenient witness. - The name bound before the colon is in scope in the predicate, and nowhere else. - The predicate is the counterpart of the receiver's [`assuming`](https://flexlang.org/flex/protocols/recv.md#assumptions): the pair is what makes an exchange compatible or not. - Neither type checking nor evaluation is affected by a predicate. ### Errors #### Sending a type that has no wire form Only a `message` struct or a `message` variant crosses a connection. ```flex invalid component Radar; component CommandPost; struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any TrackReport to CommandPost; // error: message-data-type } ``` #### A where predicate that is not bit ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } local protocol Sweep in Radar { send any report: TrackReport where report.track_id to CommandPost; // error: send-predicate-type } ``` ### Related - [recv](https://flexlang.org/flex/protocols/recv.md) — the matching statement at the other end. - [exch](https://flexlang.org/flex/protocols/exch.md) — both halves written as one global statement. - [connection](https://flexlang.org/flex/protocols/connection.md) — the `from`/`to`/`on` clause every message statement carries. - [struct](https://flexlang.org/flex/data-types/struct.md) — the `message` modifier that gives a type a wire form. --- ## system A named set of local protocols that runs together: one protocol per component, giving the whole assembly's behavior. ```flex component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Ack { track_id: uint32; } local protocol Report in Radar { send any TrackReport to CommandPost; recv _: Ack from CommandPost; } local protocol Collect in CommandPost { recv _: TrackReport from Radar; send any Ack to Radar; } system Tracking { Report; Collect; } ``` ### Members The body is a sequence of protocol references, each a path and a `;`. - Each reference names a local protocol. - No two references name protocols on the same component. The one protocol a component has here is its whole top-level behavior, so a second would be ambiguous. - A component with nothing to do in this system is simply left out. - A protocol may belong to more than one system, so one component's behavior can appear in several assemblies. A `system` says nothing new about the protocols it lists. It names the set, and adds no statements of its own. ### Errors #### Listing something that is not a local protocol ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } global protocol Report { exch any TrackReport from Radar to CommandPost; } system Tracking { Report; // error: system-protocol-not-local } ``` #### Two protocols on the same component ```flex invalid component Radar; component CommandPost; message struct TrackReport { track_id: uint32; } message struct Heartbeat { uptime_s: uint32; } local protocol Report in Radar { send any TrackReport to CommandPost; } local protocol Beat in Radar { send any Heartbeat to CommandPost; } system Tracking { Report; Beat; // error: system-duplicate-component } ``` ### Related - [local protocol](https://flexlang.org/flex/protocols/local-protocol.md) — the declarations a system lists. - [component](https://flexlang.org/flex/protocols/component.md) — the participants it covers one apiece. - [global protocol](https://flexlang.org/flex/protocols/global-protocol.md) — the same assembly described from above instead.