Ola Language Syntax

Variables

Identifier

Variables consist of numbers (0-9), ASCII uppercase and lowercase letters (a-zA-Z), underscores (_). Variables cannot start with a number, and cannot use

fn foo() {
    // declare and ine `_variable`
    u32 _aBC123 = 2;   // identifiers start with "_"
    // u32 0a = 2;  define error, identifiers can't start with number
}

Declaration

Variables need to be declared in order to be used. To avoid variables being undefined, it needs to be initialized at declaration time.

fn foo() {
    // declare and define `a`
    u32 a = 2;
    // redefine `a`
    a = 3;
}

Scope

For security reasons, variable definitions do not support Shadowing. If you need multiple adjacent variables with similar logical meanings, use a variable or type suffix.

fn foo() {
    u32 a = 5;
    {        
        u32 a = 25; // compile error: redeclared variable 'a'
    };    
    u32 a = 25; // compile error: redeclared variable 'a'

    a = 25; // ok
}

Variables differ from constants in that the scope of a variable is limited to the current function itself and global variables are not supported.

fn foo() -> u32 {
    // return a; <- not allowed
    return 2;
}

fn bar() -> u32 {
    32 a = 2;
    return foo();
}

Variables in a For-Loop loop are scoped only inside the loop.

fn foo() -> u32 {
    u32 a = 0;
    for (u32 i = 0; i < 5; i++) {
        a = a + i;
    }
    // return i; <- not allowed
    return a;
}

Data Type

Ola is a statically typed language, and variable types must be known at compile time to avoid most runtime exceptions. Three basic types and multiple complex types are supported.

Basic Types

There are three types of basic types, namely integer, field and Boolean

Integer Type

There are several types of integer types: u32, u64, and u256, and currently only unsigned integer operations are supported. All types are built on the basis of the field type. Ola provides the above-mentioned basic libs of various integer types based on the field implementation, which is convenient for developers to write complex logic. Note: The literal quantity of a number is composed of three parts: base character prefix, corresponding number, and type suffix. The default is a decimal field type literal.

u32 a = 2; // u32
u64 b = 2; // u64
u64 b  = 0xffffl; // u64
u256 d = 102411ll  // u256

Boolean

Bool indicates that the value of field is 0 or 1, which is declared using the keyword bool.

bool a = true;
bool b = false;

Complex Types

Ola supports a variety of complex types such asArrays,Slice,Tuples,Structs,Enumerations,Map

Arrays

Ola supports statically typed arrays. The data types of array elements must be consistent, and the array size must be determined at compile time.

Array elements are numbered from zero and are accessed using[index]for addressing.

Array declarations must be initialized, and the array declaration format is为typeand[](type []),and the array size must be specified. Two ways to initialize arrays are provided:

  • Split the list of elements by commas,[array_element1,array_element2,...]

  • Array declaration and initialization with consistent array elements,[array_value; size]

field[5] a = [1, 2, 3, 4, 5]; // initialize a field array with field values
bool[3] b = [true; 3]; // initialize a bool array with value true

Two-dimensional Arrays

Two-dimensional arrays are declared and used similarly to one-dimensional arrays, except that the internal elements of a two-dimensional array are also one-dimensional arrays.

Declartype [row_size][col_size], and initializ[[],[],...]

// Array of two elements of array of 3 elements

field[2][4] a = [[1, 2, 3, 4],[4, 5, 6, 7]];

field[4] b = a[1]; // should be [4, 5, 6, 7]

Array Slicing

Similar to rust, arrays can be created by slicing an array to copy the generated array,[from_index..to_index]

field[5] a = [1, 2, 3, 4, 5];
field[3] b = a[2..4];   // initialize an array copying a slice from `a`
// array b is [3, 4, 5]

Tuples

A collection of elements of two types, with each element in the collection accessed through.(t.0t.1).

fn main() -> bool {
    (field[2], bool) v = ([1, 2], true);
    v.0 = [2, 3];
    return v.1;
}

Structs

A combination of multiple data types to form a new custom combination type. Struct members are accessed via. (struct_name.struct_field)

struct Person {
    age: u32,
    id: u64,
}

fn foo() {
   Person person = Person {
        age: 18,
        id: 123456789,
    };
    person.age = 25;
}

Enumerations

The enumeration type is defined by the keyword enum.

contract Foo {
    u256 const x = 56;
    enum ActionChoices {
        GoLeft,
        GoRight,
        GoStraight,
        Sit
    }
    ActionChoices const choices = ActionChoices.GoLeft;
}

Map

TODO

Type Alias

To increase code readability, defining a type alias for each type is supported. At compile time, the type alias will be replaced with real types.

type balance = uint256;

fn main() -> balance {
    balance a = 32ll;
    a -= 2;
    return a;
}

Constant

Constants can only be declared as constant expressions when defined with the const keyword.

Compile time determination cannot be redeclared and assigned, that is, once defined, it can only be used within its scope, and it is recommended to declare with all capital letters and _ concatenation.

