2.7. Surface grammar
The transcription below normalises the layout directives in LaurelGrammar.st. { X } means
zero or more, [ X ] optional, and X , ... a comma-separated list. A file is a sequence of
declarations with no separator between them; each declaration's own terminator (; for a
procedure, the closing brace or the next leading keyword otherwise) delimits it.
program = { declaration } ;
declaration = procedure-declaration
| coroutine-declaration
| composite-declaration
| datatype-declaration
| constrained-type-declaration
| type-alias-declaration
| opaque-type-declaration
| global-variable-declaration ;
(* Types *)
type = "int" | "bool" | "real" | "float64" | "string"
| "bv" natural
| "TotalMap" type type
| "Core" identifier
| identifier
| identifier "<" type , ... ">"
| "(" type ")" ;
type-parameters = "<" identifier , ... ">" ;
(* Type, alias, and global declarations *)
datatype-declaration
= "datatype" identifier [ type-parameters ]
"{" [ constructor , ... ] "}" ;
constructor = identifier | identifier "(" [ argument , ... ] ")" ;
argument = identifier ":" type ;
constrained-type-declaration
= "constrained" identifier "=" identifier ":" type
"where" expression "witness" expression ;
type-alias-declaration
= "type" identifier [ type-parameters ] "=" type ;
opaque-type-declaration
= "opaque" identifier [ type-parameters ] ;
global-variable-declaration
= "var" identifier ":" type [ ":=" expression ] ;
(* Composites *)
composite-declaration
= "composite" identifier [ type-parameters ]
[ "extends" type , ... ]
"{" { field-declaration } { procedure-declaration } "}" ;
field-declaration = [ "var" ] identifier ":" type ;
(* Procedures: the clause order below is significant *)
procedure-declaration
= "procedure" identifier [ type-parameters ]
"(" [ parameter , ... ] ")"
[ ":" type ]
[ "returns" "(" [ parameter , ... ] ")" ]
[ "throws" "(" identifier ":" type ")" ]
{ requires-clause }
[ "invokeOn" expression ]
[ "entry" ]
[ opaque-specification ]
[ body | "external" ]
";" ;
parameter = identifier ":" type ;
body = expression ;
requires-clause = [ "free" | "checked" ] "requires" expression
[ "summary" string-literal ] ;
opaque-specification
= "opaque"
{ ensures-clause }
{ modifies-clause }
{ throws-on-clause }
{ "reads" identifier , ... }
{ "writes" identifier , ... } ;
ensures-clause = [ "free" | "checked" ] "ensures" expression
[ "summary" string-literal ] ;
modifies-clause = "modifies" "*" | "modifies" expression , ... ;
throws-on-clause = "throwsOn" expression "{" { throws-on-spec } "}" ;
throws-on-spec = "ensures" expression [ "summary" string-literal ]
| "modifies" expression , ... ;
(* Coroutines *)
coroutine-declaration
= "coroutine" identifier "(" [ parameter , ... ] ")"
[ "yields" "(" [ parameter , ... ] ")" ]
[ "resumes" "(" [ parameter , ... ] ")" ]
{ requires-clause } { ensures-clause }
{ relies-clause } { guarantees-clause }
{ modifies-clause }
[ body | "external" ]
";" ;
relies-clause = "relies" expression [ "summary" string-literal ] ;
guarantees-clause = "guarantees" expression [ "summary" string-literal ] ;
(* Unified statements and expressions *)
expression = literal
| identifier
| "(" expression ")"
| variable-declaration
| expression "(" [ expression , ... ] ")"
| "new" identifier [ "<" type , ... ">" ]
| expression "#" identifier
| assignment
| compound-assignment
| multi-assignment
| increment-expression
| unary-expression
| binary-expression
| quantifier
| "old" "(" expression ")"
| "oldGuarantee" "(" expression ")"
| "oldRelies" "(" expression ")"
| if-expression
| "assert" expression [ "summary" string-literal ]
| "assume" expression
| "throw" expression
| "return" [ expression ]
| "yield"
| block
| block identifier
| "exit" identifier
| try-expression
| while-loop
| for-loop
| do-while-loop
| expression "is" type
| expression "as" type ;
literal = "true" | "false" | natural | decimal | string-literal
| natural "bv" natural
| "<?>" | "<??>" ;
variable-declaration
= "var" identifier [ ":" type ] [ ":=" expression ] ;
assignment = expression ":=" expression ;
compound-assignment = expression compound-operator expression ;
compound-operator = "+=" | "-=" | "*=" | "/=" | "%=" | "^=" ;
multi-assignment = "assign" assignment-target , ... ":=" expression ;
assignment-target = "var" identifier [ ":" type ]
| identifier
| field-path "#" identifier ;
field-path = identifier | field-path "#" identifier ;
increment-expression
= "++" expression | "--" expression
| expression "++" | expression "--" ;
unary-expression = "!" expression | "-" expression ;
binary-expression = expression binary-operator expression ;
binary-operator = "+" | "-" | "*" | "/" | "%" | "/t" | "%t"
| "==" | "!=" | "<" | "<=" | ">" | ">="
| "&" | "|" | "&&" | "||" | "==>"
| "^" ;
quantifier = ( "forall" | "exists" ) "(" identifier ":" type ")"
[ "{" expression "}" ] "=>" expression ;
if-expression = "if" expression "then" expression
[ "else" expression ] ;
block = "{" [ expression { ";" expression } ] "}" ;
try-expression = "try" expression { catch-clause } [ finally-clause ] ;
catch-clause = "catch" identifier [ "when" expression ] expression ;
finally-clause = "finally" expression ;
while-loop = "while" "(" expression ")" { invariant-clause }
expression ;
for-loop = "for" "(" expression ";" expression ";" expression ")"
{ invariant-clause } expression ;
do-while-loop = "do" expression "while" "(" expression ")"
{ invariant-clause } ;
invariant-clause = "invariant" expression ;
A few points the productions alone do not make obvious:
-
A single anonymous output (
: T) and named outputs (returns (...)) are separate optional slots, but they are alternative user forms. Do not write both. -
Fields must precede procedures inside a composite.
-
A procedure body is one expression. Most imperative procedures use a block, which is also an expression.
-
A
returncarries no semicolon of its own; semicolons only separate it from neighbouring expressions in a block. -
A datatype constructor with no arguments may be written
NilorNil(). -
A multi-assignment field target is a plain identifier/field chain, not an arbitrary expression.
-
A local variable's
: typeannotation is grammatically optional, but an unannotatedvarwith no initializer cannot be given a type and is diagnosed. Write the annotation, or usevar x := eand let the initializer supply it. -
yield,yields,resumes,relies,guarantees,oldGuarantee, andoldReliesare coroutine-only; see Coroutines.