All checks were successful
Sophia Tests / tests (push) Successful in 48m54s
A few references to oracles still remain, but they have been removed as a feature, at least. Reviewed-on: #985 Reviewed-by: Ulf Wiger <ulfwiger@qpq.swiss> Co-authored-by: Craig Everett <zxq9@zxq9.com> Co-committed-by: Craig Everett <zxq9@zxq9.com>
293 lines
9.5 KiB
Markdown
293 lines
9.5 KiB
Markdown
# Syntax
|
|
|
|
## Lexical syntax
|
|
|
|
### Comments
|
|
|
|
Single line comments start with `//` and block comments are enclosed in `/*`
|
|
and `*/` and can be nested.
|
|
|
|
### Keywords
|
|
|
|
```
|
|
contract include let switch type record datatype if elif else function
|
|
stateful payable true false mod public entrypoint private indexed namespace
|
|
interface main using as for hiding
|
|
```
|
|
|
|
### Tokens
|
|
|
|
- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter.
|
|
- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter.
|
|
- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`)
|
|
- `QCon = (Con\.)+Con` qualified constructor
|
|
- `TVar = 'Id` type variable (e.g `'a`, `'b`)
|
|
- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators
|
|
- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators
|
|
- `String` string literal enclosed in `"` with escape character `\`
|
|
- `Char` character literal enclosed in `'` with escape character `\`
|
|
- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix
|
|
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
|
|
- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix
|
|
|
|
Valid string escape codes are
|
|
|
|
| Escape | ASCII | |
|
|
|---------------|-------------|---|
|
|
| `\b` | 8 | |
|
|
| `\t` | 9 | |
|
|
| `\n` | 10 | |
|
|
| `\v` | 11 | |
|
|
| `\f` | 12 | |
|
|
| `\r` | 13 | |
|
|
| `\e` | 27 | |
|
|
| `\xHexDigits` | *HexDigits* | |
|
|
|
|
|
|
See the [identifier encoding scheme](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/node/api/api_encoding.md) for the
|
|
details on the base58 literals.
|
|
|
|
## Layout blocks
|
|
|
|
Sophia uses Python-style layout rules to group declarations and statements. A
|
|
layout block with more than one element must start on a separate line and be
|
|
indented more than the currently enclosing layout block. Blocks with a single
|
|
element can be written on the same line as the previous token.
|
|
|
|
Each element of the block must share the same indentation and no part of an
|
|
element may be indented less than the indentation of the block. For instance
|
|
|
|
```sophia
|
|
contract Layout =
|
|
function foo() = 0 // no layout
|
|
function bar() = // layout block starts on next line
|
|
let x = foo() // indented more than 2 spaces
|
|
x
|
|
+ 1 // the '+' is indented more than the 'x'
|
|
```
|
|
|
|
## Notation
|
|
|
|
In describing the syntax below, we use the following conventions:
|
|
|
|
- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with
|
|
some associated value (like `Id`).
|
|
- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`.
|
|
- Choices are separated by vertical bars: `|`.
|
|
- Optional elements are enclosed in `[` square brackets `]`.
|
|
- `(` Parentheses `)` are used for grouping.
|
|
- Zero or more repetitions are denoted by a postfix `*`, and one or more
|
|
repetitions by a `+`.
|
|
- `Block(X)` denotes a layout block of `X`s.
|
|
- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s
|
|
separated by `S`s.
|
|
- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty.
|
|
|
|
|
|
## Declarations
|
|
|
|
A Sophia file consists of a sequence of *declarations* in a layout block.
|
|
|
|
```c
|
|
File ::= Block(TopDecl)
|
|
|
|
TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
|
| 'contract' 'interface' Con [Implement] '=' Block(Decl)
|
|
| 'namespace' Con '=' Block(Decl)
|
|
| '@compiler' PragmaOp Version
|
|
| 'include' String
|
|
| Using
|
|
|
|
Implement ::= ':' Sep1(Con, ',')
|
|
|
|
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
|
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
|
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
|
| 'let' Id [':' Type] '=' Expr
|
|
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
|
| Using
|
|
|
|
FunDecl ::= Id ':' Type // Type signature
|
|
| Id Args [':' Type] '=' Block(Stmt) // Definition
|
|
| Id Args [':' Type] Block(GuardedDef) // Guarded definitions
|
|
|
|
GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)
|
|
|
|
Using ::= 'using' Con ['as' Con] [UsingParts]
|
|
UsingParts ::= 'for' '[' Sep1(Id, ',') ']'
|
|
| 'hiding' '[' Sep1(Id, ',') ']'
|
|
|
|
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
|
|
Version ::= Sep1(Int, '.')
|
|
|
|
EModifier ::= 'payable' | 'stateful'
|
|
FModifier ::= 'stateful' | 'private'
|
|
|
|
Args ::= '(' Sep(Pattern, ',') ')'
|
|
```
|
|
|
|
Contract declarations must appear at the top-level.
|
|
|
|
For example,
|
|
```sophia
|
|
contract Test =
|
|
type t = int
|
|
entrypoint add (x : t, y : t) = x + y
|
|
```
|
|
|
|
There are three forms of type declarations: type aliases (declared with the
|
|
`type` keyword), record type definitions (`record`) and data type definitions
|
|
(`datatype`):
|
|
|
|
```c
|
|
TypeAlias ::= Type
|
|
RecordType ::= '{' Sep(FieldType, ',') '}'
|
|
DataType ::= Sep1(ConDecl, '|')
|
|
|
|
FieldType ::= Id ':' Type
|
|
ConDecl ::= Con ['(' Sep1(Type, ',') ')']
|
|
```
|
|
|
|
For example,
|
|
```sophia
|
|
record point('a) = {x : 'a, y : 'a}
|
|
datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))
|
|
type int_shape = shape(int)
|
|
```
|
|
|
|
## Types
|
|
|
|
```c
|
|
Type ::= Domain '=>' Type // Function type
|
|
| Type '(' Sep(Type, ',') ')' // Type application
|
|
| '(' Type ')' // Parens
|
|
| 'unit' | Sep(Type, '*') // Tuples
|
|
| Id | QId | TVar
|
|
|
|
Domain ::= Type // Single argument
|
|
| '(' Sep(Type, ',') ')' // Multiple arguments
|
|
```
|
|
|
|
The function type arrow associates to the right.
|
|
|
|
Example,
|
|
```sophia
|
|
'a => list('a) => (int * list('a))
|
|
```
|
|
|
|
## Statements
|
|
|
|
Function bodies are blocks of *statements*, where a statement is one of the following
|
|
|
|
```c
|
|
Stmt ::= 'switch' '(' Expr ')' Block(Case)
|
|
| 'if' '(' Expr ')' Block(Stmt)
|
|
| 'elif' '(' Expr ')' Block(Stmt)
|
|
| 'else' Block(Stmt)
|
|
| 'let' LetDef
|
|
| Using
|
|
| Expr
|
|
|
|
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
|
|
| Pattern '=' Block(Stmt) // Value definition
|
|
|
|
Case ::= Pattern '=>' Block(Stmt)
|
|
| Pattern Block(GuardedCase)
|
|
|
|
GuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)
|
|
|
|
Pattern ::= Expr
|
|
```
|
|
|
|
`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example,
|
|
|
|
```sophia
|
|
let x : int = 4
|
|
switch(f(x))
|
|
None => 0
|
|
Some(y) =>
|
|
if(y > 10)
|
|
"too big"
|
|
elif(y < 3)
|
|
"too small"
|
|
else
|
|
"just right"
|
|
```
|
|
|
|
## Expressions
|
|
|
|
```c
|
|
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
|
|
| '(' BinOp ')' // Operator lambda (+)
|
|
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
|
|
| Expr ':' Type // Type annotation 5 : int
|
|
| Expr BinOp Expr // Binary operator x + y
|
|
| UnOp Expr // Unary operator ! b
|
|
| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)
|
|
| Expr '.' Id // Projection state.x
|
|
| Expr '[' Expr ']' // Map lookup map[key]
|
|
| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }
|
|
| '[' Sep(Expr, ',') ']' // List [1, 2, 3]
|
|
| '[' Expr '|' Sep(Generator, ',') ']'
|
|
// List comprehension [k | x <- [1], if (f(x)), let k = x+1]
|
|
| '[' Expr '..' Expr ']' // List range [1..n]
|
|
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
|
|
| '(' Expr ')' // Parens (1 + 2) * 3
|
|
| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)
|
|
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
|
|
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
|
|
| AccountAddress | ContractAddress // Chain identifiers
|
|
| Signature // Signature
|
|
| '???' // Hole expression 1 + ???
|
|
|
|
Generator ::= Pattern '<-' Expr // Generator
|
|
| 'if' '(' Expr ')' // Guard
|
|
| LetDef // Definition
|
|
|
|
LamArgs ::= '(' Sep(LamArg, ',') ')'
|
|
LamArg ::= Id [':' Type]
|
|
|
|
FieldUpdate ::= Path '=' Expr
|
|
Path ::= Id // Record field
|
|
| '[' Expr ']' // Map key
|
|
| Path '.' Id // Nested record field
|
|
| Path '[' Expr ']' // Nested map key
|
|
|
|
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
|
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
|
|
| 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>'
|
|
UnOp ::= '-' | '!' | 'bnot'
|
|
```
|
|
|
|
## Operators types
|
|
|
|
| Operators | Type
|
|
| --- | ---
|
|
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
|
|
| `!` `&&` `\|\|` | logical operators
|
|
| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators
|
|
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
|
| `::` `++` | list operators
|
|
| `\|>` | functional operators
|
|
|
|
## Operator precedence
|
|
|
|
In order of highest to lowest precedence.
|
|
|
|
| Operators | Associativity
|
|
| --- | ---
|
|
| `!` `bnot`| right
|
|
| `^` | left
|
|
| `*` `/` `mod` | left
|
|
| `-` (unary) | right
|
|
| `+` `-` | left
|
|
| `<<` `>>` | left
|
|
| `::` `++` | right
|
|
| `<` `>` `=<` `>=` `==` `!=` | none
|
|
| `band` | left
|
|
| `bxor` | left
|
|
| `bor` | left
|
|
| `&&` | right
|
|
| `\|\|` | right
|
|
| `\|>` | left
|