The following notations are used by the Lexer and Syntax grammar snippets:
| Notation | Examples | Meaning |
|---|---|---|
| CAPITAL | KW_IF | A token produced by the lexer |
| ItalicCamelCase | Item | A syntactical production |
string |
x, while, * |
The exact character(s) |
| \x | \n, \r, \t, \0 | The character represented by this escape |
| x? | pub? |
An optional item |
| x* | OuterAttribute* | 0 or more of x |
| x+ | MacroMatch+ | 1 or more of x |
| xa..b | HEX_DIGIT1..6 | a to b repetitions of x |
| | | u8 | u16, Block | Item |
Either one or another |
| [ ] | [b B] |
Any of the characters listed |
| [ - ] | [a-z] |
Any of the characters in the range |
| ~[ ] | ~[b B] |
Any characters, except those listed |
~string |
~\n, ~*/ |
Any characters, except this sequence |
| ( ) | (, Parameter) |
Groups items |
Fe divides keywords into two categories:
These keywords can only be used in their correct contexts. They cannot be used as the names of:
Lexer:
KW_AS :as
KW_BREAK :break
KW_CONST :const
KW_CONTINUE :continue
KW_CONST :contract
KW_DEF :def
KW_ELIF :elif
KW_ELSE :else
KW_EMIT :emit
KW_ENUM :enum
KW_EVENT :event
KW_FALSE :false
KW_FOR :for
KW_IDX :idx
KW_IF :if
KW_IN :in
KW_LET :let
KW_NONPAYABLE :nonpayable
KW_PASS :pass
KW_PAYABLE :payable
KW_PUB :pub
KW_RETURN :return
KW_REVERT :revert
KW_SELFVALUE :self
KW_STRUCT :struct
KW_TRUE :true
KW_WHILE :while
KW_ADDRESS :address
These keywords aren't used yet, but they are reserved for future use. They have the same restrictions as strict keywords. The reasoning behind this is to make current programs forward compatible with future versions of Fe by forbidding them to use these keywords.
Lexer
KW_ABSTRACT :abstract
KW_ABSTRACT :async
KW_ABSTRACT :await
KW_DO :do
KW_EXTERNAL :external
KW_FINAL :final
KW_IMPL :impl
KW_MACRO :macro
KW_MATCH :match
KW_MUT :mut
KW_OVERRIDE :override
KW_PURE :pure
KW_SELFTYPE :Self
KW_STATIC :static
KW_SUPER :super
KW_TRAIT :trait
KW_TYPE :type
KW_TYPEOF :typeof
KW_USE :use
KW_VIEW :view
KW_VIRTUAL :virtual
KW_WHERE :where
KW_YIELD :yield
Lexer:
IDENTIFIER_OR_KEYWORD :
[a-zA-Z] [a-zA-Z0-9_]*
|_[a-zA-Z0-9_]+ Except a strict or reserved keyword
An identifier is any nonempty ASCII string of the following form:
Either
- The first character is a letter.
- The remaining characters are alphanumeric or
_.
Or
- The first character is
_. - The identifier is more than one character.
_alone is not an identifier. - The remaining characters are alphanumeric or
_.
MISSING
Lexer:
EndOfHeader :
:
Lexer:
BlockExpression :\
The contents of a block expression are indented from the parent context following Python indentation rules.
Syntax
Function :
FunctionDecorators
FunctionQualifiersdefIDENTIFIER
(FunctionParameters?)
FunctionReturnType?
EndOfHeader
BlockExpressionFunctionQualifiers :
pub?FunctionDecorators :
FunctionDecorator*FunctionDecorator :
@IDENTIFIERFunctionParameters :
FunctionParam (,FunctionParam)*,?FunctionParam :
IDENTIFIER:TypeFunctionReturnType :
->Type
A function consists of a [block], along with a name and a set of parameters.
Other than a name, all these are optional. Functions are declared with the
keyword def. Functions may declare a set of input [variables][variables]
as parameters, through which the caller passes arguments into the function, and
the output type of the value the function will return to its caller
on completion.
When referred to, a function yields a first-class value of the corresponding zero-sized [function item type], which when called evaluates to a direct call to the function.
A function header ends with a colon (:) after which the function body begins.
For example, this is a simple function:
def answer_to_life_the_universe_and_everything() -> u256:
return 42;These two terms are often used interchangeably, and what they are attempting to convey is the answer to the question "Can this item be used at this location?"
Fe knows two different types of visibility for functions and state variables: public and private. Visibility of private is the default and is used if no other visiblity is specified.
Public: External functions are part of the contract interface, which means they can be called from other contracts and via transactions.
Private: Those functions and state variables can only be accessed internally from within the same contract. This is the default visibility.
For example, this is a function that can be called externally from a transaction:
pub def answer_to_life_the_universe_and_everything() -> u256:
return 42;Syntax
Struct :
structIDENTIFIER EndOfHeader
StructField*StructField :
IDENTIFIER:Type
A struct is a nominal struct type defined with the keyword struct.
An example of a struct item and its use:
struct Point:
x: u256
y: u256
p = Point {x: 10, y: 11}
px: u256 = p.x;
Builtin functions:
abi_encode()encodes the struct as an ABI tuple and returns the encoded data as a fixed-size byte array that is equal in size to the encoding.
Syntax
Event :
eventIDENTIFIER EndOfHeader
EventField*EventField :
EventIndexability IDENTIFIER:Type
EventIndexability :
idx?
An event is a nominal event type defined with the keyword event. It is emitted with the keyword emit.
An example of a event item and its use:
event Transfer:
idx sender: address
idx receiver: address
value: u256
def transfer(to : address, value : u256):
# Heavy logic here
# All done, log the event for listeners
emit Transfer(msg.sender, _to, _value)
Syntax
Enumeration :
enumIDENTIFIER EndOfHeader
EnumField*EnumField :
IDENTIFIER,
An enumeration, also referred to as enum is a simultaneous definition of a nominal enumerated type, that can be used to create or pattern-match values of the corresponding enumerated type.
Enumerations are declared with the keyword enum.
An example of an enum item and its use:
enum Animal:
Dog,
Cat,
barker = Animal.Dog
Syntax
TypeAlias :
typeIDENTIFIER=Type
A type alias defines a new name for an existing type. Type aliases are
declared with the keyword type.
For example, the following defines the type BookMsg as a synonym for the type
bytes[100], a sequence of 100 bytes:
type BookMsg = bytes[100]
Syntax
Contract :
contractIDENTIFIER EndOfHeader
ContractMember*ContractMember:
[Visibility]?
(
[ContractField]
| [Function]
| [Struct]
| [Event]
| [Enumeration]
)Visibility :
pub?ContractField :
IDENTIFIER:Type
A contract in Fe is a collection of code that resides at a specific address on the Ethereum blockchain. It is defined with the keyword contract.
An example of a contract:
contract GuestBook:
pub guest_book: Map<address, bytes[100]>
event Signed:
idx book_msg: bytes[100]
pub def sign(book_msg: bytes[100]):
self.guest_book[msg.sender] = book_msg
emit Signed(book_msg=book_msg)
pub def get_msg(addr: address) -> bytes[100]:
return self.guest_book[addr]
Syntax
PragmaStatement :
pragma[VersionRequirementExpression]
The pragma statement is denoted with the keyword pragma. Evaluating a pragma
statement will cause the compiler to reject compilation if the version of the compiler does not conform to the given version requirement.
An example of a pragma statement:
pragma ^0.1.0
The version requirement syntax is identical to the one that is used by cargo (more info).
Syntax
RevertStatement :
revert?
The revert statement is denoted with the keyword revert. Evaluating a revert
statement will cause to revert all state changes made by the call and return with an revert error to the caller.
An example of a revert statement:
def transfer(to : address, value : u256):
if not self.in_whitelist(to):
revert
# more logic here
### 4.2 Expressions
### 4.2.1 Arithmetic Operators
> **<sup>Syntax</sup>**\
> _ArithmeticExpression_ :\
> [_Expression_] `+` [_Expression_]\
> | [_Expression_] `-` [_Expression_]\
> | [_Expression_] `*` [_Expression_]\
> | [_Expression_] `/` [_Expression_]\
> | [_Expression_] `%` [_Expression_]\
> | [_Expression_] `**` [_Expression_]\
> | [_Expression_] `&` [_Expression_]\
> | [_Expression_] `|` [_Expression_]\
> | [_Expression_] `^` [_Expression_]\
> | [_Expression_] `<<` [_Expression_]\
> | [_Expression_] `>>` [_Expression_]
Binary operators expressions are all written with [infix notation](https://en.wikipedia.org/wiki/Infix_notation).
This table summarizes the behavior of arithmetic and logical binary operators on
primitive types.
| Symbol | Integer | Status | Discussions |
|--------|-------------------------|-------------|----------------|
| `+` | Addition | IMPLEMENTED | |
| `-` | Subtraction | IMPLEMENTED | |
| `*` | Multiplication | IMPLEMENTED | |
| `/` | Division* | IMPLEMENTED | |
| `%` | Remainder | IMPLEMENTED | |
| `**` | Exponentiation | IMPLEMENTED | |
| `&` | Bitwise AND | IMPLEMENTED | |
| <code>|</code> | Bitwise OR | IMPLEMENTED | |
| `^` | Bitwise XOR | IMPLEMENTED | |
| `<<` | Left Shift | IMPLEMENTED | |
| `>>` | Right Shift | IMPLEMENTED | |
\* Integer division rounds towards zero.
Here are examples of these operators being used.
3 + 6 == 9 6 - 3 == 3 2 * 3 == 6 6 / 3 == 2 TODO: Rest 5 % 4 == 1 2 ** 4 == 16 12 & 25 == 8 12 | 25 == 29 12 ^ 25 == 21 212 << 1 == 424 212 >> 1 == 106
### 4.2.2 Comparision Operators
> **<sup>Syntax</sup>**\
> _ComparisonExpression_ :\
> [_Expression_] `==` [_Expression_]\
> | [_Expression_] `!=` [_Expression_]\
> | [_Expression_] `>` [_Expression_]\
> | [_Expression_] `<` [_Expression_]\
> | [_Expression_] `>=` [_Expression_]\
> | [_Expression_] `<=` [_Expression_]
| Symbol | Meaning | Status |
|--------|--------------------------|----------------------------|
| `==` | Equal | IMPLEMENTED |
| `!=` | Not equal | IMPLEMENTED |
| `>` | Greater than | IMPLEMENTED |
| `<` | Less than | IMPLEMENTED |
| `>=` | Greater than or equal to | IMPLEMENTED |
| `<=` | Less than or equal to | IMPLEMENTED |
Here are examples of the comparison operators being used.
123 == 123 23 != -12 12 > 11 11 >= 11 11 < 12 11 <= 11
## 5. Types system
### 5.1 Types
#### 5.1.1 Types
Every variable, item, and value in a Fe program has a type. The _type_ of a
*value* defines the interpretation of the memory holding it and the operations
that may be performed on the value.
Built-in types are tightly integrated into the language, in nontrivial ways
that are not possible to emulate in user-defined types. User-defined types have
limited capabilities.
The list of types is:
* Data types
* Base types:
* [Boolean] — `true` or `false`
* [Address] - Ethereum address
* [Numeric] — integer
* Reference types:
* Sequence types
* [Tuple]
* [Array]
* [Bytes]
* [Struct]
* [Enum]
* [HashMap]
* Other types:
* [Event]
* [Contract]
* [Function]
### 5.1.1.1 Boolean type
The `bool` type is a data type which can be either `true` or `false`.
Example:
```rust
x = true
An contract type is the type denoted by the name of an contract item.
A value of a given contract type carries the contract's public interface as
attribute functions. A new contract value can be created by either casting
an address to a contract type or by creating a new contract using the type
attribute functions create or create2.
example:
contract Foo:
pub def get_my_num() -> u256:
return 42
contract FooFactory:
pub def create2_foo() -> address:
# `0` is the value being sent and `52` is the address salt
foo: Foo = Foo.create2(0, 52)
return address(foo)The unsigned integer types consist of:
| Type | Minimum | Maximum |
|---|---|---|
u8 |
0 | 28-1 |
u16 |
0 | 216-1 |
u32 |
0 | 232-1 |
u64 |
0 | 264-1 |
u128 |
0 | 2128-1 |
u256 |
0 | 2256-1 |
The signed two's complement integer types consist of:
| Type | Minimum | Maximum |
|---|---|---|
i8 |
-(27) | 27-1 |
i16 |
-(215) | 215-1 |
i32 |
-(231) | 231-1 |
i64 |
-(263) | 263-1 |
i128 |
-(2127) | 2127-1 |
i256 |
-(2255) | 2255-1 |
MISSING
MISSING
MISSING
An struct type is the type denoted by the name of an struct item.
An enum type is the type denoted by the name of an enum item.
MISSING
MISSING
Maps a key to a value.
Example:
Map<TKey,TValue>
Where TKey is a base type and TValue is any data type.
MISSING
An event type is the type denoted by the name of an event item.
There are three places where data can be stored on the EVM:
- stack: 256-bit values placed on the stack that are loaded using
DUPoperations. - storage: 256-bit address space where 256-bit values can be stored. Accessing higher storage slots does not increase gas cost.
- memory: 256-bit address space where 256-bit values can be stored. Accessing higher memory slots increases gas cost.
Each data type described in section 5 can be stored in these locations. How data is stored is described in this section.
The following can be stored on the stack:
- base type values
- pointers to sequence type values
The size of each value stored on the stack must not exceed 256 bits. Since all base types are less than or equal to 256 bits in size, we store them on the stack. Pointers to values stored in memory may also be stored on the stack.
Example:
# function scope
foo: u256 = 42 # foo is stored on the stack
bar: u256[100] # bar is a memory pointer stored on the stackAll data types can be stored in storage.
Storage pointers for constant size values are determined at compile time.
Example:
# contract scope
foo: u256 # foo is assigned a static pointer by the compilerThe value of a base type in storage is found by simply loading the value from storage at the given pointer.
To find an element inside of a sequence type, the relative location of the element is added to the given pointer.
Maps are not assigned pointers, because they do not have a location in storage. They are instead assigned a nonce that is used to derive the location of keyed values during runtime.
Example:
# contract scope
bar: Map<address, u256> # bar is assigned a static nonce by the compiler
baz: Map<address, Map<address, u256>> # baz is assigned a static nonce by the compilerThe expression bar[0x00] would resolve to the hash of both bar's nonce and the key value
.i.e. keccak256(<bar nonce>, 0x00). Similarly, the expression baz[0x00][0x01] would resolve to
a nested hash i.e. keccak256(keccak256(<baz nonce>, 0x00), 0x01).
Reference type values can be copied from storage and into memory using the to_mem function.
Example:
my_array_var: u256[10] = self.my_array_field.to_mem()Only sequence types can be stored in memory.
The first memory slot (0x00) is used to keep track of the lowest available memory slot. Newly
allocated segments begin at the value given by this slot. When more memory has been allocated,
the value stored in 0x00 is increased.
We do not free memory after it is allocated.
Sequence type values may exceed the 256-bit stack slot size, so we store them in memory and reference them using pointers kept on the stack.
Example:
# function scope
foo: u256[100] # foo is a pointer that references 100 * 256 bits in memory.To find an element inside of a sequence type, the relative location of the element is added to the given pointer.
Reference type values in memory can be cloned using the clone function.
Example:
# with clone
foo: u256[10] = bar.clone() # `foo` points to a new segment of memory
assert foo[1] == bar[1]
foo[1] = 42
assert foo[1] != bar[1] # modifying `foo` does not modify bar
# without clone
foo: u256[10] = bar # `foo` and `bar` point to the same segment of memory
assert foo[1] == bar[1]
foo[1] = 42
assert foo[1] == bar[1] # modifying `foo` also modifies `bar`Constant size values stored on the stack or in memory can be passed into and returned by functions.