Syntax Reference¶
This page covers the complete lexical and grammatical rules for len.l1.
File Extension¶
Level 1 source files use the .l1 extension.
Whitespace and Indentation¶
Whitespace separates tokens and is otherwise ignored, except for indentation. len.l1 is indentation-sensitive:
- The body of a
spec,fn,contract,struct, andquasiblock is indented relative to its header. - A dedent (return to an earlier column) closes the block.
- Tabs and spaces may not be mixed within the same file.
Comments¶
# This is a line comment — starts at the beginning of a line after optional indentation
<#
This is a block comment.
It may span multiple lines.
#>
#begins a line comment only when it appears at the start of a line (after optional whitespace).<# … #>is a block comment and may appear anywhere.- Comments are ignored by the parser and have no effect on semantics.
Docstrings¶
Docstrings document declarations. They are preserved in the AST for tooling but ignored by validation.
"This is a single-line docstring."
"""
This is a multi-line docstring.
It may span as many lines as needed.
"""
Place a docstring immediately before or after the declaration it documents.
Identifiers¶
An identifier begins with a letter or underscore and continues with letters, digits, and underscores.
Identifiers are case-sensitive. By convention:
| Form | Used for |
|---|---|
PascalCase |
Types, relations, contracts, structs, constants |
snake_case |
Functions, specs, local binders |
dot.separated |
Module paths in import |
Literals¶
| Kind | Example | Notes |
|---|---|---|
| Integer | 42, 0, 100 |
Positive integers |
| String | "hello" |
Double-quoted; used in docstrings and quasi bodies |
| Boolean | true, false |
Reserved keywords; map to True/False constants |
Operators and Symbolic Tokens¶
The following symbolic forms are recognised by the lexer:
| Symbol | Role |
|---|---|
. |
Module path separator; field access |
: |
Type annotation separator |
( ) |
Parameter lists, formula grouping |
, |
Parameter separator |
= |
Equality; used in syntax sugar and field assignment |
< > |
Comparison; block comment delimiters (<#, #>) |
\| |
Cardinality notation (\|s\|) |
/ |
Formula division |
\ |
Backslash in formula notation |
* |
Multiplication |
+ |
Addition |
.. |
Range in quasi blocks (0 .. n) |
Sugar Operators (core.math.logic.sugar)¶
Import core.math.logic.sugar to enable these infix and prefix operators:
| Operator | Desugars to | Meaning |
|---|---|---|
A => B |
A implies B |
Implication |
A <=> B |
A iff B |
Biconditional |
A /\ B |
A and B |
Conjunction |
A \/ B |
A or B |
Disjunction |
!A |
not A |
Negation |
Example:
Top-Level Declarations¶
A .l1 file is a sequence of top-level declarations. Each declaration starts at column 0.
Type Declaration¶
Relation Declaration¶
RelDecl = "rel" Identifier "(" ParamList ")" [ "refines" QualifiedName ] ;
ParamList = Param { "," Param } ;
Param = Identifier ":" QualifiedName ;
Constant Declaration¶
Function Declaration¶
FnDecl = "fn" Identifier "(" ParamList ")" "->" Param
{ FnClause }
QuasiBlock ;
FnClause = ImplementsClause | RequiresClause | EnsuresClause ;
ImplementsClause = "implements" Application ;
RequiresClause = "requires" Formula ;
EnsuresClause = "ensures" Formula ;
fn sort(input: Seq) -> output: Seq
implements Sort(input, output)
requires NonEmpty(input)
ensures Sorted(output)
ensures Permutation(input, output)
quasi:
# pseudocode
Spec Declaration¶
SpecDecl = "spec" Identifier INDENT { GivenClause } MustClause DEDENT ;
GivenClause = "given" Param { "," Param } ;
MustClause = "must" Formula ;
spec sort_correct
given input, output: Seq
must Sort(input, output) iff
Sorted(output) and Permutation(input, output)
Contract Declaration¶
ContractDecl = "contract" Identifier [ "(" ParamList ")" ]
INDENT { ContractMember } DEDENT ;
ContractMember = RelDecl | FnDecl | SpecDecl ;
Struct Declaration¶
StructDecl = "struct" Identifier INDENT { FieldDecl } DEDENT ;
FieldDecl = Identifier ":" QualifiedName ;
Syntax Declaration¶
SyntaxDecl = "syntax" Pattern "where" BindingList "implies" QualifiedApplication ;
Pattern = token sequence ;
BindingList = Param { "," Param } ;
syntax x in s where x: Set, s: Set implies Member(x, s)
syntax A => B where A: Formula, B: Formula implies A implies B
Import Declaration¶
ImportDecl = "import" ModulePath ;
FromImport = "from" ModulePath "import" Identifier { "," Identifier } ;
ModulePath = Identifier { "." Identifier } ;
Formulas¶
A formula is a logical expression appearing in spec bodies, fn clauses, and syntax targets.
| Form | Example |
|---|---|
| Relation application | Member(x, s) |
| Equality | x = y |
| Conjunction | P and Q |
| Disjunction | P or Q |
| Negation | not P |
| Implication | P implies Q |
| Biconditional | P iff Q |
| Universal | forall x: T . P |
| Existential | exists x: T . P |
| Syntax sugar | x in s, A => B |
Precedence (highest to lowest): not > and > or > implies > iff.
Use parentheses to override precedence.
Quasi Blocks¶
A quasi block is an indented pseudocode body attached to an fn declaration.
The body is captured as raw lines. Allowed constructs depend on the active style profile.
ProceduralAlgorithm style (default) allows:
| Keyword | Meaning |
|---|---|
let <var> := <expr> |
Introduce or bind a variable |
set <var> := <expr> |
Reassign a variable |
return <expr> |
Return a value |
if <cond>: |
Conditional branch |
else: |
Else branch |
while <cond>: |
Loop |
for <var> in <range>: |
Iteration |
append(<list>, <val>) |
Append to a list |
These keywords are quasi-local — they are not part of the global len.l1 grammar.
Qualified Names¶
Declarations from other modules are referenced by their qualified name:
Or after importing:
Identifiers resolve in this order: local scope → imported modules → error.