const field ONE = 1;
const field TWO = ONE + ONE;

const field HASH_SIZE = 256;

fn hash_size() -> field {
    return HASH_SIZE;
}

Operators

Provides operators such as arithmetic, logic, relational, bits, and so on. Except for the arithmetic operation acting on numerical values, which is Mod p, all others are standard semantics.

Arithmetic operators

All arithmetic operators are Mod p.

Arithmetic operators can be combined with the assignment operator=to form new compound operators +=-=*=/=%=, with arithmetic operators having higher priority than compound operators.

Operat

Example

Explanation

+

a + b

Arithmetic addition modulo p

-

a-b

Arithmetic subtraction modulo p

*

a * b

Arithmetic multiplication modulo p

/

a / b

Arithmetic multiplication inverse modulo p

%

a%b

The modulo of arithmetic integer division

**

a**b

Power modulo p

Boolean operators

Support with AND(&&)as well as OR(||),with the latter having higher priority.

Operator

Example

Explanation

&&

a && b

Boolean operator and (AND)

||

a || b

Boolean operator or (OR)

!

! a

Boolean operator NEGATION

Relational operators

The return result of the relational operator is typebool

Operator

Example

Explanation

==

a == b

equal

!=

a != b

not equal

<

a < b

less than

>

a >b

greater than

<=

a <= b

less than or equal to

>=

a >= b

greater than or equal to

Bitwise operators

All bitwise operators are modulo p, containing bit or and non and shift operations.

Operator

Example

Explanation

&

a & b

bit and

|

a | b

bit or

^

a ^ b

XOR 32 bits

<<

a << 3

shift left

>>

a >> 3

shift right

~

~a

Complement 32 bits

Bitwise operators can be combined with the assignment operator=to form the new compound operators&=|=^=<<=>>=, with bitwise operators taking precedence over compound operators.

Control Flow

Conditional statement

Control conditional branch and select different branch programs to execute according to different conditions. If the expression value is nonzero, the branch body is executed. It comes in two forms:

  • Contains only single branchif,if conditional_expression {statements}

  • Contains multiple branches ofifandelse,if conditional_expression {statements} else {statements}.

fn foo(field a) -> field {
    
    // Similar to rust, the result of a conditional expression 
    // can be received directly by the variable
    field b = if (a + 1 == 2) { 1 } else { 3 };
    return b;
}

Note: Conditional statements support ternary conditional operators.

fn foo(field a) -> field {
    field b = a + 1 == 2 ? 1 : 3;
    return b;
}

Loop statement

Repeats the statement within the loop for a specified number of times based on the loop condition.

for-loopstatement is supported. Its syntax is for (init_expression; conditional_expression; loop_expression) {statements}

The execution process is:

  • Calculate theinit_expression,namely the loop initialization.

  • Calculate theconditional_expression.If the result istrue,the loop bodystatementsare executed, followed by theloop_expression.

  • If the result isfalse,for-loopstatement terminates. Sequential execution starts with the nextstatement.

fn foo() -> u32 {
    u32 res = 0u;
    for (u32 i = 0u; i <= 10u; i++) {
        res = res + i;
    }
    return res;
}

Functions

It is the basic module unit of Ola, containing declarations and statements.

If thefnkeyword is used, the function name must be explicitly provided. parameters, and return values are optional, and parameters are passed by value.

The function return type must be specified after->.

    • When a function call occurs, program execution control is passed from the calling function to the called function, and the parameters are passed to the called function by value.

  • The called function executes the return control to the calling function through thereturnstatement, and returns the return value to the calling function.

The basic syntax is:

fn function_name(parameter_declaration_list) -> return_parameter_list {
    // compound-statement
    statements
    return_statement
}

e.g.:

fn foo() -> u32 {
    return sum(1u, 2u)
}

fn sum(u32 a, u32 b) -> u32 {
/* 
 *  Unlike rust, the return value of 
 *  a function must be a combination of return and return value
 */
    return a + b;  
}

Imports

In order to use the code from other files, we can import them into our program using the keyword import and aswith the corresponding file name. Using import makes it easier for us to import some modular ibs, eliminating the need for repeated development. The basic syntax is as follow,path-speccan be absolute path(the full path of source file) or relative path (file path starts with./ or ../).

import "path-spec"
import "path-spec" as alias_name

e.g.:

import "./math/uint256.ola";
import "crypto/sha256.ola" as sha256;

Comment Lines

They are in-code documentation. When comments are inserted into the code, the compiler simply ignores them. Comment lines only serve as an aid in understanding the code.

ingle-line comments start with // and multi-line paragraph comments start with /* and end with */ .

Single line using//:

// Using this, we can comment a line.
fn main(field a) -> field {
    field b = a + 1 == 2 ? 1 : 3;
    return b;
}

Multi-line paragraph comments using/**/:

fn sum(u32 a, u32 b) -> u32 {
/* 
 *  Unlike rust, the return value of 
 *  a function must be a combination of return and return value
*/
    return a + b;  
}