diff --git a/languages/func/compiler-directives.mdx b/languages/func/compiler-directives.mdx index e1390688..f950753a 100644 --- a/languages/func/compiler-directives.mdx +++ b/languages/func/compiler-directives.mdx @@ -4,6 +4,8 @@ sidebarTitle: "Compiler directives" noindex: "true" --- +import { Aside } from '/snippets/aside.jsx'; + Compiler directives are keywords that begin with `#`, instructing the compiler to perform specific actions, enforce checks, or modify parameters. These directives can only be used at the outermost level of a source file and cannot be placed inside function definitions. @@ -15,10 +17,18 @@ The `#include` directive enables the inclusion of another FunC source file parse **Syntax:** ```func -#include "filename.fc"; +#include ""; ``` -Files are automatically checked for multiple inclusions. By default, the compiler will ignore redundant inclusions if the same file is included more than once. A warning will be issued if the verbosity level is 2 or higher. +where `` is the path to the FunC source file to include. + +Files are automatically checked for multiple inclusions. +By default, the compiler will ignore redundant inclusions if the same file is included more than once. +This also applies to inclusions along a path of nested inclusions. +A warning will be issued if the verbosity level is 2 or higher. + +For example, suppose that `main.fc` contains the `main` function. Suppose also that `main.fc` includes a file `A.fc`, which in turn includes a file `B.fc`, +which in turn includes `main.fc`. When `main.fc` is compiled, the inclusion of `main.fc` in `B.fc` will be ignored. If an error occurs while parsing an included file, the compiler displays an inclusion stack, showing the locations of each file in the inclusion chain. @@ -30,78 +40,158 @@ The `#pragma` directive provides additional information to the compiler beyond w The `#pragma` version directive enforces using a specific FunC compiler version when compiling the file. -The version is specified in **semantic versioning (semver)** format: _a.b.c_, where: +**Syntax:** + +```func +#pragma version ; +``` + +where `` is an optional [version operator](#operators) that allows to specify a constraint, and `` is specified +in [**semantic versioning (semver)** format](https://en.wikipedia.org/wiki/Software_versioning): _a.b.c_, where: + +- _a_ is the major version +- _b_ is the minor version +- _c_ is the patch version + +Example: + +```func +#pragma version 2.3.4; +#pragma version >2.3.4; +``` + +The first example does not use an operator and it means that the compiler must have exactly version `2.3.4`. +The second uses the greater than operator `>`, and it means that the compiler must have a version greater +than `2.3.4` (see [precedence](#equality-and-precedence) and [operators](#operators) below for details). + +#### Equality and precedence + +Two versions are **equal** if their respective major, minor, and patch numbers are equal. Two versions are **not equal** if at least one of those numbers differ. + +Example: + +- _1.2.3_ is equal to _1.2.3_ +- _3.4.5_ is not equal to _3.1.5_ + +**Precedence** of two versions _a.b.c_ and _d.e.f_ is determined the following way: + +- If _a_ is smaller than _d_, then _a.b.c_ precedes _d.e.f_ +- If _a_ is equal to _d_, and _b_ is smaller than _e_, then _a.b.c_ precedes _d.e.f_ +- If _a_ is equal to _d_, and _b_ is equal to _e_, and _c_ is smaller than _f_, then _a.b.c_ precedes _d.e.f_ + +If _a.b.c_ precedes _d.e.f_, then it is said that _a.b.c_ is smaller than _d.e.f_, or equivalently, that _d.e.f_ is greater than _a.b.c_. + +Example: -- _a_ is the major version; -- _b_ is the minor version; -- _c_ is the patch version. +- _1.0.0_ precedes _2.0.0_. Equivalently: _1.0.0_ is smaller than _2.0.0_ or _2.0.0_ is greater than _1.0.0_. +- _2.0.0_ precedes _2.1.0_. Equivalently: _2.0.0_ is smaller than _2.1.0_ or _2.1.0_ is greater than _2.0.0_. +- _2.1.0_ precedes _2.1.1_. Equivalently: _2.1.0_ is smaller than _2.1.1_ or _2.1.1_ is greater than _2.1.0_. -**Supported comparison operators** +#### Operators Developers can specify version constraints using the following operators: -- _a.b.c_ or _=a.b.c_—Requires **exactly** version _a.b.c_ of the compiler; -- _>a.b.c_—Requires the compiler version to be **greater** than _a.b.c._; - - _>=a.b.c_—Requires the compiler version to be **greater** than or **equal** to _a.b.c_; -- _\a.b.c_ - Requires the compiler version to be **greater** than _a.b.c._ +- _>=a.b.c_ - Requires the compiler version to be **greater** than or **equal** to _a.b.c_ +- _\_, _>=_, _\<_, _\<=_) , omitted parts default to zero. +For the comparison operators _=_, _>_, _>=_, _\<_, _\<=_, omitted parts default to zero. For example: -- _>a.b_ is equivalent to _>a.b.0_ and **does not** match version _a.b.0._; -- _\<=a_ is equivalent to _\<=a.0.0_ and **does not** match version _a.0.1_ version; -- _^a.b.0_ is **not the same** as _^a.b_ +- _>a.b_ is equivalent to _>a.b.0_ +- _\<=a_ is equivalent to _\<=a.0.0_ -**Examples:** +For the operator ^, omitted parts do **not** default to zero. For example: -- _^a.1.2_ matches _a.1.3_ but not _a.2.3_ or _a.1.0_; -- _^a.1_ matches all of them. +- _^a.b_ is not equivalent to _^a.b.0_ +- _^a_ is not equivalent to _^a.0.0_ -The `#pragma` version directive can be used multiple times, and the compiler must satisfy all specified conditions. +Here are some examples of constraints: + +- _^5.1.2_ matches compiler version _5.1.3_ because patch `3` is no lower than patch `2`. +- _^5.1.2_ does not match compiler version _5.2.3_ because minor `2` does not equal minor `1`. +- _^5.1.2_ does not match compiler version _5.1.1_ because patch `1` is lower than patch `2`. +- _^5.1_ matches compiler version _5.1.3_ because minor `1` is no lower than minor `1`. +- _^5.1_ matches compiler version _5.2.3_ because minor `2` is no lower than minor `1`. +- _^5.1_ matches compiler version _5.1.0_ because minor `1` is no lower than minor `1`. +- _^5.1_ does not match compiler version _5.0.2_ because minor `0` is lower than minor `1`. +- _^5_ matches compiler version _5.1.0_ because major `5` is no lower than major `5`. +- _^5_ does not match compiler version _4.1.0_ because major `4` is lower than major `5`. +- _>5.1.2_ matches compiler version _5.1.3_ because patch `3` is bigger than patch `2`. +- _>5.1.2_ matches compiler version _5.2.0_ because minor `2` is bigger than minor `1`. +- _>5.1.2_ matches compiler version _6.0.0_ because major `6` is bigger than major `5`. +- _=5.1.2_ does not match compiler version _5.2.2_ because minor `2` is not equal to minor `1`. + + ### `#pragma not-version` -The syntax of `#pragma not-version` is identical to `#pragma version`, but it fails if the specified condition is met. +The `#pragma not-version` is similar to `#pragma version`, but it fails if the specified condition is met. + +**Syntax:** -This directive is applicable for blocking specific compiler versions known to have issues. +```func +#pragma not-version ; +``` + +where `` is an optional [version operator](#operators) that allows to specify a constraint, and `` is identical as +in [`#pragma version`](#%23pragma-version). + +This directive is useful for blocking specific compiler versions known to have issues. + +Here are some examples: + +```func +#pragma not-version >2.1.3; +#pragma not-version ^3.4; +#pragma not-version 1.2.3; +``` + +In the first example, `not-version >2.1.3` matches any compiler version that is _not_ bigger than _2.1.3_, like _2.1.2_, _2.0.5_ and even _2.1.3_ itself. + +In the second example, `not-version ^3.4` matches any compiler version that does _not_ match _^3.4_, like _3.3.1_, _4.4.0_, and _3.3.9_ + +In the third example, `not-version 1.2.3` matches any compiler version different from _1.2.3_. ### `#pragma allow-post-modification` _Introduced in FunC v0.4.1_ -Using a variable before it is modified within the same expression is prohibited by default. +In Func, using a variable before it is modified within the same [expression](./expressions) is prohibited by default. -For example, the following code **will not compile**: +For example, the following code will **not** compile, because `ds` is used before it is modified in `ds~load_uint(8)`. +See [modifying notation](./expressions#modifying-notation) for more details on using symbol `~`. ```func -(x, y) = (ds, ds~load_uint(8)) +(x, y) = (ds, ds~load_uint(8)); ``` -However, this version is **valid**: +However, this version is **valid**, since `ds` is used after it is modified: ```func (x, y) = (ds~load_uint(8), ds) ``` -To override this restriction, use `#pragma allow-post-modification`. This allows variables to be modified after usage in mass assignments and function calls while sub-expressions are still computed **left to right**. +To override this restriction, use `#pragma allow-post-modification`. +This allows variables to be modified after usage in mass assignments and function calls while sub-expressions are still computed **left to right**. -In the following example, `x` will contain the initial value of `ds`: +In the following example, `x` will contain the initial value of `ds`, while `y` the modified value of `ds`: ```func #pragma allow-post-modification (x, y) = (ds, ds~load_bits(8)); ``` -Here, in `f(ds, ds~load_bits(8));`: - -- The first argument of `f` will contain the initial value of `ds`. -- The second argument will contain the 8-bit-modified value of `ds`. - -`#pragma allow-post-modification` works only for code after the pragma. + ### `#pragma compute-asm-ltr` @@ -113,12 +203,12 @@ _Introduced in FunC v0.4.1_ idict_set_ref(ds~load_dict(), ds~load_uint(8), ds~load_uint(256), ds~load_ref()) ``` -The execution order is: +The evaluation order of the call arguments is: 1. `load_ref()` -2. `load_uint(256)` -3. `load_dict()` -4. `load_uint(8)` +1. `load_uint(256)` +1. `load_dict()` +1. `load_uint(8)` This happens due to the corresponding `asm` declaration: @@ -126,26 +216,26 @@ This happens due to the corresponding `asm` declaration: cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; ``` -Here, the `asm(value index dict key_len)` notation dictates a reordering of arguments. +Here, the `asm(value index dict key_len)` notation dictates a rearrangement of arguments. -To ensure strict left-to-right computation order, use `#pragma compute-asm-ltr`. With this directive enabled, the same function call: +To ensure strict left-to-right computation order of the arguments, use `#pragma compute-asm-ltr`. +With this directive enabled, the same function call: ```func #pragma compute-asm-ltr -... +;; ... idict_set_ref(ds~load_dict(), ds~load_uint(8), ds~load_uint(256), ds~load_ref()); ``` -will be evaluated in the following order: +will evaluate its arguments in the following order: 1. `load_dict()` -2. `load_uint(8)` -3. `load_uint(256)` -4. `load_ref()` - -All `asm` reordering will occur only after computation. - -`#pragma compute-asm-ltr` works only for code after the pragma. +1. `load_uint(8)` +1. `load_uint(256)` +1. `load_ref()` -**Note:** `#pragma compute-asm-ltr` applies only to the code after the directive in the file. +and only _after_ the evaluation of all these arguments, the `asm` rearrangement will occur. + diff --git a/languages/func/global-variables.mdx b/languages/func/global-variables.mdx index 466fd91c..d8cba5c4 100644 --- a/languages/func/global-variables.mdx +++ b/languages/func/global-variables.mdx @@ -1,79 +1,156 @@ --- -title: "FunC global variables" +title: "Global variables" sidebarTitle: "Global variables" noindex: "true" --- -A FunC program primarily consists of function declarations/definitions and global variable declarations. -This section focuses on the latter. +import { Aside } from '/snippets/aside.jsx'; -**A global variable** is declared using the `global` keyword, followed by the variable's type and name. For example: +## Definition + +**A global variable** is a variable that can be read and assigned at any function in the entire program, including functions declared in other `.fc` files, +as long as those `.fc` files are [imported](./compiler-directives#%23include) after the global +variable [declaration](#declaration). It is possible to use global variables imported from other `.fc` files. + +Global variables are useful for remembering values across functions, without the need to pass those values as arguments to every single function in the program. + +Under the hood, global variables in FunC are stored inside the tuple of the [`c7` TVM control register](/foundations/whitepapers/tvm#1-3-2-list-of-control-registers), +with a maximum limit of 31 variables. + +## Declaration + +Global variables are declared using the `global` keyword, followed by the variable's type and name. For example: ```func global ((int, int) -> int) op; ``` -Here's a simple program demonstrating how to use a global functional variable: + +This defines global variable `op` of type `(int, int) -> int`. In other words, `op` can store a function that receives two arguments and returns an integer. + +Here is an example that uses global variable `op`: ```func -int check_assoc(int a, int b, int c) { - return op(op(a, b), c) == op(a, op(b, c)); +global ((int, int) -> int) op; + +int check_commutative(int a, int b) { + return op(a, b) == op(b, a); +} + +int add(int a, int b) { + return a + b; } int main() { - op = _+_; - return check_assoc(2, 3, 9); + op = add; + return check_commutative(2, 3); } ``` -In this example, the global variable `op` is assigned the addition operator `_+_`. The program then verifies the associativity of addition using three sample integers: 2, 3, and 9. - -Under the hood, global variables in FunC are stored in the `c7` control register of the TVM, with a maximum limit of 31 variables. +The example defines two auxiliary functions: `check_commutative` and `add`, in addition to the program entry point `main`. +Function `check_commutative` checks if the operator stored in the global variable `op` satisfies the commutative property for the specified inputs `a` and `b`. +Function `add`, adds its two inputs. +Function `main` assigns the addition function `add` to the global variable `op`. Then, it verifies the commutativity of addition for the specific values: `2`, `3`. +In FunC, you can _omit the type_ of global variables. In this case, the compiler determines the type based on how the variable is used. Equivalently, +instead of declaring the type, you can use the `var` keyword as a replacement for the type. -In FunC, you can _omit the type_ of global variable. -In this case, the compiler determines the type based on how the variable is used. -For example, you can rewrite the previous program like this: +For example, in the previous program you can declare the variable `op` as: ```func global op; +``` -int check_assoc(int a, int b, int c) { - return op(op(a, b), c) == op(a, op(b, c)); -} +or as: -int main() { - op = _+_; - return check_assoc(2, 3, 9); +```func +global var op; +``` + +FunC allows declaring global variable multiple times, as long as all the declarations have the same type. +The declarations can even happen in different `.fc` files. + +For example, the following does not compile, because the second declaration changes the type of `A` from `int` to `cell`: + +```func +global int A; + +int foo() { + return 0; } + +global cell A; ;; DOES NOT COMPILE, cell should be int ``` -**Declaring multiple global variables** + + + + +## Multiple declarations + +FunC allows users to declare multiple global variables using a single `global` keyword. + +The following example: ```func global int A; global cell B; global C; ``` + +is equivalent to this: + ```func global int A, cell B, C; ``` -**Restrictions on global and local variable names** +## Restrictions on declarations + +A local variable **cannot** have the same name of a previously declared global variable **but only if their types differ**. -A local variable **cannot** have the same name as a previously declared global variable. The following example is invalid and will not compile: +The following example does not compile, because the local variable `C` of type `int` has the same name as the global variable `C` of type `cell`, +and `int` and `cell` are different types. ```func global cell C; int main() { - int C = 3; ;; Error: cannot declare a local variable with the same name as a global variable + int C = 3; ;; DOES NOT COMPILE return C; } ``` -However, the following example is valid: + +However, if the local variable and the global variable have the same type and the same name, the declaration of the local variable actually means assignment +to the global variable. For example, ```func global int C; @@ -84,7 +161,4 @@ int main() { } ``` -In this case, `int C = 3;` is not declaring a new local variable -but instead assigning value `3` to the global variable `C`. -This behavior is explained in more detail in the section on [statements](/languages/func/statements#variable-declaration). - +In this case, `int C = 3;` is not declaring a new local variable, but instead assigning value `3` to the global variable `C`.