Skip to content

# Syntax

## Lexical syntax

### Comments

Single line comments start with `//` and block comments are enclosed in `/*` and `*/` and can be nested.

### Keywords

    contract elif else entrypoint false function if import include let mod namespace
    private payable stateful switch true type record datatype main interface

### 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
- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix
- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` 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 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

    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.

    File ::= Block(TopDecl)

    TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl)
           | 'namespace' Con '=' Block(Decl)
           | '@compiler' PragmaOp Version
           | 'include' String

    Decl ::= 'type'     Id ['(' TVar* ')'] '=' TypeAlias
           | 'record'   Id ['(' TVar* ')'] '=' RecordType
           | 'datatype' Id ['(' TVar* ')'] '=' DataType
           | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)

    FunDecl ::= Id ':' Type                             // Type signature
              | Id Args [':' Type] '=' Block(Stmt)      // Definition

    PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
    Version  ::= Sep1(Int, '.')

    EModifier ::= 'payable' | 'stateful'
    FModifier ::= 'stateful' | 'private'

    Args ::= '(' Sep(Pattern, ',') ')'

Contract declarations must appear at the top-level.

For example,

    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`):

    TypeAlias  ::= Type
    RecordType ::= '{' Sep(FieldType, ',') '}'
    DataType   ::= Sep1(ConDecl, '|')

    FieldType  ::= Id ':' Type
    ConDecl    ::= Con ['(' Sep1(Type, ',') ')']

For example,

    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

    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,

    'a => list('a) => (int * list('a))

## Statements

Function bodies are blocks of statements, where a statement is one of the following

    Stmt ::= 'switch' '(' Expr ')' Block(Case)
           | 'if' '(' Expr ')' Block(Stmt)
           | 'elif' '(' Expr ')' Block(Stmt)
           | 'else' Block(Stmt)
           | 'let' LetDef
           | Expr

    LetDef ::= Id Args [':' Type] '=' Block(Stmt)   // Function definition
             | Pattern '=' Block(Stmt)              // Value definition

    Case    ::= Pattern '=>' Block(Stmt)
    Pattern ::= Expr

`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example,

    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

    Expr ::= '(' LamArgs ')' '=>' Block(Stmt)   // Anonymous function    (x) => x + 1
           | '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
           | Id | Con | QId | QCon              // Identifiers           x, None, Map.member, AELib.Token
           | Int | Bytes | String | Char        // Literals              123, 0xff, #00abc123, "foo", '%'
           | AccountAddress | ContractAddress   // Chain identifiers
           | OracleAddress | OracleQueryId      // Chain identifiers

    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' | '^'
    UnOp  ::= '-' | '!'

## Operators types

Operators | Type
--- | ---
`-` `+` `*` `/` `mod` `^` | arithmetic operators
`!` `&&` `\|\|` | logical operators
`==` `!=` `<` `>` `=<` `>=` | comparison operators
`::` `++` | list operators

## Operator precendences

In order of highest to lowest precedence.

Operators | Associativity
--- | ---
`!` | right
`^` | left
`*` `/` `mod` | left
`-` (unary) | right
`+` `-` | left
`::` `++` | right
`<` `>` `=<` `>=` `==` `!=` | none
`&&` | right
`\|\|` | right