From d9dba320ccbc23f2e7a4989d5a07297a706c6592 Mon Sep 17 00:00:00 2001 From: tolk-vm Date: Thu, 31 Oct 2024 11:18:54 +0400 Subject: [PATCH] [Tolk] Get rid of ~tilda with `mutate` and `self` methods This is a very big change. If FunC has `.methods()` and `~methods()`, Tolk has only dot, one and only way to call a `.method()`. A method may mutate an object, or may not. It's a behavioral and semantic difference from FunC. - `cs.loadInt(32)` modifies a slice and returns an integer - `b.storeInt(x, 32)` modifies a builder - `b = b.storeInt()` also works, since it not only modifies, but returns - chained methods also work, they return `self` - everything works exactly as expected, similar to JS - no runtime overhead, exactly same Fift instructions - custom methods are created with ease - tilda `~` does not exist in Tolk at all --- crypto/smartcont/tolk-stdlib/common.tolk | 137 ++-- crypto/smartcont/tolk-stdlib/lisp-lists.tolk | 2 +- crypto/smartcont/tolk-stdlib/tvm-dicts.tolk | 366 ++++------- .../smartcont/tolk-stdlib/tvm-lowlevel.tolk | 6 +- tolk-tester/tests/a10.tolk | 51 +- tolk-tester/tests/a6.tolk | 14 +- tolk-tester/tests/a6_1.tolk | 20 +- .../tests/allow_post_modification.tolk | 41 +- tolk-tester/tests/asm_arg_order.tolk | 28 +- tolk-tester/tests/camel1.tolk | 250 -------- tolk-tester/tests/camel2.tolk | 204 ------ tolk-tester/tests/camel3.tolk | 95 --- tolk-tester/tests/camel4.tolk | 145 ----- tolk-tester/tests/cells-slices.tolk | 189 +++--- tolk-tester/tests/co1.tolk | 7 +- tolk-tester/tests/dicts-demo.tolk | 75 ++- tolk-tester/tests/imports/use-dicts-err.tolk | 10 +- tolk-tester/tests/imports/use-dicts.tolk | 8 +- tolk-tester/tests/invalid-call-1.tolk | 9 + tolk-tester/tests/invalid-call-2.tolk | 14 + tolk-tester/tests/invalid-call-3.tolk | 12 + tolk-tester/tests/invalid-call-4.tolk | 13 + tolk-tester/tests/invalid-call-5.tolk | 13 + tolk-tester/tests/invalid-call-6.tolk | 12 + tolk-tester/tests/invalid-call-7.tolk | 14 + tolk-tester/tests/invalid-call-8.tolk | 8 + tolk-tester/tests/invalid-cmt-old.tolk | 2 +- tolk-tester/tests/invalid-declaration-6.tolk | 8 +- tolk-tester/tests/invalid-mutate-1.tolk | 11 + tolk-tester/tests/invalid-mutate-10.tolk | 16 + tolk-tester/tests/invalid-mutate-11.tolk | 8 + tolk-tester/tests/invalid-mutate-12.tolk | 14 + tolk-tester/tests/invalid-mutate-13.tolk | 8 + tolk-tester/tests/invalid-mutate-14.tolk | 8 + tolk-tester/tests/invalid-mutate-15.tolk | 12 + tolk-tester/tests/invalid-mutate-2.tolk | 10 + tolk-tester/tests/invalid-mutate-3.tolk | 11 + tolk-tester/tests/invalid-mutate-4.tolk | 14 + tolk-tester/tests/invalid-mutate-5.tolk | 14 + tolk-tester/tests/invalid-mutate-6.tolk | 16 + tolk-tester/tests/invalid-mutate-7.tolk | 15 + tolk-tester/tests/invalid-mutate-8.tolk | 10 + tolk-tester/tests/invalid-mutate-9.tolk | 9 + tolk-tester/tests/invalid-self-1.tolk | 8 + tolk-tester/tests/invalid-self-2.tolk | 8 + tolk-tester/tests/invalid-self-3.tolk | 10 + tolk-tester/tests/invalid-self-4.tolk | 9 + tolk-tester/tests/invalid-self-5.tolk | 15 + tolk-tester/tests/invalid-self-6.tolk | 8 + tolk-tester/tests/invalid-self-7.tolk | 8 + tolk-tester/tests/invalid-typing-3.tolk | 19 + tolk-tester/tests/invalid-typing-4.tolk | 14 + tolk-tester/tests/invalid-typing-5.tolk | 14 + tolk-tester/tests/known-bugs.tolk | 27 + tolk-tester/tests/mutate-methods.tolk | 337 ++++++++++ tolk-tester/tests/no-spaces.tolk | 16 +- tolk-tester/tests/null-keyword.tolk | 22 +- tolk-tester/tests/parse-address.tolk | 113 ++++ tolk-tester/tests/pure-functions.tolk | 4 +- tolk-tester/tests/self-keyword.tolk | 213 +++++++ tolk-tester/tests/test-math.tolk | 46 +- tolk-tester/tests/unbalanced_ret_nested.tolk | 5 +- tolk-tester/tests/use-before-declare.tolk | 2 +- tolk-tester/tests/var-apply.tolk | 22 + tolk-tester/tests/w2.tolk | 14 +- tolk/abscode.cpp | 23 +- tolk/analyzer.cpp | 3 +- tolk/ast-from-tokens.cpp | 180 ++++-- tolk/ast-replacer.h | 5 +- tolk/ast-stringifier.h | 14 +- tolk/ast-visitor.h | 6 +- tolk/ast.cpp | 10 + tolk/ast.h | 78 ++- tolk/builtins.cpp | 314 ++++++---- tolk/gen-abscode.cpp | 177 +++--- tolk/lexer.cpp | 37 +- tolk/lexer.h | 3 + tolk/pipe-ast-to-legacy.cpp | 584 +++++++++++++----- tolk/pipe-generate-fif-output.cpp | 8 +- tolk/pipe-register-symbols.cpp | 152 ++--- tolk/symtable.cpp | 12 + tolk/symtable.h | 15 +- tolk/tolk.h | 156 ++--- tolk/type-expr.h | 19 +- tolk/unify-types.cpp | 2 +- 85 files changed, 2703 insertions(+), 1958 deletions(-) delete mode 100644 tolk-tester/tests/camel1.tolk delete mode 100644 tolk-tester/tests/camel2.tolk delete mode 100644 tolk-tester/tests/camel3.tolk delete mode 100644 tolk-tester/tests/camel4.tolk create mode 100644 tolk-tester/tests/invalid-call-1.tolk create mode 100644 tolk-tester/tests/invalid-call-2.tolk create mode 100644 tolk-tester/tests/invalid-call-3.tolk create mode 100644 tolk-tester/tests/invalid-call-4.tolk create mode 100644 tolk-tester/tests/invalid-call-5.tolk create mode 100644 tolk-tester/tests/invalid-call-6.tolk create mode 100644 tolk-tester/tests/invalid-call-7.tolk create mode 100644 tolk-tester/tests/invalid-call-8.tolk create mode 100644 tolk-tester/tests/invalid-mutate-1.tolk create mode 100644 tolk-tester/tests/invalid-mutate-10.tolk create mode 100644 tolk-tester/tests/invalid-mutate-11.tolk create mode 100644 tolk-tester/tests/invalid-mutate-12.tolk create mode 100644 tolk-tester/tests/invalid-mutate-13.tolk create mode 100644 tolk-tester/tests/invalid-mutate-14.tolk create mode 100644 tolk-tester/tests/invalid-mutate-15.tolk create mode 100644 tolk-tester/tests/invalid-mutate-2.tolk create mode 100644 tolk-tester/tests/invalid-mutate-3.tolk create mode 100644 tolk-tester/tests/invalid-mutate-4.tolk create mode 100644 tolk-tester/tests/invalid-mutate-5.tolk create mode 100644 tolk-tester/tests/invalid-mutate-6.tolk create mode 100644 tolk-tester/tests/invalid-mutate-7.tolk create mode 100644 tolk-tester/tests/invalid-mutate-8.tolk create mode 100644 tolk-tester/tests/invalid-mutate-9.tolk create mode 100644 tolk-tester/tests/invalid-self-1.tolk create mode 100644 tolk-tester/tests/invalid-self-2.tolk create mode 100644 tolk-tester/tests/invalid-self-3.tolk create mode 100644 tolk-tester/tests/invalid-self-4.tolk create mode 100644 tolk-tester/tests/invalid-self-5.tolk create mode 100644 tolk-tester/tests/invalid-self-6.tolk create mode 100644 tolk-tester/tests/invalid-self-7.tolk create mode 100644 tolk-tester/tests/invalid-typing-3.tolk create mode 100644 tolk-tester/tests/invalid-typing-4.tolk create mode 100644 tolk-tester/tests/invalid-typing-5.tolk create mode 100644 tolk-tester/tests/known-bugs.tolk create mode 100644 tolk-tester/tests/mutate-methods.tolk create mode 100644 tolk-tester/tests/parse-address.tolk create mode 100644 tolk-tester/tests/self-keyword.tolk create mode 100644 tolk-tester/tests/var-apply.tolk diff --git a/crypto/smartcont/tolk-stdlib/common.tolk b/crypto/smartcont/tolk-stdlib/common.tolk index de711f7df..dec12e233 100644 --- a/crypto/smartcont/tolk-stdlib/common.tolk +++ b/crypto/smartcont/tolk-stdlib/common.tolk @@ -17,11 +17,7 @@ fun createEmptyTuple(): tuple /// Appends a value to tuple, resulting in `Tuple t' = (x1, ..., xn, value)`. /// If its size exceeds 255, throws a type check exception. @pure -fun tuplePush(t: tuple, value: X): tuple - asm "TPUSH"; - -@pure -fun ~tuplePush(t: tuple, value: X): (tuple, ()) +fun tuplePush(mutate self: tuple, value: X): void asm "TPUSH"; /// Returns the first element of a non-empty tuple. @@ -336,118 +332,109 @@ fun beginParse(c: cell): slice asm "CTOS"; /// Checks if slice is empty. If not, throws an exception. -fun assertEndOfSlice(s: slice): void +fun assertEndOfSlice(self: slice): void asm "ENDS"; /// Loads the next reference from the slice. @pure -fun loadRef(s: slice): (slice, cell) +fun loadRef(mutate self: slice): cell asm( -> 1 0) "LDREF"; /// Preloads the next reference from the slice. @pure -fun preloadRef(s: slice): cell +fun preloadRef(self: slice): cell asm "PLDREF"; /// Loads a signed [len]-bit integer from a slice. @pure -fun loadInt(s: slice, len: int): (slice, int) +fun loadInt(mutate self: slice, len: int): int builtin; /// Loads an unsigned [len]-bit integer from a slice. @pure -fun loadUint(s: slice, len: int): (slice, int) +fun loadUint(mutate self: slice, len: int): int builtin; /// Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. @pure -fun loadBits(s: slice, len: int): (slice, slice) +fun loadBits(mutate self: slice, len: int): slice builtin; /// Preloads a signed [len]-bit integer from a slice. @pure -fun preloadInt(s: slice, len: int): int +fun preloadInt(self: slice, len: int): int builtin; /// Preloads an unsigned [len]-bit integer from a slice. @pure -fun preloadUint(s: slice, len: int): int +fun preloadUint(self: slice, len: int): int builtin; /// Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice. @pure -fun preloadBits(s: slice, len: int): slice +fun preloadBits(self: slice, len: int): slice builtin; /// Loads serialized amount of Toncoins (any unsigned integer up to `2^120 - 1`). @pure -fun loadCoins(s: slice): (slice, int) +fun loadCoins(mutate self: slice): int asm( -> 1 0) "LDGRAMS"; /// Loads bool (-1 or 0) from a slice @pure -fun loadBool(s: slice): (slice, int) +fun loadBool(mutate self: slice): int asm( -> 1 0) "1 LDI"; /// Shifts a slice pointer to [len] bits forward, mutating the slice. @pure -fun skipBits(s: slice, len: int): slice - asm "SDSKIPFIRST"; // todo make mutating -@pure -fun ~skipBits(s: slice, len: int): (slice, ()) +fun skipBits(mutate self: slice, len: int): self asm "SDSKIPFIRST"; /// Returns the first `0 ≤ len ≤ 1023` bits of a slice. @pure -fun getFirstBits(s: slice, len: int): slice +fun getFirstBits(self: slice, len: int): slice asm "SDCUTFIRST"; /// Returns all but the last `0 ≤ len ≤ 1023` bits of a slice. @pure -fun removeLastBits(s: slice, len: int): slice - asm "SDSKIPLAST"; // todo make mutating -@pure -fun ~removeLastBits(s: slice, len: int): (slice, ()) +fun removeLastBits(mutate self: slice, len: int): self asm "SDSKIPLAST"; /// Returns the last `0 ≤ len ≤ 1023` bits of a slice. @pure -fun getLastBits(s: slice, len: int): slice +fun getLastBits(self: slice, len: int): slice asm "SDCUTLAST"; /// Loads a dictionary (TL HashMapE structure, represented as TVM cell) from a slice. /// Returns `null` if `nothing` constructor is used. @pure -fun loadDict(s: slice): (slice, cell) +fun loadDict(mutate self: slice): cell asm( -> 1 0) "LDDICT"; /// Preloads a dictionary (cell) from a slice. @pure -fun preloadDict(s: slice): cell +fun preloadDict(self: slice): cell asm "PLDDICT"; /// Loads a dictionary as [loadDict], but returns only the remainder of the slice. @pure -fun skipDict(s: slice): slice - asm "SKIPDICT"; // todo make mutating -@pure -fun ~skipDict(s: slice): (slice, ()) +fun skipDict(mutate self: slice): self asm "SKIPDICT"; /// Loads (Maybe ^Cell) from a slice. /// In other words, loads 1 bit: if it's true, loads the first ref, otherwise returns `null`. @pure -fun loadMaybeRef(s: slice): (slice, cell) +fun loadMaybeRef(mutate self: slice): cell asm( -> 1 0) "LDOPTREF"; /// Preloads (Maybe ^Cell) from a slice. @pure -fun preloadMaybeRef(s: slice): cell +fun preloadMaybeRef(self: slice): cell asm "PLDOPTREF"; /// Loads (Maybe ^Cell), but returns only the remainder of the slice. @pure -fun ~skipMaybeRef(s: slice): (slice, ()) +fun skipMaybeRef(mutate self: slice): self asm "SKIPOPTREF"; /** @@ -464,62 +451,60 @@ fun beginCell(): builder /// Converts a builder into an ordinary `cell`. @pure -fun endCell(b: builder): cell +fun endCell(self: builder): cell asm "ENDC"; /// Stores a reference to a cell into a builder. @pure -fun storeRef(b: builder, c: cell): builder - asm(c b) "STREF"; +fun storeRef(mutate self: builder, c: cell): self + asm(c self) "STREF"; /// Stores a signed [len]-bit integer into a builder (`0 ≤ len ≤ 257`). @pure -fun storeInt(b: builder, x: int, len: int): builder +fun storeInt(mutate self: builder, x: int, len: int): self builtin; /// Stores an unsigned [len]-bit integer into a builder (`0 ≤ len ≤ 256`). @pure -fun storeUint(b: builder, x: int, len: int): builder +fun storeUint(mutate self: builder, x: int, len: int): self builtin; /// Stores a slice into a builder. @pure -fun storeSlice(b: builder, s: slice): builder +fun storeSlice(mutate self: builder, s: slice): self asm "STSLICER"; /// Stores amount of Toncoins into a builder. @pure -fun storeCoins(b: builder, x: int): builder +fun storeCoins(mutate self: builder, x: int): self asm "STGRAMS"; /// Stores bool (-1 or 0) into a builder. /// Attention: true value is `-1`, not 1! If you pass `1` here, TVM will throw an exception. @pure -fun storeBool(b: builder, x: int): builder - asm(x b) "1 STI"; +fun storeBool(mutate self: builder, x: int): self + asm(x self) "1 STI"; /// Stores dictionary (represented by TVM `cell` or `null`) into a builder. /// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. @pure -fun storeDict(b: builder, c: cell): builder - asm(c b) "STDICT"; +fun storeDict(mutate self: builder, c: cell): self + asm(c self) "STDICT"; /// Stores (Maybe ^Cell) into a builder. /// In other words, if cell is `null`, store '0' bit; otherwise, store '1' and a ref to [c]. @pure -fun storeMaybeRef(b: builder, c: cell): builder - asm(c b) "STOPTREF"; +fun storeMaybeRef(mutate self: builder, c: cell): self + asm(c self) "STOPTREF"; /// Concatenates two builders. @pure -fun storeBuilder(to: builder, from: builder): builder +fun storeBuilder(mutate self: builder, from: builder): self asm "STBR"; +/// Stores a slice representing TL addr_none$00 (two `0` bits). @pure -fun storeAddressNone(b: builder): builder - asm "0 PUSHINT" "SWAP" "2 STU"; -@pure -fun ~storeAddressNone(b: builder): (builder, ()) +fun storeAddressNone(mutate self: builder): self asm "b{00} STSLICECONST"; @@ -529,47 +514,47 @@ fun ~storeAddressNone(b: builder): (builder, ()) /// Returns the number of references in a slice. @pure -fun getRemainingRefsCount(s: slice): int +fun getRemainingRefsCount(self: slice): int asm "SREFS"; /// Returns the number of data bits in a slice. @pure -fun getRemainingBitsCount(s: slice): int +fun getRemainingBitsCount(self: slice): int asm "SBITS"; /// Returns both the number of data bits and the number of references in a slice. @pure -fun getRemainingBitsAndRefsCount(s: slice): (int, int) +fun getRemainingBitsAndRefsCount(self: slice): (int, int) asm "SBITREFS"; /// Checks whether a slice is empty (i.e., contains no bits of data and no cell references). @pure -fun isEndOfSlice(s: slice): int +fun isEndOfSlice(self: slice): int asm "SEMPTY"; /// Checks whether a slice has no bits of data. @pure -fun isEndOfSliceBits(s: slice): int +fun isEndOfSliceBits(self: slice): int asm "SDEMPTY"; /// Checks whether a slice has no references. @pure -fun isEndOfSliceRefs(s: slice): int +fun isEndOfSliceRefs(self: slice): int asm "SREMPTY"; /// Checks whether data parts of two slices coinside. @pure -fun isSliceBitsEqual(a: slice, b: slice): int +fun isSliceBitsEqual(self: slice, b: slice): int asm "SDEQ"; /// Returns the number of cell references already stored in a builder. @pure -fun getBuilderRefsCount(b: builder): int +fun getBuilderRefsCount(self: builder): int asm "BREFS"; /// Returns the number of data bits already stored in a builder. @pure -fun getBuilderBitsCount(b: builder): int +fun getBuilderBitsCount(self: builder): int asm "BBITS"; @@ -613,8 +598,8 @@ fun getBuilderBitsCount(b: builder): int /// 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. @pure -fun loadAddress(s: slice): (slice, slice) - asm( -> 1 0) "LDMSGADDR"; // todo make mutating +fun loadAddress(mutate self: slice): slice + asm( -> 1 0) "LDMSGADDR"; /// Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. /// If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. @@ -686,7 +671,7 @@ const NON_BOUNCEABLE = 0x10; /// Load msgFlags from incoming message body (4 bits). @pure -fun loadMessageFlags(s: slice): (slice, int) +fun loadMessageFlags(mutate self: slice): int asm( -> 1 0) "4 LDU"; /// Having msgFlags (4 bits), check that a message is bounced. @@ -697,38 +682,34 @@ fun isMessageBounced(msgFlags: int): int /// Skip 0xFFFFFFFF prefix (when a message is bounced). @pure -fun ~skipBouncedPrefix(s: slice): (slice, ()) +fun skipBouncedPrefix(mutate self: slice): self asm "32 PUSHINT" "SDSKIPFIRST"; /// The guideline recommends to start the body of an internal message with uint32 `op` and uint64 `queryId`. @pure -fun loadMessageOp(s: slice): (slice, int) +fun loadMessageOp(mutate self: slice): int asm( -> 1 0) "32 LDU"; @pure -fun ~skipMessageOp(s: slice): (slice, ()) +fun skipMessageOp(mutate self: slice): self asm "32 PUSHINT" "SDSKIPFIRST"; @pure -fun storeMessageOp(b: builder, op: int): builder - asm(op b) "32 STU"; -fun ~storeMessageOp(b: builder, op: int): (builder, ()) - asm(op b) "32 STU"; +fun storeMessageOp(mutate self: builder, op: int): self + asm(op self) "32 STU"; /// The guideline recommends that uint64 `queryId` should follow uint32 `op`. @pure -fun loadMessageQueryId(s: slice): (slice, int) +fun loadMessageQueryId(mutate self: slice): int asm( -> 1 0) "64 LDU"; @pure -fun ~skipMessageQueryId(s: slice): (slice, ()) +fun skipMessageQueryId(mutate self: slice): self asm "64 PUSHINT" "SDSKIPFIRST"; @pure -fun storeMessageQueryId(b: builder, queryId: int): builder - asm(queryId b) "64 STU"; -fun ~storeMessageQueryId(b: builder, queryId: int): (builder, ()) - asm(queryId b) "64 STU"; +fun storeMessageQueryId(mutate self: builder, queryId: int): self + asm(queryId self) "64 STU"; /// SEND MODES - https://docs.ton.org/tvm.pdf page 137, SENDRAWMSG diff --git a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk index 94c045237..f7a721918 100644 --- a/crypto/smartcont/tolk-stdlib/lisp-lists.tolk +++ b/crypto/smartcont/tolk-stdlib/lisp-lists.tolk @@ -24,7 +24,7 @@ fun listSplit(list: tuple): (X, tuple) /// Extracts the tail and the head of lisp-style list. @pure -fun ~listNext(list: tuple): (tuple, X) +fun listNext(mutate self: tuple): X asm( -> 1 0) "UNCONS"; /// Returns the head of lisp-style list. diff --git a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk index 1e3c10ec8..9fba24d90 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-dicts.tolk @@ -19,415 +19,279 @@ fun createEmptyDict(): cell /// Checks whether a dictionary is empty. @pure -fun dictIsEmpty(c: cell): int +fun dictIsEmpty(self: cell): int asm "DICTEMPTY"; @pure -fun iDictGet(dict: cell, keyLen: int, key: int): (slice, int) - asm(key dict keyLen) "DICTIGET" "NULLSWAPIFNOT"; +fun iDictGet(self: cell, keyLen: int, key: int): (slice, int) + asm(key self keyLen) "DICTIGET" "NULLSWAPIFNOT"; @pure -fun uDictGet(dict: cell, keyLen: int, key: int): (slice, int) - asm(key dict keyLen) "DICTUGET" "NULLSWAPIFNOT"; +fun uDictGet(self: cell, keyLen: int, key: int): (slice, int) + asm(key self keyLen) "DICTUGET" "NULLSWAPIFNOT"; @pure -fun sDictGet(dict: cell, keyLen: int, key: slice): (slice, int) - asm(key dict keyLen) "DICTGET" "NULLSWAPIFNOT"; +fun sDictGet(self: cell, keyLen: int, key: slice): (slice, int) + asm(key self keyLen) "DICTGET" "NULLSWAPIFNOT"; @pure -fun iDictSet(dict: cell, keyLen: int, key: int, value: slice): cell - asm(value key dict keyLen) "DICTISET"; +fun iDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void + asm(value key self keyLen) "DICTISET"; @pure -fun ~iDictSet(dict: cell, keyLen: int, key: int, value: slice): (cell, ()) - asm(value key dict keyLen) "DICTISET"; +fun uDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void + asm(value key self keyLen) "DICTUSET"; @pure -fun uDictSet(dict: cell, keyLen: int, key: int, value: slice): cell - asm(value key dict keyLen) "DICTUSET"; - -@pure -fun ~uDictSet(dict: cell, keyLen: int, key: int, value: slice): (cell, ()) - asm(value key dict keyLen) "DICTUSET"; - -@pure -fun sDictSet(dict: cell, keyLen: int, key: slice, value: slice): cell - asm(value key dict keyLen) "DICTSET"; - -@pure -fun ~sDictSet(dict: cell, keyLen: int, key: slice, value: slice): (cell, ()) - asm(value key dict keyLen) "DICTSET"; - - -@pure -fun iDictSetRef(dict: cell, keyLen: int, key: int, value: cell): cell - asm(value key dict keyLen) "DICTISETREF"; - -@pure -fun ~iDictSetRef(dict: cell, keyLen: int, key: int, value: cell): (cell, ()) - asm(value key dict keyLen) "DICTISETREF"; - -@pure -fun uDictSetRef(dict: cell, keyLen: int, key: int, value: cell): cell - asm(value key dict keyLen) "DICTUSETREF"; - -@pure -fun ~uDictSetRef(dict: cell, keyLen: int, key: int, value: cell): (cell, ()) - asm(value key dict keyLen) "DICTUSETREF"; - -@pure -fun sDictSetRef(dict: cell, keyLen: int, key: slice, value: cell): cell - asm(value key dict keyLen) "DICTSETREF"; - -@pure -fun ~sDictSetRef(dict: cell, keyLen: int, key: slice, value: cell): (cell, ()) - asm(value key dict keyLen) "DICTSETREF"; - - -@pure -fun iDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTIADD"; - -@pure -fun ~iDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTIADD"; - -@pure -fun uDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTUADD"; - -@pure -fun ~uDictSetIfNotExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTUADD"; - - -@pure -fun iDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTIREPLACE"; - -@pure -fun ~iDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTIREPLACE"; - -@pure -fun uDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTUREPLACE"; - -@pure -fun ~uDictSetIfExists(dict: cell, keyLen: int, key: int, value: slice): (cell, int) - asm(value key dict keyLen) "DICTUREPLACE"; +fun sDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): void + asm(value key self keyLen) "DICTSET"; @pure -fun iDictGetRef(dict: cell, keyLen: int, key: int): (cell, int) - asm(key dict keyLen) "DICTIGETREF" "NULLSWAPIFNOT"; +fun iDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void + asm(value key self keyLen) "DICTISETREF"; @pure -fun uDictGetRef(dict: cell, keyLen: int, key: int): (cell, int) - asm(key dict keyLen) "DICTUGETREF" "NULLSWAPIFNOT"; +fun uDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void + asm(value key self keyLen) "DICTUSETREF"; @pure -fun sDictGetRef(dict: cell, keyLen: int, key: slice): (cell, int) - asm(key dict keyLen) "DICTGETREF" "NULLSWAPIFNOT"; +fun sDictSetRef(mutate self: cell, keyLen: int, key: slice, value: cell): void + asm(value key self keyLen) "DICTSETREF"; @pure -fun iDictGetRefOrNull(dict: cell, keyLen: int, key: int): cell - asm(key dict keyLen) "DICTIGETOPTREF"; +fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): int + asm(value key self keyLen) "DICTIADD"; @pure -fun uDictGetRefOrNull(dict: cell, keyLen: int, key: int): cell - asm(key dict keyLen) "DICTUGETOPTREF"; - -@pure -fun sDictGetRefOrNull(dict: cell, keyLen: int, key: slice): cell - asm(key dict keyLen) "DICTGETOPTREF"; +fun uDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): int + asm(value key self keyLen) "DICTUADD"; @pure -fun iDictDelete(dict: cell, keyLen: int, key: int): (cell, int) - asm(key dict keyLen) "DICTIDEL"; +fun iDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): int + asm(value key self keyLen) "DICTIREPLACE"; @pure -fun ~iDictDelete(dict: cell, keyLen: int, key: int): (cell, int) - asm(key dict keyLen) "DICTIDEL"; +fun uDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): int + asm(value key self keyLen) "DICTUREPLACE"; -@pure -fun uDictDelete(dict: cell, keyLen: int, key: int): (cell, int) - asm(key dict keyLen) "DICTUDEL"; @pure -fun ~uDictDelete(dict: cell, keyLen: int, key: int): (cell, int) - asm(key dict keyLen) "DICTUDEL"; +fun iDictGetRef(self: cell, keyLen: int, key: int): (cell, int) + asm(key self keyLen) "DICTIGETREF" "NULLSWAPIFNOT"; @pure -fun sDictDelete(dict: cell, keyLen: int, key: slice): (cell, int) - asm(key dict keyLen) "DICTDEL"; +fun uDictGetRef(self: cell, keyLen: int, key: int): (cell, int) + asm(key self keyLen) "DICTUGETREF" "NULLSWAPIFNOT"; @pure -fun ~sDictDelete(dict: cell, keyLen: int, key: slice): (cell, int) - asm(key dict keyLen) "DICTDEL"; +fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, int) + asm(key self keyLen) "DICTGETREF" "NULLSWAPIFNOT"; @pure -fun iDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, slice, int) - asm(value key dict keyLen) "DICTISETGET" "NULLSWAPIFNOT"; +fun iDictGetRefOrNull(self: cell, keyLen: int, key: int): cell + asm(key self keyLen) "DICTIGETOPTREF"; @pure -fun ~iDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, (slice, int)) - asm(value key dict keyLen) "DICTISETGET" "NULLSWAPIFNOT"; +fun uDictGetRefOrNull(self: cell, keyLen: int, key: int): cell + asm(key self keyLen) "DICTUGETOPTREF"; @pure -fun uDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, slice, int) - asm(value key dict keyLen) "DICTUSETGET" "NULLSWAPIFNOT"; +fun sDictGetRefOrNull(self: cell, keyLen: int, key: slice): cell + asm(key self keyLen) "DICTGETOPTREF"; -@pure -fun ~uDictSetAndGet(dict: cell, keyLen: int, key: int, value: slice): (cell, (slice, int)) - asm(value key dict keyLen) "DICTUSETGET" "NULLSWAPIFNOT"; @pure -fun sDictSetAndGet(dict: cell, keyLen: int, key: slice, value: slice): (cell, slice, int) - asm(value key dict keyLen) "DICTSETGET" "NULLSWAPIFNOT"; +fun iDictDelete(mutate self: cell, keyLen: int, key: int): int + asm(key self keyLen) "DICTIDEL"; @pure -fun ~sDictSetAndGet(dict: cell, keyLen: int, key: slice, value: slice): (cell, (slice, int)) - asm(value key dict keyLen) "DICTSETGET" "NULLSWAPIFNOT"; - +fun uDictDelete(mutate self: cell, keyLen: int, key: int): int + asm(key self keyLen) "DICTUDEL"; @pure -fun iDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) - asm(value key dict keyLen) "DICTISETGETOPTREF"; +fun sDictDelete(mutate self: cell, keyLen: int, key: slice): int + asm(key self keyLen) "DICTDEL"; -@pure -fun ~iDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) - asm(value key dict keyLen) "DICTISETGETOPTREF"; @pure -fun uDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) - asm(value key dict keyLen) "DICTUSETGETOPTREF"; +fun iDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, int) + asm(value key self keyLen) "DICTISETGET" "NULLSWAPIFNOT"; @pure -fun ~uDictSetAndGetPreviousRefOrNull(dict: cell, keyLen: int, key: int, value: cell): (cell, cell) - asm(value key dict keyLen) "DICTUSETGETOPTREF"; - +fun uDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, int) + asm(value key self keyLen) "DICTUSETGET" "NULLSWAPIFNOT"; @pure -fun iDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, slice, int) - asm(key dict keyLen) "DICTIDELGET" "NULLSWAPIFNOT"; +fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, int) + asm(value key self keyLen) "DICTSETGET" "NULLSWAPIFNOT"; -@pure -fun ~iDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, (slice, int)) - asm(key dict keyLen) "DICTIDELGET" "NULLSWAPIFNOT"; @pure -fun uDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, slice, int) - asm(key dict keyLen) "DICTUDELGET" "NULLSWAPIFNOT"; +fun iDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell + asm(value key self keyLen) "DICTISETGETOPTREF"; @pure -fun ~uDictDeleteAndGet(dict: cell, keyLen: int, key: int): (cell, (slice, int)) - asm(key dict keyLen) "DICTUDELGET" "NULLSWAPIFNOT"; +fun uDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell + asm(value key self keyLen) "DICTUSETGETOPTREF"; -@pure -fun sDictDeleteAndGet(dict: cell, keyLen: int, key: slice): (cell, slice, int) - asm(key dict keyLen) "DICTDELGET" "NULLSWAPIFNOT"; @pure -fun ~sDictDeleteAndGet(dict: cell, keyLen: int, key: slice): (cell, (slice, int)) - asm(key dict keyLen) "DICTDELGET" "NULLSWAPIFNOT"; - +fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, int) + asm(key self keyLen) "DICTIDELGET" "NULLSWAPIFNOT"; @pure -fun iDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): cell - asm(value key dict keyLen) "DICTISETB"; +fun uDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, int) + asm(key self keyLen) "DICTUDELGET" "NULLSWAPIFNOT"; @pure -fun ~iDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): (cell, ()) - asm(value key dict keyLen) "DICTISETB"; +fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, int) + asm(key self keyLen) "DICTDELGET" "NULLSWAPIFNOT"; -@pure -fun uDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): cell - asm(value key dict keyLen) "DICTUSETB"; @pure -fun ~uDictSetBuilder(dict: cell, keyLen: int, key: int, value: builder): (cell, ()) - asm(value key dict keyLen) "DICTUSETB"; +fun iDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void + asm(value key self keyLen) "DICTISETB"; @pure -fun sDictSetBuilder(dict: cell, keyLen: int, key: slice, value: builder): cell - asm(value key dict keyLen) "DICTSETB"; +fun uDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void + asm(value key self keyLen) "DICTUSETB"; @pure -fun ~sDictSetBuilder(dict: cell, keyLen: int, key: slice, value: builder): (cell, ()) - asm(value key dict keyLen) "DICTSETB"; - +fun sDictSetBuilder(mutate self: cell, keyLen: int, key: slice, value: builder): void + asm(value key self keyLen) "DICTSETB"; -@pure -fun iDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTIADDB"; - -@pure -fun ~iDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTIADDB"; - -@pure -fun uDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTUADDB"; - -@pure -fun ~uDictSetBuilderIfNotExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTUADDB"; @pure -fun iDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTIREPLACEB"; +fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): int + asm(value key self keyLen) "DICTIADDB"; @pure -fun ~iDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTIREPLACEB"; +fun uDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): int + asm(value key self keyLen) "DICTUADDB"; @pure -fun uDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTUREPLACEB"; +fun iDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): int + asm(value key self keyLen) "DICTIREPLACEB"; @pure -fun ~uDictSetBuilderIfExists(dict: cell, keyLen: int, key: int, value: builder): (cell, int) - asm(value key dict keyLen) "DICTUREPLACEB"; +fun uDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): int + asm(value key self keyLen) "DICTUREPLACEB"; @pure -fun iDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, int, slice, int) +fun iDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, int) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; @pure -fun ~iDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun uDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, int, slice, int) +fun uDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, int) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; @pure -fun ~uDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun sDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, slice, slice, int) - asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; - -@pure -fun ~sDictDeleteFirstAndGet(dict: cell, keyLen: int): (cell, (slice, slice, int)) +fun sDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (slice, slice, int) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; @pure -fun iDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, int, slice, int) +fun iDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, int) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; @pure -fun ~iDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun uDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, int, slice, int) +fun uDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, int) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; @pure -fun ~uDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, (int, slice, int)) - asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun sDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, slice, slice, int) - asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; - -@pure -fun ~sDictDeleteLastAndGet(dict: cell, keyLen: int): (cell, (slice, slice, int)) +fun sDictDeleteLastAndGet(mutate self: cell, keyLen: int): (slice, slice, int) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; @pure -fun iDictGetFirst(dict: cell, keyLen: int): (int, slice, int) +fun iDictGetFirst(self: cell, keyLen: int): (int, slice, int) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; @pure -fun uDictGetFirst(dict: cell, keyLen: int): (int, slice, int) +fun uDictGetFirst(self: cell, keyLen: int): (int, slice, int) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; @pure -fun sDictGetFirst(dict: cell, keyLen: int): (slice, slice, int) +fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, int) asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2"; @pure -fun iDictGetFirstAsRef(dict: cell, keyLen: int): (int, cell, int) +fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, int) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; @pure -fun uDictGetFirstAsRef(dict: cell, keyLen: int): (int, cell, int) +fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, int) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; @pure -fun sDictGetFirstAsRef(dict: cell, keyLen: int): (slice, cell, int) +fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, int) asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2"; @pure -fun iDictGetLast(dict: cell, keyLen: int): (int, slice, int) +fun iDictGetLast(self: cell, keyLen: int): (int, slice, int) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; @pure -fun uDictGetLast(dict: cell, keyLen: int): (int, slice, int) +fun uDictGetLast(self: cell, keyLen: int): (int, slice, int) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; @pure -fun sDictGetLast(dict: cell, keyLen: int): (slice, slice, int) +fun sDictGetLast(self: cell, keyLen: int): (slice, slice, int) asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2"; @pure -fun iDictGetLastAsRef(dict: cell, keyLen: int): (int, cell, int) +fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, int) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; @pure -fun uDictGetLastAsRef(dict: cell, keyLen: int): (int, cell, int) +fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, int) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; @pure -fun sDictGetLastAsRef(dict: cell, keyLen: int): (slice, cell, int) +fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, int) asm (-> 1 0 2) "DICTMAXREF" "NULLSWAPIFNOT2"; @pure -fun iDictGetNext(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +fun iDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; @pure -fun uDictGetNext(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +fun uDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; @pure -fun iDictGetNextOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +fun iDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; @pure -fun uDictGetNextOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +fun uDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; @pure -fun iDictGetPrev(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +fun iDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; @pure -fun uDictGetPrev(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +fun uDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; @pure -fun iDictGetPrevOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +fun iDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; @pure -fun uDictGetPrevOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) - asm(pivot dict keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int) + asm(pivot self keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; /** @@ -435,13 +299,13 @@ fun uDictGetPrevOrEqual(dict: cell, keyLen: int, pivot: int): (int, slice, int) */ @pure -fun prefixDictGet(dict: cell, keyLen: int, key: slice): (slice, slice, slice, int) - asm(key dict keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, int) + asm(key self keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2"; @pure -fun prefixDictSet(dict: cell, keyLen: int, key: slice, value: slice): (cell, int) - asm(value key dict keyLen) "PFXDICTSET"; +fun prefixDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): int + asm(value key self keyLen) "PFXDICTSET"; @pure -fun prefixDictDelete(dict: cell, keyLen: int, key: slice): (cell, int) - asm(key dict keyLen) "PFXDICTDEL"; +fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): int + asm(key self keyLen) "PFXDICTDEL"; diff --git a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk index b4f44a1bf..91b35f2bd 100644 --- a/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk +++ b/crypto/smartcont/tolk-stdlib/tvm-lowlevel.tolk @@ -21,9 +21,5 @@ fun transformSliceToContinuation(s: slice): continuation /// Moves a variable or a value [x] to the top of the stack. @pure -fun stackMoveToTop(x: X): X +fun stackMoveToTop(mutate self: X): void asm "NOP"; - -/// Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. -fun stackMoveToTopImpure(x: X): void // todo needs to be deleted, check verified contracts - asm "DROP"; diff --git a/tolk-tester/tests/a10.tolk b/tolk-tester/tests/a10.tolk index 1526a1220..d46397c6a 100644 --- a/tolk-tester/tests/a10.tolk +++ b/tolk-tester/tests/a10.tolk @@ -1,3 +1,5 @@ +import "@stdlib/tvm-lowlevel" + fun pair_first(p: [X, Y]): X asm "FIRST"; fun one(dummy: tuple) { @@ -35,13 +37,34 @@ fun test88(x: int) { @method_id(89) fun test89(last: int) { var t: tuple = createEmptyTuple(); - t~tuplePush(1); - t~tuplePush(2); - t~tuplePush(3); - t~tuplePush(last); + t.tuplePush(1); + t.tuplePush(2); + t.tuplePush(3); + t.tuplePush(last); return (t.tupleAt(0), t.tupleAt(t.tupleSize() - 1), t.tupleFirst(), t.tupleLast()); } +@pure fun get10() { return 10; } + +@method_id(91) +fun touchCodegen2() { + var f = get10(); + f.stackMoveToTop(); + return f; +} + +@method_id(92) +fun testDumpDontPolluteStack() { + var f = get10(); + f.debugPrint(); + debugPrint(10); + var s = "asdf"; + s.debugPrintString(); + debugDumpStack(); + debugPrintString("my"); + return (f, getRemainingBitsCount(s)); +} + @method_id(93) fun testStartBalanceCodegen1() { var t = getMyOriginalBalanceWithExtraCurrencies(); @@ -65,7 +88,27 @@ fun testStartBalanceCodegen2() { @testcase | 88 | 5 | 234 @testcase | 88 | 50 | 0 @testcase | 89 | 4 | 1 4 1 4 +@testcase | 91 | | 10 +@testcase | 92 | | 10 32 + +@fif_codegen +""" + touchCodegen2 PROC:<{ + // + get10 CALLDICT // f + }> +""" +@fif_codegen +""" + testDumpDontPolluteStack PROC:<{ + ... + DUMPSTK + x{6d79} PUSHSLICE // f s _9 + STRDUMP DROP + SBITS // f _11 + }> +""" @fif_codegen """ diff --git a/tolk-tester/tests/a6.tolk b/tolk-tester/tests/a6.tolk index da692a78e..7f2c39461 100644 --- a/tolk-tester/tests/a6.tolk +++ b/tolk-tester/tests/a6.tolk @@ -4,8 +4,6 @@ fun f(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) { return (Dx/D,Dy/D); };;;; -fun mulDivR(x: int, y: int, z: int): int { return mulDivRound(x, y, z); } - fun calc_phi(): int { var n = 1; repeat (70) { n*=10; }; @@ -14,7 +12,7 @@ fun calc_phi(): int { do { (p,q)=(q,p+q); } while (q <= n); //;; - return mulDivR(p, n, q); + return mulDivRound(p, n, q); } fun calc_sqrt2(): int { @@ -26,7 +24,7 @@ fun calc_sqrt2(): int { var t = p + q; (p, q) = (q, t + q); } while (q <= n); - return mulDivR(p, n, q); + return mulDivRound(p, n, q); } fun calc_root(m: auto): auto { @@ -63,18 +61,14 @@ fun ataninv(base: int, q: int): int { // computes base*atan(1/q) return sum; } -fun arctanInv(base: int, q: int): int { return ataninv(base, q); } - fun calc_pi(): int { var base: int = 64; repeat (70) { base *= 10; } - return (arctanInv(base << 2, 5) - arctanInv(base, 239))~>>4; + return (ataninv(base << 2, 5) - ataninv(base, 239))~>>4; } -fun calcPi(): int { return calc_pi(); } - fun main(): int { - return calcPi(); + return calc_pi(); } /** diff --git a/tolk-tester/tests/a6_1.tolk b/tolk-tester/tests/a6_1.tolk index ecbf56dd0..4995c42d3 100644 --- a/tolk-tester/tests/a6_1.tolk +++ b/tolk-tester/tests/a6_1.tolk @@ -5,12 +5,18 @@ fun main(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) { return (Dx / D, Dy / D); } +@method_id(101) +fun testDivMod(x: int, y: int) { + return [divMod(x, y), modDiv(x, y), mulDivMod(x, y, 10)]; +} + /** - method_id | in | out -@testcase | 0 | 1 1 1 -1 10 6 | 8 2 -@testcase | 0 | 817 -31 624 -241 132272 272276 | 132 -788 -@testcase | 0 | -886 562 498 -212 -36452 -68958 | -505 -861 -@testcase | 0 | 448 -433 -444 792 150012 -356232 | -218 -572 -@testcase | 0 | -40 -821 433 -734 -721629 -741724 | -206 889 -@testcase | 0 | -261 -98 -494 868 -166153 733738 | 263 995 + method_id | in | out +@testcase | 0 | 1 1 1 -1 10 6 | 8 2 +@testcase | 0 | 817 -31 624 -241 132272 272276 | 132 -788 +@testcase | 0 | -886 562 498 -212 -36452 -68958 | -505 -861 +@testcase | 0 | 448 -433 -444 792 150012 -356232 | -218 -572 +@testcase | 0 | -40 -821 433 -734 -721629 -741724 | -206 889 +@testcase | 0 | -261 -98 -494 868 -166153 733738 | 263 995 +@testcase | 101 | 112 3 | [ 37 1 1 37 33 6 ] */ diff --git a/tolk-tester/tests/allow_post_modification.tolk b/tolk-tester/tests/allow_post_modification.tolk index 079558864..5cfa2f3d8 100644 --- a/tolk-tester/tests/allow_post_modification.tolk +++ b/tolk-tester/tests/allow_post_modification.tolk @@ -4,35 +4,32 @@ fun unsafe_tuple(x: X): tuple fun inc(x: int, y: int): (int, int) { return (x + y, y * 10); } -fun ~inc(x: int, y: int): (int, int) { - (x, y) = inc(x, y); - return (x, y); -} - -fun ~incWrap(x: int, y: int): (int, int) { - return ~inc(x, y); +fun `~inc`(mutate self: int, y: int): int { + val (newX, newY) = inc(self, y); + self = newX; + return newY; } @method_id(11) fun test_return(x: int): (int, int, int, int, int, int, int) { - return (x, x~incWrap(x / 20), x, x = x * 2, x, x += 1, x); + return (x, x.`~inc`(x / 20), x, x = x * 2, x, x += 1, x); } @method_id(12) fun test_assign(x: int): (int, int, int, int, int, int, int) { - var (x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int) = (x, x~inc(x / 20), x, x=x*2, x, x+=1, x); + var (x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int) = (x, x.`~inc`(x / 20), x, x=x*2, x, x+=1, x); return (x1, x2, x3, x4, x5, x6, x7); } @method_id(13) fun test_tuple(x: int): tuple { - var t: tuple = unsafe_tuple([x, x~incWrap(x / 20), x, x = x * 2, x, x += 1, x]); + var t: tuple = unsafe_tuple([x, x.`~inc`(x / 20), x, x = x * 2, x, x += 1, x]); return t; } @method_id(14) fun test_tuple_assign(x: int): (int, int, int, int, int, int, int) { - var [x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int] = [x, x~inc(x / 20), x, x = x * 2, x, x += 1, x]; + var [x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int] = [x, x.`~inc`(x / 20), x, x = x * 2, x, x += 1, x]; return (x1, x2, x3, x4, x5, x6, x7); } @@ -42,7 +39,7 @@ fun foo1(x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int): (int, i @method_id(15) fun test_call_1(x: int): (int, int, int, int, int, int, int) { - return foo1(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x); + return foo1(x, x.`~inc`(x / 20), x, x = x * 2, x, x += 1, x); } fun foo2(x1: int, x2: int, x3456: (int, int, int, int), x7: int): (int, int, int, int, int, int, int) { @@ -52,7 +49,7 @@ fun foo2(x1: int, x2: int, x3456: (int, int, int, int), x7: int): (int, int, int @method_id(16) fun test_call_2(x: int): (int, int, int, int, int, int, int) { - return foo2(x, x~incWrap(x / 20), (x, x = x * 2, x, x += 1), x); + return foo2(x, x.`~inc`(x / 20), (x, x = x * 2, x, x += 1), x); } fun asm_func(x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int): (int, int, int, int, int, int, int) @@ -61,28 +58,28 @@ asm @method_id(17) fun test_call_asm_old(x: int): (int, int, int, int, int, int, int) { - return asm_func(x, x += 1, x, x, x~inc(x / 20), x, x = x * 2); + return asm_func(x, x += 1, x, x, x.`~inc`(x / 20), x, x = x * 2); } @method_id(18) fun test_call_asm_new(x: int): (int, int, int, int, int, int, int) { - return asm_func(x, x~incWrap(x / 20), x, x = x * 2, x, x += 1, x); + return asm_func(x, x.`~inc`(x / 20), x, x = x * 2, x, x += 1, x); } global xx: int; @method_id(19) fun test_global(x: int): (int, int, int, int, int, int, int) { xx = x; - return (xx, xx~incWrap(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx); + return (xx, xx.`~inc`(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx); } @method_id(20) fun test_if_else(x: int): (int, int, int, int, int) { if (x > 10) { - return (x~inc(8), x + 1, x = 1, x <<= 3, x); + return (x.`~inc`(8), x + 1, x = 1, x <<= 3, x); } else { xx = 9; - return (x, x~inc(-4), x~inc(-1), x >= 1, x = x + xx); + return (x, x.`~inc`(-4), x.`~inc`(-1), x >= 1, x = x + xx); } } @@ -103,6 +100,12 @@ fun main() { @testcase | 20 | 80 | 80 89 1 8 8 @testcase | 20 | 9 | 9 -40 -10 -1 13 -@fif_codegen_avoid ~incWrap +@fif_codegen +""" + ~inc PROC:<{ + // self y + inc CALLDICT // self newY + }> +""" @code_hash 97139400653362069936987769894397430077752335662822462908581556703209313861576 */ diff --git a/tolk-tester/tests/asm_arg_order.tolk b/tolk-tester/tests/asm_arg_order.tolk index a2d66bc2d..b96e09ecb 100644 --- a/tolk-tester/tests/asm_arg_order.tolk +++ b/tolk-tester/tests/asm_arg_order.tolk @@ -2,10 +2,8 @@ fun empty_tuple2(): tuple asm "NIL"; @pure -fun tpush2(t: tuple, x: X): (tuple, ()) +fun tpush2(mutate self: tuple, x: X): void asm "TPUSH"; -fun myEmptyTuple(): tuple { return empty_tuple2(); } -fun myTuplePush(t: tuple, value: X): (tuple, ()) { return tpush2(t, value); } @pure fun asm_func_1(x: int, y: int, z: int): tuple @@ -20,31 +18,27 @@ asm (y z x -> 0) "3 TUPLE"; fun asm_func_4(a: int, b: (int, (int, int)), c: int): tuple asm (b a c -> 0) "5 TUPLE"; -fun asmFunc1(x: int, y: int, z: int): tuple { return asm_func_1(x, y, z); } -fun asmFunc3(x: int, y: int, z: int): tuple { return asm_func_3(x, y, z); } - @pure -fun asm_func_modify(a: tuple, b: int, c: int): (tuple, ()) -asm (c b a -> 0) "SWAP TPUSH SWAP TPUSH"; -fun asmFuncModify(a: tuple, b: int, c: int): (tuple, ()) { return asm_func_modify(a, b, c); } +fun asm_func_modify(mutate self: tuple, b: int, c: int): void +asm (c b self) "SWAP TPUSH SWAP TPUSH"; global t: tuple; fun foo(x: int): int { - t~myTuplePush(x); + t.tpush2(x); return x * 10; } @method_id(11) fun test_old_1(): (tuple, tuple) { t = empty_tuple2(); - var t2: tuple = asmFunc1(foo(11), foo(22), foo(33)); + var t2: tuple = asm_func_1(foo(11), foo(22), foo(33)); return (t, t2); } @method_id(12) fun test_old_2(): (tuple, tuple) { - t = myEmptyTuple(); + t = empty_tuple2(); var t2: tuple = asm_func_2(foo(11), foo(22), foo(33)); return (t, t2); } @@ -58,7 +52,7 @@ fun test_old_3(): (tuple, tuple) { @method_id(14) fun test_old_4(): (tuple, tuple) { - t = myEmptyTuple(); + t = empty_tuple2(); var t2: tuple = empty_tuple2(); // This actually computes left-to-right even without compute-asm-ltr t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); @@ -69,21 +63,21 @@ fun test_old_4(): (tuple, tuple) { fun test_old_modify(): (tuple, tuple) { t = empty_tuple2(); var t2: tuple = empty_tuple2(); - t2~asmFuncModify(foo(22), foo(33)); + t2.asm_func_modify(foo(22), foo(33)); return (t, t2); } @method_id(16) fun test_old_dot(): (tuple, tuple) { t = empty_tuple2(); - var t2: tuple = foo(11).asmFunc3(foo(22), foo(33)); + var t2: tuple = foo(11).asm_func_3(foo(22), foo(33)); return (t, t2); } @method_id(21) fun test_new_1(): (tuple, tuple) { t = empty_tuple2(); - var t2: tuple = asmFunc1(foo(11), foo(22), foo(33)); + var t2: tuple = asm_func_1(foo(11), foo(22), foo(33)); return (t, t2); } @@ -112,7 +106,7 @@ fun test_new_4(): (tuple, tuple) { fun test_new_modify(): (tuple, tuple) { t = empty_tuple2(); var t2: tuple = empty_tuple2(); - t2~asm_func_modify(foo(22), foo(33)); + t2.asm_func_modify(foo(22), foo(33)); return (t, t2); } diff --git a/tolk-tester/tests/camel1.tolk b/tolk-tester/tests/camel1.tolk deleted file mode 100644 index 291206a95..000000000 --- a/tolk-tester/tests/camel1.tolk +++ /dev/null @@ -1,250 +0,0 @@ -// Here we test "functions that just wrap other functions" (camelCase in particular): -// > builder beginCell() { return begin_cell(); } -// Such functions, when called, are explicitly inlined during code generation (even without `inline` modifier). -// It means, that `beginCell()` is replaced to `begin_cell()` (and effectively to `NEWC`). -// Moreover, body of `beginCell` is NOT codegenerated at all. -// Hence, we can write camelCase wrappers (as well as more intelligible namings around stdlib functions) -// without affecting performance and even bytecode hashes. -// This works with ~functions also. And even works with wrappers of wrappers. -// Moreover, such wrappers can reorder input parameters, see a separate test camel2.tolk. -import "@stdlib/tvm-dicts" - -fun myBeginCell(): builder { return beginCell(); } -fun myEndCell(b: builder): cell { return endCell(b); } -fun myStoreRef(b: builder, c: cell): builder { return storeRef(b, c); } -fun myStoreUint(b: builder, i: int, bw: int): builder { return storeUint(b, i, bw); } - -// 'inline' is not needed actually, but if it exists, it's just ignored -@inline -@pure -fun myBeginParse(c: cell): slice { return beginParse(c); } -@inline -@pure -fun mySkipBits(s: slice, len: int): slice { return skipBits(s, len); } -@inline -@pure -fun ~mySkipBits(s: slice, len: int): (slice, ()) { return ~skipBits(s, len); } -@inline -@pure -fun ~myLoadUint(s: slice, len: int): (slice, int) { return loadUint(s, len); } - -fun myComputeDataSize(c: cell, maxCells: int): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } - -fun dict__new(): cell { return createEmptyDict(); } -fun dict__iset(dict: cell, keyLen: int, index: int, value: slice): cell { return iDictSet(dict, keyLen, index, value); } -fun ~dict__iset(dict: cell, keyLen: int, index: int, value: slice): (cell, ()) { return ~iDictSet(dict, keyLen, index, value); } -fun dict__tryIGet(dict: cell, keyLen: int, index: int): (slice, int) { return iDictGet(dict, keyLen, index); } -fun dict__tryIGetMin(dict: cell, keyLen: int): (int, slice, int) { return iDictGetFirst(dict, keyLen); } - -@pure -fun triple_second(p: [X, Y, Z]): Y - asm "SECOND"; - -fun myEmptyTuple(): tuple { return createEmptyTuple(); } -fun emptyTuple1(): tuple { return myEmptyTuple(); } -fun emptyTuple11(): tuple { return emptyTuple1(); } -fun myTuplePush(t: tuple, value: X): tuple { return tuplePush(t, value); } -fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tuplePush(t, value); } -fun myTupleAt(t: tuple, index: int): X { return tupleAt(t, index); } -fun tripleSecond(p: [X1, Y2, Z3]): Y2 { return triple_second(p); } -@pure -fun nullValue(): X -asm "PUSHNULL"; - -fun initial1(x: tuple): tuple { return x; } -fun initial2(x: tuple): tuple { return initial1(x); } - -// int add(int x, int y) { return x + y; } // this is also a wrapper, as its body is _+_(x,y) - -fun fake1(a: int, b: int, c: int): void -asm(a b c) "DROP DROP DROP"; -fun fake2(a: int, b: int, c: int): void -asm(b c a) "DROP DROP DROP"; -fun fake3(a: int, b: int, c: int): () -asm(c a b) "DROP DROP DROP"; -fun fake4(a: int, b: int, c: int): () -asm(c b a) "DROP DROP DROP"; - -fun fake1Wrapper(a: int, b: int, c: int) { return fake1(a, b, c); } -fun fake2Wrapper(a: int, b: int, c: int) { return fake2(a, b, c); } -fun fake3Wrapper(a: int, b: int, c: int) { return fake3(a, b, c); } -fun fake4Wrapper(a: int, b: int, c: int) { return fake4(a, b, c); } - -@method_id(101) -fun test1(): [int, int, int] { - var x: int = 1; - var y: int = 1; - var to_be_ref: cell = myBeginCell().myEndCell(); - var in_c: builder = myBeginCell().myStoreUint(123, 8); - in_c = myStoreRef(in_c, to_be_ref); - var (a, b, c) = myComputeDataSize(in_c.myEndCell(), 10); - assert(!(b != 8)) throw 101; - assert(!(c != 1), 101); - return [a, b + x, c + y]; -} - -@method_id(102) -fun test2(): [[int, int, int], int, int, int] { - var dict: cell = dict__new(); - dict = dict__iset(dict, 32, 456, myBeginCell().myStoreUint(4560, 32).myEndCell().myBeginParse()); - dict.dict__iset(32, 789, myBeginCell().myStoreUint(7890, 32).myEndCell().myBeginParse()); - dict~dict__iset(32, 123, myBeginCell().myStoreUint(0, 64).myStoreUint(1230, 32).myStoreUint(1231, 32).myStoreUint(1232, 32).myEndCell().myBeginParse()); - - var (mink, minv, _) = dict__tryIGetMin(dict, 32); - // skip 64 bits - minv~mySkipBits(16); - minv = minv.mySkipBits(16); - minv.mySkipBits(11); // does nothing - (minv, _) = ~mySkipBits(minv, 16); - mySkipBits(minv, 11); // does nothing - minv~mySkipBits(16); - // load 3*32 - var minv1 = minv~myLoadUint(32); - var minv2 = minv~myLoadUint(32); - var minv3 = minv~myLoadUint(32); - - var (_, found123) = dict__tryIGet(dict, 32, 123); - var (_, found456) = dict__tryIGet(dict, 32, 456); - var (_, found789) = dict__tryIGet(dict, 32, 789); - return [[minv1, minv2, minv3], found123, found456, found789]; -} - -@method_id(103) -fun test3(): tuple { - var with34: tuple = initial2(emptyTuple1()); - with34~myTuplePush(34); - - var t: tuple = emptyTuple11(); - t = myTuplePush(t, 12); - myTuplePush(t, emptyTuple11()); // does nothing - t~myTuplePush(emptyTuple1()); - t~myTuplePush(with34.myTupleAt(0)); - t.myTuplePush("123"s); // does nothing - - var tri: [cell, int, cell] = [nullValue(), 90 + 1, null]; - var f: int = tripleSecond(tri); - (t, _) = ~myTuplePush(t, f); - - return t; -} - -@method_id(104) -fun test4(a: int, b: int, c: int): int { - fake1Wrapper(a, b, c); - fake2Wrapper(a, b, c); - fake3Wrapper(a, b, c); - fake4Wrapper(a, b, c); - return 10; -} - -fun main(): int { - var x: int = now(); - return 30; -} - -/** - method_id | in | out -@testcase | 101 | | [ 2 9 2 ] -@testcase | 102 | | [ [ 1230 1231 1232 ] -1 -1 0 ] -@testcase | 103 | | [ 12 [] 34 91 ] - -@fif_codegen -""" - main PROC:<{ - // - 30 PUSHINT - }> -""" - -@fif_codegen -""" - test1 PROC:<{ - // - NEWC // _5 - ENDC // to_be_ref - NEWC // to_be_ref _8 - 123 PUSHINT // to_be_ref _8 _9=123 - SWAP // to_be_ref _9=123 _8 - 8 STU // to_be_ref in_c - STREF // in_c - ENDC // _16 - 10 PUSHINT // _16 _17=10 - CDATASIZE // a b c - OVER // a b c b - 8 NEQINT // a b c _21 - 101 THROWIF - DUP // a b c c - 1 NEQINT // a b c _26 - 101 THROWIF - SWAP // a c b - INC // a c _30 - SWAP // a _30 c - INC // a _30 _31 - TRIPLE // _29 - }> -""" - -@fif_codegen -""" - test2 PROC:<{ - ... - 16 PUSHINT // dict minv _45=16 - SDSKIPFIRST // dict minv - 16 PUSHINT // dict minv _47=16 - SDSKIPFIRST // dict minv - 16 PUSHINT // dict minv _52=16 - SDSKIPFIRST // dict minv - 16 PUSHINT // dict minv _57=16 - SDSKIPFIRST // dict minv - ... - 32 PUSHINT // dict minv1 minv2 minv3 found123 found456 _83=32 - 789 PUSHINT // dict minv1 minv2 minv3 found123 found456 _83=32 _84=789 - s0 s7 s7 XCHG3 // found456 minv1 minv2 minv3 found123 _84=789 dict _83=32 - DICTIGET - NULLSWAPIFNOT // found456 minv1 minv2 minv3 found123 _101 _102 - NIP // found456 minv1 minv2 minv3 found123 found789 - ... - 4 TUPLE // _86 - }> -""" - -@fif_codegen -""" - test3 PROC:<{ - // - NIL // _1 - initial1 CALLDICT // with34 - ... - TRIPLE // t tri - SECOND // t f - TPUSH // t - }> -""" - -@fif_codegen -""" - test4 PROC:<{ - // a b c - s2 s1 s0 PUSH3 // a b c a b c - DROP DROP DROP - s1 s0 s2 PUSH3 // a b c b c a - DROP DROP DROP - s0 s2 s1 PUSH3 // a b c c a b - DROP DROP DROP - s0 s2 XCHG // c b a - DROP DROP DROP - 10 PUSHINT // _7=10 - }> -""" - -@fif_codegen_avoid DECLPROC myBeginCell -@fif_codegen_avoid DECLPROC myStoreUint -@fif_codegen_avoid DECLPROC myStoreRef -@fif_codegen_avoid DECLPROC myComputeDataSize -@fif_codegen_avoid DECLPROC tryIdictGet -@fif_codegen_avoid DECLPROC myEmptyTuple -@fif_codegen_avoid DECLPROC myStoreUint -@fif_codegen_avoid DECLPROC initial2 -@fif_codegen_avoid DECLPROC add -@fif_codegen_avoid DECLPROC increase -*/ diff --git a/tolk-tester/tests/camel2.tolk b/tolk-tester/tests/camel2.tolk deleted file mode 100644 index 51344b843..000000000 --- a/tolk-tester/tests/camel2.tolk +++ /dev/null @@ -1,204 +0,0 @@ -// Here we also test "functions that just wrap other functions" like in camel1.tolk, -// but when they reorder arguments, e.g. -// > T f(x,y) { return anotherF(y,x); } -// This also works, even for wrappers of wrappers, even if anotherF is asm(with reorder). -// But swapping arguments may sometimes lead to bytecode changes (see test2), -// both with compute-asm-ltr and without it. - -fun myBeginCell(): builder { return beginCell(); } -fun myEndCell(b: builder): cell { return endCell(b); } -fun myStoreRef1(b: builder, c: cell): builder { return storeRef(b, c); } -fun myStoreRef2(c: cell, b: builder): builder { return storeRef(b, c); } -fun myStoreUint1(b: builder, x: int, bw: int): builder { return storeUint(b, x, bw); } -fun myStoreUint2(b: builder, bw: int, x: int): builder { return storeUint(b, x, bw); } - -fun computeDataSize1(c: cell, maxCells: int): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } -fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } - -fun fake(a: int, b: int, c: int): void -asm "DROP DROP DROP"; -fun fake2(b: int, c: int, a: int) { return fake(a,b,c); } -fun fake3(c: int, a: int, b: int) { return fake(a,b,c); } -fun fake4(c: int, b: int, a: int) { return fake(a,b,c); } - -@method_id(101) -fun test1(): (int, int, int) { - var x: int = 1; - var y: int = 1; - var to_be_ref: cell = myBeginCell().myEndCell(); - var in_c: builder = myBeginCell().myStoreUint1(123, 8); - in_c = myStoreRef1(in_c, to_be_ref); - var (a, b, c) = computeDataSize1(in_c.myEndCell(), 10); - assert(!0, 101); - return (a, b + x, c + y); -} - -@method_id(102) -fun test2(): (int, int, int) { - var x: int = 1; - var y: int = 1; - var to_be_ref: cell = myBeginCell().myEndCell(); - var in_c: builder = myBeginCell().myStoreUint2(8, 123); - in_c = myStoreRef2(to_be_ref, in_c); - var (a, b, c) = computeDataSize2(10, in_c.myEndCell()); - return (a, b + x, c + y); -} - -@method_id(103) -fun test3(): (int, int, int) { - var x: int = 1; - var y: int = 1; - var to_be_ref: cell = beginCell().endCell(); - var in_c: builder = beginCell().storeUint(123, 8); - in_c = storeRef(in_c, to_be_ref); - var (a, b, c) = calculateCellSizeStrict(in_c.endCell(), 10); - return (a, b + x, c + y); -} - -fun beginCell1(): builder { return beginCell(); } -fun beginCell11(): builder { return beginCell1(); } -fun beginCell111(): builder { return beginCell11(); } - -fun endCell1(b: builder): cell { return endCell(b); } -fun endCell11(b: builder): cell { return endCell1(b); } - -fun beginParse1(c: cell): slice { return beginParse(c); } -fun beginParse11(c: cell): slice { return beginParse1(c); } - -fun storeInt1(b: builder, bw: int, x: int): builder { return storeInt(b, x, bw); } -fun storeInt11(bw: int, x: int, b: builder): builder { return storeInt1(b, bw, x); } -fun storeInt111(b: builder, x: int, bw: int): builder { return storeInt11(bw, x, b); } - -@method_id(104) -fun test4(): slice { - var b: builder = beginCell111(); - b = storeInt11(32, 1, b); - b = storeInt111(b, 2, 32).storeInt111(3, 32); - return b.endCell11().beginParse11(); -} - -@method_id(105) -fun test5(a: int, b: int, c: int): int { - fake(a, b, c); - fake2(b, c, a); - fake3(c, a, b); - fake4(c, b, a); - return a; -} - -fun main() { - throw 0; -} - -/** - method_id | in | out -@testcase | 101 | | 2 9 2 -@testcase | 102 | | 2 9 2 -@testcase | 103 | | 2 9 2 -@testcase | 104 | | CS{Cell{0018000000010000000200000003} bits: 0..96; refs: 0..0} - -test1 and test3 fif code is absolutely identical, test2 (due to reorder) is a bit different: - -@fif_codegen -""" - test1 PROC:<{ - // - NEWC // _5 - ENDC // to_be_ref - NEWC // to_be_ref _8 - 123 PUSHINT // to_be_ref _8 _9=123 - SWAP // to_be_ref _9=123 _8 - 8 STU // to_be_ref in_c - STREF // in_c - ENDC // _16 - 10 PUSHINT // _16 _17=10 - CDATASIZE // a b c - SWAP // a c b - INC // a c _23 - SWAP // a _23 c - INC // a _23 _24 - }> -""" - -@fif_codegen -""" - test2 PROC:<{ - // - NEWC // _5 - ENDC // to_be_ref - NEWC // to_be_ref _8 - 123 PUSHINT // to_be_ref _8 _10=123 - SWAP // to_be_ref _10=123 _8 - 8 STU // to_be_ref in_c - STREF // in_c - 10 PUSHINT - SWAP - ENDC - SWAP - CDATASIZE // a b c - SWAP // a c b - INC // a c _19 - SWAP // a _19 c - INC // a _19 _20 - }> -""" - -@fif_codegen -""" - test3 PROC:<{ - // - NEWC // _5 - ENDC // to_be_ref - NEWC // to_be_ref _8 - 123 PUSHINT // to_be_ref _8 _9=123 - SWAP // to_be_ref _9=123 _8 - 8 STU // to_be_ref in_c - STREF // in_c - ENDC // _16 - 10 PUSHINT // _16 _17=10 - CDATASIZE // a b c - SWAP // a c b - INC // a c _19 - SWAP // a _19 c - INC // a _19 _20 - }> -""" - -@fif_codegen -""" - test4 PROC:<{ - // - NEWC // b - 1 PUSHINT // b _3=1 - SWAP // _3=1 b - 32 STI // b - 2 PUSHINT - SWAP // _5=2 b - 32 STI - 3 PUSHINT - SWAP - 32 STI // b - ENDC // _11 - CTOS // _12 - }> -""" - -@fif_codegen -""" - test5 PROC:<{ - // a b c - s2 s1 s0 PUSH3 // a b c a b c - DROP DROP DROP - s2 s1 s0 PUSH3 // a b c a b c - DROP DROP DROP - s2 s1 s0 PUSH3 // a b c a b c - DROP DROP DROP - s2 PUSH - -ROT // a a b c - DROP DROP DROP - }> -""" - -@fif_codegen_avoid myStoreUint1 -@fif_codegen_avoid myStoreUint2 -*/ diff --git a/tolk-tester/tests/camel3.tolk b/tolk-tester/tests/camel3.tolk deleted file mode 100644 index 23b16e5fb..000000000 --- a/tolk-tester/tests/camel3.tolk +++ /dev/null @@ -1,95 +0,0 @@ -// Here we test that if you declare a wrapper like -// > builder beginCell() { return begin_cell(); } -// but use it NOT only as a direct call, BUT as a 1-st class function -// (save to a variable, return from a function, etc.) -// it also works, since a function becomes codegenerated (though direct calls are expectedly inlined). - -fun myBeginCell(): builder { return beginCell(); } -fun myEndCell(b: builder): cell { return endCell(b); } -fun myStoreRef(b: builder, c: cell): builder { return storeRef(b, c); } -fun myStoreUint3(i: int, bw: int, b: builder): builder { return storeUint(b, i, bw); } - -fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return calculateCellSizeStrict(c, maxCells); } - -fun myEmptyTuple(): tuple { return createEmptyTuple(); } -fun myTuplePush(t: tuple, value: X): tuple { return tuplePush(t, value); } -fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tuplePush(t, value); } -fun myTupleGetFirst(t: tuple): X { return tupleFirst(t); } - - -@inline -fun getBeginEnd(): (auto, auto) { - return (myBeginCell, myEndCell); -} - -fun begAndStore(beg: auto, store: auto, x: int): builder { - return store(x, 8, beg()); -} - -fun test1(): (int, int, int) { - var (_, computer) = (0, computeDataSize2); - var (beg, end) = getBeginEnd(); - - var t: tuple = myEmptyTuple(); - t~myTuplePush(myStoreRef); - var refStorer = myTupleGetFirst(t); - - var x: int = 1; - var y: int = 1; - var to_be_ref: cell = myBeginCell().myEndCell(); - var in_c: builder = begAndStore(beg, myStoreUint3, 123); - in_c = refStorer(in_c, to_be_ref); - var (a, b, c) = computer(10, end(in_c)); - return (a, b + x, c + y); -} - -fun main(): (int, int, int) { - return test1(); -} - -/** - method_id | in | out -@testcase | 0 | | 2 9 2 - -@fif_codegen DECLPROC myBeginCell -@fif_codegen DECLPROC computeDataSize2 - -@fif_codegen -""" - myStoreUint3 PROC:<{ - // i bw b - SWAP // i b bw - STUX // _3 - }> -""" - -@fif_codegen -""" - myStoreRef PROC:<{ - // b c - SWAP // c b - STREF // _2 - }> -""" - -@fif_codegen -""" - CONT:<{ - computeDataSize2 CALLDICT - }> // computer - getBeginEnd INLINECALLDICT // computer beg end - NIL // computer beg end t - ... - NEWC // computer beg end refStorer _19 - ENDC // computer beg end refStorer to_be_ref - ... - CONT:<{ - myStoreUint3 CALLDICT - }> - ... - begAndStore CALLDICT // computer to_be_ref end refStorer in_c -""" - -@fif_codegen_avoid myEmptyTuple -@fif_codegen_avoid myTuplePush -*/ diff --git a/tolk-tester/tests/camel4.tolk b/tolk-tester/tests/camel4.tolk deleted file mode 100644 index a33e3fd36..000000000 --- a/tolk-tester/tests/camel4.tolk +++ /dev/null @@ -1,145 +0,0 @@ -// Here we test that a just-return function is not a valid wrapper, it will not be inlined. -// (doesn't use all arguments, has different pureness, has method_id, etc.) - -fun myStoreUint(b: builder, x: int, unused: int): builder { return storeUint(b, x, x); } -fun throwIf(excNo: int, cond: int) { assert(!cond) throw excNo; } - -fun initial1(x: auto) { return x; } -fun initial2(x: auto) { return initial1(x); } - -@pure -fun asm_func_4(a: int, b: (int, (int, int)), c: int): tuple -asm (b a c -> 0) "5 TUPLE"; -fun asmFunc4(a: int, b: (int, (int, int)), c: int): tuple { return asm_func_4(a, b, c); } - -fun postpone_elections(): int { - return false; -} - -fun setAndGetData(ret: int): int { - var c: cell = beginCell().storeUint(ret, 8).endCell(); - setContractData(c); - var s: slice = getContractData().beginParse(); - throwIf(101, 0); - return s~loadUint(8); -} - -fun setAndGetDataWrapper(ret: int): int { - return setAndGetData(ret); -} - -@method_id(101) -fun test1(): int { - var c: cell = beginCell().myStoreUint(32, 10000000).endCell(); - var s: slice = c.beginParse(); - return s~loadUint(32); -} - -get fun test2(ret: int): int { - return setAndGetDataWrapper(ret); -} - -@method_id(103) -fun test3(): int { - return initial2(10); -} - -global t: tuple; - -fun foo(x: int): int { - t~tuplePush(x); - return x * 10; -} - -@method_id(104) -fun test4(): (tuple, tuple) { - t = createEmptyTuple(); - var t2: tuple = asmFunc4(foo(11), (foo(22), (foo(33), foo(44))), foo(55)); - return (t, t2); -} - -@method_id(105) -fun test5(): int { - if (1) { - return postpone_elections(); - } - return 123; -} - -@method_id(106) -fun test6(): int { - return add2(1, 2); // doesn't inline since declared below -} - -fun main(ret: int): int { - return setAndGetDataWrapper(ret); -} - -fun onExternalMessage(ret: int): int { - return setAndGetData(ret); -} - -// currently, functions implemented after usage, can't be inlined, since inlining is legacy, not AST -fun add2(x: int, y: int): int { return x + y; } - -/** - method_id | in | out -@testcase | 101 | | 32 -@testcase | 103 | | 10 -@testcase | 104 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ] -@testcase | 105 | | 0 -@testcase | 106 | | 3 -@testcase | 74435 | 99 | 99 -@testcase | 0 | 98 | 98 -@testcase | -1 | 97 | 97 - -@fif_codegen DECLPROC myStoreUint -@fif_codegen DECLPROC throwIf -@fif_codegen DECLPROC postpone_elections -@fif_codegen DECLPROC add2 -@fif_codegen 74435 DECLMETHOD test2 - -@fif_codegen -""" - test3 PROC:<{ - // - 10 PUSHINT // _0=10 - initial2 CALLDICT // _1 - }> -""" - -@fif_codegen -""" - test2 PROC:<{ - // ret - setAndGetData CALLDICT // _1 - }> -""" - -@fif_codegen -""" - 11 PUSHINT - foo CALLDICT - 22 PUSHINT - foo CALLDICT - 33 PUSHINT - foo CALLDICT - 44 PUSHINT - foo CALLDICT - 55 PUSHINT - foo CALLDICT - asmFunc4 CALLDICT // t2 -""" - -@fif_codegen -""" - test6 PROC:<{ - // - 1 PUSHINT // _0=1 - 2 PUSHINT // _0=1 _1=2 - add2 CALLDICT // _2 - }> -""" - -@fif_codegen_avoid setAndGetDataWrapper -*/ diff --git a/tolk-tester/tests/cells-slices.tolk b/tolk-tester/tests/cells-slices.tolk index 1bf0742d9..e1d28b8b1 100644 --- a/tolk-tester/tests/cells-slices.tolk +++ b/tolk-tester/tests/cells-slices.tolk @@ -1,121 +1,136 @@ -fun store_u32(b: builder, value: int): builder { - return b.storeUint(value, 32); -} -fun ~store_u32(b: builder, value: int): (builder, ()) { - return ~storeUint(b, value, 32); +fun store_u32(mutate self: builder, value: int): self { + return self.storeUint(value, 32); } -fun load_u32(cs: slice): (slice, int) { - return cs.loadUint(32); +fun load_u32(mutate self: slice): int { + return self.loadUint(32); } -fun my_loadInt(s: slice, len: int): (slice, int) - asm(s len -> 1 0) "LDIX"; // top is "value slice" -fun my_storeInt(b: builder, x: int, len: int): builder - asm(x b len) "STIX"; -fun ~my_storeInt(b: builder, x: int, len: int): (builder, ()) - asm(x b len) "STIX"; +fun myLoadInt(mutate self: slice, len: int): int + asm(-> 1 0) "LDIX"; +fun myStoreInt(mutate self: builder, x: int, len: int): self + asm(x self len) "STIX"; @method_id(101) fun test1(): [int,int,int,int,int] { var b: builder = beginCell().storeUint(1, 32); b = b.storeUint(2, 32); - b~storeUint(3, 32); + b.storeUint(3, 32); b = b.store_u32(4); - b~store_u32(5); + b.store_u32(5); var cs: slice = b.endCell().beginParse(); - var (cs redef, one: int) = cs.loadUint(32); - var (two: int, three: int) = (cs~loadUint(32), cs~load_u32()); - var (cs redef, four: int) = cs.load_u32(); - var five: int = cs~load_u32(); + var one: int = cs.loadUint(32); + var (two: int, three: int) = (cs.loadUint(32), cs.load_u32()); + var four: int = cs.load_u32(); + var five: int = cs.load_u32(); return [one,two,three,four,five]; } @method_id(102) fun test2(): [int,int,int] { - var b: builder = beginCell().my_storeInt(1, 32); - b = b.my_storeInt(2, 32); - b~my_storeInt(3, 32); + var b: builder = beginCell().myStoreInt(1, 32); + b = b.myStoreInt(2, 32); + b.myStoreInt(3, 32); var cs: slice = b.endCell().beginParse(); - var (cs redef, one: int) = cs.my_loadInt(32); - var (two: int, three: int) = (cs~my_loadInt(32), cs~my_loadInt(32)); + var one: int = cs.myLoadInt(32); + var (two: int, three: int) = (cs.myLoadInt(32), cs.myLoadInt(32)); return [one,two,three]; } @method_id(103) fun test3(ret: int): int { - var (_, same: int) = beginCell().storeUint(ret,32).endCell().beginParse().loadUint(32); + val same: int = beginCell().storeUint(ret,32).endCell().beginParse().loadUint(32); return same; } @method_id(104) fun test4(): [int,int] { - var b: builder = my_storeInt(beginCell(), 1, 32); - b = storeInt(storeInt(b, 2, 32), 3, 32); + var b: builder = beginCell().myStoreInt(1, 32); + b = b.storeInt(2, 32).storeInt(3, 32); var cs: slice = b.endCell().beginParse(); - var cs32: slice = cs.getFirstBits(32); // todo s.first_bits()~loadUint() doesn't work, 'lvalue expected' - var (one, _, three) = (cs32~loadInt(32), cs~skipBits(64), cs~load_u32()); + var (one, _, three) = (cs.getFirstBits(32).loadUint(32), cs.skipBits(64), cs.load_u32()); return [one,three]; } @method_id(105) fun test5(): [int,int] { - var cref: cell = endCell(store_u32(beginCell(), 105)); + var cref: cell = endCell(beginCell().store_u32(105)); var c: cell = beginCell().storeRef(cref).storeRef(cref).store_u32(1).endCell(); var cs: slice = beginParse(c); - // todo I want cs~loadRef().beginParse()~load_u32(), but 'lvalue expected' - var ref1 = cs~loadRef().beginParse(); - var ref2 = cs~loadRef().beginParse(); - var sto5x2: int = ref1~load_u32() + ref2~loadUint(32); - return [sto5x2, cs~load_u32()]; + var sto5x2: int = cs.loadRef().beginParse().load_u32() + cs.loadRef().beginParse().loadUint(32); + return [sto5x2, cs.load_u32()]; +} + +@method_id(106) +fun test6() { + return beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32); } +@method_id(107) +fun test7() { + // since .store() methods now mutate, this piece of code works not as earlier (mutates uri_builder) + var uri_builder = beginCell(); + var uri_slice = uri_builder.storeSlice(".json").endCell().beginParse(); + var image_slice = uri_builder.storeSlice(".png").endCell().beginParse(); + return (uri_builder.getBuilderBitsCount(), uri_slice.getRemainingBitsCount(), image_slice.getRemainingBitsCount()); +} + +@method_id(108) +fun test8() { + var uri_builder = beginCell(); + var fresh = uri_builder; + var uri_slice = fresh.storeSlice(".json").endCell().beginParse(); + var fresh redef = uri_builder; + var image_slice = fresh.storeSlice(".png").endCell().beginParse(); + return (uri_builder.getBuilderBitsCount(), uri_slice.getRemainingBitsCount(), image_slice.getRemainingBitsCount()); +} -fun ~sumNumbersInSlice(s: slice): (slice, int) { + +fun sumNumbersInSlice(mutate self: slice): int { var result = 0; - while (!s.isEndOfSliceBits()) { - result += s~loadUint(32); + while (!self.isEndOfSliceBits()) { + result += self.loadUint(32); } - return (s, result); + return result; } -@method_id(106) -fun test6() { +@method_id(110) +fun test10() { var ref = beginCell().storeInt(100, 32).endCell(); var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeRef(ref).endCell().beginParse(); - var result = (getRemainingBitsCount(s), s~sumNumbersInSlice(), getRemainingBitsCount(s), isEndOfSlice(s), isEndOfSliceBits(s), isEndOfSliceRefs(s)); - var ref2: cell = s~loadRef(); + var result = (getRemainingBitsCount(s), s.sumNumbersInSlice(), getRemainingBitsCount(s), isEndOfSlice(s), isEndOfSliceBits(s), isEndOfSliceRefs(s)); + var ref2: cell = s.loadRef(); var s2: slice = ref2.beginParse(); s.assertEndOfSlice(); - return (result, s2~loadInt(32), s2.isEndOfSlice()); + return (result, s2.loadInt(32), s2.isEndOfSlice()); } -@method_id(107) -fun test7() { +@method_id(111) +fun test11() { var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeInt(3, 32).storeInt(4, 32).storeInt(5, 32).storeInt(6, 32).storeInt(7, 32).endCell().beginParse(); var size1 = getRemainingBitsCount(s); - s~skipBits(32); + s.skipBits(32); var s1: slice = s.getFirstBits(64); - var n1 = s1~loadInt(32); + var n1 = s1.loadInt(32); var size2 = getRemainingBitsCount(s); - s~loadInt(32); + s.loadInt(32); var size3 = getRemainingBitsCount(s); - s~removeLastBits(32); + s.removeLastBits(32); var size4 = getRemainingBitsCount(s); - var n2 = s~loadInt(32); + var n2 = s.loadInt(32); var size5 = getRemainingBitsCount(s); return (n1, n2, size1, size2, size3, size4, size5); } -@method_id(108) -fun test108() { +@method_id(112) +fun test12() { var (result1, result2) = (0, 0); try { beginCell().storeRef(beginCell().endCell()).endCell().beginParse().assertEndOfSlice(); @@ -132,45 +147,45 @@ fun test108() { return (result1, result2); } -@method_id(109) -fun test109() { +@method_id(113) +fun test13() { var ref2 = beginCell().storeInt(1, 32).endCell(); var ref1 = beginCell().storeInt(1, 32).storeRef(ref2).endCell(); var c = beginCell().storeInt(444, 32).storeRef(ref1).storeRef(ref1).storeRef(ref1).storeRef(ref2).storeInt(4, 32).endCell(); var (n_cells1, n_bits1, n_refs1) = c.calculateCellSizeStrict(10); var s = c.beginParse(); - s~loadRef(); - s~loadRef(); - var n = s~loadInt(32); + s.loadRef(); + s.loadRef(); + var n = s.loadInt(32); var (n_cells2, n_bits2, n_refs2) = s.calculateSliceSizeStrict(10); return ([n_cells1, n_bits1, n_refs1], [n_cells2, n_bits2, n_refs2], n); } -@method_id(110) +@method_id(114) fun test110(x: int) { var s = beginCell().storeBool(x < 0).storeBool(0).storeBool(x).endCell().beginParse(); - return (s~loadBool(), s~loadBool(), s~loadBool()); + return (s.loadBool(), s.loadBool(), s.loadBool()); } -@method_id(111) +@method_id(115) fun test111() { var s = beginCell().storeMessageOp(123).storeMessageQueryId(456) .storeAddressNone().storeAddressNone() .storeUint(0, 32) .storeUint(123, 32).storeUint(456, 64).storeUint(789, 64) .endCell().beginParse(); - var op1 = s~loadUint(32); - var q1 = s~loadUint(64); + var op1 = s.loadUint(32); + var q1 = s.loadUint(64); if (s.addressIsNone()) { - s~skipBits(2); + s.skipBits(2); } - if (s~loadBool() == 0) { - assert(s~loadBool() == 0) throw 444; - s~skipBits(32); + if (s.loadBool() == 0) { + assert(s.loadBool() == 0) throw 444; + s.skipBouncedPrefix(); } - var op2 = s~loadMessageOp(); - var q2 = s~loadMessageQueryId(); - s~skipBits(64); + var op2 = s.loadMessageOp(); + var q2 = s.loadMessageQueryId(); + s.skipBits(64); s.assertEndOfSlice(); assert(isMessageBounced(0x001)) throw 444; return (op1, q1, op2, q2); @@ -186,11 +201,31 @@ fun main(): int { @testcase | 103 | 103 | 103 @testcase | 104 | | [ 1 3 ] @testcase | 105 | | [ 210 1 ] -@testcase | 106 | | 64 3 0 0 -1 0 100 -1 -@testcase | 107 | | 2 3 224 192 160 128 96 -@testcase | 108 | | 9 100 -@testcase | 109 | | [ 3 128 5 ] [ 2 96 3 ] 444 -@testcase | 110 | -1 | -1 0 -1 -@testcase | 110 | 0 | 0 0 0 -@testcase | 111 | | 123 456 123 456 +@testcase | 107 | | 72 40 72 +@testcase | 108 | | 0 40 32 +@testcase | 110 | | 64 3 0 0 -1 0 100 -1 +@testcase | 111 | | 2 3 224 192 160 128 96 +@testcase | 112 | | 9 100 +@testcase | 113 | | [ 3 128 5 ] [ 2 96 3 ] 444 +@testcase | 114 | -1 | -1 0 -1 +@testcase | 114 | 0 | 0 0 0 +@testcase | 115 | | 123 456 123 456 + +Note, that since 'compute-asm-ltr' became on be default, chaining methods codegen is not quite optimal. +@fif_codegen +""" + test6 PROC:<{ + // + NEWC // _1 + 1 PUSHINT // _1 _2=1 + SWAP // _2=1 _1 + 32 STU // _0 + 2 PUSHINT // _0 _6=2 + SWAP // _6=2 _0 + 32 STU // _0 + 3 PUSHINT // _0 _10=3 + SWAP // _10=3 _0 + 32 STU // _0 + }> +""" */ diff --git a/tolk-tester/tests/co1.tolk b/tolk-tester/tests/co1.tolk index 5ad9d8e41..f124e1de8 100644 --- a/tolk-tester/tests/co1.tolk +++ b/tolk-tester/tests/co1.tolk @@ -43,9 +43,6 @@ asm "SDEQ"; fun stslicer(b: builder, s: slice): builder asm "STSLICER"; -fun myStoreUint(b: builder, x: int, len: int): builder { return storeUint(b, x, len); } -fun endSlice(b: builder): slice { return endcs(b); } - fun main() { var i1: int = iget1(); var i2: int = iget2(); @@ -59,8 +56,8 @@ fun main() { var s2: slice = sget2(); var s3: slice = newc().stslicer(str1).stslicer(str2r).endcs(); - assert(sdeq(s1, newc().myStoreUint(str1int, 12 * nibbles).endcs())) throw int111; - assert(sdeq(s2, newc().storeUint(str2int, 6 * nibbles).endSlice())) throw 112; + assert(sdeq(s1, newc().storeUint(str1int, 12 * nibbles).endcs())) throw int111; + assert(sdeq(s2, newc().storeUint(str2int, 6 * nibbles).endcs())) throw 112; assert(sdeq(s3, newc().storeUint(0x636f6e737431AABBCC, 18 * nibbles).endcs())) throw 113; var i4: int = iget240(); diff --git a/tolk-tester/tests/dicts-demo.tolk b/tolk-tester/tests/dicts-demo.tolk index 5852b175c..291bd2ea4 100644 --- a/tolk-tester/tests/dicts-demo.tolk +++ b/tolk-tester/tests/dicts-demo.tolk @@ -1,8 +1,7 @@ import "@stdlib/tvm-dicts" -fun ~addIntToIDict(iDict: cell, key: int, number: int): (cell, ()) { - iDict~iDictSetBuilder(32, key, beginCell().storeInt(number, 32)); - return (iDict, ()); +fun addIntToIDict(mutate self: cell, key: int, number: int): void { + return self.iDictSetBuilder(32, key, beginCell().storeInt(number, 32)); } fun calculateDictLen(d: cell) { @@ -15,40 +14,40 @@ fun calculateDictLen(d: cell) { return len; } -fun ~loadTwoDigitNumberFromSlice(s: slice): (slice, int) { - var n1 = s~loadInt(8); - var n2 = s~loadInt(8); - return (s, (n1 - 48) * 10 + (n2 - 48)); +fun loadTwoDigitNumberFromSlice(mutate self: slice): int { + var n1 = self.loadInt(8); + var n2 = self.loadInt(8); + return (n1 - 48) * 10 + (n2 - 48); } @method_id(101) fun test101(getK1: int, getK2: int, getK3: int) { var dict = createEmptyDict(); - dict~uDictSetBuilder(32, 1, beginCell().storeUint(1, 32)); - var (old1: slice, found1) = dict~uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse()); - var (old2: slice, found2) = dict~uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse()); + dict.uDictSetBuilder(32, 1, beginCell().storeUint(1, 32)); + var (old1: slice, found1) = dict.uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse()); + var (old2: slice, found2) = dict.uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse()); var (cur3: slice, found3) = dict.uDictGet(32, getK3); return ( - found1 ? old1~loadUint(32) : -1, - found2 ? old2~loadUint(32) : -1, - found3 ? cur3~loadUint(32) : -1 + found1 ? old1.loadUint(32) : -1, + found2 ? old2.loadUint(32) : -1, + found3 ? cur3.loadUint(32) : -1 ); } @method_id(102) fun test102() { var dict = createEmptyDict(); - dict~addIntToIDict(2, 102); - dict~addIntToIDict(1, 101); - dict~addIntToIDict(4, 104); - dict~addIntToIDict(3, 103); + dict.addIntToIDict(2, 102); + dict.addIntToIDict(1, 101); + dict.addIntToIDict(4, 104); + dict.addIntToIDict(3, 103); var deleted = createEmptyTuple(); var shouldBreak = false; while (!shouldBreak) { - var (kDel, kVal, wasDel) = dict~iDictDeleteLastAndGet(32); + var (kDel, kVal, wasDel) = dict.iDictDeleteLastAndGet(32); if (wasDel) { - deleted~tuplePush([kDel, kVal~loadInt(32)]); + deleted.tuplePush([kDel, kVal.loadInt(32)]); } else { shouldBreak = true; } @@ -59,38 +58,38 @@ fun test102() { @method_id(103) fun test103() { var dict = createEmptyDict(); - dict~uDictSetBuilderIfNotExists(32, 1,beginCell().storeInt(1, 32)); - dict~uDictSetBuilderIfNotExists(32, 1,beginCell().storeInt(1, 32)); + dict.uDictSetBuilderIfNotExists(32, 1,beginCell().storeInt(1, 32)); + dict.uDictSetBuilderIfNotExists(32, 1,beginCell().storeInt(1, 32)); var len1 = calculateDictLen(dict); - dict~uDictSetBuilderIfExists(32, 2,beginCell().storeInt(1, 32)); - dict~uDictSetBuilderIfExists(32, 2,beginCell().storeInt(1, 32)); + dict.uDictSetBuilderIfExists(32, 2,beginCell().storeInt(1, 32)); + dict.uDictSetBuilderIfExists(32, 2,beginCell().storeInt(1, 32)); var len2 = calculateDictLen(dict); - dict~uDictSetBuilder(32, 3,beginCell().storeInt(1, 32)); - dict~uDictSetBuilderIfExists(32, 3,beginCell().storeInt(1, 32)); + dict.uDictSetBuilder(32, 3,beginCell().storeInt(1, 32)); + dict.uDictSetBuilderIfExists(32, 3,beginCell().storeInt(1, 32)); var len3 = calculateDictLen(dict); - var (delK1, _, _) = dict~uDictDeleteFirstAndGet(32); - var (delK2, _, _) = dict~uDictDeleteFirstAndGet(32); - var (delK3, _, _) = dict~uDictDeleteFirstAndGet(32); + var (delK1, _, _) = dict.uDictDeleteFirstAndGet(32); + var (delK2, _, _) = dict.uDictDeleteFirstAndGet(32); + var (delK3, _, _) = dict.uDictDeleteFirstAndGet(32); return (len1, len2, len3, delK1, delK2, delK3); } @method_id(104) fun test104() { var dict = createEmptyDict(); - dict~sDictSetBuilder(32, "7800", beginCell().storeUint(5 + 48, 8).storeUint(6 + 48, 8)); - dict~sDictSet(32, "key1", "12"); - var (old1, _) = dict~sDictSetAndGet(32, "key1", "34"); - var (old2, _) = dict~sDictDeleteAndGet(32, "key1"); + dict.sDictSetBuilder(32, "7800", beginCell().storeUint(5 + 48, 8).storeUint(6 + 48, 8)); + dict.sDictSet(32, "key1", "12"); + var (old1, _) = dict.sDictSetAndGet(32, "key1", "34"); + var (old2, _) = dict.sDictDeleteAndGet(32, "key1"); var (restK, restV, _) = dict.sDictGetFirst(32); - var (restK1, restV1, _) = dict~sDictDeleteLastAndGet(32); + var (restK1, restV1, _) = dict.sDictDeleteLastAndGet(32); assert (restK.isSliceBitsEqual(restK1)) throw 123; assert (restV.isSliceBitsEqual(restV1)) throw 123; return ( - old1~loadTwoDigitNumberFromSlice(), - old2~loadTwoDigitNumberFromSlice(), - restV~loadTwoDigitNumberFromSlice(), - restK~loadTwoDigitNumberFromSlice(), - restK~loadTwoDigitNumberFromSlice() + old1.loadTwoDigitNumberFromSlice(), + old2.loadTwoDigitNumberFromSlice(), + restV.loadTwoDigitNumberFromSlice(), + restK.loadTwoDigitNumberFromSlice(), + restK.loadTwoDigitNumberFromSlice() ); } diff --git a/tolk-tester/tests/imports/use-dicts-err.tolk b/tolk-tester/tests/imports/use-dicts-err.tolk index a4ee9aede..c5ba89d22 100644 --- a/tolk-tester/tests/imports/use-dicts-err.tolk +++ b/tolk-tester/tests/imports/use-dicts-err.tolk @@ -1,18 +1,18 @@ fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell { var dict: cell = createEmptyDict(); - dict~idict_set_builder(32, 3, begin_cell().store_int(30, 32)); - dict~idict_set_builder(32, 4, begin_cell().store_int(40, 32)); - dict~idict_set_builder(32, 5, begin_cell().store_int(valueAt5, 32)); + dict.idict_set_builder(32, 3, begin_cell().store_int(30, 32)); + dict.idict_set_builder(32, 4, begin_cell().store_int(40, 32)); + dict.idict_set_builder(32, 5, begin_cell().store_int(valueAt5, 32)); return dict; } fun lookupIdxByValue(idict32: cell, value: int): int { var cur_key = -1; do { - var (cur_key redef, cs: slice, found: int) = idict32.idict_get_next?(32, cur_key); + var (cur_key redef, cs: slice, found: int) = idict32.idictGetNext(32, cur_key); // one-line condition (via &) doesn't work, since right side is calculated immediately if (found) { - if (cs~load_int(32) == value) { + if (cs.loadInt(32) == value) { return cur_key; } } diff --git a/tolk-tester/tests/imports/use-dicts.tolk b/tolk-tester/tests/imports/use-dicts.tolk index 358a5673a..26a9a9ccd 100644 --- a/tolk-tester/tests/imports/use-dicts.tolk +++ b/tolk-tester/tests/imports/use-dicts.tolk @@ -2,9 +2,9 @@ import "@stdlib/tvm-dicts" fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell { var dict: cell = createEmptyDict(); - dict~iDictSetBuilder(32, 3, beginCell().storeInt(30, 32)); - dict~iDictSetBuilder(32, 4, beginCell().storeInt(40, 32)); - dict~iDictSetBuilder(32, 5, beginCell().storeInt(valueAt5, 32)); + dict.iDictSetBuilder(32, 3, beginCell().storeInt(30, 32)); + dict.iDictSetBuilder(32, 4, beginCell().storeInt(40, 32)); + dict.iDictSetBuilder(32, 5, beginCell().storeInt(valueAt5, 32)); return dict; } @@ -14,7 +14,7 @@ fun lookupIdxByValue(idict32: cell, value: int): int { var (cur_key redef, cs: slice, found: int) = idict32.iDictGetNext(32, cur_key); // one-line condition (via &) doesn't work, since right side is calculated immediately if (found) { - if (cs~loadInt(32) == value) { + if (cs.loadInt(32) == value) { return cur_key; } } diff --git a/tolk-tester/tests/invalid-call-1.tolk b/tolk-tester/tests/invalid-call-1.tolk new file mode 100644 index 000000000..1c32422ee --- /dev/null +++ b/tolk-tester/tests/invalid-call-1.tolk @@ -0,0 +1,9 @@ +fun main() { + return true(); +} + +/** +@compilation_should_fail +The message is weird now, but later I'll rework error messages anyway. +@stderr cannot apply expression of type int to an expression of type (): cannot unify type () -> ??3 with int + */ diff --git a/tolk-tester/tests/invalid-call-2.tolk b/tolk-tester/tests/invalid-call-2.tolk new file mode 100644 index 000000000..5a8c9fa5d --- /dev/null +++ b/tolk-tester/tests/invalid-call-2.tolk @@ -0,0 +1,14 @@ +fun add1(x: int) { + return x + 1; +} + +fun main() { + val adder_fn = add1; + var x = 10; + return adder_fn(mutate x); +} + +/** +@compilation_should_fail +@stderr `mutate` used for non-mutate argument + */ diff --git a/tolk-tester/tests/invalid-call-3.tolk b/tolk-tester/tests/invalid-call-3.tolk new file mode 100644 index 000000000..ac98df704 --- /dev/null +++ b/tolk-tester/tests/invalid-call-3.tolk @@ -0,0 +1,12 @@ +fun with2Params(x: int, y: int) { + +} + +fun main() { + return with2Params(1); +} + +/** +@compilation_should_fail +@stderr too few arguments in call to `with2Params`, expected 2, have 1 + */ diff --git a/tolk-tester/tests/invalid-call-4.tolk b/tolk-tester/tests/invalid-call-4.tolk new file mode 100644 index 000000000..c8f7dcebf --- /dev/null +++ b/tolk-tester/tests/invalid-call-4.tolk @@ -0,0 +1,13 @@ +fun methodWith1Param(self: int, param: int) { + +} + +fun main() { + val x = 10; + x.methodWith1Param(2, "asdf"); +} + +/** +@compilation_should_fail +@stderr too many arguments in call to `methodWith1Param`, expected 1, have 2 + */ diff --git a/tolk-tester/tests/invalid-call-5.tolk b/tolk-tester/tests/invalid-call-5.tolk new file mode 100644 index 000000000..89ab026a9 --- /dev/null +++ b/tolk-tester/tests/invalid-call-5.tolk @@ -0,0 +1,13 @@ +fun inc(x: int) { + return x + 1; +} + +fun main() { + return inc(_); +} + +/** +@compilation_should_fail +@stderr rvalue expected +@stderr inc(_) + */ diff --git a/tolk-tester/tests/invalid-call-6.tolk b/tolk-tester/tests/invalid-call-6.tolk new file mode 100644 index 000000000..cbf598066 --- /dev/null +++ b/tolk-tester/tests/invalid-call-6.tolk @@ -0,0 +1,12 @@ +fun nothing() { +} + +fun main() { + val x = 0; + return x.nothing(); +} + +/** +@compilation_should_fail +@stderr `nothing` has no parameters and can not be called as method + */ diff --git a/tolk-tester/tests/invalid-call-7.tolk b/tolk-tester/tests/invalid-call-7.tolk new file mode 100644 index 000000000..4ad038c9e --- /dev/null +++ b/tolk-tester/tests/invalid-call-7.tolk @@ -0,0 +1,14 @@ +fun main() { + beginCell() + .storeAddressNone() + .storeUint(3, 32) + .storeUnexisting() + .storeInt(1, 32) + .endCell(); +} + +/** +@compilation_should_fail +@stderr undefined symbol `storeUnexisting` +@stderr .storeUnexisting() + */ diff --git a/tolk-tester/tests/invalid-call-8.tolk b/tolk-tester/tests/invalid-call-8.tolk new file mode 100644 index 000000000..c613d7d9c --- /dev/null +++ b/tolk-tester/tests/invalid-call-8.tolk @@ -0,0 +1,8 @@ +fun main() { + var incoming_ton: int = get_incoming_value().3(); +} + +/** +@compilation_should_fail +@stderr expected method name, got `3` + */ diff --git a/tolk-tester/tests/invalid-cmt-old.tolk b/tolk-tester/tests/invalid-cmt-old.tolk index eaf58db83..58927d3a0 100644 --- a/tolk-tester/tests/invalid-cmt-old.tolk +++ b/tolk-tester/tests/invalid-cmt-old.tolk @@ -1,5 +1,5 @@ fun main(): int { - ;; this is not a comment + ;; here is not a comment } /** diff --git a/tolk-tester/tests/invalid-declaration-6.tolk b/tolk-tester/tests/invalid-declaration-6.tolk index 731c299ba..42cb7b953 100644 --- a/tolk-tester/tests/invalid-declaration-6.tolk +++ b/tolk-tester/tests/invalid-declaration-6.tolk @@ -1,8 +1,8 @@ -fun main() { - val imm = 10; +get seqno(self: int) { + return 0; } /** @compilation_should_fail -@stderr immutable variables are not supported yet -*/ +@stderr get methods can't have `mutate` and `self` params + */ diff --git a/tolk-tester/tests/invalid-mutate-1.tolk b/tolk-tester/tests/invalid-mutate-1.tolk new file mode 100644 index 000000000..237940fc9 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-1.tolk @@ -0,0 +1,11 @@ +fun f(x: int) {} + +fun cantAssignToVal() { + val x = 10; + f(x += 1); +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `x` + */ diff --git a/tolk-tester/tests/invalid-mutate-10.tolk b/tolk-tester/tests/invalid-mutate-10.tolk new file mode 100644 index 000000000..8cd37c517 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-10.tolk @@ -0,0 +1,16 @@ +fun increment(mutate x: int) { + x = x + 1; +} + +fun cantCallMutatingAsAMember() { + var x = 0; + x.increment(); + return x; +} + +/** +@compilation_should_fail +@stderr function `increment` mutates parameter `x` +@stderr consider calling `increment(mutate x)`, not `x.increment`() +@stderr alternatively, rename parameter to `self` to make it a method + */ diff --git a/tolk-tester/tests/invalid-mutate-11.tolk b/tolk-tester/tests/invalid-mutate-11.tolk new file mode 100644 index 000000000..9f2c2601e --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-11.tolk @@ -0,0 +1,8 @@ +fun load32(self: slice): int { + return self.loadUint(32); +} + +/** +@compilation_should_fail +@stderr modifying `self` (call a mutating method), which is immutable by default + */ diff --git a/tolk-tester/tests/invalid-mutate-12.tolk b/tolk-tester/tests/invalid-mutate-12.tolk new file mode 100644 index 000000000..c8c8c68ec --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-12.tolk @@ -0,0 +1,14 @@ +fun increment(mutate x: int) { + +} + +fun main() { + var x = 0; + var inc = increment; + inc(x); +} + +/** +@compilation_should_fail +@stderr saving `increment` into a variable is impossible, since it has `mutate` parameters and thus can only be called directly + */ diff --git a/tolk-tester/tests/invalid-mutate-13.tolk b/tolk-tester/tests/invalid-mutate-13.tolk new file mode 100644 index 000000000..ad861fd88 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-13.tolk @@ -0,0 +1,8 @@ +fun onInternalMessage(mutate in_msg_body: slice) { + +} + +/** +@compilation_should_fail +@stderr invalid declaration of a reserved function + */ diff --git a/tolk-tester/tests/invalid-mutate-14.tolk b/tolk-tester/tests/invalid-mutate-14.tolk new file mode 100644 index 000000000..2ba645d13 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-14.tolk @@ -0,0 +1,8 @@ +fun main(cs: slice) { + return loadInt(cs, 32); +} + +/** +@compilation_should_fail +@stderr `loadInt` is a mutating method; consider calling `cs.loadInt()`, not `loadInt(cs)` + */ diff --git a/tolk-tester/tests/invalid-mutate-15.tolk b/tolk-tester/tests/invalid-mutate-15.tolk new file mode 100644 index 000000000..f6874fb8c --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-15.tolk @@ -0,0 +1,12 @@ +fun asdf(mutate cs: slice) {} + +fun main(cs: slice) { + cs.asdf(); +} + +/** +@compilation_should_fail +@stderr function `asdf` mutates parameter `cs` +@stderr consider calling `asdf(mutate cs)`, not `cs.asdf`() +@stderr alternatively, rename parameter to `self` to make it a method + */ diff --git a/tolk-tester/tests/invalid-mutate-2.tolk b/tolk-tester/tests/invalid-mutate-2.tolk new file mode 100644 index 000000000..7501fdaf5 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-2.tolk @@ -0,0 +1,10 @@ +fun cantAssignToVal() { + val x = 10; + var y = 20; + [y, x] = [30, 40]; +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `x` + */ diff --git a/tolk-tester/tests/invalid-mutate-3.tolk b/tolk-tester/tests/invalid-mutate-3.tolk new file mode 100644 index 000000000..c49973f71 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-3.tolk @@ -0,0 +1,11 @@ +const op_increase = 0x123; + +fun cantAssignToConst() { + var x = 10; + (x, op_increase) = (20, 30); +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `op_increase` + */ diff --git a/tolk-tester/tests/invalid-mutate-4.tolk b/tolk-tester/tests/invalid-mutate-4.tolk new file mode 100644 index 000000000..f25a707cb --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-4.tolk @@ -0,0 +1,14 @@ + +fun inc(mutate x: int) { + x += 1; +} + +fun cantPassToMutatingFunction() { + val myVal = 10; + inc(mutate myVal); +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `myVal` + */ diff --git a/tolk-tester/tests/invalid-mutate-5.tolk b/tolk-tester/tests/invalid-mutate-5.tolk new file mode 100644 index 000000000..fd8d11924 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-5.tolk @@ -0,0 +1,14 @@ +fun cantCallMutatingMethod(c: cell) { + val s: slice = c.beginParse(); + if (1) { + var s: slice = c.beginParse(); + s.loadRef(); // this is ok, 's' is another variable + } + val i = s.loadUint(32); +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `s` (call a mutating method) +@stderr s.loadUint + */ diff --git a/tolk-tester/tests/invalid-mutate-6.tolk b/tolk-tester/tests/invalid-mutate-6.tolk new file mode 100644 index 000000000..bb577ae47 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-6.tolk @@ -0,0 +1,16 @@ +const op_increase = 0x123; + +fun inc(mutate x: int): int { + x += 10; + return x + 1; +} + +fun cantCallMutatingFunctionWithImmutable() { + return inc(mutate op_increase); +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `op_increase` (call a mutating function) +@stderr inc(mutate op_increase) + */ diff --git a/tolk-tester/tests/invalid-mutate-7.tolk b/tolk-tester/tests/invalid-mutate-7.tolk new file mode 100644 index 000000000..5b6b6afe4 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-7.tolk @@ -0,0 +1,15 @@ +fun incBoth(mutate x: int, mutate y: int) { + x += 10; + y += 10; +} + +fun cantCallMutatingFunctionWithRvalue() { + var x = 10; + incBoth(mutate x, mutate 30); +} + +/** +@compilation_should_fail +@stderr lvalue expected (call a mutating function) +@stderr incBoth(mutate x, mutate 30) + */ diff --git a/tolk-tester/tests/invalid-mutate-8.tolk b/tolk-tester/tests/invalid-mutate-8.tolk new file mode 100644 index 000000000..0dd7c5687 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-8.tolk @@ -0,0 +1,10 @@ +fun cantRedefImmutable() { + val x = 10; + var (y: int, x redef) = (20, 30); + return (y, x); +} + +/** +@compilation_should_fail +@stderr modifying an immutable variable `x` (left side of assignment) + */ diff --git a/tolk-tester/tests/invalid-mutate-9.tolk b/tolk-tester/tests/invalid-mutate-9.tolk new file mode 100644 index 000000000..7e79052e4 --- /dev/null +++ b/tolk-tester/tests/invalid-mutate-9.tolk @@ -0,0 +1,9 @@ +fun increment(self: int) { + self = self + 1; +} + +/** +@compilation_should_fail +@stderr modifying `self` (left side of assignment), which is immutable by default +@stderr probably, you want to declare `mutate self` + */ diff --git a/tolk-tester/tests/invalid-self-1.tolk b/tolk-tester/tests/invalid-self-1.tolk new file mode 100644 index 000000000..40b54f163 --- /dev/null +++ b/tolk-tester/tests/invalid-self-1.tolk @@ -0,0 +1,8 @@ +fun cantReturnFromSelf(mutate self: int): self { + return 2; +} + +/** +@compilation_should_fail +@stderr invalid return from `self` function + */ diff --git a/tolk-tester/tests/invalid-self-2.tolk b/tolk-tester/tests/invalid-self-2.tolk new file mode 100644 index 000000000..c4aa758b2 --- /dev/null +++ b/tolk-tester/tests/invalid-self-2.tolk @@ -0,0 +1,8 @@ +fun cantUseSelfAsType(mutate x: int) { + var y: self = x; +} + +/** +@compilation_should_fail +@stderr `self` type can be used only as a return type of a function (enforcing it to be chainable) + */ diff --git a/tolk-tester/tests/invalid-self-3.tolk b/tolk-tester/tests/invalid-self-3.tolk new file mode 100644 index 000000000..330ac2495 --- /dev/null +++ b/tolk-tester/tests/invalid-self-3.tolk @@ -0,0 +1,10 @@ +fun cantReturnSelf(mutate x: int): int { + x += 1; + return self; +} + +/** +@compilation_should_fail +@stderr using `self` in a non-member function (it does not accept the first `self` parameter) +@stderr return self + */ diff --git a/tolk-tester/tests/invalid-self-4.tolk b/tolk-tester/tests/invalid-self-4.tolk new file mode 100644 index 000000000..f4856a465 --- /dev/null +++ b/tolk-tester/tests/invalid-self-4.tolk @@ -0,0 +1,9 @@ +fun cantReturnNothingFromSelf(mutate self: int): self { + self = self + 1; +} + +/** +@compilation_should_fail +@stderr missing return; forgot `return self`? +@stderr } + */ diff --git a/tolk-tester/tests/invalid-self-5.tolk b/tolk-tester/tests/invalid-self-5.tolk new file mode 100644 index 000000000..a007a93c2 --- /dev/null +++ b/tolk-tester/tests/invalid-self-5.tolk @@ -0,0 +1,15 @@ +fun increment(mutate self: int): self { + self = self + 1; + return self; +} + +fun cantReturnAnotherSelf(mutate self: int): self { + self = self + 1; + var x = 0; + return x.increment(); +} + +/** +@compilation_should_fail +@stderr invalid return from `self` function + */ diff --git a/tolk-tester/tests/invalid-self-6.tolk b/tolk-tester/tests/invalid-self-6.tolk new file mode 100644 index 000000000..588c70ab2 --- /dev/null +++ b/tolk-tester/tests/invalid-self-6.tolk @@ -0,0 +1,8 @@ +fun increment(x: int, self: int): int { + return x + self; +} + +/** +@compilation_should_fail +@stderr `self` can only be the first parameter + */ diff --git a/tolk-tester/tests/invalid-self-7.tolk b/tolk-tester/tests/invalid-self-7.tolk new file mode 100644 index 000000000..2fa2da492 --- /dev/null +++ b/tolk-tester/tests/invalid-self-7.tolk @@ -0,0 +1,8 @@ +fun increment(x: int): int { + return self + 1; +} + +/** +@compilation_should_fail +@stderr using `self` in a non-member function + */ diff --git a/tolk-tester/tests/invalid-typing-3.tolk b/tolk-tester/tests/invalid-typing-3.tolk new file mode 100644 index 000000000..fb4b0bc51 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-3.tolk @@ -0,0 +1,19 @@ +fun incInt(mutate self: int): self { + self += 1; + return self; +} + +fun appendBuilder(mutate self: builder): self { + self.storeUint(1, 32); + return self; +} + +fun cantMixDifferentThis() { + var x = 0; + return x.incInt().appendBuilder().incInt(); +} + +/** +@compilation_should_fail +@stderr cannot apply function appendBuilder : builder -> (builder, ()) to arguments of type int: cannot unify type int with builder + */ diff --git a/tolk-tester/tests/invalid-typing-4.tolk b/tolk-tester/tests/invalid-typing-4.tolk new file mode 100644 index 000000000..0e6553690 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-4.tolk @@ -0,0 +1,14 @@ +fun incNotChained(mutate self: int) { + self = self + 1; +} + +fun cantCallNotChainedMethodsInAChain(x: int) { + return x.incNotChained().incNotChained(); +} + +/** +The error is very weird, but nevertheless, the type system prevents of doing such errors. + +@compilation_should_fail +@stderr cannot apply function incNotChained : int -> (int, ()) to arguments of type (): cannot unify type () with int + */ diff --git a/tolk-tester/tests/invalid-typing-5.tolk b/tolk-tester/tests/invalid-typing-5.tolk new file mode 100644 index 000000000..ba3450de2 --- /dev/null +++ b/tolk-tester/tests/invalid-typing-5.tolk @@ -0,0 +1,14 @@ +fun incNotChained(mutate self: int) { + self = self + 1; +} + +fun failWhenReturnANotChainedValue(x: int): int { + return x.incNotChained(); +} + +/** +The error is very weird, but nevertheless, the type system prevents of doing such errors. + +@compilation_should_fail +@stderr previous function return type int cannot be unified with return statement expression type (): cannot unify type () with int + */ diff --git a/tolk-tester/tests/known-bugs.tolk b/tolk-tester/tests/known-bugs.tolk new file mode 100644 index 000000000..4de6a3752 --- /dev/null +++ b/tolk-tester/tests/known-bugs.tolk @@ -0,0 +1,27 @@ +fun increment(mutate x: int): int { + x = x + 1; + return x; +} + +@method_id(101) +fun bugWithModifyingMethodInsideSameExpression() { + /* + The same bug existed in FunC: +#pragma allow-post-modification; +(int, int) ~increment(int x) { x = x + 5; return (x, x); } +int main() { int x = 0; x += x~increment(); return x; } + It's related to using a variable modified by ~method inside the same expression. + */ + var x = 0; + x = x + increment(mutate x); + return x; +} + +fun main() { + +} + +/** +// correct: 2 +@testcase | 101 | | 1 + */ diff --git a/tolk-tester/tests/mutate-methods.tolk b/tolk-tester/tests/mutate-methods.tolk new file mode 100644 index 000000000..b9184ca9a --- /dev/null +++ b/tolk-tester/tests/mutate-methods.tolk @@ -0,0 +1,337 @@ +fun incrementInPlace(mutate self: int, byValue: int): void { + self = self + byValue; +} + +fun incrementTwoInPlace(mutate self: int, mutate y: int, byValue: int): int { + self.incrementInPlace(byValue); + y += byValue; + return self + y; +} + +@method_id(101) +fun testIncrement1() { + var x = 50; + var y = 30; + incrementInPlace(mutate x, 10); + incrementInPlace(mutate x, 10); + incrementInPlace(mutate y, 10); + y.incrementInPlace(10); + incrementInPlace(mutate y, 10); + return (x, y); +} + +@method_id(102) +fun testIncrement2() { + var x = 50; + var y = 30; + val sum1 = incrementTwoInPlace(mutate x, mutate y, 10); + val sum2 = x.incrementTwoInPlace(mutate y, 10); + return (x, y, sum1, sum2); +} + + +fun load_next(mutate cs: slice): int { + return loadInt(mutate cs, 32); +} + +fun myLoadInt(mutate self: slice, len: int): int + asm(-> 1 0) "LDIX"; +fun myStoreInt(mutate self: builder, x: int, len: int): self + asm(x self len) "STIX"; + +@inline_ref +fun unpack_utils_info(mutate utils_info_sl: slice): (int, int) { + return ( + utils_info_sl.myLoadInt(32), + utils_info_sl.myLoadInt(32) + ); +} + +@method_id(103) +fun testSlices1() { + var b: builder = beginCell().storeInt(1, 32).myStoreInt(2, 32); + b.myStoreInt(3, 32); + var c: cell = b.myStoreInt(4, 32).storeInt(5, 32).endCell(); + var cs = c.beginParse(); + var first = cs.preloadInt(32); + unpack_utils_info(mutate cs); + return (first, cs.myLoadInt(32), cs.loadInt(32)); +} + +fun load_decimal_symbol(mutate self: slice): int { + // load decimal from bits using utf-8 table + var n: int = self.loadUint(8); + n = n - 48; + assert(n >= 0) throw 400; + assert(n <= 9) throw 400; + return n; +} + +@method_id(104) +fun testSlices2() { + var cs = "123"; + return (cs.load_decimal_symbol(), cs.load_decimal_symbol(), cs.load_decimal_symbol()); +} + +global v1: int; +global v2: int; +global v3: int; + +@method_id(105) +fun testGlobals() { + v1 = 0; + v2 = 0; + v3 = 100; + v3 += incrementTwoInPlace(mutate v1, mutate v2, 5); + return (v1, v2, v3); +} + +fun withNameShadowing(mutate x: int, pivot: int, extra: int) { + x += pivot; + if (pivot < 100) { + var x = 100 + extra; + if (pivot < 50) { + var x = 50 + extra; + return x + extra; + } else { + x += extra; + return x + extra; + } + } else { + x += extra; + return -100 + extra; + } +} + +@method_id(106) +fun testNameShadowing() { + var x = 0; + var sum = 0; + sum += withNameShadowing(mutate x, 100, 10); + sum += withNameShadowing(mutate x, 50, 10); + sum += withNameShadowing(mutate x, 0, 10); + return (x, sum); +} + +fun updateTwoItems(mutate self: (int, int), byValue: int) { + val (first, second) = self; + self = (first + byValue, second + byValue); +} + +@method_id(107) +fun testMutableTensor() { + var t = (40, 50); + t.updateTwoItems(10); + updateTwoItems(mutate t, 10); + return t; +} + +@pure +fun myStoreUint(mutate self: builder, x: int, len: int): self + asm(x self len) "STIX"; + +@pure +fun myStoreU32(mutate self: builder, x: int): self { + return self.storeUint(x, 32); +} + +fun getSumOfNumbersInCell(c: cell): int { + var sum = 0; + var s = c.beginParse(); + var n_numbers = s.getRemainingBitsCount() / 32; + repeat (n_numbers) { + sum += s.loadUint(32); + } + return sum; +} + +@method_id(110) +fun testStoreChaining() { + var b = beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32); + b.storeUint(4, 32); + b.myStoreUint(5, 32).storeUint(6, 32); + storeUint(mutate b, 7, 32); + b = b.storeUint(8, 32); + b = b.storeUint(9, 32).storeUint(10, 32); + + return getBuilderBitsCount(b); +} + +@method_id(111) +fun testStoreChainingCustom() { + var b = beginCell().myStoreUint(1, 32).myStoreUint(2, 32).myStoreUint(3, 32); + b.myStoreUint(4, 32); + b.myStoreUint(5, 32).myStoreUint(6, 32); + myStoreUint(mutate b, 7, 32); + b = b.myStoreUint(8, 32); + b = b.myStoreUint(9, 32).myStoreUint(10, 32); + val sum1 = getSumOfNumbersInCell(b.endCell()); + + b = beginCell().myStoreU32(1).storeUint(2, 32).myStoreU32(3); + b.myStoreU32(4); + b.myStoreU32(5).myStoreU32(6); + myStoreU32(mutate b, 7); + b = b.myStoreU32(8); + b = b.storeUint(9, 32).myStoreU32(10); + val sum2 = getSumOfNumbersInCell(b.endCell()); + + return (sum1, sum2); +} + +fun myStoreU32_and_mutate_x(mutate self: builder, mutate x: int): void { + return myStoreUint(mutate self, x += 10, 32); +} + +@method_id(112) +fun testStoreAndMutateBoth() { + var x = 3; + var b: builder = beginCell().myStoreUint(1, 32); + b.myStoreU32_and_mutate_x(mutate x); + b.myStoreU32(3).myStoreU32_and_mutate_x(mutate x); + b.myStoreU32_and_mutate_x(mutate x); + + var cs: slice = b.endCell().beginParse(); + var (n1,n2,n3,n4,n5) = (cs.loadUint(32),cs.loadUint(32),cs.loadUint(32),cs.loadUint(32),cs.loadUint(32)); + assert(n5 == x) throw 100; + + return [n1,n2,n3,n4,n5]; +} + +global ccc: builder; + +@method_id(113) +fun testStoreChainingForGlobal() { + ccc = beginCell().storeUint(1, 32).myStoreUint(2, 32).myStoreU32(3); + ccc.storeUint(4, 32); + ccc.storeUint(5, 32).myStoreU32(6); + storeUint(mutate ccc, 7, 32); + ccc = ccc.myStoreU32(8); + ccc = ccc.storeUint(9, 32).myStoreUint(10, 32); + + return getBuilderBitsCount(ccc); +} + +fun alwaysThrows(): int { throw 123; return 123; } +fun loadIntFromCell(c: cell, len: int) { return c.beginParse().loadUint(len); } + +@method_id(114) +fun testLoadIntForTemporaryObject() { + val c0 = beginCell().storeUint(0, 32).endCell(); + val c4 = beginCell().storeUint(4, 32).endCell(); + return ( + beginCell().storeUint(1, 32).endCell().beginParse().loadUint(32), + beginCell().storeUint(2, 32).endCell().beginParse().loadUint(32), + c0.beginParse().loadUint(32) ? alwaysThrows() : loadIntFromCell(c4, 32) + ); +} + +@pure +fun myStoreUint_pure(mutate self: builder): void + asm "STIX"; + +fun myStoreUint_impure(mutate self: builder): void + asm "STIX"; + +fun testStoreUintPureUnusedResult() { + var b = beginCell(); + b.myStoreUint_pure(); + var s = b.endCell().beginParse(); + val ii = s.loadUint(32); + return 0; +} + +fun testStoreUintImpureUnusedResult() { + var b = beginCell(); + b.myStoreUint_impure(); + var s = b.endCell().beginParse(); + val ii = s.loadUint(32); + return 0; +} + +global counter: int; + +fun writeNext2(mutate self: builder): self { + return self.storeUint(counter += 1, 32).storeUint(counter += 1, 32); +} + +fun resetCounter(mutate self: builder): self { + counter = 0; + return self; +} + +@method_id(115) +fun testExplicitReturn() { + counter = 0; + return ( + beginCell().writeNext2().writeNext2().resetCounter().writeNext2().endCell().getSumOfNumbersInCell(), + counter + ); +} + + +fun main(){} + +/** +@testcase | 101 | | 70 60 +@testcase | 102 | | 70 50 100 120 +@testcase | 103 | | 1 3 4 +@testcase | 104 | | 1 2 3 +@testcase | 105 | | 5 5 110 +@testcase | 106 | | 160 110 +@testcase | 107 | | 60 70 +@testcase | 110 | | 320 +@testcase | 111 | | 55 55 +@testcase | 112 | | [ 1 13 3 23 33 ] +@testcase | 113 | | 320 +@testcase | 114 | | 1 2 4 +@testcase | 115 | | 13 2 + +@fif_codegen +""" + incrementInPlace PROC:<{ + // self byValue + ADD // self + }> +""" + +@fif_codegen +""" + testIncrement2 PROC:<{ + ... + incrementTwoInPlace CALLDICT // x y sum1 + -ROT + 10 PUSHINT // sum1 x y _9=10 + incrementTwoInPlace CALLDICT // sum1 x y sum2 + s1 s3 s0 XCHG3 // x y sum1 sum2 + }> +""" + +@fif_codegen +""" + load_next PROC:<{ + // cs + 32 LDI // _1 cs + SWAP // cs _1 + }> +""" + +@fif_codegen +""" + testStoreUintPureUnusedResult PROC:<{ + // + 0 PUSHINT // _12=0 + }> +""" + +@fif_codegen +""" + testStoreUintImpureUnusedResult PROC:<{ + // + NEWC // b + STIX // _2 + DROP // + 0 PUSHINT // _12=0 + }> +""" + + */ diff --git a/tolk-tester/tests/no-spaces.tolk b/tolk-tester/tests/no-spaces.tolk index aecfdabaa..0d4c3b678 100644 --- a/tolk-tester/tests/no-spaces.tolk +++ b/tolk-tester/tests/no-spaces.tolk @@ -30,13 +30,11 @@ global `some()var`:int; } @method_id(113)fun`unary+bitwise-constant`():[int,int,int]{ - // todo spaces are still not allowed before ~ - return [~-~~+-3, ~+3-~ 9, -(-~+-20-~ 10+3+~ 38&39)]; + return [~-~~+-3, ~+3-~9, -(-~+-20-~ 10+3+~38&39)]; } @method_id(114)fun`unary+bitwize-parametrized`(c3:int, c9:int, c20:int, c10:int, c38:int):[int,int,int]{ - // todo spaces are still not allowed before ~ - return [~-~~+-c3, ~+c3-~ `c9`, -(-~+-c20-~ c10+c3+~ c38&39)]; + return [~-~~+-c3, ~+c3-~`c9`, -(-~+-c20-~c10+c3+~c38&39)]; } fun add3(a: int, b: int, c: int) { return a+b+c; } @@ -49,16 +47,16 @@ fun add3(a: int, b: int, c: int) { return a+b+c; } return [add3(fst2,snd2,trd2),add3(fst1,snd1,trd1)]; } -fun `load:u32`(cs: slice): (slice, int) { - return cs.loadUint(32); +fun `load:u32`(mutate self: slice): int { + return self.loadUint(32); } @method_id(116) fun `call_~_via_backticks`():[int,int,int,int] { var b:builder = beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32).storeUint(4, 32); var `cs`:slice = b.endCell().beginParse(); - var (`cs` redef,one:int) = `cs`.`loadUint`(32); - var (two:int,three:int) = (`cs`~`loadUint`(32), cs~`load:u32`()); - var (cs redef,four:int) = cs.`load:u32`(); + val one:int=`cs`.`loadUint`(32); + val (two:int,three:int) = (`cs`.`loadUint`(32), cs.`load:u32`()); + val four:int = cs.`load:u32`(); return [one,two,three,four]; } diff --git a/tolk-tester/tests/null-keyword.tolk b/tolk-tester/tests/null-keyword.tolk index f0f85fd60..cdfe5acf9 100644 --- a/tolk-tester/tests/null-keyword.tolk +++ b/tolk-tester/tests/null-keyword.tolk @@ -12,8 +12,8 @@ fun test1() { var t = createEmptyTuple(); do { - var num = numbers~listNext(); - t~tuplePush(num); + var num = numbers.listNext(); + t.tuplePush(num); } while (numbers != null); return (h, numbers == null, t); @@ -63,7 +63,7 @@ fun test4() { @method_id(105) fun test5() { var n = getUntypedNull(); - return !(null == n) ? n~loadInt(32) : 100; + return !(null == n) ? n.loadInt(32) : 100; } @method_id(106) @@ -75,7 +75,7 @@ fun test6(x: int) { fun test7() { var b = beginCell().storeMaybeRef(null); var s = b.endCell().beginParse(); - var c = s~loadMaybeRef(); + var c = s.loadMaybeRef(); return (null == c) * 10 + (b != null); } @@ -145,14 +145,14 @@ fun main() { """ test7 PROC:<{ ... - LDOPTREF // b _17 _16 + LDOPTREF // b _20 _19 DROP // b c - ISNULL // b _10 - 10 MULCONST // b _12 - SWAP // _12 b - ISNULL // _12 _13 - 0 EQINT // _12 _14 - ADD // _15 + ISNULL // b _13 + 10 MULCONST // b _15 + SWAP // _15 b + ISNULL // _15 _16 + 0 EQINT // _15 _17 + ADD // _18 }> """ */ diff --git a/tolk-tester/tests/parse-address.tolk b/tolk-tester/tests/parse-address.tolk new file mode 100644 index 000000000..385aa3b53 --- /dev/null +++ b/tolk-tester/tests/parse-address.tolk @@ -0,0 +1,113 @@ +const cc1 = "0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e"a; +const cc2 = "EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"a; + +fun verifyAddr(addr: slice, workchain: int, number: int) { + assert (addr.getRemainingBitsCount() == 3 + 8 + 256) throw 112; + addr.skipBits(3); + assert (addr.loadUint(8) == workchain) throw 111; + assert (addr.loadUint(256) == number) throw 111; +} + +fun main() { + verifyAddr("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"a, 255, 23158417847463239084714197001737581570653996933128112807891516801582625927987); + verifyAddr("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"a, 0, 0); + verifyAddr("EQCRDM9h4k3UJdOePPuyX40mCgA4vxge5Dc5vjBR8djbEKC5"a, 0, 65607996509792174074532427555986248720836864382484024657400295821210434460432); + verifyAddr("UQCOgxbCOjOLH_cEuQdGgS23zBM5SrQQepMFedjK-oixYbis"a, 0, 64460038539088394980732229180523693489682583665805557562964506609821558550881); + verifyAddr("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_"a, 0, 99002318936150612861744867526221033858534811876886359650897405270877291973920); + verifyAddr("Ef8BtXO9bcTMXjg9bgivKh4lhJmZWQPP6_rb9vfjlTP5FJtM"a, 255, 772910975127952880303441415761050161913031788763061162001556772893733681428); + verifyAddr("Ef89xh-uy860-mCcvS8zcAUs8bApmxLGygDLEKjUk5RL-311"a, 255, 27941138149036269893630478666581900122707382189183906805784676408403709676539); + verifyAddr("Ef_vA6yRfmt2P4UHnxlrQUZFcBnKux8mL2eMqBgpeMFPorr4"a, 255, 108109262375472472702582493362335418330829651067377177643099076957184687427490); + verifyAddr("Ef8o6AM9sUZ8rOqLFY8PYeaC3gbopZR1BMkE8fcD0r5NnmCi"a, 255, 18502444830824300068094395885436326119386947594392869497312068745716154912158); + verifyAddr("Ef_fvrd0hBoVJUxoi7wH173Zk8NPiyVvxh5IoYSjEYZbOhsu"a, 255, 101202732337223525952216789200341489000836292542250083765062769181728788863802); + verifyAddr("Ef9nzj6RBc4mQ6p3ng7mGJ7tp7MbzERhe7obkM9A0wnCCEcf"a, 255, 46952625717497919357580310066854892621799390294920450816077086267929711460872); + verifyAddr("Ef9rU-_AAnBkHB71TIC3QvUf5LcAsvj0B4IoYzAXLpEFd5CA"a, 255, 48545777798729612074233611768739897492467685225150339217043102685589809464695); + verifyAddr("Ef9LynHHKgBxY6-l-W_dWN-CtGT2_ji5rN3EzOI-p9zWEfq6"a, 255, 34281152017620085319078796986198022632548048219136747083019177301186013091345); + verifyAddr("Ef9hMd78gzSiVsK0zz0AHtEja8x1UoB_NDZMjn-l86NQK_2Y"a, 255, 43962460814164090767878334494257755557842170134382045184921495822637115592747); + verifyAddr("Ef80FNJ5NJO4-0QwlVAWckUZXdk-PfYDexDZ1-ju9SxhF0A6"a, 255, 23557057702048801338698514499604413540742716310574705490458593067566768087319); + verifyAddr("Ef_fdIbThooPs4_r2DE_Z6ZsWycJdHLnsuKAJHTcbaZaipez"a, 255, 101071650030310556115830521522496708686577365303530257137459798093298869361290); + verifyAddr("Ef_lva0qEiZhWrrZJl-IJxyCcTQmmTo71fIWyQ31HxJ8NurV"a, 255, 103914771557158282349484109182290824591675204108148026180964788916630125182006); + verifyAddr("Ef8sMGKypw006AeRYqimLjmY2Ufp-SHk8C0ZJBNgVBlzw_Nr"a, 255, 19987255184378161380023126214650814972824352533523055905552702178965809886147); + verifyAddr("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff+W72r5gqPrHF"a, 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr("EQCaSCHVak-jIc9ANutTAfHpZNM3YdGky7yaDzsTrg0WhFlm"a, 0, 69783625181781015447914682554083924798054947959007050695795761257887453484676); + verifyAddr("EQBS9U3AfD15fGmOtRMXQAxcPVBwNuItfLcDni9fkbTyyNX0"a, 0, 37523067738561024305547433298623118197038688994386001017161816416175242146504); + verifyAddr("EQBiMNL9qNWMAkJHuM0BFneYcuHL17kzS4pswpaEO-NGWrFG"a, 0, 44412924025649114419413541526870954696667907029239618728289150652715284776538); + verifyAddr("EQAUzE-Nef80O9dLZy91HfPiOb6EEQ8YqyWKyIU-KeaYLNUi"a, 0, 9407242825041766837311851458322335726136775042891143504070507665010681354284); + verifyAddr("EQD-nhrinjv0B4LTgr0dRHTHwH1MOsgGhKBXJZd7vESMZUf1"a, 0, 115166810931401616117484448645661180241548402534908005320733783571353775148133); + verifyAddr("EQAVD3Fni9I6j8XeSIl-wAGBEhqhame6OtAY0GScKT0D9X6f"a, 0, 9525855215156855607080079714361451576383963668563135377495902959388099150837); + verifyAddr("EQC6ACq3VANZjqfRBy7JMHkpLwqQ9qyYJsCIGx1mYbQgxaKw"a, 0, 84130484652351964071210477536969520113177637645401392541565606610268614566085); + verifyAddr("EQCIJLNFIko5CvpKn9oAkrDgLocDOoD4vwmHxNx_fsG_LkwW"a, 0, 61579391178099797614367237687950512448308156724136883899001108680249616482094); + verifyAddr("EQCe4AYIBce1pAk2qJJPSs1OzyZRlKjkfq8zuC8D7erv6DUP"a, 0, 71861245445432818728925844931259040612664802586395398157190478191760507596776); + verifyAddr("EQCtrtTXEAoSpoERmiqOnICe9LHxn2N89N4BH9qdHlrG-U0i"a, 0, 78559023162479717496981724991265882229440558807791659796411897368395464230649); + verifyAddr("EQBBlraAps0OZaB9Q8ePQn2wVAaL1G411A-dNppyWe3X3GIT"a, 0, 29666621803903557832193058147214384979915773445007872807927344851911086823388); + verifyAddr("EQBiASqUqaVizrozLRbszkWC2kETbkhpO2qniDVDPPg2_0W8"a, 0, 44328719889509369519441680467651025944540360433148852643949783408843779749631); + verifyAddr("EQBu2Q1EO8gIoNA1qoGWnHUudKfmqlKEDTQE-DxN-_4sdg14"a, 0, 50137910719490808065414827264266674858051167131188257457782826342827836714102); + verifyAddr("EQA5bvxWd5-q2vJUVqR9AlbEIfdFysLR0PXGgVlBf8x5hWuF"a, 0, 25977927117604457079092522008276392864656238504700352770597256138254994667909); + verifyAddr("EQBguMSHjFv5bfoOdshr3ruS9ymSZzhRKMovoNrxGxZXvmee"a, 0, 43748489720571123896506696370504498290006245978262404519821633796370658121662); + verifyAddr("EQAxL0oF1-zNgimPKthbDnYS4xj94rHtfNRN7_Pd1r2LNNv3"a, 0, 22246882279393590648219842750911786376759362211171398419754426796438233910068); + verifyAddr("EQANX1uRKGZfyPIwEaIXrR0ZOqadct5q10dvKxWIxx7SQqzW"a, 0, 6048549475100840191738010856156544571222758030966479209409932714701987172930); + verifyAddr("EQBitdFDoU5DWSjfKq7AsO29RIwAnBzzvcVVSn5ekQoB9Liv"a, 0, 44647902768175374073183447303109856895983123510911038181495138821771906122228); + verifyAddr("EQBgbux7VSjqJHP7ByRK1q4QuVZbpSCesNgvz5qad3lfXX_B"a, 0, 43618018778298854282398238948198420936771670943015013768514626213000552996701); + verifyAddr("EQDisBd8U7M3CEOZ8gcWCdetdmJi3AI31zIT5qBwOdmUbsxY"a, 0, 102533830955233207294921564956803510155400341370448300698800842506363763004526); + verifyAddr("EQAZpn_eynVlf7Ii2d6jP_p1URPrdF9F3S7DiudQyelkjzwE"a, 0, 11602000355550451044739442929923326898313570892134000961608306166632391730319); + verifyAddr("EQDE0HBgfkOiqHezLtExBGTvOs8eitthHQosBjW3BmDy1y2K"a, 0, 89021598108837008984355105304701054698583123510131754065320641619941010764503); + verifyAddr("EQDyT36zktBN9PVWvZ1joRxhIfEUgCPt4F2isa-enUA_d6CP"a, 0, 109600164736599393471831241268953938618560132398692391517933933264745646800759); + verifyAddr("EQDSMUGwt25IQd3_yHjI03F71G8Kp2GMaMEv2TiWoTKbsyRH"a, 0, 95072727086440754059372943502908629555499501854161516009430039520728770059187); + verifyAddr("EQAgK1EcrvEuL9sCtoj3cNhVNOuf3lo5GIPE2gn1fwZZYB3j"a, 0, 14550545393206146289454646242321274637527057595221202748348667645886114191712); + verifyAddr("EQCDKqL5w_6MD-Z7AOButu-uR-ZJTsgNU1fu464hn9grY81U"a, 0, 59328315557704100696483472039557119625141880163887490602190749720459366378339); + verifyAddr("EQB1aVMyFBhnlYXmQjsma0S63kvxKU7ccZKFNCFTwX7ASPv4"a, 0, 53106696421104300082516512931084483581353095629408473618166869610568148238408); + verifyAddr("EQBbjrXHoxDyh1ZYGBdBoQgLaScxW6pZR1hEhJC8BqF-5Kgq"a, 0, 41412616102566803060532874463898939692666425753852274254609049615175463829220); + verifyAddr("EQC-QeZ13QP0lszxNKt380fCWuaV94vwC/bfuqmrlg1/fJPA"a, 0, 86055876869280374285292827775555707420719385459150221433115419095878595346300); + verifyAddr("EQAiUwpF27vXCngqNhf_TQ5E_06ah0G4zuSrnfU7CLLaht5H"a, 0, 15525356059048115813946213102829493539706126913595626308144289257869196581510); + verifyAddr("EQBqiVjmhe2iVGmgOSDO1FGjSiz_AMtb1w7lLEiP4XIF_SFy"a, 0, 48187833566271418625754761625661652107159264793429628379411792200127405491709); + verifyAddr("EQDmwvaK2d_SbaPdpOM60effPWeKsksgDVwFPEyxuftM396K"a, 0, 104376425077737068747642645125299653296942252727305637929378053253273342397663); + verifyAddr("EQDWtPZZgF7wvIMUHZQojuD3utiuivsW7WslRJ33dgv-5yc8"a, 0, 97114682311034709685427168495629428400170984047839002197324103884924936519399); + verifyAddr("EQAA7z0JI0JKqbN-1uENKz9JrxIO5ZRY-ehMeg9fPncx50Ck"a, 0, 422697701361909095759185681783393186844038628935759044330165207027374567911); + verifyAddr("EQBVUHRoCq6coQYUwOAhGSoAmQ6Mpm7dFlDYon6HMgWV8Ftr"a, 0, 38588743302295548905191533977469452945717219128199196974980570837505276220912); + verifyAddr("EQCTdvDCf0bA5dOPI1-44tB2ZfNcMGiklzvg27TovgDEqM6E"a, 0, 66700138358140658950710678965721715920748906761125730971082529064117803730088); + verifyAddr("EQBDBKE5WGKIlnoi3OOzw7vkKKIX55eWjPvgxJWwek8AyL2J"a, 0, 30313140970524770883308749215942283658935592719811899513010665548955593408712); + verifyAddr("EQAvCSyLCo21GrqLAifdov4WkOxuGQCjMRxgF1cXSaNzLHZe"a, 0, 21274912932379789207153885262858116665851037273450532982121514600400844714796); + verifyAddr("EQCsLpDeHB2qpRbmsCb_0xmsYVNx1NgeYrvHGT1TDrHkDgL4"a, 0, 77880084760844670718511036557364450189323549135231036747480176919181282894862); + verifyAddr("EQCTQ8kPwyX92r48gCIL_pLN_RcQT9ghZygnmDTYkOkuW_j5"a, 0, 66609755171046741472463433430016629628609840960137226492652665879987546041947); + verifyAddr("EQCTrFRSHt-tfk7WxK9ZHQmqLcgxXxTK7wGfCEbqgY2W9Mcx"a, 0, 66794468397542182731534559853537484892417154018190804733043974345563210356468); + verifyAddr("EQCv28y49GdaLncoclv0ISdDlMUY_cxDPGNWFCPT8t4GuqUJ"a, 0, 79543100951881731989812212377176715376834409392116644269458867858071577560762); + verifyAddr("EQCVL-k6deDR56Z8pcb0Btg0lGfaivOGfdDCD1vvyRsyL9vS"a, 0, 67479265933941008511790471646564661743401752930295407567346938670637286896175); + verifyAddr("EQD6t2dXDjZxF1DqunKF-8dEWivJdliY_0FYiCXnthuqnDCa"a, 0, 113402258385556889021060606279033166272577193563727959698596277924908309916316); + verifyAddr("EQDE98XNzXiPq7VnbJJ2M4-Ht3tX_OWR0xUTTnDC8NObLmyU"a, 0, 89091094739778473356272490822716056624384395256388481953562551087642791090990); + verifyAddr("EQDfeRDE1TDhwt478CDR0Q7MDwqcTUhfjqyTT59mgoAaF6f7"a, 0, 101079669463449311486034260688909914923832300293253430457119371423825321269783); + verifyAddr("EQDijcEyUKa-QgCbeGlggQk1uBtt2ZRHyW4Y4gB4R6MN6RLW"a, 0, 102473162609487797404330889623966425536887610061087715571345738626121871855081); + verifyAddr("EQDOtFOt41skbjBkZF89oYXpoDECjlxIzD-ShWAOYyzuxqLA"a, 0, 93495056812773926196963707371665512785268729004579280701087533371037976424134); + verifyAddr("EQDuJKSFWU7AYqH6KLFfAbYvMuz346eWmJvG6_2NYE42_B4T"a, 0, 107715199938628393100813870735031557263256555616273999363057194279168054802172); + verifyAddr("EQDwGu4vFv1e3wn8min_iy7OPJXegOYTFQ5bZFZ5a5ZPiBpX"a, 0, 108602665568837301744601989570019709742180613578164394799026726718721456754568); + verifyAddr("EQC4G2ph6AS_mD_-cIv4aIYm1z5jAgCW_TTDEr72ygXOP2X-"a, 0, 83274003234732023403481554420859495155084746906198543572711543697320249249343); + verifyAddr("EQDpUkyAa6lZ12P3ZB2PL_rmWwI1I55BU4kxw_rssFL5dswA"a, 0, 105534303174146507629736518862713754948570412188900908177600861330298381728118); + verifyAddr("EQDoIA20MF1qEcSPtROdCu5ukGx9dVjgMeJh1oQ4A4cf_Jif"a, 0, 104993214557977037193613824776415934089204193426692473563548548423424814817276); + verifyAddr("EQDpUkyAa6lZ12P3ZB2PL_rmWwI1I55BU4kxw_rssFL5dswA"a, 0, 105534303174146507629736518862713754948570412188900908177600861330298381728118); + verifyAddr("EQClLO4EnZ_rTyV1GVpWy53pLgWJRki5c4ZzuM_1O_ClBkO9"a, 0, 74711004027159342540251007601464500186374346239921204216319145006974068892934); + verifyAddr("EQDmkj65Ab_m0aZaW8IpKw4kYqIgITw_HRstYEkVQ6NIYCyW"a, 0, 104290347741656803921830951060768893809692975574470790497562993373950614128736); + verifyAddr("EQCqNTwAYUNhPFS0RgqZoTLGJcQQxbAJ7csUo4YO3_TONLab"a, 0, 76987241268612358571638783428744566580605181728938801022059780105627411729972); + verifyAddr("EQCL3DmCynaRK7-vsfeNmd4Jj-UxAIHPvA4qS2xwaL6UpLbF"a, 0, 63260589232981964910240894899061676480139492286430589202252472895352724165796); + verifyAddr("EQDbU1SVEjBE73oUqgAoM9gDcShUkM5EC2PgoCjuwVUKo-Ee"a, 0, 99203745911752606845646497420891218522647962685916739950275357890977532807843); + verifyAddr("EQD02VdcF4TDbCKLLhZJ39NQTu6aWq2LnLjp0oXqbNu_BANK"a, 0, 110748343802097970709980079967961144373090790244250392237586606542170934198020); + verifyAddr("EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA"a, 0, 51839428943991432793039248316067731096592274748149794482308513726460953499586); + verifyAddr("UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA"a, 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr("EQAUTbQiM522Y_XJ_T98QPhPhTmb4nV--VSPiha8kC6kRfPO"a, 0, 9183547432069678364603018431103042146626948674383548774683927217595824907333); + verifyAddr("EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE"a, 0, 45985353862647206060987594732861817093328871106941773337270673759241903247880); + verifyAddr("UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA"a, 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr("kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP"a, 0, 91561894446285001782438967260723928368560331318344957259023550817453781559870); + verifyAddr("kf-Dfdg-YQXaR2Q97gZJ4fGBtmV1DHOU1y1RPyyZZtRy_Ikh"a, 255, 59475331506450494976393625198911249698879029820580340449086829444312920781564); + verifyAddr("0:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"a, 0, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr("0:0000000000000000000000000000000000000000000000000000000000000000"a, 0, 0); + verifyAddr("0:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFfffffffffffffffffffffffffffff"a, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + verifyAddr("0:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"a, 0, 23158417847463239084714197001737581570653996933128112807891516801582625927987); + verifyAddr("0:0000000000000000000000000000000000000000000000000000000000000000"a, 0, 0); + verifyAddr("1:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"a, 1, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr("9:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"a, 9, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr("99:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"a, 99, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + verifyAddr("-1:527964d55cfa6eb731f4bfc07e9d025098097ef8505519e853986279bd8400d8"a, 255, 37304138005561100291416421295333982606153966175434134130332440738068913455320); + + return cc1.isSliceBitsEqual(cc2); +} + +/** +@testcase | 0 | | -1 + */ diff --git a/tolk-tester/tests/pure-functions.tolk b/tolk-tester/tests/pure-functions.tolk index 6e7a6ddad..8598e85c2 100644 --- a/tolk-tester/tests/pure-functions.tolk +++ b/tolk-tester/tests/pure-functions.tolk @@ -13,8 +13,8 @@ fun f_pure2(): int { fun get_contract_data(): (int, int) { var c: cell = getContractData(); var cs: slice = c.beginParse(); - cs~loadBits(32); - var value: int = cs~loadUint(16); + cs.loadBits(32); + var value: int = cs.loadUint(16); return (1, value); } diff --git a/tolk-tester/tests/self-keyword.tolk b/tolk-tester/tests/self-keyword.tolk new file mode 100644 index 000000000..a339e7d01 --- /dev/null +++ b/tolk-tester/tests/self-keyword.tolk @@ -0,0 +1,213 @@ +fun incChained(mutate self: int): self { + self = self + 1; + return self; +} + +fun incChained2(mutate self: int): self { + return self.incChained(); +} + +fun incChained3(mutate self: int): self { + incChained(mutate self); + return self; +} + +fun incChained4(mutate self: int): self { + self.incChained(); + return self; +} + +@method_id(101) +fun testIncChainedCodegen(x: int) { + return x.incChained().incChained2().incChained3().incChained4(); +} + +@method_id(102) +fun testIncChained() { + var x: int = 10; + incChained(mutate x); + x.incChained(); + x.incChained2(); + x.incChained2().incChained(); + x = x.incChained(); + x = x.incChained2().incChained().incChained2(); + return x.incChained(); +} + +fun incChainedWithMiddleReturn(mutate self: int, maxValue: int): self { + if (self >= maxValue) { + return self; + } + self += 1; + return self; +} + +@method_id(103) +fun testIncChainedWithMiddleReturn(x: int) { + x.incChainedWithMiddleReturn(10).incChainedWithMiddleReturn(10); + x = x.incChainedWithMiddleReturn(10).incChainedWithMiddleReturn(10); + return x.incChainedWithMiddleReturn(10).incChainedWithMiddleReturn(999); +} + +fun incChainedMutatingBoth(mutate self: int, mutate y: int): self { + self += 1; + y += 1; + return self; +} + +global c104: int; + +@method_id(104) +fun testIncChainedMutatingBoth() { + var (x, y) = (0, 0); + c104 = 0; + x.incChainedMutatingBoth(mutate y).incChainedMutatingBoth(mutate y); + incChainedMutatingBoth(mutate x, mutate y); + x = x.incChainedMutatingBoth(mutate c104).incChainedMutatingBoth(mutate c104).incChainedMutatingBoth(mutate y); + return (x, y, c104); +} + +fun incTensorChained(mutate self: (int, int)): self { + val (f, s) = self; + self = (f + 1, s + 1); + return self; +} + +@method_id(105) +fun testIncTensorChained(f: int, s: int) { + var tens = (f, s); + tens.incTensorChained().incTensorChained(); + return tens.incTensorChained().incTensorChained(); +} + +fun incConditionalChainable(mutate self: int, mutate another: int, ifLessThan: int): self { + another += 1; + return self.incChained() < ifLessThan ? self.incChained().incChained() : self; +} + +@method_id(106) +fun testIncConditionalChainable(x: int) { + var y = 0; + x.incConditionalChainable(mutate y, 5).incConditionalChainable(mutate y, 5); + x = x.incConditionalChainable(mutate y, 5).incConditionalChainable(mutate y, 5); + return (x.incConditionalChainable(mutate y, 5), y); +} + +fun checkNotEq(self: int, throwIfEq: int): void { + if (self == throwIfEq) { + throw 100 + throwIfEq; + } +} + +@method_id(107) +fun testNotMutatingSelf(arg: int) { + try { + arg.checkNotEq(100); + arg.checkNotEq(101); + arg.checkNotEq(102); + return 0; + } catch (code) { + return code; + } +} + +global c108: int; + +fun checkNotEqChainable(self: int, throwIfEq: int): self { + c108 += 1; + if (self != throwIfEq) { + return self; + } + throw 100 + throwIfEq; + return self; +} + +@method_id(108) +fun testNotMutatingChainableSelf(arg: int) { + c108 = 0; + try { + arg.checkNotEqChainable(100).checkNotEqChainable(101).checkNotEqChainable(102); + arg = arg.checkNotEqChainable(100).checkNotEqChainable(101).checkNotEqChainable(102); + return (arg, c108); + } catch (code) { + return (code, c108); + } +} + +global onceFailed109: int; + +fun checkNotEqChainableMutateAnother(self: int, throwIfEq: int, mutate toInc: int): self { + if (onceFailed109) { return self; } + toInc += 1; + try { return self.checkNotEqChainable(throwIfEq); } + catch { onceFailed109 = 1; return self; } +} + +global c109: int; + +@method_id(109) +fun testNotMutatingChainableSelfMutateAnother(initial: int) { + val arg = initial; + var x = 0; + c108 = 0; + c109 = 0; + onceFailed109 = 0; + arg.checkNotEqChainableMutateAnother(100, mutate x) + .checkNotEqChainableMutateAnother(101, mutate c109) + .checkNotEqChainableMutateAnother(102, mutate x); + return (arg, c108, c109, x); +} + + +fun main() { } + +/** +@testcase | 101 | 5 | 9 +@testcase | 102 | | 20 +@testcase | 103 | 1 | 7 +@testcase | 103 | 100 | 101 +@testcase | 103 | 8 | 11 +@testcase | 104 | | 6 4 2 +@testcase | 105 | 1 2 | 5 6 +@testcase | 106 | -20 | -5 5 +@testcase | 106 | -1 | 8 5 +@testcase | 106 | 7 | 12 5 +@testcase | 107 | 200 | 0 +@testcase | 107 | 102 | 202 +@testcase | 108 | 200 | 200 6 +@testcase | 108 | 101 | 201 0 +@testcase | 109 | 200 | 200 3 1 2 +@testcase | 109 | 100 | 100 0 0 1 +@testcase | 109 | 102 | 102 2 1 2 + +@fif_codegen +""" + incChained PROC:<{ + // self + INC // self + }> + incChained2 PROC:<{ + // self + incChained CALLDICT // self + }> + incChained3 PROC:<{ + // self + incChained CALLDICT // self + }> + incChained4 PROC:<{ + // self + incChained CALLDICT // self + }> +""" + +@fif_codegen +""" + testIncChainedCodegen PROC:<{ + // x + incChained CALLDICT // x + incChained2 CALLDICT // x + incChained3 CALLDICT // x + incChained4 CALLDICT // x + }> +""" + */ diff --git a/tolk-tester/tests/test-math.tolk b/tolk-tester/tests/test-math.tolk index 7c9873f09..893035fde 100644 --- a/tolk-tester/tests/test-math.tolk +++ b/tolk-tester/tests/test-math.tolk @@ -243,7 +243,7 @@ fun tanh_f258(x: int, n: int): int { repeat (n) { a = (c -= Two) + mulDivRound(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 } - a = (stackMoveToTop(3) << 254) + mulDivRound(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 + a = (3 << 254) + mulDivRound(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 // y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a return x - (mulDivRound(x, x2, a + (x2 ~>> 7)) ~>> 7); } @@ -257,12 +257,12 @@ fun expm1_f257(x: int): int { // (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 var x2: int = mulDivRound(x, x, 1 << 255); // x^2 as fixed261 var Two: int = (1 << 251); // 2. as fixed250 - var a: int = stackMoveToTop(39) << 250; // a=2n+5 as fixed250 + var a: int = 39 << 250; // a=2n+5 as fixed250 var c = a; repeat (17) { a = (c -= Two) + mulDivRound(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 } - a = (stackMoveToTop(3) << 254) + mulDivRound(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 + a = (3 << 254) + mulDivRound(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 // now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) var t: int = (x ~>> 4) - a; // t:=x-a as fixed254 return x - mulDivRound(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; // x - x^2 * (x-a) / (a + x*(x-a)) @@ -306,12 +306,12 @@ fun fixed248_exp2(x: int): int { fun tan_f260_inlined(x: int): int { var x2: int = mulrshiftr256(x, x); // x^2 as fixed264 var Two: int = (1 << 251); // 2. as fixed250 - var a: int = stackMoveToTop(33) << 250; // a=2n+5 as fixed250 + var a: int = 33 << 250; // a=2n+5 as fixed250 var c = a; repeat (14) { a = (c -= Two) - mulDivRound(x2, 1 << 236, a); // a := 2k+1-x^2/a as fixed250, k=n+1,n,...,2 } - a = (stackMoveToTop(3) << 254) - mulDivRound(x2, 1 << 240, a); // a := 3-x^2/a as fixed254 + a = (3 << 254) - mulDivRound(x2, 1 << 240, a); // a := 3-x^2/a as fixed254 // y = x/(1-a') = x + x*a'/(1-a') = x + x*x^2/(a-x^2) where a' = x^2/a return x + (mulDivRound(x / 2, x2, a - (x2 ~>> 10)) ~>> 9); } @@ -330,12 +330,12 @@ fun tan_f260(x: int): int { fun tan_f258_inlined(x: int): int { var x2: int = mulrshiftr256(x, x); // x^2 as fixed260 var Two: int = (1 << 251); // 2. as fixed250 - var a: int = stackMoveToTop(41) << 250; // a=2n+5 as fixed250 + var a: int = 41 << 250; // a=2n+5 as fixed250 var c = a; repeat (18) { a = (c -= Two) - mulDivRound(x2, 1 << 240, a); // a := 2k+1-x^2/a as fixed250, k=n+1,n,...,2 } - a = (stackMoveToTop(3) << 254) - mulDivRound(x2, 1 << 244, a); // a := 3-x^2/a as fixed254 + a = (3 << 254) - mulDivRound(x2, 1 << 244, a); // a := 3-x^2/a as fixed254 // y = x/(1-a') = x + x*a'/(1-a') = x + x*x^2/(a-x^2) where a' = x^2/a return x + (mulDivRound(x / 2, x2, a - (x2 ~>> 6)) ~>> 5); } @@ -546,9 +546,8 @@ fun atanh_f261(x: int, n: int): int { fun log_aux_f257(x: int): (int, int) { var s: int = log2_floor_p1(x); x <<= 256 - s; - var t: int = stackMoveToTop(-1 << 256); + var t: int = -1 << 256; if ((x >> 249) <= 90) { - // t~stackMoveToTop(); t >>= 1; s -= 1; } @@ -593,7 +592,7 @@ fun pow33b(m: int): int { fun log_auxx_f260(x: int): (int, int, int) { var s: int = log2_floor_p1(x) - 1; x <<= 255 - s; // rescale to 1 <= x < 2 as fixed255 - var t: int = stackMoveToTop(2873) << 244; // ~ (33/32)^11 ~ sqrt(2) as fixed255 + var t: int = 2873 << 244; // ~ (33/32)^11 ~ sqrt(2) as fixed255 var x1: int = (x - t) >> 1; var q: int = mulDivRound(x1, 65, x1 + t) + 11; // crude approximation to round(log(x)/log(33/32)) // t = 1; repeat (q) { t *= 33; } // t:=33^q, 0<=q<=22 @@ -742,13 +741,14 @@ fun atan_aux_prereduce(x: int): (int, int, int) { var tc: int = 7214596; // tan(13*theta) as fixed24 where theta=atan(1/32) var t1: int = mulDivRound(xu - tc, 1 << 88, xu * tc + (1 << 48)); // tan(x') as fixed64 where x'=atan(x)-13*theta // t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta - var q: int = mulDivRound(t1 * 3073, 1 << 59, t1 * t1 + (stackMoveToTop(3) << 128)) + 13; // approximately round(atan(x)/theta), 0<=q<=25 + var q: int = mulDivRound(t1 * 3073, 1 << 59, t1 * t1 + (3 << 128)) + 13; // approximately round(atan(x)/theta), 0<=q<=25 var (pa, pb) = (33226912, 5232641); // (32+I)^5 var (qh, ql) = divMod(q, 5); var (a, b) = (1 << (5 * (51 - q)), 0); // (1/32^q, 0) as fixed255 repeat (ql) { // a+b*I *= 32+I - (a, b) = (sub_rev(stackMoveToTop(b), 32 * a), a + 32 * b); // same as (32 * a - b, 32 * b + a), but more efficient + b.stackMoveToTop(); + (a, b) = (sub_rev(b, 32 * a), a + 32 * b); // same as (32 * a - b, 32 * b + a), but more efficient } repeat (qh) { // a+b*I *= (32+I)^5 = pa + pb*I @@ -807,7 +807,7 @@ fun atan_auxx_f256(x: int): (int, int) { @inline_ref fun atan_f255(x: int): int { var s: int = (x ~>> 256); - stackMoveToTop(x); + x.stackMoveToTop(); if (s) { x = lshift256divr(-1 << 255, x); // x:=-1/x as fixed256 } else { @@ -880,7 +880,7 @@ fun fixed248_acos(x: int): int { @inline_ref fun fixed248_atan(x: int): int { var s: int = (x ~>> 249); - stackMoveToTop(x); + x.stackMoveToTop(); if (s) { s = sign(s); x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 @@ -897,7 +897,7 @@ fun fixed248_atan(x: int): int { @inline_ref fun fixed248_acot(x: int): int { var s: int = (x ~>> 249); - stackMoveToTop(x); + x.stackMoveToTop(); if (s) { x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 s = 0; @@ -918,7 +918,7 @@ fun fixed248_acot(x: int): int { /// fixed252 nrand(); @inline_ref fun nrand_f252(): int { - var (x, s, t, A, B, r0) = (nan(), stackMoveToTop(29483) << 236, stackMoveToTop(-3167) << 239, 12845, 16693, 9043); + var (x, s, t, A, B, r0) = (nan(), 29483 << 236, -3167 << 239, 12845, 16693, 9043); // 4/sqrt(e*Pi) = 1.369 loop iterations on average do { var (u, v) = (random() / 16 + 1, mulDivRound(random() - (1 << 255), 7027, 1 << 16)); // fixed252; 7027=ceil(sqrt(8/e)*2^12) @@ -950,7 +950,7 @@ fun nrand_f252(): int { /// fixed252 nrand_fast(); @inline_ref fun nrand_fast_f252(): int { - var t: int = stackMoveToTop(-3) << 253; // -6. as fixed252 + var t: int = -3 << 253; // -6. as fixed252 repeat (12) { t += random() / 16; // add together 12 uniformly random numbers } @@ -979,8 +979,8 @@ fun fixed248_nrand_fast(): int { } @pure -fun ~tset(t: tuple, idx: int, value: X): (tuple, ()) -asm(t value idx) "SETINDEXVAR"; +fun tset(mutate self: tuple, idx: int, value: X): void + asm(self value idx) "SETINDEXVAR"; // computes 1-acos(x)/Pi by a very simple, extremely slow (~70k gas) and imprecise method // fixed256 acos_prepare_slow(fixed255 x); @@ -1014,12 +1014,12 @@ fun asin_slow_f255(x: int): int { fun test_nrand(n: int): tuple { var t: tuple = createEmptyTuple(); repeat (255) { - t~tuplePush(0); + t.tuplePush(0); } repeat (n) { var x: int = fixed248_nrand(); var bucket: int = (abs(x) >> 243); // 255 buckets starting from x=0, each 1/32 wide - t~tset(bucket, t.tupleAt(bucket) + 1); + t.tset(bucket, t.tupleAt(bucket) + 1); } return t; } @@ -1210,14 +1210,14 @@ fun main() { // return atan_aux_f256(1); // atan(1/2^256)*2^261 = 32 //return fixed248_nrand(); // return test_nrand(100000); - var One2: int = stackMoveToTop(1 << 255); + var One2: int = 1 << 255; // return asin_f255(One); // return asin_f255(-2 * One ~/ -3); var arg: int = mulDivRound(12, One2, 17); // 12/17 // return [ asin_slow_f255(arg), asin_f255(arg) ]; // return [ acos_slow_f255(arg), acos_f255(arg) ]; // return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); // 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255 - var One3: int = stackMoveToTop(1 << 248); + var One3: int = 1 << 248; // return fixed248_atan(One) ~/ 5); // atan(1/5) // return fixed248_acot(One ~/ 239); // atan(1/5) } diff --git a/tolk-tester/tests/unbalanced_ret_nested.tolk b/tolk-tester/tests/unbalanced_ret_nested.tolk index 4d294ae95..05e609240 100644 --- a/tolk-tester/tests/unbalanced_ret_nested.tolk +++ b/tolk-tester/tests/unbalanced_ret_nested.tolk @@ -17,11 +17,8 @@ fun bar(x: int, y: int): (int, int) { } return (x + 1, y); } -fun bar2(x: int, y: int): (int,int) { - return bar(x, y); -} fun main(x: int, y: int): (int, int) { - (x, y) = bar2(x, y); + (x, y) = bar(x, y); return (x, y * 10); } /** diff --git a/tolk-tester/tests/use-before-declare.tolk b/tolk-tester/tests/use-before-declare.tolk index 2486df1c3..384569b93 100644 --- a/tolk-tester/tests/use-before-declare.tolk +++ b/tolk-tester/tests/use-before-declare.tolk @@ -1,7 +1,7 @@ fun main(): int { var c: cell = my_begin_cell().storeInt(demo_10, 32).my_end_cell(); var cs: slice = my_begin_parse(c); - var ten: int = cs~loadInt(32); + var ten: int = cs.loadInt(32); return 1 + demo1(ten) + demo_var; } diff --git a/tolk-tester/tests/var-apply.tolk b/tolk-tester/tests/var-apply.tolk new file mode 100644 index 000000000..9bee862ac --- /dev/null +++ b/tolk-tester/tests/var-apply.tolk @@ -0,0 +1,22 @@ +fun getBeginCell() { + return beginCell; +} + +fun getBeginParse() { + return beginParse; +} + +@method_id(101) +fun testVarApply1() { + var (_, f_end_cell) = (0, endCell); + var b: builder = (getBeginCell())().storeInt(1, 32); + b.storeInt(2, 32); + var s = (getBeginParse())(f_end_cell(b)); + return (s.loadInt(32), s.loadInt(32)); +} + +fun main() {} + +/** +@testcase | 101 | | 1 2 + */ diff --git a/tolk-tester/tests/w2.tolk b/tolk-tester/tests/w2.tolk index 24820f143..728b18d3f 100644 --- a/tolk-tester/tests/w2.tolk +++ b/tolk-tester/tests/w2.tolk @@ -1,6 +1,6 @@ @method_id(101) fun test1(cs: slice) { - return cs~loadUint(8)+cs~loadUint(8)+cs~loadUint(8)+cs~loadUint(8); + return cs.loadUint(8)+cs.loadUint(8)+cs.loadUint(8)+cs.loadUint(8); } @method_id(102) @@ -12,15 +12,15 @@ fun test2(cs: slice) { } fun main(cs: slice) { - return (cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8)); + return (cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), cs.loadUint(8)); } fun f(cs: slice) { - return (cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), - cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), - cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), - cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), - cs~loadUint(8), cs~loadUint(8), cs~loadUint(8), cs~loadUint(8)); + return (cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), + cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), + cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), + cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), + cs.loadUint(8), cs.loadUint(8), cs.loadUint(8), cs.loadUint(8)); } diff --git a/tolk/abscode.cpp b/tolk/abscode.cpp index c0190b640..c1add6839 100644 --- a/tolk/abscode.cpp +++ b/tolk/abscode.cpp @@ -25,12 +25,8 @@ namespace tolk { * */ -TmpVar::TmpVar(var_idx_t _idx, bool _is_tmp_unnamed, TypeExpr* _type, SymDef* sym, SrcLocation loc) - : v_type(_type), idx(_idx), is_tmp_unnamed(_is_tmp_unnamed), coord(0), where(loc) { - if (sym) { - name = sym->sym_idx; - sym->value->idx = _idx; - } +TmpVar::TmpVar(var_idx_t _idx, TypeExpr* _type, sym_idx_t sym_idx, SrcLocation loc) + : v_type(_type), idx(_idx), sym_idx(sym_idx), coord(0), where(loc) { if (!_type) { v_type = TypeExpr::new_hole(); } @@ -59,8 +55,8 @@ void TmpVar::dump(std::ostream& os) const { } void TmpVar::show(std::ostream& os, int omit_idx) const { - if (!is_tmp_unnamed) { - os << G.symbols.get_name(name); + if (!is_unnamed()) { + os << G.symbols.get_name(sym_idx); if (omit_idx >= 2) { return; } @@ -462,16 +458,13 @@ void CodeBlob::print(std::ostream& os, int flags) const { os << "-------- END ---------\n\n"; } -var_idx_t CodeBlob::create_var(bool is_tmp_unnamed, TypeExpr* var_type, SymDef* sym, SrcLocation location) { - vars.emplace_back(var_cnt, is_tmp_unnamed, var_type, sym, location); - if (sym) { - sym->value->idx = var_cnt; - } +var_idx_t CodeBlob::create_var(TypeExpr* var_type, var_idx_t sym_idx, SrcLocation location) { + vars.emplace_back(var_cnt, var_type, sym_idx, location); return var_cnt++; } bool CodeBlob::import_params(FormalArgList arg_list) { - if (var_cnt || in_var_cnt || op_cnt) { + if (var_cnt || in_var_cnt) { return false; } std::vector list; @@ -480,7 +473,7 @@ bool CodeBlob::import_params(FormalArgList arg_list) { SymDef* arg_sym; SrcLocation arg_loc; std::tie(arg_type, arg_sym, arg_loc) = par; - list.push_back(create_var(arg_sym == nullptr, arg_type, arg_sym, arg_loc)); + list.push_back(create_var(arg_type, arg_sym ? arg_sym->sym_idx : 0, arg_loc)); } emplace_back(loc, Op::_Import, list); in_var_cnt = var_cnt; diff --git a/tolk/analyzer.cpp b/tolk/analyzer.cpp index b2ea55ec8..719df9b7d 100644 --- a/tolk/analyzer.cpp +++ b/tolk/analyzer.cpp @@ -46,10 +46,9 @@ int CodeBlob::split_vars(bool strict) { if (k != 1) { var.coord = ~((n << 8) + k); for (int i = 0; i < k; i++) { - auto v = create_var(vars[j].is_tmp_unnamed, comp_types[i], 0, vars[j].where); + auto v = create_var(comp_types[i], vars[j].sym_idx, vars[j].where); tolk_assert(v == n + i); tolk_assert(vars[v].idx == v); - vars[v].name = vars[j].name; vars[v].coord = ((int)j << 8) + i + 1; } n += k; diff --git a/tolk/ast-from-tokens.cpp b/tolk/ast-from-tokens.cpp index 5116fcf57..1a1d199ec 100644 --- a/tolk/ast-from-tokens.cpp +++ b/tolk/ast-from-tokens.cpp @@ -131,7 +131,8 @@ static AnyV maybe_replace_eq_null_with_isNull_call(V v) { auto v_ident = createV(v->loc, "__isNull"); // built-in function AnyV v_null = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs(); - AnyV v_isNull = createV(v->loc, v_ident, createV(v->loc, {v_null})); + AnyV v_arg = createV(v->loc, v_null, false); + AnyV v_isNull = createV(v->loc, v_ident, createV(v->loc, {v_arg})); if (v->tok == tok_neq) { v_isNull = createV(v->loc, "!", tok_logical_not, v_isNull); } @@ -165,7 +166,7 @@ static TypeExpr* parse_type1(Lexer& lex, V genericsT_list) { return TypeExpr::new_atomic(TypeExpr::_Builder); case tok_continuation: lex.next(); - return TypeExpr::new_atomic(TypeExpr::_Cont); + return TypeExpr::new_atomic(TypeExpr::_Continutaion); case tok_tuple: lex.next(); return TypeExpr::new_atomic(TypeExpr::_Tuple); @@ -177,6 +178,8 @@ static TypeExpr* parse_type1(Lexer& lex, V genericsT_list) { return TypeExpr::new_tensor({}); case tok_bool: lex.error("bool type is not supported yet"); + case tok_self: + lex.error("`self` type can be used only as a return type of a function (enforcing it to be chainable)"); case tok_identifier: if (int idx = genericsT_list ? genericsT_list->lookup_idx(lex.cur_str()) : -1; idx != -1) { lex.next(); @@ -229,13 +232,27 @@ static TypeExpr* parse_type(Lexer& lex, V genericsT_list) { AnyV parse_expr(Lexer& lex); -static AnyV parse_parameter(Lexer& lex, V genericsT_list) { +static AnyV parse_parameter(Lexer& lex, V genericsT_list, bool is_first) { SrcLocation loc = lex.cur_location(); - // argument name (or underscore for an unnamed parameter) + // optional keyword `mutate` meaning that a function will mutate a passed argument (like passed by reference) + bool declared_as_mutate = false; + bool is_param_self = false; + if (lex.tok() == tok_mutate) { + lex.next(); + declared_as_mutate = true; + } + + // parameter name (or underscore for an unnamed parameter) std::string_view param_name; if (lex.tok() == tok_identifier) { param_name = lex.cur_str(); + } else if (lex.tok() == tok_self) { + if (!is_first) { + lex.error("`self` can only be the first parameter"); + } + param_name = "self"; + is_param_self = true; } else if (lex.tok() != tok_underscore) { lex.unexpected("parameter name"); } @@ -245,8 +262,14 @@ static AnyV parse_parameter(Lexer& lex, V genericsT_list) { // parameter type after colon, also mandatory (even explicit ":auto") lex.expect(tok_colon, "`: `"); TypeExpr* param_type = parse_type(lex, genericsT_list); + if (declared_as_mutate && !param_type->has_fixed_width()) { + throw ParseError(loc, "`mutate` parameter must be strictly typed"); + } + if (is_param_self && !param_type->has_fixed_width()) { + throw ParseError(loc, "`self` parameter must be strictly typed"); + } - return createV(loc, v_ident, param_type); + return createV(loc, v_ident, param_type, declared_as_mutate); } static AnyV parse_global_var_declaration(Lexer& lex, const std::vector>& annotations) { @@ -301,21 +324,52 @@ static AnyV parse_constant_declaration(Lexer& lex, const std::vector(loc, v_ident, declared_type, init_value); } -static AnyV parse_parameter_list(Lexer& lex, V genericsT_list) { +// "parameters" are at function declaration: `fun f(param1: int, mutate param2: slice)` +static V parse_parameter_list(Lexer& lex, V genericsT_list) { SrcLocation loc = lex.cur_location(); std::vector params; lex.expect(tok_oppar, "parameter list"); if (lex.tok() != tok_clpar) { - params.push_back(parse_parameter(lex, genericsT_list)); + params.push_back(parse_parameter(lex, genericsT_list, true)); while (lex.tok() == tok_comma) { lex.next(); - params.push_back(parse_parameter(lex, genericsT_list)); + params.push_back(parse_parameter(lex, genericsT_list, false)); } } lex.expect(tok_clpar, "`)`"); return createV(loc, std::move(params)); } +// "arguments" are at function call: `f(arg1, mutate arg2)` +static AnyV parse_argument(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + + // keyword `mutate` is necessary when a parameter is declared `mutate` (to make mutation obvious for the reader) + bool passed_as_mutate = false; + if (lex.tok() == tok_mutate) { + lex.next(); + passed_as_mutate = true; + } + + AnyV expr = parse_expr(lex); + return createV(loc, expr, passed_as_mutate); +} + +static V parse_argument_list(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + std::vector args; + lex.expect(tok_oppar, "`(`"); + if (lex.tok() != tok_clpar) { + args.push_back(parse_argument(lex)); + while (lex.tok() == tok_comma) { + lex.next(); + args.push_back(parse_argument(lex)); + } + } + lex.expect(tok_clpar, "`)`"); + return createV(loc, std::move(args)); +} + // parse (expr) / [expr] / identifier / number static AnyV parse_expr100(Lexer& lex) { SrcLocation loc = lex.cur_location(); @@ -384,6 +438,10 @@ static AnyV parse_expr100(Lexer& lex) { lex.next(); return createV(loc); } + case tok_self: { + lex.next(); + return createV(loc); + } case tok_identifier: { std::string_view str_val = lex.cur_str(); lex.next(); @@ -400,48 +458,25 @@ static AnyV parse_expr100(Lexer& lex) { } } -// parse E(expr) +// parse E(args) static AnyV parse_expr90(Lexer& lex) { AnyV res = parse_expr100(lex); if (lex.tok() == tok_oppar) { - lex.next(); - - SrcLocation loc = lex.cur_location(); - std::vector args; - if (lex.tok() != tok_clpar) { - args.push_back(parse_expr(lex)); - while (lex.tok() == tok_comma) { - lex.next(); - args.push_back(parse_expr(lex)); - } - } - lex.expect(tok_clpar, "`)`"); - - return createV(res->loc, res, createV(loc, std::move(args))); + return createV(res->loc, res, parse_argument_list(lex)); } return res; } -// parse E .method ~method E (left-to-right) +// parse E.method(...) (left-to-right) static AnyV parse_expr80(Lexer& lex) { AnyV lhs = parse_expr90(lex); - while (lex.tok() == tok_identifier && (lex.cur_str()[0] == '.' || lex.cur_str()[0] == '~')) { + while (lex.tok() == tok_dot) { + SrcLocation loc = lex.cur_location(); + lex.next(); + lex.check(tok_identifier, "method name"); std::string_view method_name = lex.cur_str(); lex.next(); - - SrcLocation loc = lex.cur_location(); - std::vector args; - lex.expect(tok_oppar, "`(`"); - if (lex.tok() != tok_clpar) { - args.push_back(parse_expr(lex)); - while (lex.tok() == tok_comma) { - lex.next(); - args.push_back(parse_expr(lex)); - } - } - lex.expect(tok_clpar, "`)`"); - - lhs = createV(lhs->loc, method_name, lhs, createV(loc, std::move(args))); + lhs = createV(loc, method_name, lhs, parse_argument_list(lex)); } return lhs; } @@ -586,11 +621,11 @@ AnyV parse_expr(Lexer& lex) { AnyV parse_statement(Lexer& lex); -static AnyV parse_var_declaration_lhs(Lexer& lex) { +static AnyV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) { SrcLocation loc = lex.cur_location(); if (lex.tok() == tok_oppar) { lex.next(); - AnyV first = parse_var_declaration_lhs(lex); + AnyV first = parse_var_declaration_lhs(lex, is_immutable); if (lex.tok() == tok_clpar) { lex.next(); return createV(loc, first); @@ -598,17 +633,17 @@ static AnyV parse_var_declaration_lhs(Lexer& lex) { std::vector args(1, first); while (lex.tok() == tok_comma) { lex.next(); - args.push_back(parse_var_declaration_lhs(lex)); + args.push_back(parse_var_declaration_lhs(lex, is_immutable)); } lex.expect(tok_clpar, "`)`"); return createV(loc, std::move(args)); } if (lex.tok() == tok_opbracket) { lex.next(); - std::vector args(1, parse_var_declaration_lhs(lex)); + std::vector args(1, parse_var_declaration_lhs(lex, is_immutable)); while (lex.tok() == tok_comma) { lex.next(); - args.push_back(parse_var_declaration_lhs(lex)); + args.push_back(parse_var_declaration_lhs(lex, is_immutable)); } lex.expect(tok_clbracket, "`]`"); return createV(loc, std::move(args)); @@ -625,7 +660,7 @@ static AnyV parse_var_declaration_lhs(Lexer& lex) { lex.next(); marked_as_redef = true; } - return createV(loc, v_ident, declared_type, marked_as_redef); + return createV(loc, v_ident, declared_type, is_immutable, marked_as_redef); } if (lex.tok() == tok_underscore) { TypeExpr* declared_type = nullptr; @@ -634,21 +669,17 @@ static AnyV parse_var_declaration_lhs(Lexer& lex) { lex.next(); declared_type = parse_type(lex, nullptr); } - return createV(loc, createV(loc), declared_type, false); + return createV(loc, createV(loc), declared_type, true, false); } lex.unexpected("variable name"); } static AnyV parse_local_vars_declaration(Lexer& lex) { SrcLocation loc = lex.cur_location(); - bool immutable = lex.tok() == tok_val; + bool is_immutable = lex.tok() == tok_val; lex.next(); - if (immutable) { - lex.error("immutable variables are not supported yet"); - } - - AnyV lhs = parse_var_declaration_lhs(lex); + AnyV lhs = parse_var_declaration_lhs(lex, is_immutable); if (lex.tok() != tok_assign) { lex.error("variables declaration must be followed by assignment: `var xxx = ...`"); } @@ -885,10 +916,10 @@ static AnyV parse_asm_func_body(Lexer& lex, V param_list) { std::vector arg_order, ret_order; if (lex.tok() == tok_oppar) { lex.next(); - while (lex.tok() == tok_identifier || lex.tok() == tok_int_const) { + while (lex.tok() == tok_identifier || lex.tok() == tok_self) { int arg_idx = param_list->lookup_idx(lex.cur_str()); if (arg_idx == -1) { - lex.unexpected("argument name"); + lex.unexpected("parameter name"); } arg_order.push_back(arg_idx); lex.next(); @@ -1006,17 +1037,44 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vectoras(); } - V param_list = parse_parameter_list(lex, genericsT_list)->as(); + V v_param_list = parse_parameter_list(lex, genericsT_list)->as(); + bool accepts_self = !v_param_list->empty() && v_param_list->get_param(0)->get_identifier()->name == "self"; + int n_mutate_params = v_param_list->get_mutate_params_count(); TypeExpr* ret_type = nullptr; + bool returns_self = false; if (lex.tok() == tok_colon) { // : (if absent, it means "auto infer", not void) lex.next(); - ret_type = parse_type(lex, genericsT_list); + if (lex.tok() == tok_self) { + if (!accepts_self) { + lex.error("only a member function can return `self` (which accepts `self` first parameter)"); + } + lex.next(); + returns_self = true; + ret_type = TypeExpr::new_unit(); + } else { + ret_type = parse_type(lex, genericsT_list); + } } - if (is_entrypoint && (is_get_method || genericsT_list || !annotations.empty())) { + if (is_entrypoint && (is_get_method || genericsT_list || n_mutate_params || accepts_self)) { throw ParseError(loc, "invalid declaration of a reserved function"); } + if (is_get_method && (genericsT_list || n_mutate_params || accepts_self)) { + throw ParseError(loc, "get methods can't have `mutate` and `self` params"); + } + + if (n_mutate_params) { + std::vector ret_tensor_items; + ret_tensor_items.reserve(1 + n_mutate_params); + for (AnyV v_param : v_param_list->get_params()) { + if (v_param->as()->declared_as_mutate) { + ret_tensor_items.emplace_back(v_param->as()->param_type); + } + } + ret_tensor_items.emplace_back(ret_type ? ret_type : TypeExpr::new_hole()); + ret_type = TypeExpr::new_tensor(std::move(ret_tensor_items)); + } AnyV v_body = nullptr; @@ -1030,17 +1088,19 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vector(loc, v_ident, param_list, v_body); + auto f_declaration = createV(loc, v_ident, v_param_list, v_body); f_declaration->ret_type = ret_type ? ret_type : TypeExpr::new_hole(); f_declaration->is_entrypoint = is_entrypoint; f_declaration->genericsT_list = genericsT_list; f_declaration->marked_as_get_method = is_get_method; f_declaration->marked_as_builtin = v_body->type == ast_empty; + f_declaration->accepts_self = accepts_self; + f_declaration->returns_self = returns_self; for (auto v_annotation : annotations) { switch (v_annotation->kind) { @@ -1054,7 +1114,7 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vectormarked_as_pure = true; break; case AnnotationKind::method_id: - if (is_get_method || genericsT_list || is_entrypoint) { + if (is_get_method || genericsT_list || is_entrypoint || n_mutate_params || accepts_self) { v_annotation->error("@method_id can be specified only for regular functions"); } f_declaration->method_id = v_annotation->get_arg()->get_item(0)->as(); diff --git a/tolk/ast-replacer.h b/tolk/ast-replacer.h index 16a9f64c2..478994e8b 100644 --- a/tolk/ast-replacer.h +++ b/tolk/ast-replacer.h @@ -80,8 +80,8 @@ class ASTReplacerInFunctionBody : public ASTReplacer { virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } @@ -109,9 +109,10 @@ class ASTReplacerInFunctionBody : public ASTReplacer { case ast_string_const: return replace(v->as()); case ast_bool_const: return replace(v->as()); case ast_null_keyword: return replace(v->as()); + case ast_self_keyword: return replace(v->as()); case ast_function_call: return replace(v->as()); + case ast_dot_method_call: return replace(v->as()); case ast_underscore: return replace(v->as()); - case ast_dot_tilde_call: return replace(v->as()); case ast_unary_operator: return replace(v->as()); case ast_binary_operator: return replace(v->as()); case ast_ternary_operator: return replace(v->as()); diff --git a/tolk/ast-stringifier.h b/tolk/ast-stringifier.h index cabda4990..759873b04 100644 --- a/tolk/ast-stringifier.h +++ b/tolk/ast-stringifier.h @@ -40,11 +40,14 @@ class ASTStringifier final : public ASTVisitor { {ast_string_const, "ast_string_const"}, {ast_bool_const, "ast_bool_const"}, {ast_null_keyword, "ast_null_keyword"}, + {ast_self_keyword, "ast_self_keyword"}, + {ast_argument, "ast_argument"}, + {ast_argument_list, "ast_argument_list"}, {ast_function_call, "ast_function_call"}, + {ast_dot_method_call, "ast_dot_method_call"}, {ast_global_var_declaration, "ast_global_var_declaration"}, {ast_constant_declaration, "ast_constant_declaration"}, {ast_underscore, "ast_underscore"}, - {ast_dot_tilde_call, "ast_dot_tilde_call"}, {ast_unary_operator, "ast_unary_operator"}, {ast_binary_operator, "ast_binary_operator"}, {ast_ternary_operator, "ast_ternary_operator"}, @@ -125,12 +128,12 @@ class ASTStringifier final : public ASTVisitor { } return {}; } + case ast_dot_method_call: + return static_cast(v->as()->method_name); case ast_global_var_declaration: return static_cast(v->as()->get_identifier()->name); case ast_constant_declaration: return static_cast(v->as()->get_identifier()->name); - case ast_dot_tilde_call: - return static_cast(v->as()->method_name); case ast_unary_operator: return static_cast(v->as()->operator_name); case ast_binary_operator: @@ -208,11 +211,14 @@ class ASTStringifier final : public ASTVisitor { case ast_string_const: return handle_vertex(v->as()); case ast_bool_const: return handle_vertex(v->as()); case ast_null_keyword: return handle_vertex(v->as()); + case ast_self_keyword: return handle_vertex(v->as()); + case ast_argument: return handle_vertex(v->as()); + case ast_argument_list: return handle_vertex(v->as()); case ast_function_call: return handle_vertex(v->as()); + case ast_dot_method_call: return handle_vertex(v->as()); case ast_global_var_declaration: return handle_vertex(v->as()); case ast_constant_declaration: return handle_vertex(v->as()); case ast_underscore: return handle_vertex(v->as()); - case ast_dot_tilde_call: return handle_vertex(v->as()); case ast_unary_operator: return handle_vertex(v->as()); case ast_binary_operator: return handle_vertex(v->as()); case ast_ternary_operator: return handle_vertex(v->as()); diff --git a/tolk/ast-visitor.h b/tolk/ast-visitor.h index 6fe9ed5d9..d0a7bfaf6 100644 --- a/tolk/ast-visitor.h +++ b/tolk/ast-visitor.h @@ -75,9 +75,10 @@ class ASTVisitorFunctionBody : public ASTVisitor { virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } @@ -103,9 +104,10 @@ class ASTVisitorFunctionBody : public ASTVisitor { case ast_string_const: return visit(v->as()); case ast_bool_const: return visit(v->as()); case ast_null_keyword: return visit(v->as()); + case ast_self_keyword: return visit(v->as()); case ast_function_call: return visit(v->as()); + case ast_dot_method_call: return visit(v->as()); case ast_underscore: return visit(v->as()); - case ast_dot_tilde_call: return visit(v->as()); case ast_unary_operator: return visit(v->as()); case ast_binary_operator: return visit(v->as()); case ast_ternary_operator: return visit(v->as()); diff --git a/tolk/ast.cpp b/tolk/ast.cpp index f0506ef48..b1af51005 100644 --- a/tolk/ast.cpp +++ b/tolk/ast.cpp @@ -86,6 +86,16 @@ int Vertex::lookup_idx(std::string_view param_name) const { return -1; } +int Vertex::get_mutate_params_count() const { + int n = 0; + for (AnyV param : children) { + if (param->as()->declared_as_mutate) { + n++; + } + } + return n; +} + void Vertex::mutate_set_src_file(const SrcFile* file) const { const_cast(this)->file = file; } diff --git a/tolk/ast.h b/tolk/ast.h index a233f09d3..fd2b27cbf 100644 --- a/tolk/ast.h +++ b/tolk/ast.h @@ -68,11 +68,14 @@ enum ASTNodeType { ast_string_const, ast_bool_const, ast_null_keyword, + ast_self_keyword, + ast_argument, + ast_argument_list, ast_function_call, + ast_dot_method_call, ast_global_var_declaration, ast_constant_declaration, ast_underscore, - ast_dot_tilde_call, ast_unary_operator, ast_binary_operator, ast_ternary_operator, @@ -284,14 +287,51 @@ struct Vertex final : ASTNodeLeaf { : ASTNodeLeaf(ast_null_keyword, loc) {} }; +template<> +struct Vertex final : ASTNodeLeaf { + explicit Vertex(SrcLocation loc) + : ASTNodeLeaf(ast_self_keyword, loc) {} +}; + +template<> +struct Vertex final : ASTNodeUnary { + bool passed_as_mutate; // when called `f(mutate arg)`, not `f(arg)` + + AnyV get_expr() const { return child; } + + explicit Vertex(SrcLocation loc, AnyV expr, bool passed_as_mutate) + : ASTNodeUnary(ast_argument, loc, expr), passed_as_mutate(passed_as_mutate) {} +}; + +template<> +struct Vertex final : ASTNodeVararg { + const std::vector& get_arguments() const { return children; } + auto get_arg(int i) const { return children.at(i)->as(); } + + explicit Vertex(SrcLocation loc, std::vector arguments) + : ASTNodeVararg(ast_argument_list, loc, std::move(arguments)) {} +}; + template<> struct Vertex final : ASTNodeBinary { - // even for f(1,2,3), f (lhs) is called with a single arg (tensor "(1,2,3)") (rhs) AnyV get_called_f() const { return lhs; } - auto get_called_arg() const { return rhs->as(); } + auto get_arg_list() const { return rhs->as(); } + int get_num_args() const { return rhs->as()->size(); } + auto get_arg(int i) const { return rhs->as()->get_arg(i); } + + Vertex(SrcLocation loc, AnyV lhs_f, V arguments) + : ASTNodeBinary(ast_function_call, loc, lhs_f, arguments) {} +}; + +template<> +struct Vertex final : ASTNodeBinary { + std::string_view method_name; + + AnyV get_obj() const { return lhs; } + auto get_arg_list() const { return rhs->as(); } - Vertex(SrcLocation loc, AnyV lhs_f, V arg) - : ASTNodeBinary(ast_function_call, loc, lhs_f, arg) {} + Vertex(SrcLocation loc, std::string_view method_name, AnyV lhs, V arguments) + : ASTNodeBinary(ast_dot_method_call, loc, lhs, arguments), method_name(method_name) {} }; template<> @@ -321,17 +361,6 @@ struct Vertex final : ASTNodeLeaf { : ASTNodeLeaf(ast_underscore, loc) {} }; -template<> -struct Vertex final : ASTNodeBinary { - std::string_view method_name; // starts with . or ~ - - AnyV get_lhs() const { return lhs; } - auto get_arg() const { return rhs->as(); } - - Vertex(SrcLocation loc, std::string_view method_name, AnyV lhs, V arg) - : ASTNodeBinary(ast_dot_tilde_call, loc, lhs, arg), method_name(method_name) {} -}; - template<> struct Vertex final : ASTNodeUnary { std::string_view operator_name; @@ -475,11 +504,13 @@ struct Vertex final : ASTNodeVararg { template<> struct Vertex final : ASTNodeUnary { TypeExpr* param_type; + bool declared_as_mutate; // declared as `mutate param_name` - auto get_identifier() const { return child->as(); } // for underscore, its str_val is empty + auto get_identifier() const { return child->as(); } // for underscore, name is empty + bool is_underscore() const { return child->as()->name.empty(); } - Vertex(SrcLocation loc, V name_identifier, TypeExpr* param_type) - : ASTNodeUnary(ast_parameter, loc, name_identifier), param_type(param_type) {} + Vertex(SrcLocation loc, V name_identifier, TypeExpr* param_type, bool declared_as_mutate) + : ASTNodeUnary(ast_parameter, loc, name_identifier), param_type(param_type), declared_as_mutate(declared_as_mutate) {} }; template<> @@ -491,6 +522,8 @@ struct Vertex final : ASTNodeVararg { : ASTNodeVararg(ast_parameter_list, loc, std::move(params)) {} int lookup_idx(std::string_view param_name) const; + int get_mutate_params_count() const; + bool has_mutate_params() const { return get_mutate_params_count() > 0; } }; template<> @@ -519,12 +552,13 @@ struct Vertex final : ASTNodeUnary { template<> struct Vertex final : ASTNodeUnary { TypeExpr* declared_type; + bool is_immutable; // declared via 'val', not 'var' bool marked_as_redef; // var (existing_var redef, new_var: int) = ... AnyV get_identifier() const { return child; } // ast_identifier / ast_underscore - Vertex(SrcLocation loc, AnyV name_identifier, TypeExpr* declared_type, bool marked_as_redef) - : ASTNodeUnary(ast_local_var, loc, name_identifier), declared_type(declared_type), marked_as_redef(marked_as_redef) {} + Vertex(SrcLocation loc, AnyV name_identifier, TypeExpr* declared_type, bool is_immutable, bool marked_as_redef) + : ASTNodeUnary(ast_local_var, loc, name_identifier), declared_type(declared_type), is_immutable(is_immutable), marked_as_redef(marked_as_redef) {} }; template<> @@ -552,6 +586,8 @@ struct Vertex final : ASTNodeVararg { bool marked_as_get_method = false; bool marked_as_inline = false; bool marked_as_inline_ref = false; + bool accepts_self = false; + bool returns_self = false; V method_id = nullptr; bool is_asm_function() const { return children.at(2)->type == ast_asm_body; } diff --git a/tolk/builtins.cpp b/tolk/builtins.cpp index a123b0a85..d18cfa644 100644 --- a/tolk/builtins.cpp +++ b/tolk/builtins.cpp @@ -29,44 +29,60 @@ using namespace std::literals::string_literals; SymDef* define_builtin_func_impl(const std::string& name, SymValAsmFunc* func_val) { sym_idx_t name_idx = G.symbols.lookup_add(name); SymDef* def = define_global_symbol(name_idx); - if (!def) { - std::cerr << "fatal: global function `" << name << "` already defined" << std::endl; - std::exit(1); - } - func_val->flags |= SymValFunc::flagBuiltinFunction; + tolk_assert(!def->value); + def->value = func_val; #ifdef TOLK_DEBUG - dynamic_cast(def->value)->name = name; + def->value->sym_name = name; #endif return def; } -SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, bool impure = false) { - return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure}); -} - -SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, bool impure = false) { - return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure}); -} +// given func_type = `(slice, int) -> slice` and func flags, create SymDef for parameters +// currently (see at the bottom) parameters of built-in functions are unnamed: +// built-in functions are created using a resulting type +static std::vector define_builtin_parameters(const TypeExpr* func_type, int func_flags) { + // `loadInt()`, `storeInt()`: they accept `self` and mutate it; no other options available in built-ins for now + bool is_mutate_self = func_flags & SymValFunc::flagHasMutateParams; + // func_type a map (params_type -> ret_type), probably surrounded by forall (internal representation of ) + TypeExpr* params_type = func_type->constr == TypeExpr::te_ForAll ? func_type->args[0]->args[0] : func_type->args[0]; + std::vector parameters; + + if (params_type->constr == TypeExpr::te_Tensor) { // multiple parameters: it's a tensor + parameters.reserve(params_type->args.size()); + for (int i = 0; i < static_cast(params_type->args.size()); ++i) { + SymDef* sym_def = define_parameter(i, {}); + SymValVariable* sym_val = new SymValVariable(i, params_type->args[i]); + if (i == 0 && is_mutate_self) { + sym_val->flags |= SymValVariable::flagMutateParameter; + } + sym_def->value = sym_val; + parameters.emplace_back(sym_def); + } + } else { // single parameter + SymDef* sym_def = define_parameter(0, {}); + SymValVariable* sym_val = new SymValVariable(0, params_type); + if (is_mutate_self) { + sym_val->flags |= SymValVariable::flagMutateParameter; + } + sym_def->value = sym_val; + parameters.emplace_back(sym_def); + } -SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, bool impure = false) { - return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), !impure}); + return parameters; } -SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool impure = false) { - return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}); +static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, int flags) { + return define_builtin_func_impl(name, new SymValAsmFunc(define_builtin_parameters(func_type, flags), func_type, func, flags | SymValFunc::flagBuiltinFunction)); } -SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool impure = false) { - return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}); +static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, int flags) { + return define_builtin_func_impl(name, new SymValAsmFunc(define_builtin_parameters(func_type, flags), func_type, make_simple_compile(macro), flags | SymValFunc::flagBuiltinFunction)); } -SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, - std::initializer_list arg_order, std::initializer_list ret_order = {}, - bool impure = false) { - return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure}); +static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, int flags, + std::initializer_list arg_order, std::initializer_list ret_order) { + return define_builtin_func_impl(name, new SymValAsmFunc(define_builtin_parameters(func_type, flags), func_type, func, flags | SymValFunc::flagBuiltinFunction, arg_order, ret_order)); } bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& out, std::vector& in, @@ -963,7 +979,7 @@ AsmOp compile_throw(std::vector& res, std::vector& args, Src } } -AsmOp compile_throw_if_unless(std::vector& res, std::vector& args) { +AsmOp compile_throw_if_unless(std::vector& res, std::vector& args, SrcLocation) { tolk_assert(res.empty() && args.size() == 3); VarDescr &x = args[0], &y = args[1], &z = args[2]; if (!z.always_true() && !z.always_false()) { @@ -1007,10 +1023,10 @@ AsmOp compile_bool_const(std::vector& res, std::vector& args return AsmOp::Const(val ? "TRUE" : "FALSE"); } -// (slice, int) load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; -// (slice, int) load_uint(slice s, int len) asm( -> 1 0) "LDUX"; -// int preload_int(slice s, int len) asm "PLDIX"; -// int preload_uint(slice s, int len) asm "PLDUX"; +// fun loadInt (mutate s: slice, len: int): int asm(s len -> 1 0) "LDIX"; +// fun loadUint (mutate s: slice, len: int): int asm( -> 1 0) "LDUX"; +// fun preloadInt (s: slice, len: int): int asm "PLDIX"; +// fun preloadUint(s: slice, len: int): int asm "PLDUX"; AsmOp compile_fetch_int(std::vector& res, std::vector& args, bool fetch, bool sgnd) { tolk_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch); auto &y = args[1], &r = res.back(); @@ -1032,8 +1048,8 @@ AsmOp compile_fetch_int(std::vector& res, std::vector& args, return exec_op((fetch ? "LD"s : "PLD"s) + (sgnd ? "IX" : "UX"), 2, 1 + (unsigned)fetch); } -// builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; -// builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +// fun storeInt (mutate self: builder, x: int, len: int): self asm(x b len) "STIX"; +// fun storeUint (mutate self: builder, x: int, len: int): self asm(x b len) "STUX"; AsmOp compile_store_int(std::vector& res, std::vector& args, bool sgnd) { tolk_assert(args.size() == 3 && res.size() == 1); auto& z = args[2]; @@ -1044,6 +1060,8 @@ AsmOp compile_store_int(std::vector& res, std::vector& args, return exec_op("ST"s + (sgnd ? "IX" : "UX"), 3, 1); } +// fun loadBits (mutate self: slice, len: int): self asm(s len -> 1 0) "LDSLICEX" +// fun preloadBits(self: slice, len: int): slice asm(s len -> 1 0) "PLDSLICEX" AsmOp compile_fetch_slice(std::vector& res, std::vector& args, bool fetch) { tolk_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch); auto& y = args[1]; @@ -1058,7 +1076,7 @@ AsmOp compile_fetch_slice(std::vector& res, std::vector& arg return exec_op(fetch ? "LDSLICEX" : "PLDSLICEX", 2, 1 + (unsigned)fetch); } -// _at(tuple t, int index) asm "INDEXVAR"; +// fun at(t: tuple, index: int): X asm "INDEXVAR"; AsmOp compile_tuple_at(std::vector& res, std::vector& args, SrcLocation) { tolk_assert(args.size() == 2 && res.size() == 1); auto& y = args[1]; @@ -1079,102 +1097,146 @@ AsmOp compile_is_null(std::vector& res, std::vector& args, S void define_builtins() { using namespace std::placeholders; - auto Unit = TypeExpr::new_unit(); - auto Int = TypeExpr::new_atomic(TypeExpr::_Int); - auto Cell = TypeExpr::new_atomic(TypeExpr::_Cell); - auto Slice = TypeExpr::new_atomic(TypeExpr::_Slice); - auto Builder = TypeExpr::new_atomic(TypeExpr::_Builder); - // auto Null = TypeExpr::new_atomic(TypeExpr::_Null); - auto Tuple = TypeExpr::new_atomic(TypeExpr::_Tuple); - auto Int2 = TypeExpr::new_tensor({Int, Int}); - auto Int3 = TypeExpr::new_tensor({Int, Int, Int}); - auto TupleInt = TypeExpr::new_tensor({Tuple, Int}); - auto SliceInt = TypeExpr::new_tensor({Slice, Int}); - auto X = TypeExpr::new_var(0); - auto Y = TypeExpr::new_var(1); - auto Z = TypeExpr::new_var(2); - auto XY = TypeExpr::new_tensor({X, Y}); - auto arith_bin_op = TypeExpr::new_map(Int2, Int); - auto arith_un_op = TypeExpr::new_map(Int, Int); - auto impure_un_op = TypeExpr::new_map(Int, Unit); - auto fetch_int_op = TypeExpr::new_map(SliceInt, SliceInt); - auto prefetch_int_op = TypeExpr::new_map(SliceInt, Int); - auto store_int_op = TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), Builder); - auto store_int_method = - TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), TypeExpr::new_tensor({Builder, Unit})); - auto fetch_slice_op = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice})); - auto prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice); - //auto arith_null_op = TypeExpr::new_map(TypeExpr::new_unit(), Int); - auto throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit)); - - // prevent unused vars warnings (there vars are created to acquire initial id of TypeExpr::value) - static_cast(Z); - static_cast(XY); - static_cast(Cell); - - define_builtin_func("_+_", arith_bin_op, compile_add); - define_builtin_func("_-_", arith_bin_op, compile_sub); - define_builtin_func("-_", arith_un_op, compile_unary_minus); - define_builtin_func("+_", arith_un_op, compile_unary_plus); - define_builtin_func("_*_", arith_bin_op, compile_mul); - define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); - define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); - define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); - define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); - define_builtin_func("_<<_", arith_bin_op, compile_lshift); - define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); - define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); - define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); - define_builtin_func("!_", arith_un_op, compile_logical_not); - define_builtin_func("~_", arith_un_op, compile_bitwise_not); - define_builtin_func("_&_", arith_bin_op, compile_bitwise_and); - define_builtin_func("_|_", arith_bin_op, compile_bitwise_or); - define_builtin_func("_^_", arith_bin_op, compile_bitwise_xor); - define_builtin_func("^_+=_", arith_bin_op, compile_add); - define_builtin_func("^_-=_", arith_bin_op, compile_sub); - define_builtin_func("^_*=_", arith_bin_op, compile_mul); - define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); - define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); - define_builtin_func("^_<<=_", arith_bin_op, compile_lshift); - define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); - define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and); - define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or); - define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor); - define_builtin_func("mulDivFloor", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1)); - define_builtin_func("mulDivRound", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0)); - define_builtin_func("mulDivCeil", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1)); - define_builtin_func("mulDivMod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2)); - define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2)); - define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5)); - define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4)); - define_builtin_func("_>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 1)); - define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6)); - define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3)); - define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7)); - define_builtin_func("__true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true)); - define_builtin_func("__false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false)); - define_builtin_func("__null", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_unit(), X)), AsmOp::Const("PUSHNULL")); - define_builtin_func("__isNull", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null); - define_builtin_func("__throw", impure_un_op, compile_throw, true); - define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg, true); - define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), std::bind(compile_throw_if_unless, _1, _2), true); - define_builtin_func("loadInt", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0}); - define_builtin_func("loadUint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0}); - define_builtin_func("loadBits", fetch_slice_op, std::bind(compile_fetch_slice, _1, _2, true), {}, {1, 0}); - define_builtin_func("preloadInt", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true)); - define_builtin_func("preloadUint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false)); - define_builtin_func("preloadBits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false)); - define_builtin_func("storeInt", store_int_op, std::bind(compile_store_int, _1, _2, true), {1, 0, 2}); - define_builtin_func("storeUint", store_int_op, std::bind(compile_store_int, _1, _2, false), {1, 0, 2}); - define_builtin_func("~storeInt", store_int_method, std::bind(compile_store_int, _1, _2, true), {1, 0, 2}); - define_builtin_func("~storeUint", store_int_method, std::bind(compile_store_int, _1, _2, false), {1, 0, 2}); - define_builtin_func("tupleAt", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at); + + TypeExpr* Unit = TypeExpr::new_unit(); + TypeExpr* Int = TypeExpr::new_atomic(TypeExpr::_Int); + TypeExpr* Slice = TypeExpr::new_atomic(TypeExpr::_Slice); + TypeExpr* Builder = TypeExpr::new_atomic(TypeExpr::_Builder); + TypeExpr* Tuple = TypeExpr::new_atomic(TypeExpr::_Tuple); + TypeExpr* Int2 = TypeExpr::new_tensor({Int, Int}); + TypeExpr* Int3 = TypeExpr::new_tensor({Int, Int, Int}); + TypeExpr* TupleInt = TypeExpr::new_tensor({Tuple, Int}); + TypeExpr* SliceInt = TypeExpr::new_tensor({Slice, Int}); + TypeExpr* X = TypeExpr::new_var(0); + TypeExpr* arith_bin_op = TypeExpr::new_map(Int2, Int); + TypeExpr* arith_un_op = TypeExpr::new_map(Int, Int); + TypeExpr* impure_un_op = TypeExpr::new_map(Int, Unit); + TypeExpr* fetch_int_op_mutate = TypeExpr::new_map(SliceInt, SliceInt); + TypeExpr* prefetch_int_op = TypeExpr::new_map(SliceInt, Int); + TypeExpr* store_int_mutate = TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), TypeExpr::new_tensor({Builder, Unit})); + TypeExpr* fetch_slice_op_mutate = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice})); + TypeExpr* prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice); + TypeExpr* throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit)); + + define_builtin_func("_+_", arith_bin_op, compile_add, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_-_", arith_bin_op, compile_sub, + SymValFunc::flagMarkedAsPure); + define_builtin_func("-_", arith_un_op, compile_unary_minus, + SymValFunc::flagMarkedAsPure); + define_builtin_func("+_", arith_un_op, compile_unary_plus, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_*_", arith_bin_op, compile_mul, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_<<_", arith_bin_op, compile_lshift, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("!_", arith_un_op, compile_logical_not, + SymValFunc::flagMarkedAsPure); + define_builtin_func("~_", arith_un_op, compile_bitwise_not, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_&_", arith_bin_op, compile_bitwise_and, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_|_", arith_bin_op, compile_bitwise_or, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_^_", arith_bin_op, compile_bitwise_xor, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_+=_", arith_bin_op, compile_add, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_-=_", arith_bin_op, compile_sub, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_*=_", arith_bin_op, compile_mul, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_<<=_", arith_bin_op, compile_lshift, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or, + SymValFunc::flagMarkedAsPure); + define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor, + SymValFunc::flagMarkedAsPure); + define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3), + SymValFunc::flagMarkedAsPure); + define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7), + SymValFunc::flagMarkedAsPure); + define_builtin_func("mulDivFloor", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("mulDivRound", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0), + SymValFunc::flagMarkedAsPure); + define_builtin_func("mulDivCeil", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1), + SymValFunc::flagMarkedAsPure); + define_builtin_func("mulDivMod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2), + SymValFunc::flagMarkedAsPure); + define_builtin_func("__true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true), + SymValFunc::flagMarkedAsPure); + define_builtin_func("__false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false), + SymValFunc::flagMarkedAsPure); + define_builtin_func("__null", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_unit(), X)), AsmOp::Const("PUSHNULL"), + SymValFunc::flagMarkedAsPure); + define_builtin_func("__isNull", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null, + SymValFunc::flagMarkedAsPure); + define_builtin_func("__throw", impure_un_op, compile_throw, + 0); + define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg, + 0); + define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), compile_throw_if_unless, + 0); + define_builtin_func("loadInt", fetch_int_op_mutate, std::bind(compile_fetch_int, _1, _2, true, true), + SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf, {}, {1, 0}); + define_builtin_func("loadUint", fetch_int_op_mutate, std::bind(compile_fetch_int, _1, _2, true, false), + SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf, {}, {1, 0}); + define_builtin_func("loadBits", fetch_slice_op_mutate, std::bind(compile_fetch_slice, _1, _2, true), + SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf, {}, {1, 0}); + define_builtin_func("preloadInt", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true), + SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf); + define_builtin_func("preloadUint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false), + SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf); + define_builtin_func("preloadBits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false), + SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf); + define_builtin_func("storeInt", store_int_mutate, std::bind(compile_store_int, _1, _2, true), + SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf | SymValFunc::flagReturnsSelf, {1, 0, 2}, {}); + define_builtin_func("storeUint", store_int_mutate, std::bind(compile_store_int, _1, _2, false), + SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf | SymValFunc::flagReturnsSelf, {1, 0, 2}, {}); + define_builtin_func("tupleAt", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at, + SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf); define_builtin_func("debugPrint", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)), - AsmOp::Custom("s0 DUMP DROP", 1, 1), true); + AsmOp::Custom("s0 DUMP DROP", 1, 1), + 0); define_builtin_func("debugPrintString", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)), - AsmOp::Custom("STRDUMP DROP", 1, 1), true); + AsmOp::Custom("STRDUMP DROP", 1, 1), + 0); define_builtin_func("debugDumpStack", TypeExpr::new_map(Unit, Unit), - AsmOp::Custom("DUMPSTK", 0, 0), true); + AsmOp::Custom("DUMPSTK", 0, 0), + 0); } } // namespace tolk diff --git a/tolk/gen-abscode.cpp b/tolk/gen-abscode.cpp index 97fb5d3f2..fb085ae9c 100644 --- a/tolk/gen-abscode.cpp +++ b/tolk/gen-abscode.cpp @@ -41,21 +41,22 @@ Expr::Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list _arglist) } } -bool Expr::deduce_type() { +void Expr::deduce_type() { if (e_type) { - return true; + return; } switch (cls) { case _Apply: { if (!sym) { - return false; + return; } - SymVal* sym_val = dynamic_cast(sym->value); + SymValFunc* sym_val = dynamic_cast(sym->value); if (!sym_val || !sym_val->get_type()) { - return false; + return; } std::vector arg_types; - for (const auto& arg : args) { + arg_types.reserve(args.size()); + for (const Expr* arg : args) { arg_types.push_back(arg->e_type); } TypeExpr* fun_type = TypeExpr::new_map(TypeExpr::new_tensor(arg_types), TypeExpr::new_hole()); @@ -69,7 +70,7 @@ bool Expr::deduce_type() { } e_type = fun_type->args[1]; TypeExpr::remove_indirect(e_type); - return true; + return; } case _VarApply: { tolk_assert(args.size() == 2); @@ -84,7 +85,27 @@ bool Expr::deduce_type() { } e_type = fun_type->args[1]; TypeExpr::remove_indirect(e_type); - return true; + return; + } + case _GrabMutatedVars: { + tolk_assert(args.size() == 2 && args[0]->cls == _Apply && sym); + SymValFunc* called_f = dynamic_cast(sym->value); + tolk_assert(called_f->has_mutate_params()); + TypeExpr* sym_type = called_f->get_type(); + if (sym_type->constr == TypeExpr::te_ForAll) { + TypeExpr::remove_forall(sym_type); + } + tolk_assert(sym_type->args[1]->constr == TypeExpr::te_Tensor); + e_type = sym_type->args[1]->args[sym_type->args[1]->args.size() - 1]; + TypeExpr::remove_indirect(e_type); + return; + } + case _ReturnSelf: { + tolk_assert(args.size() == 2 && sym); + Expr* this_arg = args[1]; + e_type = this_arg->e_type; + TypeExpr::remove_indirect(e_type); + return; } case _Letop: { tolk_assert(args.size() == 2); @@ -99,25 +120,7 @@ bool Expr::deduce_type() { } e_type = args[0]->e_type; TypeExpr::remove_indirect(e_type); - return true; - } - case _LetFirst: { - tolk_assert(args.size() == 2); - TypeExpr* rhs_type = TypeExpr::new_tensor({args[0]->e_type, TypeExpr::new_hole()}); - try { - // std::cerr << "in implicit assignment of a modifying method: " << rhs_type << " and " << args[1]->e_type << std::endl; - unify(rhs_type, args[1]->e_type); - } catch (UnifyError& ue) { - std::ostringstream os; - os << "cannot implicitly assign an expression of type " << args[1]->e_type - << " to a variable or pattern of type " << rhs_type << " in modifying method `" << G.symbols.get_name(val) - << "` : " << ue; - throw ParseError(here, os.str()); - } - e_type = rhs_type->args[1]; - TypeExpr::remove_indirect(e_type); - // std::cerr << "result type is " << e_type << std::endl; - return true; + return; } case _CondExpr: { tolk_assert(args.size() == 3); @@ -139,46 +142,46 @@ bool Expr::deduce_type() { } e_type = args[1]->e_type; TypeExpr::remove_indirect(e_type); - return true; + return; } + default: + throw Fatal("unexpected cls=" + std::to_string(cls) + " in Expr::deduce_type()"); } - return false; } -int Expr::define_new_vars(CodeBlob& code) { +void Expr::define_new_vars(CodeBlob& code) { switch (cls) { case _Tensor: case _MkTuple: { - int res = 0; - for (const auto& x : args) { - res += x->define_new_vars(code); + for (Expr* item : args) { + item->define_new_vars(code); } - return res; + break; } case _Var: if (val < 0) { - val = code.create_var(false, e_type, sym, here); - return 1; + val = code.create_var(e_type, sym->sym_idx, here); + sym->value->idx = val; } break; case _Hole: if (val < 0) { - val = code.create_var(true, e_type, nullptr, here); + val = code.create_tmp_var(e_type, here); } break; + default: + break; } - return 0; } -int Expr::predefine_vars() { +void Expr::predefine_vars() { switch (cls) { case _Tensor: case _MkTuple: { - int res = 0; - for (const auto& x : args) { - res += x->predefine_vars(); + for (Expr* item : args) { + item->predefine_vars(); } - return res; + break; } case _Var: if (!sym) { @@ -188,12 +191,15 @@ int Expr::predefine_vars() { if (!sym) { throw ParseError{here, std::string{"redefined variable `"} + G.symbols.get_name(~val) + "`"}; } - sym->value = new SymVal{SymValKind::_Var, -1, e_type}; - return 1; + sym->value = new SymValVariable(-1, e_type); + if (is_immutable()) { + dynamic_cast(sym->value)->flags |= SymValVariable::flagImmutable; + } } break; + default: + break; } - return 0; } var_idx_t Expr::new_tmp(CodeBlob& code) const { @@ -217,7 +223,7 @@ std::vector pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, Src auto unpacked_type = rhs->e_type->args.at(0); std::vector tmp{code.create_tmp_var(unpacked_type, rhs->here)}; code.emplace_back(lhs->here, Op::_UnTuple, tmp, std::move(right)); - auto tvar = new Expr{Expr::_Var}; + auto tvar = new Expr{Expr::_Var, lhs->here}; tvar->set_val(tmp[0]); tvar->set_location(rhs->here); tvar->e_type = unpacked_type; @@ -255,7 +261,7 @@ std::vector pre_compile_tensor(const std::vector& args, CodeB res_lists[i] = args[i]->pre_compile(code, lval_globs); for (size_t j = 0; j < res_lists[i].size(); ++j) { TmpVar& var = code.vars.at(res_lists[i][j]); - if (!lval_globs && !var.is_tmp_unnamed) { + if (!lval_globs && !var.is_unnamed()) { var.on_modification.push_back([&modified_vars, i, j, cur_ops = code.cur_ops, done = false](SrcLocation here) mutable { if (!done) { done = true; @@ -303,39 +309,39 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vector res; - SymDef* applied_sym = sym; - auto func = dynamic_cast(applied_sym->value); - // replace `beginCell()` with `begin_cell()` - // todo it should be done at AST level, see comment above detect_if_function_just_wraps_another() - if (func && func->is_just_wrapper_for_another_f()) { - // todo currently, f is inlined only if anotherF is declared (and processed) before - if (!dynamic_cast(func)->code) { // if anotherF is processed after - func->flags |= SymValFunc::flagUsedAsNonCall; - res = pre_compile_tensor(args, code, lval_globs); - } else { - // body is { Op::_Import; Op::_Call; Op::_Return; } - const std::unique_ptr& op_call = dynamic_cast(func)->code->ops->next; - applied_sym = op_call->fun_ref; - // a function may call anotherF with shuffled arguments: f(x,y) { return anotherF(y,x) } - // then op_call looks like (_1,_0), so use op_call->right for correct positions in Op::_Call below - // it's correct, since every argument has width 1 - std::vector res_inner = pre_compile_tensor(args, code, lval_globs); - res.reserve(res_inner.size()); - for (var_idx_t right_idx : op_call->right) { - res.emplace_back(res_inner[right_idx]); - } - } - } else { - res = pre_compile_tensor(args, code, lval_globs); - } + std::vector res = pre_compile_tensor(args, code, lval_globs);; auto rvect = new_tmp_vect(code); - auto& op = code.emplace_back(here, Op::_Call, rvect, res, applied_sym); + auto& op = code.emplace_back(here, Op::_Call, rvect, res, sym); if (flags & _IsImpure) { op.set_impure(code); } return rvect; } + case _GrabMutatedVars: { + SymValFunc* func_val = dynamic_cast(sym->value); + tolk_assert(func_val && func_val->has_mutate_params()); + tolk_assert(args.size() == 2 && args[0]->cls == _Apply && args[1]->cls == _Tensor); + auto right = args[0]->pre_compile(code); // apply (returning function result and mutated) + std::vector> local_globs; + if (!lval_globs) { + lval_globs = &local_globs; + } + auto left = args[1]->pre_compile(code, lval_globs); // mutated (lvalue) + auto rvect = new_tmp_vect(code); + left.push_back(rvect[0]); + for (var_idx_t v : left) { + code.on_var_modification(v, here); + } + code.emplace_back(here, Op::_Let, std::move(left), std::move(right)); + add_set_globs(code, local_globs, here); + return rvect; + } + case _ReturnSelf: { + tolk_assert(args.size() == 2 && sym); + Expr* this_arg = args[1]; + auto right = args[0]->pre_compile(code); + return this_arg->pre_compile(code); + } case _Var: case _Hole: if (val < 0) { @@ -372,7 +378,10 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vector(sym->value)) { fun_ref->flags |= SymValFunc::flagUsedAsNonCall; if (!fun_ref->arg_order.empty() || !fun_ref->ret_order.empty()) { - throw ParseError(here, "Saving " + sym->name() + " into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack"); + throw ParseError(here, "saving `" + sym->name() + "` into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack"); + } + if (fun_ref->has_mutate_params()) { + throw ParseError(here, "saving `" + sym->name() + "` into a variable is impossible, since it has `mutate` parameters and thus can only be called directly"); } } auto rvect = new_tmp_vect(code); @@ -387,22 +396,6 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vectorpre_compile(code); - std::vector> local_globs; - if (!lval_globs) { - lval_globs = &local_globs; - } - auto left = args[0]->pre_compile(code, lval_globs); - left.push_back(rvect[0]); - for (var_idx_t v : left) { - code.on_var_modification(v, here); - } - code.emplace_back(here, Op::_Let, std::move(left), std::move(right)); - add_set_globs(code, local_globs, here); - return rvect; - } case _MkTuple: { auto left = new_tmp_vect(code); auto right = args[0]->pre_compile(code); diff --git a/tolk/lexer.cpp b/tolk/lexer.cpp index 0a2dd79c2..17eb4544c 100644 --- a/tolk/lexer.cpp +++ b/tolk/lexer.cpp @@ -323,8 +323,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { static TokenType maybe_keyword(std::string_view str) { switch (str.size()) { case 1: - if (str == "~") return tok_bitwise_not; // todo attention - if (str == "_") return tok_underscore; // todo attention + if (str == "_") return tok_underscore; break; case 2: if (str == "do") return tok_do; @@ -347,6 +346,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { if (str == "void") return tok_void; if (str == "bool") return tok_bool; if (str == "auto") return tok_auto; + if (str == "self") return tok_self; if (str == "tolk") return tok_tolk; if (str == "type") return tok_type; if (str == "enum") return tok_enum; @@ -368,6 +368,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { if (str == "assert") return tok_assert; if (str == "import") return tok_import; if (str == "global") return tok_global; + if (str == "mutate") return tok_mutate; if (str == "repeat") return tok_repeat; if (str == "struct") return tok_struct; if (str == "export") return tok_export; @@ -394,8 +395,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { lex->skip_chars(1); while (!lex->is_eof()) { char c = lex->char_at(); - // the pattern of valid identifier first symbol is provided in trie, here we test for identifier middle - bool allowed_in_identifier = std::isalnum(c) || c == '_' || c == '$' || c == '?' || c == '!' || c == '\''; + bool allowed_in_identifier = std::isalnum(c) || c == '_' || c == '$'; if (!allowed_in_identifier) { break; } @@ -438,28 +438,6 @@ struct ChunkIdentifierInBackticks final : ChunkLexerBase { } }; -// Handle ~`some_method` and .`some_method` todo to be removed later -struct ChunkDotTildeAndBackticks final : ChunkLexerBase { - bool parse(Lexer* lex) const override { - const char* str_begin = lex->c_str(); - lex->skip_chars(2); - while (!lex->is_eof() && lex->char_at() != '`' && lex->char_at() != '\n') { - lex->skip_chars(1); - } - if (lex->char_at() != '`') { - lex->error("unclosed backtick `"); - } - - std::string_view in_backticks(str_begin + 2, lex->c_str() - str_begin - 2); - std::string full = std::string(1, *str_begin) + static_cast(in_backticks); - std::string* allocated = new std::string(full); - lex->skip_chars(1); - std::string_view str_val(allocated->c_str(), allocated->size()); - lex->add_token(tok_identifier, str_val); - return true; - } -}; - // // ---------------------------------------------------------------------- // Here we define a grammar of Tolk. @@ -500,11 +478,8 @@ struct TolkLanguageGrammar { trie.add_prefix("\n", singleton()); trie.add_pattern("[0-9]", singleton()); - // todo think of . ~ - trie.add_pattern("[a-zA-Z_$.~]", singleton()); + trie.add_pattern("[a-zA-Z_$]", singleton()); trie.add_prefix("`", singleton()); - // todo to be removed after ~ becomes invalid and . becomes a separate token - trie.add_pattern("[.~]`", singleton()); register_token("+", 1, tok_plus); register_token("-", 1, tok_minus); @@ -528,6 +503,8 @@ struct TolkLanguageGrammar { register_token("&", 1, tok_bitwise_and); register_token("|", 1, tok_bitwise_or); register_token("^", 1, tok_bitwise_xor); + register_token("~", 1, tok_bitwise_not); + register_token(".", 1, tok_dot); register_token("==", 2, tok_eq); register_token("!=", 2, tok_neq); register_token("<=", 2, tok_leq); diff --git a/tolk/lexer.h b/tolk/lexer.h index 8e04018cd..8a25f9526 100644 --- a/tolk/lexer.h +++ b/tolk/lexer.h @@ -38,6 +38,8 @@ enum TokenType { tok_var, tok_val, tok_redef, + tok_mutate, + tok_self, tok_annotation_at, tok_colon, @@ -52,6 +54,7 @@ enum TokenType { tok_null, tok_identifier, + tok_dot, tok_plus, tok_minus, diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index 3e713e726..c735698cd 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -37,7 +37,87 @@ static int calc_sym_idx(std::string_view sym_name) { return G.symbols.lookup(sym_name); } +void Expr::fire_error_rvalue_expected() const { + // generally, almost all vertices are rvalue, that's why code leading to "not rvalue" + // should be very strange, like `var x = _` + throw ParseError(here, "rvalue expected"); +} + +void Expr::fire_error_lvalue_expected(const std::string& details) const { + // "lvalue expected" is when a user modifies something unmodifiable + // example: `f() = 32` + // example: `loadUint(c.beginParse(), 32)` (since `loadUint()` mutates the first argument) + throw ParseError(here, "lvalue expected (" + details + ")"); +} + +void Expr::fire_error_modifying_immutable(const std::string& details) const { + // "modifying immutable variable" is when a user assigns to a variable declared `val` + // example: `immutable_val = 32` + // example: `(regular_var, immutable_val) = f()` + // for better error message, try to print out variable name if possible + std::string variable_name; + if (cls == _Var || cls == _Const) { + variable_name = sym->name(); + } else if (cls == _Tensor || cls == _MkTuple) { + for (const Expr* arg : (cls == _Tensor ? args : args[0]->args)) { + if (arg->is_immutable() && (arg->cls == _Var || arg->cls == _Const)) { + variable_name = arg->sym->name(); + break; + } + } + } + + if (variable_name == "self") { + throw ParseError(here, "modifying `self` (" + details + "), which is immutable by default; probably, you want to declare `mutate self`"); + } else if (!variable_name.empty()) { + throw ParseError(here, "modifying an immutable variable `" + variable_name + "` (" + details + ")"); + } else { + throw ParseError(here, "modifying an immutable variable (" + details + ")"); + } +} + +GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN +static void fire_error_invalid_mutate_arg_passed(SrcLocation loc, const SymDef* func_sym, const SymDef* param_sym, bool called_as_method, bool arg_passed_as_mutate, AnyV arg_expr) { + std::string func_name = func_sym->name(); + std::string arg_str(arg_expr->type == ast_identifier ? arg_expr->as()->name : "obj"); + const SymValFunc* func_val = dynamic_cast(func_sym->value); + const SymValVariable* param_val = dynamic_cast(param_sym->value); + + // case: `loadInt(cs, 32)`; suggest: `cs.loadInt(32)` + if (param_val->is_mutate_parameter() && !arg_passed_as_mutate && !called_as_method && param_val->idx == 0 && func_val->does_accept_self()) { + throw ParseError(loc, "`" + func_name + "` is a mutating method; consider calling `" + arg_str + "." + func_name + "()`, not `" + func_name + "(" + arg_str + ")`"); + } + // case: `cs.mutating_function()`; suggest: `mutating_function(mutate cs)` or make it a method + if (param_val->is_mutate_parameter() && called_as_method && param_val->idx == 0 && !func_val->does_accept_self()) { + throw ParseError(loc, "function `" + func_name + "` mutates parameter `" + param_sym->name() + "`; consider calling `" + func_name + "(mutate " + arg_str + ")`, not `" + arg_str + "." + func_name + "`(); alternatively, rename parameter to `self` to make it a method"); + } + // case: `mutating_function(arg)`; suggest: `mutate arg` + if (param_val->is_mutate_parameter() && !arg_passed_as_mutate) { + throw ParseError(loc, "function `" + func_name + "` mutates parameter `" + param_sym->name() + "`; you need to specify `mutate` when passing an argument, like `mutate " + arg_str + "`"); + } + // case: `usual_function(mutate arg)` + if (!param_val->is_mutate_parameter() && arg_passed_as_mutate) { + throw ParseError(loc, "incorrect `mutate`, since `" + func_name + "` does not mutate this parameter"); + } + throw Fatal("unreachable"); +} + +namespace blk_fl { +enum { end = 1, ret = 2, empty = 4 }; +typedef int val; +constexpr val init = end | empty; +void combine(val& x, const val y) { + x |= y & ret; + x &= y | ~(end | empty); +} +void combine_parallel(val& x, const val y) { + x &= y | ~(ret | empty); + x |= y & end; +} +} // namespace blk_fl + Expr* process_expr(AnyV v, CodeBlob& code); +blk_fl::val process_statement(AnyV v, CodeBlob& code); static void check_global_func(SrcLocation loc, sym_idx_t func_name) { SymDef* sym_def = lookup_symbol(func_name); @@ -46,22 +126,6 @@ static void check_global_func(SrcLocation loc, sym_idx_t func_name) { } } -static Expr* make_func_apply(Expr* fun, Expr* x) { - Expr* res{nullptr}; - if (fun->cls == Expr::_GlobFunc) { - if (x->cls == Expr::_Tensor) { - res = new Expr{Expr::_Apply, fun->sym, x->args}; - } else { - res = new Expr{Expr::_Apply, fun->sym, {x}}; - } - res->flags = Expr::_IsRvalue | (fun->flags & Expr::_IsImpure); - } else { - res = new Expr{Expr::_VarApply, {fun, x}}; - res->flags = Expr::_IsRvalue; - } - return res; -} - static void check_import_exists_when_using_sym(AnyV v_usage, const SymDef* used_sym) { if (!v_usage->loc.is_symbol_from_same_or_builtin_file(used_sym->loc)) { const SrcFile* declared_in = used_sym->loc.get_src_file(); @@ -77,7 +141,7 @@ static void check_import_exists_when_using_sym(AnyV v_usage, const SymDef* used_ } } -static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_name, TypeExpr* var_type) { +static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_name, TypeExpr* var_type, bool is_immutable) { SymDef* sym = lookup_symbol(calc_sym_idx(var_name)); if (sym) { // creating a new variable, but something found in symtable if (sym->level != G.scope_level) { @@ -89,7 +153,7 @@ static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_nam Expr* x = new Expr{Expr::_Var, loc}; x->val = ~calc_sym_idx(var_name); x->e_type = var_type; - x->flags = Expr::_IsLvalue; + x->flags = Expr::_IsLvalue | (is_immutable ? Expr::_IsImmutable : 0); return x; } @@ -109,8 +173,13 @@ static Expr* process_expr(V v, CodeBlob& code) { t == tok_set_mod || t == tok_set_lshift || t == tok_set_rshift || t == tok_set_bitwise_and || t == tok_set_bitwise_or || t == tok_set_bitwise_xor) { Expr* x = process_expr(v->get_lhs(), code); - x->chk_lvalue(); x->chk_rvalue(); + if (!x->is_lvalue()) { + x->fire_error_lvalue_expected("left side of assignment"); + } + if (x->is_immutable()) { + x->fire_error_modifying_immutable("left side of assignment"); + } sym_idx_t name = G.symbols.lookup_add("^_" + operator_name + "_"); Expr* y = process_expr(v->get_rhs(), code); y->chk_rvalue(); @@ -126,7 +195,12 @@ static Expr* process_expr(V v, CodeBlob& code) { } if (t == tok_assign) { Expr* x = process_expr(v->get_lhs(), code); - x->chk_lvalue(); + if (!x->is_lvalue()) { + x->fire_error_lvalue_expected("left side of assignment"); + } + if (x->is_immutable()) { + x->fire_error_modifying_immutable("left side of assignment"); + } Expr* y = process_expr(v->get_rhs(), code); y->chk_rvalue(); x->predefine_vars(); @@ -191,54 +265,6 @@ static Expr* process_expr(V v, CodeBlob& code) { return res; } -static Expr* process_expr(V v, CodeBlob& code) { - Expr* res = process_expr(v->get_lhs(), code); - bool modify = v->method_name[0] == '~'; - Expr* obj = res; - if (modify) { - obj->chk_lvalue(); - } else { - obj->chk_rvalue(); - } - sym_idx_t name_idx = calc_sym_idx(v->method_name); - const SymDef* sym = lookup_symbol(name_idx); - if (!sym || !dynamic_cast(sym->value)) { - sym_idx_t name1 = G.symbols.lookup(v->method_name.substr(1)); - if (name1) { - const SymDef* sym1 = lookup_symbol(name1); - if (sym1 && dynamic_cast(sym1->value)) { - name_idx = name1; - } - } - } - check_global_func(v->loc, name_idx); - sym = lookup_symbol(name_idx); - SymValFunc* val = sym ? dynamic_cast(sym->value) : nullptr; - if (!val) { - v->error("undefined method call"); - } - Expr* x = process_expr(v->get_arg(), code); - x->chk_rvalue(); - if (x->cls == Expr::_Tensor) { - res = new Expr{Expr::_Apply, name_idx, {obj}}; - res->args.insert(res->args.end(), x->args.begin(), x->args.end()); - } else { - res = new Expr{Expr::_Apply, name_idx, {obj, x}}; - } - res->here = v->loc; - res->flags = Expr::_IsRvalue | (val->is_marked_as_pure() ? 0 : Expr::_IsImpure); - res->deduce_type(); - if (modify) { - Expr* tmp = res; - res = new Expr{Expr::_LetFirst, {obj->copy(), tmp}}; - res->here = v->loc; - res->flags = tmp->flags; - res->set_val(name_idx); - res->deduce_type(); - } - return res; -} - static Expr* process_expr(V v, CodeBlob& code) { Expr* cond = process_expr(v->get_cond(), code); cond->chk_rvalue(); @@ -253,19 +279,194 @@ static Expr* process_expr(V v, CodeBlob& code) { return res; } -static Expr* process_expr(V v, CodeBlob& code) { +static Expr* process_function_arguments(SymDef* func_sym, V v, Expr* lhs_of_dot_call, CodeBlob& code) { + SymValFunc* func_val = dynamic_cast(func_sym->value); + int delta_self = lhs_of_dot_call ? 1 : 0; + int n_arguments = static_cast(v->get_arguments().size()) + delta_self; + int n_parameters = static_cast(func_val->parameters.size()); + + // Tolk doesn't have optional parameters currently, so just compare counts + if (n_parameters < n_arguments) { + v->error("too many arguments in call to `" + func_sym->name() + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); + } + if (n_arguments < n_parameters) { + v->error("too few arguments in call to `" + func_sym->name() + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self)); + } + + std::vector apply_args; + apply_args.reserve(n_arguments); + if (lhs_of_dot_call) { + apply_args.push_back(lhs_of_dot_call); + } + for (int i = delta_self; i < n_arguments; ++i) { + auto v_arg = v->get_arg(i - delta_self); + if (SymDef* param_sym = func_val->parameters[i]) { // can be null (for underscore parameter) + SymValVariable* param_val = dynamic_cast(param_sym->value); + if (param_val->is_mutate_parameter() != v_arg->passed_as_mutate) { + fire_error_invalid_mutate_arg_passed(v_arg->loc, func_sym, param_sym, false, v_arg->passed_as_mutate, v_arg->get_expr()); + } + } + + Expr* arg = process_expr(v_arg->get_expr(), code); + arg->chk_rvalue(); + apply_args.push_back(arg); + } + + Expr* apply = new Expr{Expr::_Apply, func_sym, std::move(apply_args)}; + apply->flags = Expr::_IsRvalue | (!func_val->is_marked_as_pure() * Expr::_IsImpure); + apply->here = v->loc; + apply->deduce_type(); + + return apply; +} + +static Expr* process_function_call(V v, CodeBlob& code) { // special error for "null()" which is a FunC syntax if (v->get_called_f()->type == ast_null_keyword) { v->error("null is not a function: use `null`, not `null()`"); } - Expr* res = process_expr(v->get_called_f(), code); - Expr* x = process_expr(v->get_called_arg(), code); - x->chk_rvalue(); - res = make_func_apply(res, x); - res->here = v->loc; - res->deduce_type(); - return res; + // most likely it's a global function, but also may be `some_var(args)` or even `getF()(args)` + Expr* lhs = process_expr(v->get_called_f(), code); + if (lhs->cls != Expr::_GlobFunc) { + Expr* tensor_arg = new Expr(Expr::_Tensor, v->loc); + std::vector type_list; + type_list.reserve(v->get_num_args()); + for (int i = 0; i < v->get_num_args(); ++i) { + auto v_arg = v->get_arg(i); + if (v_arg->passed_as_mutate) { + v_arg->error("`mutate` used for non-mutate argument"); + } + Expr* arg = process_expr(v_arg->get_expr(), code); + arg->chk_rvalue(); + tensor_arg->pb_arg(arg); + type_list.push_back(arg->e_type); + } + tensor_arg->flags = Expr::_IsRvalue; + tensor_arg->e_type = TypeExpr::new_tensor(std::move(type_list)); + + Expr* var_apply = new Expr{Expr::_VarApply, {lhs, tensor_arg}}; + var_apply->here = v->loc; + var_apply->flags = Expr::_IsRvalue; + var_apply->deduce_type(); + return var_apply; + } + + Expr* apply = process_function_arguments(lhs->sym, v->get_arg_list(), nullptr, code); + + if (dynamic_cast(apply->sym->value)->has_mutate_params()) { + const std::vector& args = apply->args; + SymValFunc* func_val = dynamic_cast(apply->sym->value); + tolk_assert(func_val->parameters.size() == args.size()); + Expr* grabbed_vars = new Expr(Expr::_Tensor, v->loc); + std::vector type_list; + for (int i = 0; i < static_cast(args.size()); ++i) { + SymDef* param_def = func_val->parameters[i]; + if (param_def && dynamic_cast(param_def->value)->is_mutate_parameter()) { + if (!args[i]->is_lvalue()) { + args[i]->fire_error_lvalue_expected("call a mutating function"); + } + if (args[i]->is_immutable()) { + args[i]->fire_error_modifying_immutable("call a mutating function"); + } + grabbed_vars->pb_arg(args[i]->copy()); + type_list.emplace_back(args[i]->e_type); + } + } + grabbed_vars->flags = Expr::_IsRvalue; + Expr* grab_mutate = new Expr(Expr::_GrabMutatedVars, apply->sym, {apply, grabbed_vars}); + grab_mutate->here = v->loc; + grab_mutate->flags = apply->flags; + grab_mutate->deduce_type(); + return grab_mutate; + } + + return apply; +} + +static Expr* process_dot_method_call(V v, CodeBlob& code) { + sym_idx_t name_idx = calc_sym_idx(v->method_name); + check_global_func(v->loc, name_idx); + SymDef* func_sym = lookup_symbol(name_idx); + SymValFunc* func_val = dynamic_cast(func_sym->value); + tolk_assert(func_val != nullptr); + + Expr* obj = process_expr(v->get_obj(), code); + obj->chk_rvalue(); + + if (func_val->parameters.empty()) { + v->error("`" + func_sym->name() + "` has no parameters and can not be called as method"); + } + if (!func_val->does_accept_self() && func_val->parameters[0] && dynamic_cast(func_val->parameters[0]->value)->is_mutate_parameter()) { + fire_error_invalid_mutate_arg_passed(v->loc, func_sym, func_val->parameters[0], true, false, v->get_obj()); + } + + Expr* apply = process_function_arguments(func_sym, v->get_arg_list(), obj, code); + + Expr* obj_lval = apply->args[0]; + if (!obj_lval->is_lvalue()) { + if (obj_lval->cls == Expr::_ReturnSelf) { + obj_lval = obj_lval->args[1]; + } else { + Expr* tmp_var = create_new_underscore_variable(v->loc, obj_lval->e_type); + tmp_var->define_new_vars(code); + Expr* assign_to_tmp_var = new Expr(Expr::_Letop, {tmp_var, obj_lval}); + assign_to_tmp_var->here = v->loc; + assign_to_tmp_var->flags = Expr::_IsRvalue; + assign_to_tmp_var->deduce_type(); + apply->args[0] = assign_to_tmp_var; + obj_lval = tmp_var; + } + } + + if (func_val->has_mutate_params()) { + tolk_assert(func_val->parameters.size() == apply->args.size()); + Expr* grabbed_vars = new Expr(Expr::_Tensor, v->loc); + std::vector type_list; + for (int i = 0; i < static_cast(apply->args.size()); ++i) { + SymDef* param_sym = func_val->parameters[i]; + if (param_sym && dynamic_cast(param_sym->value)->is_mutate_parameter()) { + Expr* ith_arg = apply->args[i]; + if (ith_arg->is_immutable()) { + ith_arg->fire_error_modifying_immutable("call a mutating method"); + } + + Expr* var_to_mutate = nullptr; + if (ith_arg->is_lvalue()) { + var_to_mutate = ith_arg->copy(); + } else if (i == 0) { + var_to_mutate = obj_lval; + } else { + ith_arg->fire_error_lvalue_expected("call a mutating method"); + } + tolk_assert(var_to_mutate->is_lvalue() && !var_to_mutate->is_immutable()); + grabbed_vars->pb_arg(var_to_mutate); + type_list.emplace_back(var_to_mutate->e_type); + } + } + grabbed_vars->flags = Expr::_IsRvalue; + + Expr* grab_mutate = new Expr(Expr::_GrabMutatedVars, func_sym, {apply, grabbed_vars}); + grab_mutate->here = v->loc; + grab_mutate->flags = apply->flags; + grab_mutate->deduce_type(); + + apply = grab_mutate; + } + + if (func_val->does_return_self()) { + Expr* self_arg = obj_lval; + tolk_assert(self_arg->is_lvalue()); + + Expr* return_self = new Expr(Expr::_ReturnSelf, func_sym, {apply, self_arg}); + return_self->here = v->loc; + return_self->flags = Expr::_IsRvalue; + return_self->deduce_type(); + + apply = return_self; + } + + return apply; } static Expr* process_expr(V v, CodeBlob& code) { @@ -285,7 +486,8 @@ static Expr* process_expr(V v, CodeBlob& code) { for (int i = 1; i < v->size(); ++i) { Expr* x = process_expr(v->get_item(i), code); res->pb_arg(x); - f &= x->flags; + f &= (x->flags | Expr::_IsImmutable); + f |= (x->flags & Expr::_IsImmutable); type_list.push_back(x->e_type); } res->here = v->loc; @@ -315,7 +517,8 @@ static Expr* process_expr(V v, CodeBlob& code) { for (int i = 1; i < v->size(); ++i) { Expr* x = process_expr(v->get_item(i), code); res->pb_arg(x); - f &= x->flags; + f &= (x->flags | Expr::_IsImmutable); + f |= (x->flags & Expr::_IsImmutable); type_list.push_back(x->e_type); } res->here = v->loc; @@ -419,21 +622,36 @@ static Expr* process_expr(V v) { return res; } -static Expr* process_expr([[maybe_unused]] V v) { +static Expr* process_expr(V v) { SymDef* builtin_sym = lookup_symbol(calc_sym_idx("__null")); Expr* res = new Expr{Expr::_Apply, builtin_sym, {}}; + res->here = v->loc; res->flags = Expr::_IsRvalue; res->deduce_type(); return res; } +static Expr* process_expr(V v, CodeBlob& code) { + if (!code.func_val->does_accept_self()) { + v->error("using `self` in a non-member function (it does not accept the first `self` parameter)"); + } + SymDef* sym = lookup_symbol(calc_sym_idx("self")); + tolk_assert(sym); + SymValVariable* sym_val = dynamic_cast(sym->value); + Expr* res = new Expr(Expr::_Var, v->loc); + res->sym = sym; + res->val = sym_val->idx; + res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (sym_val->is_immutable() ? Expr::_IsImmutable : 0); + res->e_type = sym_val->get_type(); + return res; +} + static Expr* process_identifier(V v) { SymDef* sym = lookup_symbol(calc_sym_idx(v->name)); if (sym && dynamic_cast(sym->value)) { check_import_exists_when_using_sym(v, sym); - auto val = dynamic_cast(sym->value); Expr* res = new Expr{Expr::_GlobVar, v->loc}; - res->e_type = val->get_type(); + res->e_type = sym->value->get_type(); res->sym = sym; res->flags = Expr::_IsLvalue | Expr::_IsRvalue | Expr::_IsImpure; return res; @@ -441,19 +659,20 @@ static Expr* process_identifier(V v) { if (sym && dynamic_cast(sym->value)) { check_import_exists_when_using_sym(v, sym); auto val = dynamic_cast(sym->value); - Expr* res = new Expr{Expr::_None, v->loc}; - res->flags = Expr::_IsRvalue; + Expr* res = nullptr; if (val->get_kind() == SymValConst::IntConst) { - res->cls = Expr::_Const; + res = new Expr{Expr::_Const, v->loc}; res->intval = val->get_int_value(); - res->e_type = TypeExpr::new_atomic(tok_int); + res->e_type = TypeExpr::new_atomic(TypeExpr::_Int); } else if (val->get_kind() == SymValConst::SliceConst) { - res->cls = Expr::_SliceConst; + res = new Expr{Expr::_SliceConst, v->loc}; res->strval = val->get_str_value(); - res->e_type = TypeExpr::new_atomic(tok_slice); + res->e_type = TypeExpr::new_atomic(TypeExpr::_Slice); } else { v->error("invalid symbolic constant type"); } + res->flags = Expr::_IsLvalue | Expr::_IsRvalue | Expr::_IsImmutable; + res->sym = sym; return res; } if (sym && dynamic_cast(sym->value)) { @@ -463,28 +682,26 @@ static Expr* process_identifier(V v) { if (!sym) { check_global_func(v->loc, calc_sym_idx(v->name)); sym = lookup_symbol(calc_sym_idx(v->name)); + tolk_assert(sym); } res->sym = sym; - SymVal* val = nullptr; bool impure = false; - if (sym) { - val = dynamic_cast(sym->value); - } - if (!val) { - v->error("undefined identifier '" + static_cast(v->name) + "'"); - } - if (val->kind == SymValKind::_Func) { - res->e_type = val->get_type(); + bool immutable = false; + if (const SymValFunc* func_val = dynamic_cast(sym->value)) { + res->e_type = func_val->get_type(); res->cls = Expr::_GlobFunc; - impure = !dynamic_cast(val)->is_marked_as_pure(); - } else { - tolk_assert(val->idx >= 0); - res->val = val->idx; - res->e_type = val->get_type(); + impure = !func_val->is_marked_as_pure(); + } else if (const SymValVariable* var_val = dynamic_cast(sym->value)) { + tolk_assert(var_val->idx >= 0) + res->val = var_val->idx; + res->e_type = var_val->get_type(); + immutable = var_val->is_immutable(); // std::cerr << "accessing variable " << lex.cur().str << " : " << res->e_type << std::endl; + } else { + v->error("undefined identifier '" + static_cast(v->name) + "'"); } // std::cerr << "accessing symbol " << lex.cur().str << " : " << res->e_type << (val->impure ? " (impure)" : " (pure)") << std::endl; - res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0); + res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0) | (immutable ? Expr::_IsImmutable : 0); res->deduce_type(); return res; } @@ -495,12 +712,12 @@ Expr* process_expr(AnyV v, CodeBlob& code) { return process_expr(v->as(), code); case ast_unary_operator: return process_expr(v->as(), code); - case ast_dot_tilde_call: - return process_expr(v->as(), code); case ast_ternary_operator: return process_expr(v->as(), code); case ast_function_call: - return process_expr(v->as(), code); + return process_function_call(v->as(), code); + case ast_dot_method_call: + return process_dot_method_call(v->as(), code); case ast_parenthesized_expr: return process_expr(v->as()->get_expr(), code); case ast_tensor: @@ -515,6 +732,8 @@ Expr* process_expr(AnyV v, CodeBlob& code) { return process_expr(v->as()); case ast_null_keyword: return process_expr(v->as()); + case ast_self_keyword: + return process_expr(v->as(), code); case ast_identifier: return process_identifier(v->as()); case ast_underscore: @@ -524,31 +743,22 @@ Expr* process_expr(AnyV v, CodeBlob& code) { } } -namespace blk_fl { -enum { end = 1, ret = 2, empty = 4 }; -typedef int val; -constexpr val init = end | empty; -void combine(val& x, const val y) { - x |= y & ret; - x &= y | ~(end | empty); -} -void combine_parallel(val& x, const val y) { - x &= y | ~(ret | empty); - x |= y & end; -} -} // namespace blk_fl - static Expr* process_local_vars_lhs(AnyV v, CodeBlob& code) { switch (v->type) { case ast_local_var: { - if (v->as()->marked_as_redef) { - return process_identifier(v->as()->get_identifier()->as()); + auto v_var = v->as(); + if (v_var->marked_as_redef) { + Expr* redef_var = process_identifier(v_var->get_identifier()->as()); + if (redef_var->is_immutable()) { + redef_var->fire_error_modifying_immutable("left side of assignment"); + } + return redef_var; } - TypeExpr* declared_type = v->as()->declared_type; + TypeExpr* var_type = v_var->declared_type ? v_var->declared_type : TypeExpr::new_hole(); if (auto v_ident = v->as()->get_identifier()->try_as()) { - return create_new_local_variable(v->loc, v_ident->name, declared_type ? declared_type : TypeExpr::new_hole()); + return create_new_local_variable(v->loc, v_ident->name, var_type, v_var->is_immutable); } else { - return create_new_underscore_variable(v->loc, declared_type ? declared_type : TypeExpr::new_hole()); + return create_new_underscore_variable(v->loc, var_type); } } case ast_parenthesized_expr: @@ -588,7 +798,6 @@ static Expr* process_local_vars_lhs(AnyV v, CodeBlob& code) { static blk_fl::val process_vertex(V v, CodeBlob& code) { Expr* x = process_local_vars_lhs(v->get_lhs(), code); - x->chk_lvalue(); Expr* y = process_expr(v->get_assigned_val(), code); y->chk_rvalue(); x->predefine_vars(); @@ -602,11 +811,83 @@ static blk_fl::val process_vertex(V v, CodeBlob& cod return blk_fl::end; } +static bool is_expr_valid_as_return_self(Expr* return_expr) { + // `return self` + if (return_expr->cls == Expr::_Var && return_expr->val == 0) { + return true; + } + if (return_expr->cls == Expr::_ReturnSelf) { + return is_expr_valid_as_return_self(return_expr->args[1]); + } + if (return_expr->cls == Expr::_CondExpr) { + return is_expr_valid_as_return_self(return_expr->args[1]) && is_expr_valid_as_return_self(return_expr->args[2]); + } + return false; +} + +// for mutating functions, having `return expr`, transform it to `return (modify_var1, ..., expr)` +static Expr* wrap_return_value_with_mutate_params(SrcLocation loc, CodeBlob& code, Expr* return_expr) { + Expr* tmp_var; + if (return_expr->cls != Expr::_Var) { + // `return complex_expr` - extract this into temporary variable (eval it before return) + // this is mandatory if it assigns to one of modified vars + tmp_var = create_new_underscore_variable(loc, return_expr->e_type); + tmp_var->predefine_vars(); + tmp_var->define_new_vars(code); + Expr* assign_to_tmp_var = new Expr(Expr::_Letop, {tmp_var, return_expr}); + assign_to_tmp_var->here = loc; + assign_to_tmp_var->flags = tmp_var->flags | Expr::_IsRvalue; + assign_to_tmp_var->deduce_type(); + assign_to_tmp_var->pre_compile(code); + } else { + tmp_var = return_expr; + } + + Expr* ret_tensor = new Expr(Expr::_Tensor, loc); + std::vector type_list; + for (SymDef* p_sym: code.func_val->parameters) { + if (p_sym && dynamic_cast(p_sym->value)->is_mutate_parameter()) { + Expr* p_expr = new Expr{Expr::_Var, p_sym->loc}; + p_expr->sym = p_sym; + p_expr->val = p_sym->value->idx; + p_expr->flags = Expr::_IsRvalue; + p_expr->e_type = p_sym->value->get_type(); + ret_tensor->pb_arg(p_expr); + type_list.emplace_back(p_expr->e_type); + } + } + ret_tensor->pb_arg(tmp_var); + type_list.emplace_back(tmp_var->e_type); + ret_tensor->flags = Expr::_IsRvalue; + ret_tensor->e_type = TypeExpr::new_tensor(std::move(type_list)); + return ret_tensor; +} + static blk_fl::val process_vertex(V v, CodeBlob& code) { Expr* expr = process_expr(v->get_return_value(), code); + if (code.func_val->does_return_self()) { + if (!is_expr_valid_as_return_self(expr)) { + v->error("invalid return from `self` function"); + } + Expr* var_self = new Expr(Expr::_Var, v->loc); + var_self->flags = Expr::_IsRvalue | Expr::_IsLvalue; + var_self->e_type = code.func_val->parameters[0]->value->get_type(); + Expr* assign_to_self = new Expr(Expr::_Letop, {var_self, expr}); + assign_to_self->here = v->loc; + assign_to_self->flags = Expr::_IsRvalue; + assign_to_self->deduce_type(); + assign_to_self->pre_compile(code); + Expr* empty_tensor = new Expr(Expr::_Tensor, {}); + empty_tensor->here = v->loc; + empty_tensor->flags = Expr::_IsRvalue; + empty_tensor->e_type = TypeExpr::new_tensor({}); + expr = empty_tensor; + } + if (code.func_val->has_mutate_params()) { + expr = wrap_return_value_with_mutate_params(v->loc, code, expr); + } expr->chk_rvalue(); try { - // std::cerr << "in return: "; unify(expr->e_type, code.ret_type); } catch (UnifyError& ue) { std::ostringstream os; @@ -619,22 +900,29 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { return blk_fl::ret; } -static void append_implicit_ret_stmt(V v, CodeBlob& code) { - TypeExpr* ret_type = TypeExpr::new_unit(); +static void append_implicit_ret_stmt(SrcLocation loc_end, CodeBlob& code) { + Expr* expr = new Expr{Expr::_Tensor, {}}; + expr->flags = Expr::_IsRvalue; + expr->here = loc_end; + expr->e_type = TypeExpr::new_unit(); + if (code.func_val->does_return_self()) { + throw ParseError(loc_end, "missing return; forgot `return self`?"); + } + if (code.func_val->has_mutate_params()) { + expr = wrap_return_value_with_mutate_params(loc_end, code, expr); + } try { - // std::cerr << "in implicit return: "; - unify(ret_type, code.ret_type); + unify(expr->e_type, code.ret_type); } catch (UnifyError& ue) { std::ostringstream os; os << "previous function return type " << code.ret_type - << " cannot be unified with implicit end-of-block return type " << ret_type << ": " << ue; - throw ParseError(v->loc_end, os.str()); + << " cannot be unified with implicit end-of-block return type " << expr->e_type << ": " << ue; + throw ParseError(loc_end, os.str()); } - code.emplace_back(v->loc_end, Op::_Return); + std::vector tmp_vars = expr->pre_compile(code); + code.emplace_back(loc_end, Op::_Return, std::move(tmp_vars)); } -blk_fl::val process_statement(AnyV v, CodeBlob& code); - static blk_fl::val process_vertex(V v, CodeBlob& code, bool no_new_scope = false) { if (!no_new_scope) { open_scope(v->loc); @@ -792,7 +1080,7 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { static Expr* process_catch_variable(AnyV catch_var, TypeExpr* var_type) { if (auto v_ident = catch_var->try_as()) { - return create_new_local_variable(catch_var->loc, v_ident->name, var_type); + return create_new_local_variable(catch_var->loc, v_ident->name, var_type, true); } return create_new_underscore_variable(catch_var->loc, var_type); } @@ -882,7 +1170,7 @@ blk_fl::val process_statement(AnyV v, CodeBlob& code) { case ast_try_catch_statement: return process_vertex(v->as(), code); default: { - auto expr = process_expr(v, code); + Expr* expr = process_expr(v, code); expr->chk_rvalue(); expr->pre_compile(code); return blk_fl::end; @@ -890,18 +1178,16 @@ blk_fl::val process_statement(AnyV v, CodeBlob& code) { } } -static FormalArg process_vertex(V v, int fa_idx) { - if (v->get_identifier()->name.empty()) { - return std::make_tuple(v->param_type, (SymDef*)nullptr, v->loc); +static FormalArg process_vertex(V v, SymDef* param_sym) { + if (!param_sym) { + return std::make_tuple(v->param_type, nullptr, v->loc); } SymDef* new_sym_def = define_symbol(calc_sym_idx(v->get_identifier()->name), true, v->loc); - if (!new_sym_def) { - v->error("cannot define symbol"); - } - if (new_sym_def->value) { - v->error("redefined argument"); + if (!new_sym_def || new_sym_def->value) { + v->error("redefined parameter"); } - new_sym_def->value = new SymVal{SymValKind::_Param, fa_idx, v->param_type}; + const SymValVariable* param_val = dynamic_cast(param_sym->value); + new_sym_def->value = new SymValVariable(*param_val); return std::make_tuple(v->param_type, new_sym_def, v->loc); } @@ -911,13 +1197,13 @@ static void convert_function_body_to_CodeBlob(V v, Vloc); - CodeBlob* blob = new CodeBlob{static_cast(v->get_identifier()->name), v->loc, v->ret_type}; + CodeBlob* blob = new CodeBlob{static_cast(v->get_identifier()->name), v->loc, sym_val, v->ret_type}; if (v->marked_as_pure) { blob->flags |= CodeBlob::_ForbidImpure; } FormalArgList legacy_arg_list; for (int i = 0; i < v->get_num_params(); ++i) { - legacy_arg_list.emplace_back(process_vertex(v->get_param(i), i)); + legacy_arg_list.emplace_back(process_vertex(v->get_param(i), sym_val->parameters[i])); } blob->import_params(std::move(legacy_arg_list)); @@ -931,7 +1217,7 @@ static void convert_function_body_to_CodeBlob(V v, Vloc_end, *blob); } blob->close_blk(v_body->loc_end); diff --git a/tolk/pipe-generate-fif-output.cpp b/tolk/pipe-generate-fif-output.cpp index 65225d828..91a99f96a 100644 --- a/tolk/pipe-generate-fif-output.cpp +++ b/tolk/pipe-generate-fif-output.cpp @@ -39,10 +39,10 @@ bool SymValCodeFunc::does_need_codegen() const { if (flags & flagUsedAsNonCall) { return true; } - // when a function f() is just `return anotherF(...args)`, it doesn't need to be codegenerated at all, - // since all its usages are inlined - return !is_just_wrapper_for_another_f(); - // in the future, we may want to implement a true AST inlining for `inline` functions also + // currently, there is no inlining, all functions are codegenerated + // (but actually, unused ones are later removed by Fift) + // in the future, we may want to implement a true AST inlining for "simple" functions + return true; } void SymValCodeFunc::set_code(CodeBlob* code) { diff --git a/tolk/pipe-register-symbols.cpp b/tolk/pipe-register-symbols.cpp index 2e6d26dd8..569d434aa 100644 --- a/tolk/pipe-register-symbols.cpp +++ b/tolk/pipe-register-symbols.cpp @@ -74,68 +74,6 @@ static td::RefInt256 calculate_method_id_by_func_name(std::string_view func_name return td::make_refint((crc & 0xffff) | 0x10000); } -static bool is_parameter_of_function(AnyV v_variable, V v_func) { - return v_variable->type == ast_identifier && v_func->get_param_list()->lookup_idx(v_variable->as()->name) != -1; -} - -// if a function looks like `T f(...args) { return anotherF(...args); }`, -// set a bit to flags -// then, all calls to `f(...)` will be effectively replaced with `anotherF(...)` -// todo this function (and optimization) was done before implementing AST, but after AST and registering symbols in advance, -// its behavior became a bit wrong: if anotherF is declared before f, than it's detected here, but still not inlined, -// since inlining is done is legacy code, using Expr -// in the future, inlining should be done on AST level, but it's impossible until all names resolving (including scopes) -// is also done on AST level -// in the future, when working on AST level, inlining should become much more powerful -// (for instance, it should inline `return anotherF(constants)`, etc.) -static bool detect_if_function_just_wraps_another(V v) { - if (v->method_id || v->marked_as_get_method || v->marked_as_builtin || v->marked_as_inline_ref || v->is_entrypoint) { - return false; - } - for (int i = 0; i < v->get_num_params(); ++i) { - if (v->get_param(i)->param_type->get_width() != 1 || v->get_param(i)->param_type->has_unknown_inside()) { - return false; // avoid situations like `f(int a, (int,int) b)`, inlining will be cumbersome - } - } - - auto v_body = v->get_body()->try_as(); - if (!v_body || v_body->size() != 1 || v_body->get_item(0)->type != ast_return_statement) { - return false; - } - - auto v_return = v_body->get_item(0)->as(); - auto v_anotherF = v_return->get_return_value()->try_as(); - if (!v_anotherF) { - return false; - } - - V called_arg = v_anotherF->get_called_arg(); - if (v_anotherF->get_called_f()->type != ast_identifier) { - return false; - } - - std::string_view called_name = v_anotherF->get_called_f()->try_as()->name; - std::string_view function_name = v->get_identifier()->name; - - const std::vector& v_arg_items = called_arg->get_items(); - std::set used_args; - for (AnyV v_arg : v_arg_items) { - if (!is_parameter_of_function(v_arg, v)) { - return false; - } - used_args.emplace(v_arg->as()->name); - } - if (static_cast(used_args.size()) != v->get_num_params() || used_args.size() != v_arg_items.size()) { - return false; - } - - // ok, f_current is a wrapper - if (G.is_verbosity(2)) { - std::cerr << function_name << " -> " << called_name << std::endl; - } - return true; -} - static void calc_arg_ret_order_of_asm_function(V v_body, V param_list, TypeExpr* ret_type, std::vector& arg_order, std::vector& ret_order) { int cnt = param_list->size(); @@ -201,7 +139,7 @@ static void register_constant(V v) { // todo currently, constant value calculation is dirty and roughly: init_value is evaluated to fif code // and waited to be a single expression // although it works, of course it should be later rewritten using AST calculations, as well as lots of other parts - CodeBlob code("tmp", v->loc, nullptr); + CodeBlob code("tmp", v->loc, nullptr, nullptr); Expr* x = process_expr(init_value, code); if (!x->is_rvalue()) { v->get_init_value()->error("expression is not strictly Rvalue"); @@ -211,9 +149,9 @@ static void register_constant(V v) { } SymValConst* sym_val = nullptr; if (x->cls == Expr::_Const) { // Integer constant - sym_val = new SymValConst{static_cast(G.all_constants.size()), x->intval}; + sym_val = new SymValConst(static_cast(G.all_constants.size()), x->intval); } else if (x->cls == Expr::_SliceConst) { // Slice constant (string) - sym_val = new SymValConst{static_cast(G.all_constants.size()), x->strval}; + sym_val = new SymValConst(static_cast(G.all_constants.size()), x->strval); } else if (x->cls == Expr::_Apply) { // even "1 + 2" is Expr::_Apply (it applies `_+_`) code.emplace_back(v->loc, Op::_Import, std::vector()); auto tmp_vars = x->pre_compile(code); @@ -241,14 +179,14 @@ static void register_constant(V v) { if (op.origin.is_null() || !op.origin->is_valid()) { init_value->error("precompiled expression did not result in a valid integer constant"); } - sym_val = new SymValConst{static_cast(G.all_constants.size()), op.origin}; + sym_val = new SymValConst(static_cast(G.all_constants.size()), op.origin); } else { init_value->error("integer or slice literal or constant expected"); } sym_def->value = sym_val; #ifdef TOLK_DEBUG - dynamic_cast(sym_def->value)->name = v->get_identifier()->name; + sym_def->value->sym_name = v->get_identifier()->name; #endif G.all_constants.push_back(sym_def); } @@ -259,35 +197,68 @@ static void register_global_var(V v) { fire_error_redefinition_of_symbol(v->get_identifier(), sym_def); } - sym_def->value = new SymValGlobVar{static_cast(G.all_global_vars.size()), v->declared_type}; + sym_def->value = new SymValGlobVar(static_cast(G.all_global_vars.size()), v->declared_type); #ifdef TOLK_DEBUG - dynamic_cast(sym_def->value)->name = v->get_identifier()->name; + sym_def->value->sym_name = v->get_identifier()->name; #endif G.all_global_vars.push_back(sym_def); } +static SymDef* register_parameter(V v, int idx) { + if (v->is_underscore()) { + return nullptr; + } + SymDef* sym_def = define_parameter(calc_sym_idx(v->get_identifier()->name), v->loc); + if (sym_def->value) { + // todo always false now, how to detect similar parameter names? (remember about underscore) + v->error("redefined parameter"); + } + + SymValVariable* sym_val = new SymValVariable(idx, v->param_type); + if (v->declared_as_mutate) { + sym_val->flags |= SymValVariable::flagMutateParameter; + } + if (!v->declared_as_mutate && idx == 0 && v->get_identifier()->name == "self") { + sym_val->flags |= SymValVariable::flagImmutable; + } + sym_def->value = sym_val; +#ifdef TOLK_DEBUG + sym_def->value->sym_name = v->get_identifier()->name; +#endif + return sym_def; +} + static void register_function(V v) { std::string_view func_name = v->get_identifier()->name; - // calculate TypeExpr of a function: it's a map (args -> ret), probably surrounded by forall - TypeExpr* func_type = nullptr; - if (int n_args = v->get_num_params()) { - std::vector arg_types; - arg_types.reserve(n_args); - for (int idx = 0; idx < n_args; ++idx) { - arg_types.emplace_back(v->get_param(idx)->param_type); + // calculate TypeExpr of a function: it's a map (params -> ret), probably surrounded by forall + TypeExpr* params_tensor_type = nullptr; + int n_params = v->get_num_params(); + int n_mutate_params = 0; + std::vector parameters_syms; + if (n_params) { + std::vector param_tensor_items; + param_tensor_items.reserve(n_params); + parameters_syms.reserve(n_params); + for (int i = 0; i < n_params; ++i) { + auto v_param = v->get_param(i); + n_mutate_params += static_cast(v_param->declared_as_mutate); + param_tensor_items.emplace_back(v_param->param_type); + parameters_syms.emplace_back(register_parameter(v_param, i)); } - func_type = TypeExpr::new_map(TypeExpr::new_tensor(std::move(arg_types)), v->ret_type); + params_tensor_type = TypeExpr::new_tensor(std::move(param_tensor_items)); } else { - func_type = TypeExpr::new_map(TypeExpr::new_unit(), v->ret_type); + params_tensor_type = TypeExpr::new_unit(); } + + TypeExpr* function_type = TypeExpr::new_map(params_tensor_type, v->ret_type); if (v->genericsT_list) { std::vector type_vars; type_vars.reserve(v->genericsT_list->size()); for (int idx = 0; idx < v->genericsT_list->size(); ++idx) { type_vars.emplace_back(v->genericsT_list->get_item(idx)->created_type); } - func_type = TypeExpr::new_forall(std::move(type_vars), func_type); + function_type = TypeExpr::new_forall(std::move(type_vars), function_type); } if (v->marked_as_builtin) { const SymDef* builtin_func = lookup_symbol(G.symbols.lookup(func_name)); @@ -297,7 +268,7 @@ static void register_function(V v) { } #ifdef TOLK_DEBUG // in release, we don't need this check, since `builtin` is used only in stdlib, which is our responsibility - if (!func_val->sym_type->equals_to(func_type) || func_val->is_marked_as_pure() != v->marked_as_pure) { + if (!func_val->sym_type->equals_to(function_type) || func_val->is_marked_as_pure() != v->marked_as_pure) { v->error("declaration for `builtin` function doesn't match an actual one"); } #endif @@ -309,7 +280,7 @@ static void register_function(V v) { fire_error_redefinition_of_symbol(v->get_identifier(), sym_def); } if (G.is_verbosity(1)) { - std::cerr << "fun " << func_name << " : " << func_type << std::endl; + std::cerr << "fun " << func_name << " : " << function_type << std::endl; } if (v->marked_as_pure && v->ret_type->get_width() == 0) { v->error("a pure function should return something, otherwise it will be optimized out anyway"); @@ -317,11 +288,11 @@ static void register_function(V v) { SymValFunc* sym_val = nullptr; if (const auto* v_seq = v->get_body()->try_as()) { - sym_val = new SymValCodeFunc{static_cast(G.all_code_functions.size()), func_type, v->marked_as_pure}; + sym_val = new SymValCodeFunc(std::move(parameters_syms), static_cast(G.all_code_functions.size()), function_type); } else if (const auto* v_asm = v->get_body()->try_as()) { std::vector arg_order, ret_order; calc_arg_ret_order_of_asm_function(v_asm, v->get_param_list(), v->ret_type, arg_order, ret_order); - sym_val = new SymValAsmFunc{func_type, std::move(arg_order), std::move(ret_order), v->marked_as_pure}; + sym_val = new SymValAsmFunc(std::move(parameters_syms), function_type, std::move(arg_order), std::move(ret_order), 0); } else { v->error("Unexpected function body statement"); } @@ -341,6 +312,9 @@ static void register_function(V v) { } else if (v->is_entrypoint) { sym_val->method_id = calculate_method_id_for_entrypoint(func_name); } + if (v->marked_as_pure) { + sym_val->flags |= SymValFunc::flagMarkedAsPure; + } if (v->marked_as_inline) { sym_val->flags |= SymValFunc::flagInline; } @@ -353,13 +327,19 @@ static void register_function(V v) { if (v->is_entrypoint) { sym_val->flags |= SymValFunc::flagIsEntrypoint; } - if (detect_if_function_just_wraps_another(v)) { - sym_val->flags |= SymValFunc::flagWrapsAnotherF; + if (n_mutate_params) { + sym_val->flags |= SymValFunc::flagHasMutateParams; + } + if (v->accepts_self) { + sym_val->flags |= SymValFunc::flagAcceptsSelf; + } + if (v->returns_self) { + sym_val->flags |= SymValFunc::flagReturnsSelf; } sym_def->value = sym_val; #ifdef TOLK_DEBUG - dynamic_cast(sym_def->value)->name = func_name; + sym_def->value->sym_name = func_name; #endif if (dynamic_cast(sym_val)) { G.all_code_functions.push_back(sym_def); diff --git a/tolk/symtable.cpp b/tolk/symtable.cpp index f8f64c50f..abaeb0846 100644 --- a/tolk/symtable.cpp +++ b/tolk/symtable.cpp @@ -126,6 +126,18 @@ SymDef* define_global_symbol(sym_idx_t name_idx, SrcLocation loc) { return registered; // registered->value is nullptr; it means, it's just created } +SymDef* define_parameter(sym_idx_t name_idx, SrcLocation loc) { + // note, that parameters (defined at function declaration) are not inserted into symtable + // their SymDef is registered to be inserted into SymValFunc::parameters + // (and later ->value is filled with SymValVariable) + + SymDef* registered = new SymDef(0, name_idx, loc); +#ifdef TOLK_DEBUG + registered->sym_name = registered->name(); +#endif + return registered; +} + SymDef* define_symbol(sym_idx_t name_idx, bool force_new, SrcLocation loc) { if (!name_idx) { return nullptr; diff --git a/tolk/symtable.h b/tolk/symtable.h index 75b0aa2f2..a52e8d333 100644 --- a/tolk/symtable.h +++ b/tolk/symtable.h @@ -17,6 +17,7 @@ #pragma once #include "src-file.h" +#include "type-expr.h" #include #include @@ -25,14 +26,23 @@ namespace tolk { typedef int var_idx_t; typedef int sym_idx_t; -enum class SymValKind { _Param, _Var, _Func, _Typename, _GlobVar, _Const }; +enum class SymValKind { _Var, _Func, _GlobVar, _Const }; struct SymValBase { SymValKind kind; int idx; - SymValBase(SymValKind kind, int idx) : kind(kind), idx(idx) { + TypeExpr* sym_type; +#ifdef TOLK_DEBUG + std::string sym_name; // seeing symbol name in debugger makes it much easier to delve into Tolk sources +#endif + + SymValBase(SymValKind kind, int idx, TypeExpr* sym_type) : kind(kind), idx(idx), sym_type(sym_type) { } virtual ~SymValBase() = default; + + TypeExpr* get_type() const { + return sym_type; + } }; @@ -98,6 +108,7 @@ void close_scope(); SymDef* lookup_symbol(sym_idx_t idx); SymDef* define_global_symbol(sym_idx_t name_idx, SrcLocation loc = {}); +SymDef* define_parameter(sym_idx_t name_idx, SrcLocation loc); SymDef* define_symbol(sym_idx_t name_idx, bool force_new, SrcLocation loc); } // namespace tolk diff --git a/tolk/tolk.h b/tolk/tolk.h index 199194355..971ca35dd 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -69,13 +69,14 @@ using const_idx_t = int; struct TmpVar { TypeExpr* v_type; var_idx_t idx; - bool is_tmp_unnamed; - sym_idx_t name; + sym_idx_t sym_idx; int coord; SrcLocation where; std::vector> on_modification; - TmpVar(var_idx_t _idx, bool _is_tmp_unnamed, TypeExpr* _type, SymDef* sym, SrcLocation loc); + TmpVar(var_idx_t _idx, TypeExpr* _type, sym_idx_t sym_idx, SrcLocation loc); + bool is_unnamed() const { return sym_idx == 0; } + void show(std::ostream& os, int omit_idx = 0) const; void dump(std::ostream& os) const; void set_location(SrcLocation loc); @@ -401,40 +402,56 @@ struct AsmOpList; * */ -struct SymVal : SymValBase { - TypeExpr* sym_type; - SymVal(SymValKind kind, int idx, TypeExpr* sym_type = nullptr) - : SymValBase(kind, idx), sym_type(sym_type) { +struct SymValVariable : SymValBase { + enum SymValFlag { + flagMutateParameter = 1, // parameter was declared with `mutate` keyword + flagImmutable = 2, // variable was declared via `val` (not `var`) + }; + int flags{0}; + + ~SymValVariable() override = default; + SymValVariable(int val, TypeExpr* sym_type) + : SymValBase(SymValKind::_Var, val, sym_type) {} + + bool is_function_parameter() const { + return idx >= 0; } - ~SymVal() override = default; - TypeExpr* get_type() const { - return sym_type; + bool is_mutate_parameter() const { + return flags & flagMutateParameter; + } + bool is_local_var() const { + return idx == -1; + } + bool is_immutable() const { + return flags & flagImmutable; } }; -struct SymValFunc : SymVal { +struct SymValFunc : SymValBase { enum SymValFlag { flagInline = 1, // marked `@inline` flagInlineRef = 2, // marked `@inline_ref` - flagWrapsAnotherF = 4, // `fun thisF(...args) { return anotherF(...args); }` (calls to thisF will be inlined) flagUsedAsNonCall = 8, // used not only as `f()`, but as a 1-st class function (assigned to var, pushed to tuple, etc.) flagMarkedAsPure = 16, // declared as `pure`, can't call impure and access globals, unused invocations are optimized out flagBuiltinFunction = 32, // was created via `define_builtin_func()`, not from source code flagGetMethod = 64, // was declared via `get func(): T`, method_id is auto-assigned flagIsEntrypoint = 128, // it's `main` / `onExternalMessage` / etc. + flagHasMutateParams = 256, // has parameters declared as `mutate` + flagAcceptsSelf = 512, // is a member function (has `self` first parameter) + flagReturnsSelf = 1024, // return type is `self` (returns the mutated 1st argument), calls can be chainable }; td::RefInt256 method_id; // todo why int256? it's small int flags{0}; + std::vector parameters; // [i]-th may be nullptr for underscore; if not, its val is SymValVariable std::vector arg_order, ret_order; -#ifdef TOLK_DEBUG - std::string name; // seeing function name in debugger makes it much easier to delve into Tolk sources -#endif + ~SymValFunc() override = default; - SymValFunc(int val, TypeExpr* _ft, bool marked_as_pure) - : SymVal(SymValKind::_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0) {} - SymValFunc(int val, TypeExpr* _ft, std::initializer_list _arg_order, std::initializer_list _ret_order, bool marked_as_pure) - : SymVal(SymValKind::_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0), arg_order(_arg_order), ret_order(_ret_order) { + SymValFunc(std::vector parameters, int val, TypeExpr* sym_type, int flags) + : SymValBase(SymValKind::_Func, val, sym_type), flags(flags), parameters(std::move(parameters)) { + } + SymValFunc(std::vector parameters, int val, TypeExpr* sym_type, int flags, std::initializer_list arg_order, std::initializer_list ret_order) + : SymValBase(SymValKind::_Func, val, sym_type), flags(flags), parameters(std::move(parameters)), arg_order(arg_order), ret_order(ret_order) { } const std::vector* get_arg_order() const { @@ -450,9 +467,6 @@ struct SymValFunc : SymVal { bool is_inline_ref() const { return flags & flagInlineRef; } - bool is_just_wrapper_for_another_f() const { - return flags & flagWrapsAnotherF; - } bool is_marked_as_pure() const { return flags & flagMarkedAsPure; } @@ -465,32 +479,35 @@ struct SymValFunc : SymVal { bool is_entrypoint() const { return flags & flagIsEntrypoint; } + bool has_mutate_params() const { + return flags & flagHasMutateParams; + } + bool does_accept_self() const { + return flags & flagAcceptsSelf; + } + bool does_return_self() const { + return flags & flagReturnsSelf; + } }; struct SymValCodeFunc : SymValFunc { CodeBlob* code; bool is_really_used{false}; // calculated via dfs; unused functions are not codegenerated ~SymValCodeFunc() override = default; - SymValCodeFunc(int val, TypeExpr* _ft, bool marked_as_pure) : SymValFunc(val, _ft, marked_as_pure), code(nullptr) { + SymValCodeFunc(std::vector parameters, int val, TypeExpr* _ft) + : SymValFunc(std::move(parameters), val, _ft, 0), code(nullptr) { } bool does_need_codegen() const; void set_code(CodeBlob* code); }; struct SymValGlobVar : SymValBase { - TypeExpr* sym_type; - int out_idx{0}; bool is_really_used{false}; // calculated via dfs from used functions; unused globals are not codegenerated -#ifdef TOLK_DEBUG - std::string name; // seeing variable name in debugger makes it much easier to delve into Tolk sources -#endif - SymValGlobVar(int val, TypeExpr* gvtype, int oidx = 0) - : SymValBase(SymValKind::_GlobVar, val), sym_type(gvtype), out_idx(oidx) { + + SymValGlobVar(int val, TypeExpr* gvtype) + : SymValBase(SymValKind::_GlobVar, val, gvtype) { } ~SymValGlobVar() override = default; - TypeExpr* get_type() const { - return sym_type; - } }; struct SymValConst : SymValBase { @@ -499,14 +516,12 @@ struct SymValConst : SymValBase { td::RefInt256 intval; std::string strval; ConstKind kind; -#ifdef TOLK_DEBUG - std::string name; // seeing const name in debugger makes it much easier to delve into Tolk sources -#endif + SymValConst(int idx, td::RefInt256 value) - : SymValBase(SymValKind::_Const, idx), intval(value), kind(IntConst) { + : SymValBase(SymValKind::_Const, idx, TypeExpr::new_atomic(TypeExpr::_Int)), intval(std::move(value)), kind(IntConst) { } SymValConst(int idx, std::string value) - : SymValBase(SymValKind::_Const, idx), strval(value), kind(SliceConst) { + : SymValBase(SymValKind::_Const, idx, TypeExpr::new_atomic(TypeExpr::_Slice)), strval(std::move(value)), kind(SliceConst) { } ~SymValConst() override = default; td::RefInt256 get_int_value() const { @@ -529,9 +544,10 @@ struct SymValConst : SymValBase { struct Expr { enum ExprCls { - _None, _Apply, _VarApply, + _GrabMutatedVars, + _ReturnSelf, _MkTuple, _Tensor, _Const, @@ -539,14 +555,13 @@ struct Expr { _GlobFunc, _GlobVar, _Letop, - _LetFirst, _Hole, _CondExpr, _SliceConst, }; ExprCls cls; int val{0}; - enum { _IsRvalue = 2, _IsLvalue = 4, _IsImpure = 32 }; + enum { _IsRvalue = 2, _IsLvalue = 4, _IsImmutable = 8, _IsImpure = 32 }; int flags{0}; SrcLocation here; td::RefInt256 intval; @@ -554,8 +569,6 @@ struct Expr { SymDef* sym{nullptr}; TypeExpr* e_type{nullptr}; std::vector args; - explicit Expr(ExprCls c = _None) : cls(c) { - } Expr(ExprCls c, SrcLocation loc) : cls(c), here(loc) { } Expr(ExprCls c, std::vector _args) : cls(c), args(std::move(_args)) { @@ -585,33 +598,38 @@ struct Expr { bool is_lvalue() const { return flags & _IsLvalue; } + bool is_immutable() const { + return flags & _IsImmutable; + } bool is_mktuple() const { return cls == _MkTuple; } void chk_rvalue() const { if (!is_rvalue()) { - throw ParseError(here, "rvalue expected"); - } - } - void chk_lvalue() const { - if (!is_lvalue()) { - throw ParseError(here, "lvalue expected"); + fire_error_rvalue_expected(); } } - bool deduce_type(); + void deduce_type(); void set_location(SrcLocation loc) { here = loc; } SrcLocation get_location() const { return here; } - int define_new_vars(CodeBlob& code); - int predefine_vars(); + void define_new_vars(CodeBlob& code); + void predefine_vars(); std::vector pre_compile(CodeBlob& code, std::vector>* lval_globs = nullptr) const; var_idx_t new_tmp(CodeBlob& code) const; std::vector new_tmp_vect(CodeBlob& code) const { return {new_tmp(code)}; } + + GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN + void fire_error_rvalue_expected() const; + GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN + void fire_error_lvalue_expected(const std::string& details) const; + GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN + void fire_error_modifying_immutable(const std::string& details) const; }; /* @@ -1324,24 +1342,17 @@ struct SymValAsmFunc : SymValFunc { simple_compile_func_t simple_compile; compile_func_t ext_compile; ~SymValAsmFunc() override = default; - SymValAsmFunc(TypeExpr* ft, std::vector&& arg_order, std::vector&& ret_order, bool marked_as_pure) - : SymValFunc(-1, ft, marked_as_pure) { + SymValAsmFunc(std::vector parameters, TypeExpr* ft, std::vector&& arg_order, std::vector&& ret_order, int flags) + : SymValFunc(std::move(parameters), -1, ft, flags) { this->arg_order = std::move(arg_order); this->ret_order = std::move(ret_order); } - SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool marked_as_pure) - : SymValFunc(-1, ft, marked_as_pure), simple_compile(std::move(_compile)) { - } - SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, bool marked_as_pure) - : SymValFunc(-1, ft, marked_as_pure), ext_compile(std::move(_compile)) { - } - SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool marked_as_pure = false) - : SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), simple_compile(std::move(_compile)) { + SymValAsmFunc(std::vector parameters, TypeExpr* ft, simple_compile_func_t _compile, int flags) + : SymValFunc(std::move(parameters), -1, ft, flags), simple_compile(std::move(_compile)) { } - SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool marked_as_pure = false) - : SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), ext_compile(std::move(_compile)) { + SymValAsmFunc(std::vector parameters, TypeExpr* ft, simple_compile_func_t _compile, int flags, + std::initializer_list arg_order, std::initializer_list ret_order) + : SymValFunc(std::move(parameters), -1, ft, flags, arg_order, ret_order), simple_compile(std::move(_compile)) { } void set_code(std::vector code); bool compile(AsmOpList& dest, std::vector& out, std::vector& in, SrcLocation where) const; @@ -1349,29 +1360,32 @@ struct SymValAsmFunc : SymValFunc { struct CodeBlob { enum { _ForbidImpure = 4 }; - int var_cnt, in_var_cnt, op_cnt; + int var_cnt, in_var_cnt; TypeExpr* ret_type; + const SymValCodeFunc* func_val; std::string name; SrcLocation loc; std::vector vars; std::unique_ptr ops; std::unique_ptr* cur_ops; + std::vector debug_ttt; std::stack*> cur_ops_stack; int flags = 0; bool require_callxargs = false; - CodeBlob(std::string name, SrcLocation loc, TypeExpr* ret) - : var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), name(std::move(name)), loc(loc), cur_ops(&ops) { + CodeBlob(std::string name, SrcLocation loc, const SymValCodeFunc* func_val, TypeExpr* ret_type) + : var_cnt(0), in_var_cnt(0), ret_type(ret_type), func_val(func_val), name(std::move(name)), loc(loc), cur_ops(&ops) { } template Op& emplace_back(Args&&... args) { Op& res = *(*cur_ops = std::make_unique(args...)); cur_ops = &(res.next); + debug_ttt.push_back(&res); return res; } bool import_params(FormalArgList arg_list); - var_idx_t create_var(bool is_tmp_unnamed, TypeExpr* var_type, SymDef* sym, SrcLocation loc); + var_idx_t create_var(TypeExpr* var_type, var_idx_t sym_idx, SrcLocation loc); var_idx_t create_tmp_var(TypeExpr* var_type, SrcLocation loc) { - return create_var(true, var_type, nullptr, loc); + return create_var(var_type, 0, loc); } int split_vars(bool strict = false); bool compute_used_code_vars(); diff --git a/tolk/type-expr.h b/tolk/type-expr.h index 0e2a870f9..21a35a8e3 100644 --- a/tolk/type-expr.h +++ b/tolk/type-expr.h @@ -2,28 +2,20 @@ #include #include -#include "lexer.h" namespace tolk { struct TypeExpr { enum Kind { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_ForAll }; - // todo not _ - enum AtomicType { - _Int = tok_int, - _Cell = tok_cell, - _Slice = tok_slice, - _Builder = tok_builder, - _Cont = tok_continuation, - _Tuple = tok_tuple, - }; + enum AtomicType { _Int, _Cell, _Slice, _Builder, _Continutaion, _Tuple }; Kind constr; int value; int minw, maxw; static constexpr int w_inf = 1023; std::vector args; bool was_forall_var = false; - TypeExpr(Kind _constr, int _val = 0) : constr(_constr), value(_val), minw(0), maxw(w_inf) { + + explicit TypeExpr(Kind _constr, int _val = 0) : constr(_constr), value(_val), minw(0), maxw(w_inf) { } TypeExpr(Kind _constr, int _val, int width) : constr(_constr), value(_val), minw(width), maxw(width) { } @@ -48,6 +40,7 @@ struct TypeExpr { args.insert(args.end(), list.begin(), list.end()); compute_width(); } + bool is_atomic() const { return constr == te_Atomic; } @@ -127,9 +120,7 @@ struct TypeExpr { static TypeExpr* new_forall(std::vector list, TypeExpr* body) { return new TypeExpr{te_ForAll, body, std::move(list)}; } - static TypeExpr* new_forall(std::initializer_list list, TypeExpr* body) { - return new TypeExpr{te_ForAll, body, std::move(list)}; - } + static bool remove_indirect(TypeExpr*& te, TypeExpr* forbidden = nullptr); static std::vector remove_forall(TypeExpr*& te); static bool remove_forall_in(TypeExpr*& te, TypeExpr* te2, const std::vector& new_vars); diff --git a/tolk/unify-types.cpp b/tolk/unify-types.cpp index cc2073ede..cee71942b 100644 --- a/tolk/unify-types.cpp +++ b/tolk/unify-types.cpp @@ -264,7 +264,7 @@ std::ostream& TypeExpr::print(std::ostream& os, int lex_level) const { return os << "slice"; case _Builder: return os << "builder"; - case _Cont: + case _Continutaion: return os << "cont"; case _Tuple: return os << "tuple";