diff --git a/docs/develop/func/stdlib.mdx b/docs/develop/func/stdlib.mdx index 4d45d72a24..6947b8894b 100644 --- a/docs/develop/func/stdlib.mdx +++ b/docs/develop/func/stdlib.mdx @@ -9,7 +9,6 @@ toc_max_heading_level: 6 This section discusses the [stdlib.fc](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc) library with standard functions used in FunC. ::: - Currently, the library is just a wrapper for the most common assembler of the TVM commands which are not built-in. Each TVM command description used in the library can be found in the [TVM documentation](/learn/tvm-instructions/tvm-overview) section. Some descriptions were borrowed for this document. Some functions are commented out in the file. It means that they have already become built-ins for optimization purposes. However, the type signature and semantics remain the same. @@ -17,291 +16,433 @@ Some functions are commented out in the file. It means that they have already be Note that some less common commands are not presented in the stdlib. Someday they will also be added. ## Tuple manipulation primitives + The names and the types are mostly self-explaining. See [polymorhism with forall](/develop/func/functions#polymorphism-with-forall) for more info on the polymorphic functions. Note that currently values of atomic type `tuple` cannot be cast into composite tuple types (e.g. `[int, cell]`) and vise versa. ### Lisp-style lists + Lists can be represented as nested 2-element tuples. Empty list is conventionally represented as TVM `null` value (it can be obtained by calling `null()`). For example, the tuple `(1, (2, (3, null)))` represents the list `[1, 2, 3]`. Elements of a list can be of different types. + #### cons + ```func forall X -> tuple cons(X head, tuple tail) asm "CONS"; ``` + Adds an element to the beginning of a lisp-style list. + #### uncons + ```func forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; ``` + Extracts the head and the tail of lisp-style list. + #### list_next + ```func forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; ``` + Extracts the head and tail of a lisp-style list. Can be used as a [(non-)modifying method](/develop/func/statements#methods-calls). + ```func () foo(tuple xs) { - (_, int x) = xs.list_next(); ;; get the first element, `_` means do not use tail list + (_, int x) = xs.list_next(); ;; get the first element, `_` means do not use tail list int y = xs~list_next(); ;; pop the first element int z = xs~list_next(); ;; pop the second element } ``` + #### car + ```func forall X -> X car(tuple list) asm "CAR"; ``` + Returns the head of a lisp-style list. + #### cdr + ```func tuple cdr(tuple list) asm "CDR"; ``` + Returns the tail of a lisp-style list. + ### Other tuple primitives + #### empty_tuple + ```func tuple empty_tuple() asm "NIL"; ``` + Creates 0-element tuple. + #### tpush + ```func forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; ``` + Appends the value `x` to the `Tuple t = (x1, ..., xn)` but only if the resulting `Tuple t' = (x1, ..., xn, x)` is no longer than 255 characters. Otherwise, a type check exception is thrown. + #### single + ```func forall X -> [X] single(X x) asm "SINGLE"; ``` + Creates a singleton, i.e., a tuple of length one. + #### unsingle + ```func forall X -> X unsingle([X] t) asm "UNSINGLE"; ``` + Unpacks a singleton. + #### pair + ```func forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; ``` + Creates a pair. + #### unpair + ```func forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; ``` + Unpacks a pair. + #### triple + ```func forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; ``` + Creates a triple. #### untriple + ```func forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; ``` + Unpacks a triple. #### tuple4 + ```func forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; ``` + Creates 4-element tuple. #### untuple4 + ```func forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; ``` + Unpacks 4-element tuple. #### first + ```func forall X -> X first(tuple t) asm "FIRST"; ``` + Returns the first element of a tuple. #### second + ```func forall X -> X second(tuple t) asm "SECOND"; ``` + Returns the second element of a tuple. #### third + ```func forall X -> X third(tuple t) asm "THIRD"; ``` + Returns the third element of a tuple. #### fourth + ```func forall X -> X fourth(tuple t) asm "3 INDEX"; ``` + Returns the fourth element of a tuple. #### pair_first + ```func forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; ``` + Returns the first element of a pair. #### pair_second + ```func forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; ``` + Returns the second element of a pair. #### triple_first + ```func forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; ``` + Returns the first element of a triple. #### triple_second + ```func forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; ``` + Returns the second element of a triple. #### triple_third + ```func forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; ``` + Returns the third element of a triple. ## Domain specific primitives + ### Extracting info from c7 + Some useful information regarding smart contract invocation can be found in the [c7 special register](/learn/tvm-instructions/tvm-overview#control-registers). These primitives serve for convenient data extraction. + #### now + ```func int now() asm "NOW"; ``` + Returns the current Unix time as an Integer + #### my_address + ```func slice my_address() asm "MYADDR"; ``` + Returns the internal address of the current smart contract as a Slice with `MsgAddressInt`. If necessary, it can be parsed further using primitives such as `parse_std_addr`. + #### get_balance + ```func [int, cell] get_balance() asm "BALANCE"; ``` + Returns the remaining balance of the smart contract as `tuple` consisting of `int` (the remaining balance in nanotoncoins) and `cell` (a dictionary with 32-bit keys representing the balance of “extra currencies”). Note that RAW primitives such as `send_raw_message` do not update this field. + #### cur_lt + ```func int cur_lt() asm "LTIME"; ``` + Returns the logical time of the current transaction. + #### block_lt + ```func int block_lt() asm "BLOCKLT"; ``` + Returns the starting logical time of the current block. + #### config_param + ```func cell config_param(int x) asm "CONFIGOPTPARAM"; ``` + Returns the value of the global configuration parameter with integer index `i` as `cell` or `null` value. + ### Hashes + #### cell_hash + ```func int cell_hash(cell c) asm "HASHCU"; ``` + Computes the representation hash of `cell c` and returns it as a 256-bit unsigned integer `x`. Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. + #### slice_hash + ```func int slice_hash(slice s) asm "HASHSU"; ``` + Computes the hash of `slice s` and returns it as a 256-bit unsigned integer `x`. The result is the same as if an ordinary cell containing only data and references from `s` had been created and its hash computed by `cell_hash`. + #### string_hash + ```func int string_hash(slice s) asm "SHA256U"; ``` + Computes sha256 of the data bits of `slice s`. If the bit length of `s` is not divisible by eight, it throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. ### Signature checks + #### check_signature + ```func int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; ``` + Checks the Ed25519 `signature` of `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) using `public_key` (also represented by a 256-bit unsigned integer). The signature must contain at least 512 data bits; only the first 512 bits are used. If the signature is valid, the result is `-1`; otherwise, it is `0`. Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. That is, if `hash` is computed as the hash of some data, this data is hashed _twice_, the second hashing occurring inside `CHKSIGNS`. + #### check_data_signature + ```func int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; ``` + Checks whether `signature` is a valid Ed25519 signature of the data portion of `slice data` using `public_key`, similarly to `check_signature`. If the bit length of `data` is not divisible by eight, it throws a cell underflow exception. The verification of Ed25519 signatures is a standard one, with sha256 used to reduce `data` to the 256-bit number that is actually signed. ### Computation of boc size + The primitives below may be useful for computing storage fees for user-provided data. + #### compute_data_size? + ```func (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; ``` + Returns `(x, y, z, -1)` or `(null, null, null, 0)`. Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` in the DAG rooted at `cell c`, effectively returning the total storage used by this DAG taking into account the identification of equal cells. The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG with a hash table of visited cell hashes used to prevent visits of already-visited cells. The total count of visited cells `x` cannot exceed non-negative `max_cells`; otherwise, the computation is aborted before visiting the `(max_cells + 1)`-st cell and a zero flag is returned to indicate failure. If `c` is `null`, it returns `x = y = z = 0`. + #### slice_compute_data_size? + ```func (int, int, int, int) slice_compute_data_size?(slice s, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; ``` + Similar to `compute_data_size?` but accepting `slice s` instead of `cell`. The returned value of `x` does not take into account the cell that contains the slice `s` itself; however, the data bits and the cell references of `s` are accounted for in `y` and `z`. + #### compute_data_size + ```func (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; ``` + A non-quiet version of `compute_data_size?` that throws a cell overflow exception (8) on failure. + #### slice_compute_data_size + ```func (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; ``` + A non-quiet version of `slice_compute_data_size?` that throws a cell overflow exception (8) on failure. + ### Persistent storage save and load + #### get_data + ```func cell get_data() asm "c4 PUSH"; ``` + Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. + #### set_data + ```func () set_data(cell c) impure asm "c4 POP"; ``` + Sets cell `c` as persistent contract data. You can update the persistent contract storage with this primitive. + ### Continuation primitives + #### get_c3 + ```func cont get_c3() impure asm "c3 PUSH"; ``` + Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. The primitive returns the current value of `c3`. + #### set_c3 + ```func () set_c3(cont c) impure asm "c3 POP"; ``` + Updates the current value of `c3`. Usually, it is used for updating smart contract code in runtime. Note that after execution of this primitive, the current code (and the stack of recursive function calls) won't change, but any other function call will use a function from the new code. + #### bless + ```func cont bless(slice s) impure asm "BLESS"; ``` + Transforms `slice s` into a simple ordinary continuation `c` with `c.code = s`, and an empty stack, and savelist. ### Gas related primitives + #### accept_message + ```func () accept_message() impure asm "ACCEPT"; ``` + Sets the current gas limit `gl` to its maximum allowed value `gm` and resets the gas credit `gc` to zero, decreasing the value of `gr` by `gc` in the process. In other words, the current smart contract agrees to buy some gas to finish the current transaction. This action is required to process external messages that carry no value (hence no gas). For more details check [accept_message effects](/develop/smart-contracts/guidelines/accept) + #### set_gas_limit + ```func () set_gas_limit(int limit) impure asm "SETGASLIMIT"; ``` + Sets the current gas limit `gl` to the minimum of `limit` and `gm`, and resets the gas credit `gc` to zero. At that point, if the amount of consumed gas (including the present instruction) exceeds the resulting value of `gl`, an (unhandled) out of gas exception is thrown before setting new gas limits. Notice that `set_gas_limit` with an argument `limit ≥ 2^63 − 1` is equivalent to `accept_message`. For more details check [accept_message effects](/develop/smart-contracts/guidelines/accept) + #### commit + ```func () commit() impure asm "COMMIT"; ``` + Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) so that the current execution is considered “successful” with the saved values even if an exception is thrown later. + #### buy_gas + ```func () buy_gas(int gram) impure asm "BUYGAS"; ``` + :::caution `BUYGAS` opcode is currently not implemented ::: @@ -309,101 +450,131 @@ Commits the current state of registers `c4` (“persistent data”) and `c5` ( Computes the amount of gas that can be bought for `gram` nanotoncoins and sets `gl` accordingly in the same way as `set_gas_limit`. ### Actions primitives + #### raw_reserve + ```func () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; ``` + Creates an output action which would reserve exactly `amount` nanotoncoins (if `mode = 0`), at most `amount` nanotoncoins (if `mode = 2`), or all but `amount` nanotoncoins (if `mode = 1` or `mode = 3`) from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying `amount` nanotoncoins (or `b − amount` nanotoncoins, where `b` is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in `mode` means that the external action does not fail if the specified amount cannot be reserved; instead, all the remaining balance is reserved. Bit +8 in `mode` means `amount <- -amount` before performing any further actions. Bit +4 in `mode` means that `amount` is increased by the original balance of the current account (before the compute phase), including all extra currencies before performing any other checks and actions. Currently, `amount` must be a non-negative integer, and `mode` must be in the range `0..15`. + #### raw_reserve_extra + ```func () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; ``` + Similar to `raw_reserve` but also accepts a dictionary `extra_amount` (represented by `cell` or `null`) with extra currencies. In this way, currencies other than Toncoin can be reserved. + #### send_raw_message + ```func () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; ``` + Sends a raw message contained in `msg`, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have a dummy value `addr_none` (to be automatically replaced with the current smart contract address), and `ihr_fee`, `fwd_fee`, `created_lt` and `created_at` fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). The integer parameter `mode` contains the flags. There are currently 3 Modes and 3 Flags for messages. You can combine a single mode with several (maybe none) flags to get a required `mode`. Combination simply means getting sum of their values. A table with descriptions of Modes and Flags is given below. -| Mode | Description | -|:-|:-| -| `0` | Ordinary message | -| `64` | Carry all the remaining value of the inbound message in addition to the value initially indicated in the new message | +| Mode | Description | +| :---- | :--------------------------------------------------------------------------------------------------------------------- | +| `0` | Ordinary message | +| `64` | Carry all the remaining value of the inbound message in addition to the value initially indicated in the new message | | `128` | Carry all the remaining balance of the current smart contract instead of the value originally indicated in the message | -| Flag | Description | -|:-|:-| -| `+1` | Pay transfer fees separately from the message value | -| `+2` | Ignore any errors arising while processing this message during the action phase | +| Flag | Description | +| :---- | :-------------------------------------------------------------------------------------------- | +| `+1` | Pay transfer fees separately from the message value | +| `+2` | Ignore any errors arising while processing this message during the action phase | | `+16` | In the case of action fail - bounce transaction. No effect if `+2` is used. | | `+32` | Current account must be destroyed if its resulting balance is zero (often used with Mode 128) | For example, if you want to send a regular message and pay transfer fees separately, use the Mode `0` and Flag `+1` to get `mode = 1`. If you want to send the whole contract balance and destroy it immidiately, use the Mode `128` and Flag `+32` to get `mode = 160`. #### set_code + ```func () set_code(cell new_code) impure asm "SETCODE"; ``` + Creates an output action that would change this smart contract code to that given by cell `new_code`. Notice that this change will take effect only after the successful termination of the current run of the smart contract. (Cf. [set_c3](/develop/func/stdlib#set_c3.)) ### Random number generator primitives + The pseudo-random number generator uses the random seed, an unsigned 256-bit Integer, and (sometimes) other data kept in [c7](/learn/tvm-instructions/tvm-overview#control-registers). The initial value of the random seed before a smart contract is executed in TON Blockchain is a hash of the smart contract address and the global block random seed. If there are several runs of the same smart contract inside a block, then all of these runs will have the same random seed. This can be fixed, for example, by running `randomize_lt` before using the pseudo-random number generator for the first time. :::caution Keep in mind that random numbers generated by the functions below can be predicted if you do not use additional tricks. - - [Random number generation](/develop/smart-contracts/guidelines/random-number-generation) -::: + +- [Random number generation](/develop/smart-contracts/guidelines/random-number-generation) + ::: + #### random + ```func int random() impure asm "RANDU256"; ``` + Generates a new pseudo-random unsigned 256-bit integer `x`. The algorithm is as follows: if `r` is the old value of the random seed considered a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its `sha512(r)` is computed; the first 32 bytes of this hash are stored as the new value `r'` of the random seed, and the remaining 32 bytes are returned as the next random value `x`. + #### rand + ```func int rand(int range) impure asm "RAND"; ``` + Generates a new pseudo-random integer `z` in the range `0..range−1` (or `range..−1` if `range < 0`). More precisely, an unsigned random value `x` is generated as in `random`; then `z := x * range / 2^256` is computed. #### get_seed + ```func int get_seed() impure asm "RANDSEED"; ``` + Returns the current random seed as an unsigned 256-bit integer. + #### set_seed + ```func int set_seed(int seed) impure asm "SETRAND"; ``` + Sets a random seed to an unsigned 256-bit `seed`. #### randomize + ```func () randomize(int x) impure asm "ADDRAND"; ``` + Mixes an unsigned 256-bit integer `x` into a random seed `r` by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with a big-endian representation of the old seed `r`, and the second with a big-endian representation of `x`. #### randomize_lt + ```func () randomize_lt() impure asm "LTIME" "ADDRAND"; ``` + Equivalent to `randomize(cur_lt());`. ### Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme. + ```func addr_none$00 = MsgAddressExt; addr_extern$01 len:(## 8) external_address:(bits len) = MsgAddressExt; - + anycast_info$_ depth:(#<= 30) { depth >= 1 } rewrite_pfx:(bits depth) = Anycast; - + addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; - + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) workchain_id:int32 address:(bits addr_len) = MsgAddressInt; _ _:MsgAddressInt = MsgAddress; @@ -413,55 +584,74 @@ int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress dest:MsgAddressInt value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; - + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; ``` + A deserialized `MsgAddress` is represented by the tuple `t` as follows: -* `addr_none` is represented by `t = (0)`, i.e., a tuple containing exactly -one integer that equals zero -* `addr_extern` is represented by `t = (1, s)`, where slice `s` contains the -field `external_address`. In other words, `t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s` -* `addr_std` is represented by `t = (2, u, x, s)`, where `u` is either `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if `anycast` is present). Next, integer `x` is the `workchain_id`, and slice `s` contains the address -* `addr_var` is represented by `t = (3, u, x, s)`, where `u`, `x`, and `s` have the same meaning as for `addr_std` + +- `addr_none` is represented by `t = (0)`, i.e., a tuple containing exactly + one integer that equals zero +- `addr_extern` is represented by `t = (1, s)`, where slice `s` contains the + field `external_address`. In other words, `t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s` +- `addr_std` is represented by `t = (2, u, x, s)`, where `u` is either `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if `anycast` is present). Next, integer `x` is the `workchain_id`, and slice `s` contains the address +- `addr_var` is represented by `t = (3, u, x, s)`, where `u`, `x`, and `s` have the same meaning as for `addr_std` #### load_msg_addr + ```func (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; ``` + Loads from `slice s` the only prefix that is a valid `MsgAddress` and returns both this prefix `s'` and the remainder `s''` of `s` as slices. + #### parse_addr + ```func tuple parse_addr(slice s) asm "PARSEMSGADDR"; ``` + Decomposes `slice s` containing a valid `MsgAddress` into `tuple t` with separate fields of this `MsgAddress`. If `s` is not a valid `MsgAddress`, a cell deserialization exception is thrown. #### parse_std_addr + ```func (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; ``` + Parses slice `s` containing a valid `MsgAddressInt` (usually `msg_addr_std`), applies rewriting from the `anycast` (if present) to the same-length prefix of the address, and returns both the workchain and the 256-bit address as integers. If the address is not 256-bit or if `s` is not a valid serialization of `MsgAddressInt`, throws a cell `deserialization` exception. + #### parse_var_addr + ```func (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; ``` + A variant of `parse_std_addr` that returns the (rewritten) address as a slice `s`, even if it is not exactly 256 bit long (represented by `msg_addr_var`). ## Debug primitives + Currently, only one function is available. + #### dump_stack + ```func () dump_stack() impure asm "DUMPSTK"; ``` + Dumps the stack (at most the top 255 values) and shows the total stack depth. ## Slice primitives -It is said that a primitive *loads* some data if it returns the data and the remainder of the slice (so it can also be used as a [modifying method](/develop/func/statements#modifying-methods)). -It is said that a primitive *preloads* some data if it returns only the data (it can be used as a [non-modifying method](/develop/func/statements#non-modifying-methods)). +It is said that a primitive _loads_ some data if it returns the data and the remainder of the slice (so it can also be used as a [modifying method](/develop/func/statements#modifying-methods)). + +It is said that a primitive _preloads_ some data if it returns only the data (it can be used as a [non-modifying method](/develop/func/statements#non-modifying-methods)). Unless otherwise stated, loading and preloading primitives read data from a prefix of the slice. + #### begin_parse + ```func slice begin_parse(cell c) asm "CTOS"; ``` @@ -469,216 +659,329 @@ slice begin_parse(cell c) asm "CTOS"; Converts `cell` into `slice`. Notice that `c` must be either an ordinary cell or an exotic cell (see [TVM.pdf](https://ton.org/tvm.pdf), 3.1.2) which is automatically loaded to yield an ordinary cell `c'`converted into `slice` afterwards. #### end_parse + ```func () end_parse(slice s) impure asm "ENDS"; ``` + Checks if `s` is empty. If not, throws an exception. + #### load_ref + ```func (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; ``` + Loads the first reference from a slice. + #### preload_ref + ```func cell preload_ref(slice s) asm "PLDREF"; ``` + Preloads the first reference from a slice. + #### load_int + ```func ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; ``` + Loads a signed `len`-bit integer from a slice. + #### load_uint + ```func ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; ``` + Loads an unsigned `len`-bit integer from a slice. + #### preload_int + ```func ;; int preload_int(slice s, int len) asm "PLDIX"; ``` + Preloads a signed `len`-bit integer from a slice. + #### preload_uint + ```func ;; int preload_uint(slice s, int len) asm "PLDUX"; ``` + Preloads an unsigned `len`-bit integer from a slice. + #### load_bits + ```func ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; ``` + Loads the first `0 ≤ len ≤ 1023` bits from slice `s` into a separate slice `s''`. #### preload_bits + ```func ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; ``` + Preloads the first `0 ≤ len ≤ 1023` bits from slice `s` into a separate slice `s''`. #### load_coins + ```func (slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; ``` + Loads serialized amount of Toncoins (any unsigned integer up to `2^120 - 1`). #### skip_bits + ```func slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; ``` + Returns all but the first `0 ≤ len ≤ 1023` bits of `s`. + #### first_bits + ```func slice first_bits(slice s, int len) asm "SDCUTFIRST"; ``` + Returns the first `0 ≤ len ≤ 1023` bits of `s`. + #### skip_last_bits + ```func slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; ``` + Returns all but the last `0 ≤ len ≤ 1023` bits of `s`. + #### slice_last + ```func slice slice_last(slice s, int len) asm "SDCUTLAST"; ``` + Returns the last `0 ≤ len ≤ 1023` bits of `s`. + #### load_dict + ```func (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; ``` + Loads a dictionary `D` from slice `s`. May be applied to dictionaries or to values of arbitrary `Maybe ^Y` types (returns `null` if `nothing` constructor is used). + #### preload_dict + ```func cell preload_dict(slice s) asm "PLDDICT"; ``` + Preloads a dictionary `D` from slice `s`. + #### skip_dict + ```func slice skip_dict(slice s) asm "SKIPDICT"; ``` + Loads a dictionary as `load_dict` but returns only the remainder of the slice. + ### Slice size primitives + #### slice_refs + ```func int slice_refs(slice s) asm "SREFS"; ``` + Returns the number of references in slice `s`. + #### slice_bits + ```func int slice_bits(slice s) asm "SBITS"; ``` + Returns the number of data bits in slice `s`. + #### slice_bits_refs + ```func (int, int) slice_bits_refs(slice s) asm "SBITREFS"; ``` + Returns both the number of data bits and the number of references in `s`. + #### slice_empty? + ```func int slice_empty?(slice s) asm "SEMPTY"; ``` + Checks whether slice `s` is empty (i.e., contains no bits of data and no cell references). + #### slice_data_empty? + ```func int slice_data_empty?(slice s) asm "SDEMPTY"; ``` + Checks whether slice `s` has no bits of data. + #### slice_refs_empty? + ```func int slice_refs_empty?(slice s) asm "SREMPTY"; ``` + Checks whether slice `s` has no references. + #### slice_depth + ```func int slice_depth(slice s) asm "SDEPTH"; ``` + Returns the depth of slice `s`. If `s` has no references, then returns `0`; otherwise, the returned value is one plus the maximum of depths of cells referred to from `s`. ## Builder primitives -It is said that a primitive *stores* a value `x` into a builder `b` if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. It can be used as a [non-modifying method](/develop/func/statements#non-modifying-methods). + +It is said that a primitive _stores_ a value `x` into a builder `b` if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. It can be used as a [non-modifying method](/develop/func/statements#non-modifying-methods). All of the primitives listed below verify whether there is enough space in the `builder`first, and then the range of the value being serialized. + #### begin_cell + ```func builder begin_cell() asm "NEWC"; ``` + Creates a new empty `builder`. + #### end_cell + ```func cell end_cell(builder b) asm "ENDC"; ``` + Converts `builder` into an ordinary `cell`. + #### store_ref + ```func builder store_ref(builder b, cell c) asm(c b) "STREF"; ``` + Stores a reference to cell `c` into builder `b`. + #### store_uint + ```func builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; ``` + Stores an unsigned `len`-bit integer `x` into `b` for `0 ≤ len ≤ 256`. + #### store_int + ```func builder store_int(builder b, int x, int len) asm(x b len) "STIX"; ``` + Stores a signed `len`-bit integer `x` into `b` for `0 ≤ len ≤ 257`. + #### store_slice + ```func builder store_slice(builder b, slice s) asm "STSLICER"; ``` + Stores slice `s` into builder `b`. + #### store_grams + ```func builder store_grams(builder b, int x) asm "STGRAMS"; ``` + #### store_coins + ```func builder store_coins(builder b, int x) asm "STGRAMS"; ``` + Stores (serializes) an integer `x` in the range `0..2^120 − 1` into builder `b`. The serialization of `x` consists of a 4-bit unsigned big-endian integer `l`, which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, followed by an `8l`-bit unsigned big-endian representation of `x`. If `x` does not belong to the supported range, a range check exception is thrown. It is the most common way of storing Toncoins. #### store_dict + ```func builder store_dict(builder b, cell c) asm(c b) "STDICT"; ``` + Stores dictionary `D` represented by cell `c` or `null` into builder `b`. In other words, stores `1`-bit and a reference to `c` if `c` is not `null` and `0`-bit otherwise. + #### store_maybe_ref + ```func builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; ``` + Equivalent to `store_dict`. ### Builder size primitives + #### builder_refs + ```func int builder_refs(builder b) asm "BREFS"; ``` + Returns the number of cell references already stored in builder `b`. + #### builder_bits + ```func int builder_bits(builder b) asm "BBITS"; ``` + Returns the number of data bits already stored in builder `b`. + #### builder_depth + ```func int builder_depth(builder b) asm "BDEPTH"; ``` + Returns the depth of builder `b`. If no cell references are stored in `b`, then returns `0`; otherwise, the returned value is one plus the maximum of depths of cells referred to from `b`. ## Cell primitives + #### cell_depth + ```func int cell_depth(cell c) asm "CDEPTH"; ``` + Returns the depth of cell `c`. If `c` has no references, then return `0`; otherwise, the returned value is one plus the maximum of depths of cells referred to from `c`. If `c` is a `null` instead of a cell, it returns zero. + #### cell_null? + ```func int cell_null?(cell c) asm "ISNULL"; ``` + Checks whether `c` is a `null`. Usually a `null`-cell represents an empty dictionary. FunC also has polymorphic `null?` built-in. (See [built-ins](/develop/func/builtins#other-primitives).) ## Dictionaries primitives @@ -688,15 +991,18 @@ The dictionary primitives below are low-level and do not check that the structur ::: As said in [TVM.pdf](https://ton.org/tvm.pdf): + > Dictionaries admit two different representations as TVM stack values: -> * A slice `s` with a serialization of a TL-B value of type `HashmapE(n, X)`. In other words, `s` consists either of one bit equal to zero (if the dictionary is empty) or of one bit equal to one and a reference to a cell containing the root of the binary tree, i.e., a serialized value of type `Hashmap(n, X)`. -> * A “Maybe cell” `c^?`, i.e., a value that is either a cell (containing a serialized value of type `Hashmap(n, X)` as before) or a `null` (corresponding to an empty dictionary, cf. [null values](/develop/func/types#null-values)). When a “Maybe cell” `c^?` is used to represent a dictionary, we usually denote it by `D`. +> +> - A slice `s` with a serialization of a TL-B value of type `HashmapE(n, X)`. In other words, `s` consists either of one bit equal to zero (if the dictionary is empty) or of one bit equal to one and a reference to a cell containing the root of the binary tree, i.e., a serialized value of type `Hashmap(n, X)`. +> - A “Maybe cell” `c^?`, i.e., a value that is either a cell (containing a serialized value of type `Hashmap(n, X)` as before) or a `null` (corresponding to an empty dictionary, cf. [null values](/develop/func/types#null-values)). When a “Maybe cell” `c^?` is used to represent a dictionary, we usually denote it by `D`. > > Most of the dictionary primitives listed below accept and return dictionaries in the second form, which is more convenient for stack manipulation. However, serialized dictionaries inside larger TL-B objects use the first representation. In FunC dictionaries are also represented by the `cell` type with the implicit assumption that it may be a `null` value. There are no separate types for dictionaries with different key lengths or value types (after all, it's FunC, not FunC++). ### Taxonomy note + A dictionary primitive may interpret the keys of the dictionary either as unsigned `l`-bit integers, as signed `l`-bit integers, or as `l`-bit slices. The primitives listed below differ by the prefix before the word `dict` in their names. `i` stands for signed integer keys, `u` stands for unsigned integer keys, and an empty prefix stands for slice keys. For example, `udict_set` is a set-by-key function for dictionaries with unsigned integer keys; `idict_set` is the corresponding function for dictionaries with signed integer keys; `dict_set` is the function for dictionaries with slice keys. @@ -706,9 +1012,11 @@ An empty prefix is used in the titles. Also, some of the primitives have their counterparts prefixed with `~`. It makes it possible to use them as [modifying methods](/develop/func/statements#modifying-methods). ### Dictionary's values -A value in a dictionary may be stored either directly as a subslice of an inner dictionary cell or as a reference to a separate cell. In the first case, it is not guaranteed that if the value fits into a cell, it fits into the dictionary (because a part of the inner cell may already be occupied by a part of the corresponding key). On the other hand, the second storing way is less gas-efficient. The second way is equivalent to storing in the first way a slice with empty data bits and exactly one reference to the value. + +Values within a dictionary can be stored either as a subslice within an inner dictionary cell or through a reference to a separate cell. In the former scenario, it is not assured that a value small enough to fit within a cell will also fit within the dictionary, as part of the inner cell's space may already be occupied by a portion of the corresponding key. Conversely, the latter method of storage is less efficient in terms of gas usage. Storing a value using the second method is tantamount to inserting a slice with no data bits and a single reference to the value in the first method​​. #### dict_set + ```func cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; @@ -717,68 +1025,99 @@ cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; ``` + Sets the value associated with `key_len`-bit key `index` in dictionary `dict` to `value` (a slice) and returns the resulting dictionary. + #### dict_set_ref + ```func cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; ``` + Similar to `dict_set` but with the value set to a reference to cell `value`. + #### dict_get? + ```func (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; ``` -Looks up key `index` in dictionary `dict` with `key_len`-bit keys. On success, returns the value found as a slice along with a `-1` flag indicating success. If fails, it returns `(null, 0)`. + +Searches for the key `index` within the `dict` dictionary, which uses keys of `key_len` bits. If successful, it retrieves the associated value as a `slice` and returns a flag value of `-1` to indicate **success**. If the search fails, it returns `(null, 0)​​`. + #### dict_get_ref? + ```func (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; ``` + Similar to `dict_get?` but returns the first reference of the found value. + #### dict_get_ref + ```func cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; ``` + A variant of `dict_get_ref?` that returns `null` instead of the value if the key `index` is absent from the dictionary `dict`. #### dict_set_get_ref + ```func (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; ``` + Sets the value associated with `index` to `value` (if `value` is `null`, then the key is deleted instead) and returns the old value (or `null` if the value was absent). + #### dict_delete? + ```func (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; ``` + Deletes `key_len`-bit key `index` from the dictionary `dict`. If the key is present, returns the modified dictionary `dict'` and the success flag `−1`. Otherwise, returns the original dictionary `dict` and `0`. + #### dict_delete_get? + ```func (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; ``` + Deletes `key_len`-bit key `index` from dictionary `dict`. If the key is present, returns the modified dictionary `dict'`, the original value `x` associated with the key k (represented by a Slice), and the success flag `−1`. Otherwise, returns `(dict, null, 0)`. + #### dict_add? + ```func (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; ``` + An `add` counterpart of `dict_set` sets the value associated with key `index` in dictionary `dict` to `value` but only if it is not already present in `D`. Returns either modified version of the dictionary and `-1` flag or `(dict, 0)`. + #### dict_replace? + ```func (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; ``` + A `replace` operation similar to `dict_set` but which sets the value of key `index` in dictionary `dict` to `value` only if the key was already present in `dict`. Returns either modified version of the dictionary and `-1` flag or `(dict, 0)`. + ### Builder counterparts + The following primitives accept the new value as a builder instead of a slice, which often is more convenient if the value needs to be serialized from several components computed in the stack. The net effect is roughly equivalent to converting b into a slice and executing the corresponding primitive listed above. + #### dict_set_builder + ```func cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; @@ -787,20 +1126,29 @@ cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(va (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; ``` + Similar to `dict_set` but accepts a builder. + #### dict_add_builder? + ```func (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; ``` + Similar to `dict_add?` but accepts a builder. + #### dict_replace_builder? + ```func (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; ``` + Similar to `dict_replace?` but accepts a builder. + #### dict_delete_get_min + ```func (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; @@ -809,11 +1157,13 @@ Similar to `dict_replace?` but accepts a builder. (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; ``` + Computes the minimum key `k` in dictionary `dict`, removes it, and returns `(dict', k, x, -1)`, where `dict'` is the modified version of `dict` and `x` is the value associated with `k`. If the dict is empty, returns `(dict, null, null, 0)`. Note that the key returned by `idict_delete_get_min` may differ from the key returned by `dict_delete_get_min` and `udict_delete_get_min`. #### dict_delete_get_max + ```func (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; @@ -822,118 +1172,174 @@ Note that the key returned by `idict_delete_get_min` may differ from the key ret (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; ``` + Computes the maximum key `k` in dictionary `dict`, removes it, and returns `(dict', k, x, -1)`, where `dict'` is the modified version of `dict` and `x` is the value associated with `k`. If the dict is empty, returns `(dict, null, null, 0)`. #### dict_get_min? + ```func (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; ``` + Computes the minimum key `k` in dictionary `dict`, the associated value `x`, and returns `(k, x, -1)`. If the dictionary is empty, returns `(null, null, 0)`. + #### dict_get_max? + ```func (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; ``` + Computes the maximum key `k` in dictionary `dict`, the associated value `x`, and returns `(k, x, -1)`. If the dictionary is empty, returns `(null, null, 0)`. + #### dict_get_min_ref? + ```func (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; ``` + Similar to `dict_get_min?` but returns the only reference in the value as a reference. + #### dict_get_max_ref? + ```func (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; ``` + Similar to `dict_get_max?` but returns the only reference in the value as a reference. + #### dict_get_next? + ```func (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; ``` + Computes the minimum key `k` in dictionary `dict` that is greater than `pivot`; returns `k`, associated value, and a flag indicating success. If the dictionary is empty, returns `(null, null, 0)`. + #### dict_get_nexteq? + ```func (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; ``` + Similar to `dict_get_next?` but computes the minimum key `k` that is greater than or equal to `pivot`. + #### dict_get_prev? + ```func (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; ``` + Similar to `dict_get_next?` but computes the maximum key `k` smaller than `pivot`. + #### dict_get_preveq? + ```func (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; ``` + Similar to `dict_get_prev?` but computes the maximum key `k` smaller than or equal to `pivot`. + #### new_dict + ```func cell new_dict() asm "NEWDICT"; ``` + Creates an empty dictionary, which is actually a `null` value. Special case of `null()`. + #### dict_empty? + ```func int dict_empty?(cell c) asm "DICTEMPTY"; ``` + Checks whether a dictionary is empty. Equivalent to `cell_null?`. ## Prefix dictionaries primitives + TVM also supports dictionaries with non-fixed length keys which form a prefix code (i.e., there is no key that is a prefix to another key). Learn more about them in the [TVM Instruction](/learn/tvm-instructions/tvm-overview) section. #### pfxdict_get? + ```func (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; ``` + Returns `(s', x, s'', -1)` or `(null, null, s, 0)`. Looks up the unique prefix of slice `key` present in the prefix code dictionary `dict`. If found, the prefix of `s` is returned as `s'` and the corresponding value (also a slice) as `x`. The remainder of `s` is returned as slice `s''`. If no prefix of `s` is key in prefix code dictionary `dict`, it returns the unchanged `s` and a zero flag to indicate failure. + #### pfxdict_set? + ```func (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; ``` + Similar to `dict_set` but may fail if the key is a prefix of another key presented in the dictionary. Indicating success, returns a flag. + #### pfxdict_delete? + ```func (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; ``` + Similar to `dict_delete?`. ## Special primitives + #### null + ```func forall X -> X null() asm "PUSHNULL"; ``` + By the TVM type `Null`, FunC represents the absence of a value of some atomic type. So `null` can actually have any atomic type. + #### ~impure_touch + ```func forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; ``` -Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. (c.f. [impure specifier](/develop/func/functions#impure-specifier)) +Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. (c.f. [impure specifier](/develop/func/functions#impure-specifier)) ## Other primitives + #### min + ```func int min(int x, int y) asm "MIN"; ``` + Computes the minimum of two integers `x` and `y`. + #### max + ```func int max(int x, int y) asm "MAX"; ``` + Computes the maximum of two integers `x` and `y`. + #### minmax + ```func (int, int) minmax(int x, int y) asm "MINMAX"; ``` + Sorts two integers. + #### abs + ```func int abs(int x) asm "ABS"; ``` + Computes the absolute value of the integer `x`.