
* Allow compile-time constants as toplevel declarations * Remove the test that fails on toplevel consts * Warn when shadowing a constant * Allow records to be used as compile time constants * Allow data constructors in compile-time constants * Disable some warnings for toplevel constants Since variables and functions cannot be used in the definition of a compile time constants, the following warnings are not going to be reported: * Used/Unused variable * Used/Unused function * Do not reverse constants declarations * Add tests for all valid expressions * Add test for accessing const from namespace * Revert "Do not reverse constants declarations" This reverts commit c4647fadacd134866e4be9c2ab4b0d54870a35fd. * Add test for assigining constant to a constant * Show empty map or record error when assigning to const * Report all invalid constant expressions before fail * Allow accessing records fields in toplevel consts * Undo a mistake * Add test for warning on const shadowing * Show error message when using pattern matching for consts * Remove unused error * Ban toplevel constants in contract interfaces * Varibles rename * Change the error message for invalid_const_id * Make constants public in namespaces and private in contracts * Add a warning about unused constants in contracts * Use ban_when_const for function applications * Test for qualified access of constants in functions * Add failing tests * Add test for the unused const warning * Update CHANGELOG * Update all_syntax test file * Treat expr and type inside bound as bound * Allow typed ids to be used for constants * List valid exprs in the error message for invalid exprs * Fix tests * Update the docs about constants * Update syntax docs * Check validity of const exprs in a separate functions * Call both resolve_const and resolve_fun from resolve_var
9.4 KiB
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 constructorTVar = '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_
separatorsBytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*
byte array literal with optional_
separatorsString
string literal enclosed in"
with escape character\
Char
character literal enclosed in'
with escape character\
AccountAddress
base58-encoded 32 byte account pubkey withak_
prefixContractAddress
base58-encoded 32 byte contract address withct_
prefixOracleAddress
base58-encoded 32 byte oracle address withok_
prefixOracleQueryId
base58-encoded 32 byte oracle query id withoq_
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 (likeId
). - 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 ofX
s.Sep(X, S)
is short for[X (S X)*]
, i.e. a possibly empty sequence ofX
s separated byS
s.Sep1(X, S)
is short forX (S X)*
, i.e. same asSep
, but must not be empty.
Declarations
A Sophia file consists of a sequence of declarations in a layout block.
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,
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
| 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,
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
| '(' 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
| OracleAddress | OracleQueryId // Chain identifiers
| '???' // 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' | '^'
| '|>'
UnOp ::= '-' | '!'
Operators types
Operators | Type |
---|---|
- + * / mod ^ |
arithmetic operators |
! && ` |
|
== != < > =< >= |
comparison operators |
:: ++ |
list operators |
` | >` |
Operator precedence
In order of highest to lowest precedence.
Operators | Associativity |
---|---|
! |
right |
^ |
left |
* / mod |
left |
- (unary) |
right |
+ - |
left |
:: ++ |
right |
< > =< >= == != |
none |
&& |
right |
` | |
` | >` |