mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
Merge pull request #1477 from ton-blockchain/tolk-v0.7
Tolk v0.7: overhaul compiler internals and the type system; `bool` type
This commit is contained in:
commit
1b7c46f496
137 changed files with 8936 additions and 4485 deletions
|
@ -1,7 +1,7 @@
|
|||
// Standard library for Tolk (LGPL licence).
|
||||
// It contains common functions that are available out of the box, the user doesn't have to import anything.
|
||||
// More specific functions are required to be imported explicitly, like "@stdlib/tvm-dicts".
|
||||
tolk 0.6
|
||||
tolk 0.7
|
||||
|
||||
/**
|
||||
Tuple manipulation primitives.
|
||||
|
@ -17,17 +17,17 @@ 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<X>(mutate self: tuple, value: X): void
|
||||
fun tuplePush<T>(mutate self: tuple, value: T): void
|
||||
asm "TPUSH";
|
||||
|
||||
/// Returns the first element of a non-empty tuple.
|
||||
@pure
|
||||
fun tupleFirst<X>(t: tuple): X
|
||||
fun tupleFirst<T>(t: tuple): T
|
||||
asm "FIRST";
|
||||
|
||||
/// Returns the [`index`]-th element of a tuple.
|
||||
@pure
|
||||
fun tupleAt<X>(t: tuple, index: int): X
|
||||
fun tupleAt<T>(t: tuple, index: int): T
|
||||
builtin;
|
||||
|
||||
/// Returns the size of a tuple (elements count in it).
|
||||
|
@ -37,7 +37,7 @@ fun tupleSize(t: tuple): int
|
|||
|
||||
/// Returns the last element of a non-empty tuple.
|
||||
@pure
|
||||
fun tupleLast(t: tuple): int
|
||||
fun tupleLast<T>(t: tuple): T
|
||||
asm "LAST";
|
||||
|
||||
|
||||
|
@ -205,7 +205,7 @@ fun stringHash(s: slice): int
|
|||
/// That is, if [hash] is computed as the hash of some data, these data are hashed twice,
|
||||
/// the second hashing occurring inside `CHKSIGNS`.
|
||||
@pure
|
||||
fun isSignatureValid(hash: int, signature: slice, publicKey: int): int
|
||||
fun isSignatureValid(hash: int, signature: slice, publicKey: int): bool
|
||||
asm "CHKSIGNU";
|
||||
|
||||
/// Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `publicKey`,
|
||||
|
@ -214,7 +214,7 @@ fun isSignatureValid(hash: int, signature: slice, publicKey: int): int
|
|||
/// The verification of Ed25519 signatures is the standard one,
|
||||
/// with sha256 used to reduce [data] to the 256-bit number that is actually signed.
|
||||
@pure
|
||||
fun isSliceSignatureValid(data: slice, signature: slice, publicKey: int): int
|
||||
fun isSliceSignatureValid(data: slice, signature: slice, publicKey: int): bool
|
||||
asm "CHKSIGNS";
|
||||
|
||||
/// Generates a new pseudo-random unsigned 256-bit integer x.
|
||||
|
@ -259,14 +259,14 @@ fun randomizeByLogicalTime(): void
|
|||
/// otherwise the computation is aborted before visiting the `(maxCells + 1)`-st cell and
|
||||
/// a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`.
|
||||
@pure
|
||||
fun calculateCellSize(c: cell, maxCells: int): (int, int, int, int)
|
||||
fun calculateCellSize(c: cell, maxCells: int): (int, int, int, bool)
|
||||
asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
|
||||
|
||||
/// Similar to [calculateCellSize], but accepting a `slice` [s] instead of a `cell`.
|
||||
/// The returned value of `x` does not take into account the cell that contains the `slice` [s] itself;
|
||||
/// however, the data bits and the cell references of [s] are accounted for in `y` and `z`.
|
||||
@pure
|
||||
fun calculateSliceSize(s: slice, maxCells: int): (int, int, int, int)
|
||||
fun calculateSliceSize(s: slice, maxCells: int): (int, int, int, bool)
|
||||
asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
|
||||
|
||||
/// A non-quiet version of [calculateCellSize] that throws a cell overflow exception (`8`) on failure.
|
||||
|
@ -306,11 +306,11 @@ fun getBuilderDepth(b: builder): int
|
|||
*/
|
||||
|
||||
/// Dump a variable [x] to the debug log.
|
||||
fun debugPrint<X>(x: X): void
|
||||
fun debugPrint<T>(x: T): void
|
||||
builtin;
|
||||
|
||||
/// Dump a string [x] to the debug log.
|
||||
fun debugPrintString<X>(x: X): void
|
||||
fun debugPrintString<T>(x: T): void
|
||||
builtin;
|
||||
|
||||
/// Dumps the stack (at most the top 255 values) and shows the total stack depth.
|
||||
|
@ -382,7 +382,7 @@ fun loadCoins(mutate self: slice): int
|
|||
|
||||
/// Loads bool (-1 or 0) from a slice
|
||||
@pure
|
||||
fun loadBool(mutate self: slice): int
|
||||
fun loadBool(mutate self: slice): bool
|
||||
asm( -> 1 0) "1 LDI";
|
||||
|
||||
/// Shifts a slice pointer to [len] bits forward, mutating the slice.
|
||||
|
@ -482,7 +482,7 @@ fun storeCoins(mutate self: builder, x: int): self
|
|||
/// 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(mutate self: builder, x: int): self
|
||||
fun storeBool(mutate self: builder, x: bool): self
|
||||
asm(x self) "1 STI";
|
||||
|
||||
/// Stores dictionary (represented by TVM `cell` or `null`) into a builder.
|
||||
|
@ -529,22 +529,22 @@ fun getRemainingBitsAndRefsCount(self: slice): (int, int)
|
|||
|
||||
/// Checks whether a slice is empty (i.e., contains no bits of data and no cell references).
|
||||
@pure
|
||||
fun isEndOfSlice(self: slice): int
|
||||
fun isEndOfSlice(self: slice): bool
|
||||
asm "SEMPTY";
|
||||
|
||||
/// Checks whether a slice has no bits of data.
|
||||
@pure
|
||||
fun isEndOfSliceBits(self: slice): int
|
||||
fun isEndOfSliceBits(self: slice): bool
|
||||
asm "SDEMPTY";
|
||||
|
||||
/// Checks whether a slice has no references.
|
||||
@pure
|
||||
fun isEndOfSliceRefs(self: slice): int
|
||||
fun isEndOfSliceRefs(self: slice): bool
|
||||
asm "SREMPTY";
|
||||
|
||||
/// Checks whether data parts of two slices coinside.
|
||||
@pure
|
||||
fun isSliceBitsEqual(self: slice, b: slice): int
|
||||
fun isSliceBitsEqual(self: slice, b: slice): bool
|
||||
asm "SDEQ";
|
||||
|
||||
/// Returns the number of cell references already stored in a builder.
|
||||
|
@ -621,10 +621,10 @@ fun parseStandardAddress(s: slice): (int, int)
|
|||
fun createAddressNone(): slice
|
||||
asm "b{00} PUSHSLICE";
|
||||
|
||||
/// Returns if a slice pointer contains an empty address (`-1` for true, `0` for false, as always).
|
||||
/// Returns if a slice pointer contains an empty address.
|
||||
/// In other words, a slice starts with two `0` bits (TL addr_none$00).
|
||||
@pure
|
||||
fun addressIsNone(s: slice): int
|
||||
fun addressIsNone(s: slice): bool
|
||||
asm "2 PLDU" "0 EQINT";
|
||||
|
||||
|
||||
|
@ -677,8 +677,8 @@ fun loadMessageFlags(mutate self: slice): int
|
|||
/// Having msgFlags (4 bits), check that a message is bounced.
|
||||
/// Effectively, it's `msgFlags & 1` (the lowest bit present).
|
||||
@pure
|
||||
fun isMessageBounced(msgFlags: int): int
|
||||
asm "1 PUSHINT" "AND";
|
||||
fun isMessageBounced(msgFlags: int): bool
|
||||
asm "2 PUSHINT" "MODR";
|
||||
|
||||
/// Skip 0xFFFFFFFF prefix (when a message is bounced).
|
||||
@pure
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// A part of standard library for Tolk
|
||||
tolk 0.6
|
||||
tolk 0.7
|
||||
|
||||
/**
|
||||
Gas and payment related primitives.
|
||||
|
@ -61,3 +61,9 @@ fun calculateOriginalMessageFee(workchain: int, incomingFwdFee: int): int
|
|||
/// If it has no debt, `0` is returned.
|
||||
fun getMyStorageDuePayment(): int
|
||||
asm "DUEPAYMENT";
|
||||
|
||||
/// Returns the amount of nanotoncoins charged for storage.
|
||||
/// (during storage phase preceeding to current computation phase)
|
||||
@pure
|
||||
fun getMyStoragePaidPayment(): int
|
||||
asm "STORAGEFEES";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// A part of standard library for Tolk
|
||||
tolk 0.6
|
||||
tolk 0.7
|
||||
|
||||
/**
|
||||
Lisp-style lists are nested 2-elements tuples: `(1, (2, (3, null)))` represents list `[1, 2, 3]`.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// A part of standard library for Tolk
|
||||
tolk 0.6
|
||||
tolk 0.7
|
||||
|
||||
/**
|
||||
Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular).
|
||||
|
@ -19,20 +19,20 @@ fun createEmptyDict(): cell
|
|||
|
||||
/// Checks whether a dictionary is empty.
|
||||
@pure
|
||||
fun dictIsEmpty(self: cell): int
|
||||
fun dictIsEmpty(self: cell): bool
|
||||
asm "DICTEMPTY";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictGet(self: cell, keyLen: int, key: int): (slice, int)
|
||||
fun iDictGet(self: cell, keyLen: int, key: int): (slice, bool)
|
||||
asm(key self keyLen) "DICTIGET" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun uDictGet(self: cell, keyLen: int, key: int): (slice, int)
|
||||
fun uDictGet(self: cell, keyLen: int, key: int): (slice, bool)
|
||||
asm(key self keyLen) "DICTUGET" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun sDictGet(self: cell, keyLen: int, key: slice): (slice, int)
|
||||
fun sDictGet(self: cell, keyLen: int, key: slice): (slice, bool)
|
||||
asm(key self keyLen) "DICTGET" "NULLSWAPIFNOT";
|
||||
|
||||
|
||||
|
@ -63,33 +63,33 @@ fun sDictSetRef(mutate self: cell, keyLen: int, key: slice, value: cell): void
|
|||
|
||||
|
||||
@pure
|
||||
fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): int
|
||||
fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
|
||||
asm(value key self keyLen) "DICTIADD";
|
||||
|
||||
@pure
|
||||
fun uDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): int
|
||||
fun uDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
|
||||
asm(value key self keyLen) "DICTUADD";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): int
|
||||
fun iDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
|
||||
asm(value key self keyLen) "DICTIREPLACE";
|
||||
|
||||
@pure
|
||||
fun uDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): int
|
||||
fun uDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
|
||||
asm(value key self keyLen) "DICTUREPLACE";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictGetRef(self: cell, keyLen: int, key: int): (cell, int)
|
||||
fun iDictGetRef(self: cell, keyLen: int, key: int): (cell, bool)
|
||||
asm(key self keyLen) "DICTIGETREF" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun uDictGetRef(self: cell, keyLen: int, key: int): (cell, int)
|
||||
fun uDictGetRef(self: cell, keyLen: int, key: int): (cell, bool)
|
||||
asm(key self keyLen) "DICTUGETREF" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, int)
|
||||
fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, bool)
|
||||
asm(key self keyLen) "DICTGETREF" "NULLSWAPIFNOT";
|
||||
|
||||
|
||||
|
@ -107,28 +107,28 @@ fun sDictGetRefOrNull(self: cell, keyLen: int, key: slice): cell
|
|||
|
||||
|
||||
@pure
|
||||
fun iDictDelete(mutate self: cell, keyLen: int, key: int): int
|
||||
fun iDictDelete(mutate self: cell, keyLen: int, key: int): bool
|
||||
asm(key self keyLen) "DICTIDEL";
|
||||
|
||||
@pure
|
||||
fun uDictDelete(mutate self: cell, keyLen: int, key: int): int
|
||||
fun uDictDelete(mutate self: cell, keyLen: int, key: int): bool
|
||||
asm(key self keyLen) "DICTUDEL";
|
||||
|
||||
@pure
|
||||
fun sDictDelete(mutate self: cell, keyLen: int, key: slice): int
|
||||
fun sDictDelete(mutate self: cell, keyLen: int, key: slice): bool
|
||||
asm(key self keyLen) "DICTDEL";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, int)
|
||||
fun iDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool)
|
||||
asm(value key self keyLen) "DICTISETGET" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun uDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, int)
|
||||
fun uDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool)
|
||||
asm(value key self keyLen) "DICTUSETGET" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, int)
|
||||
fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, bool)
|
||||
asm(value key self keyLen) "DICTSETGET" "NULLSWAPIFNOT";
|
||||
|
||||
|
||||
|
@ -142,15 +142,15 @@ fun uDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cel
|
|||
|
||||
|
||||
@pure
|
||||
fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, int)
|
||||
fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool)
|
||||
asm(key self keyLen) "DICTIDELGET" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun uDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, int)
|
||||
fun uDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool)
|
||||
asm(key self keyLen) "DICTUDELGET" "NULLSWAPIFNOT";
|
||||
|
||||
@pure
|
||||
fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, int)
|
||||
fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, bool)
|
||||
asm(key self keyLen) "DICTDELGET" "NULLSWAPIFNOT";
|
||||
|
||||
|
||||
|
@ -168,129 +168,129 @@ fun sDictSetBuilder(mutate self: cell, keyLen: int, key: slice, value: builder):
|
|||
|
||||
|
||||
@pure
|
||||
fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): int
|
||||
fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
|
||||
asm(value key self keyLen) "DICTIADDB";
|
||||
|
||||
@pure
|
||||
fun uDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): int
|
||||
fun uDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
|
||||
asm(value key self keyLen) "DICTUADDB";
|
||||
|
||||
@pure
|
||||
fun iDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): int
|
||||
fun iDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
|
||||
asm(value key self keyLen) "DICTIREPLACEB";
|
||||
|
||||
@pure
|
||||
fun uDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): int
|
||||
fun uDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
|
||||
asm(value key self keyLen) "DICTUREPLACEB";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, int)
|
||||
fun iDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
|
||||
asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, int)
|
||||
fun uDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
|
||||
asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun sDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (slice, slice, int)
|
||||
fun sDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (slice, slice, bool)
|
||||
asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, int)
|
||||
fun iDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
|
||||
asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, int)
|
||||
fun uDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
|
||||
asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun sDictDeleteLastAndGet(mutate self: cell, keyLen: int): (slice, slice, int)
|
||||
fun sDictDeleteLastAndGet(mutate self: cell, keyLen: int): (slice, slice, bool)
|
||||
asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictGetFirst(self: cell, keyLen: int): (int, slice, int)
|
||||
fun iDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
|
||||
asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetFirst(self: cell, keyLen: int): (int, slice, int)
|
||||
fun uDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
|
||||
asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, int)
|
||||
fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, bool)
|
||||
asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, int)
|
||||
fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
|
||||
asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, int)
|
||||
fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
|
||||
asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, int)
|
||||
fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, bool)
|
||||
asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictGetLast(self: cell, keyLen: int): (int, slice, int)
|
||||
fun iDictGetLast(self: cell, keyLen: int): (int, slice, bool)
|
||||
asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetLast(self: cell, keyLen: int): (int, slice, int)
|
||||
fun uDictGetLast(self: cell, keyLen: int): (int, slice, bool)
|
||||
asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun sDictGetLast(self: cell, keyLen: int): (slice, slice, int)
|
||||
fun sDictGetLast(self: cell, keyLen: int): (slice, slice, bool)
|
||||
asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, int)
|
||||
fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
|
||||
asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, int)
|
||||
fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
|
||||
asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, int)
|
||||
fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, bool)
|
||||
asm (-> 1 0 2) "DICTMAXREF" "NULLSWAPIFNOT2";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun iDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun uDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun iDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun iDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun uDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2";
|
||||
|
||||
|
||||
@pure
|
||||
fun iDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun iDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun uDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun iDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun iDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
||||
fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
|
||||
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
|
||||
|
||||
|
||||
|
@ -299,13 +299,13 @@ fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, int)
|
|||
*/
|
||||
|
||||
@pure
|
||||
fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, int)
|
||||
fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, bool)
|
||||
asm(key self keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2";
|
||||
|
||||
@pure
|
||||
fun prefixDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): int
|
||||
fun prefixDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): bool
|
||||
asm(value key self keyLen) "PFXDICTSET";
|
||||
|
||||
@pure
|
||||
fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): int
|
||||
fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): bool
|
||||
asm(key self keyLen) "PFXDICTDEL";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// A part of standard library for Tolk
|
||||
tolk 0.6
|
||||
tolk 0.7
|
||||
|
||||
/// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls.
|
||||
/// The primitive returns the current value of `c3`.
|
||||
|
|
|
@ -35,7 +35,7 @@ fun test88(x: int) {
|
|||
}
|
||||
|
||||
@method_id(89)
|
||||
fun test89(last: int) {
|
||||
fun test89(last: int): (int, int, int, int) {
|
||||
var t: tuple = createEmptyTuple();
|
||||
t.tuplePush(1);
|
||||
t.tuplePush(2);
|
||||
|
|
|
@ -9,6 +9,7 @@ fun calc_phi(): int {
|
|||
repeat (70) { n*=10; };
|
||||
var p= 1;
|
||||
var `q`=1;
|
||||
_=`q`;
|
||||
do {
|
||||
(p,q)=(q,p+q);
|
||||
} while (q <= n); //;;
|
||||
|
@ -27,7 +28,7 @@ fun calc_sqrt2(): int {
|
|||
return mulDivRound(p, n, q);
|
||||
}
|
||||
|
||||
fun calc_root(m: auto): auto {
|
||||
fun calc_root(m: int) {
|
||||
var base: int=1;
|
||||
repeat(70) { base *= 10; }
|
||||
var (a, b, c) = (1,0,-m);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@deprecated
|
||||
fun twice(f: auto, x: auto): auto {
|
||||
fun twice(f: int -> int, x: int) {
|
||||
return f (f (x));
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,11 @@ fun `~inc`(mutate self: int, y: int): int {
|
|||
return newY;
|
||||
}
|
||||
|
||||
fun eq<X>(v: X): X { return v; }
|
||||
fun eq2(v: (int, int)) { return v; }
|
||||
fun mul2(mutate dest: int, v: int): int { dest = v*2; return dest; }
|
||||
fun multens(mutate self: (int, int), v: (int, int)): (int, int) { var (f, s) = self; var (m1, m2) = v; self = (f*m1, s*m2); return self; }
|
||||
|
||||
@method_id(11)
|
||||
fun test_return(x: int): (int, int, int, int, int, int, int) {
|
||||
return (x, x.`~inc`(x / 20), x, x = x * 2, x, x += 1, x);
|
||||
|
@ -53,8 +58,7 @@ fun test_call_2(x: int): (int, int, int, int, int, int, int) {
|
|||
}
|
||||
|
||||
fun asm_func(x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int): (int, int, int, int, int, int, int)
|
||||
asm
|
||||
(x4 x5 x6 x7 x1 x2 x3->0 1 2 3 4 5 6) "NOP";
|
||||
asm (x4 x5 x6 x7 x1 x2 x3->0 1 2 3 4 5 6) "NOP";
|
||||
|
||||
@method_id(17)
|
||||
fun test_call_asm_old(x: int): (int, int, int, int, int, int, int) {
|
||||
|
@ -68,9 +72,9 @@ fun test_call_asm_new(x: int): (int, int, int, int, int, int, int) {
|
|||
|
||||
global xx: int;
|
||||
@method_id(19)
|
||||
fun test_global(x: int): (int, int, int, int, int, int, int) {
|
||||
fun test_global(x: int) {
|
||||
xx = x;
|
||||
return (xx, xx.`~inc`(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx);
|
||||
return (x, xx, xx.`~inc`(xx / 20), eq(xx += (x *= 0)), xx = xx * 2, xx, xx += 1, xx, x);
|
||||
}
|
||||
|
||||
@method_id(20)
|
||||
|
@ -79,10 +83,33 @@ fun test_if_else(x: int): (int, int, int, int, int) {
|
|||
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) as int, x = x + xx);
|
||||
}
|
||||
}
|
||||
|
||||
@method_id(21)
|
||||
fun test_assign_with_inner(x: int) {
|
||||
return (x, x += 10, [(x, x += 20, eq(x -= 50), x)], eq2((x, x *= eq(x /= 2))));
|
||||
}
|
||||
|
||||
@method_id(22)
|
||||
fun test_assign_with_mutate(x: int) {
|
||||
return (x, mul2(mutate x, x += 5), x.`~inc`(mul2(mutate x, x)), x);
|
||||
}
|
||||
|
||||
@method_id(23)
|
||||
fun test_assign_tensor(x: (int, int)) {
|
||||
var fs = (0, 0);
|
||||
return (x, x = (20, 30), fs = x.multens((1, 2)), fs.multens(multens(mutate x, (-1, -1))), x, fs);
|
||||
}
|
||||
|
||||
global fs: (int, int);
|
||||
@method_id(24)
|
||||
fun test_assign_tensor_global(x: (int, int)) {
|
||||
fs = (0, 0);
|
||||
return (x, x = (20, 30), fs = x.multens((1, 2)), fs.multens(multens(mutate x, (-1, -1))), x, fs);
|
||||
}
|
||||
|
||||
fun main() {
|
||||
}
|
||||
|
||||
|
@ -96,9 +123,13 @@ fun main() {
|
|||
@testcase | 16 | 100 | 100 50 105 210 210 211 211
|
||||
@testcase | 17 | 100 | 101 50 106 212 100 101 101
|
||||
@testcase | 18 | 100 | 210 210 211 211 100 50 105
|
||||
@testcase | 19 | 100 | 100 50 105 210 210 211 211
|
||||
@testcase | 19 | 100 | 100 100 50 105 210 210 211 211 0
|
||||
@testcase | 20 | 80 | 80 89 1 8 8
|
||||
@testcase | 20 | 9 | 9 -40 -10 -1 13
|
||||
@testcase | 21 | 100 | 100 110 [ 110 130 80 80 ] 80 3200
|
||||
@testcase | 22 | 100 | 100 210 4200 630
|
||||
@testcase | 23 | 1 1 | 1 1 20 30 20 60 -400 -3600 -20 -60 -400 -3600
|
||||
@testcase | 24 | 1 1 | 1 1 20 30 20 60 -400 -3600 -20 -60 -400 -3600
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
|
@ -107,5 +138,5 @@ fun main() {
|
|||
inc CALLDICT // self newY
|
||||
}>
|
||||
"""
|
||||
@code_hash 97139400653362069936987769894397430077752335662822462908581556703209313861576
|
||||
@code_hash 7627024945492125068389905298530400936797031708759561372406088054030801992712
|
||||
*/
|
||||
|
|
28
tolk-tester/tests/assignment-tests.tolk
Normal file
28
tolk-tester/tests/assignment-tests.tolk
Normal file
|
@ -0,0 +1,28 @@
|
|||
fun extractFromTypedTuple(params: [int]) {
|
||||
var [payload: int] = params;
|
||||
return payload + 10;
|
||||
}
|
||||
|
||||
@method_id(101)
|
||||
fun test101(x: int) {
|
||||
var params = [x];
|
||||
return extractFromTypedTuple(params);
|
||||
}
|
||||
|
||||
fun autoInferIntNull(x: int) {
|
||||
if (x > 10) { return null; }
|
||||
return x;
|
||||
}
|
||||
|
||||
fun main(value: int) {
|
||||
var (x: int, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
|
||||
if (x == null && y == null) { return null; }
|
||||
return x == null || y == null ? -1 : x + y;
|
||||
}
|
||||
|
||||
/**
|
||||
@testcase | 0 | 3 | 9
|
||||
@testcase | 0 | 6 | -1
|
||||
@testcase | 0 | 11 | (null)
|
||||
@testcase | 101 | 78 | 88
|
||||
*/
|
|
@ -1,20 +1,20 @@
|
|||
fun lshift(): int {
|
||||
fun lshift(): bool {
|
||||
return (1 << 0) == 1;
|
||||
}
|
||||
|
||||
fun rshift(): int {
|
||||
fun rshift(): bool {
|
||||
return (1 >> 0) == 1;
|
||||
}
|
||||
|
||||
fun lshift_var(i: int): int {
|
||||
fun lshift_var(i: int): bool {
|
||||
return (1 << i) == 1;
|
||||
}
|
||||
|
||||
fun rshift_var(i: int): int {
|
||||
fun rshift_var(i: int): bool {
|
||||
return (1 >> i) == 1;
|
||||
}
|
||||
|
||||
fun main(x: int): int {
|
||||
fun main(x: int): bool {
|
||||
if (x == 0) {
|
||||
return lshift();
|
||||
} else if (x == 1) {
|
||||
|
@ -31,12 +31,71 @@ fun main(x: int): int {
|
|||
}
|
||||
|
||||
@method_id(11)
|
||||
fun is_claimed(index: int): int {
|
||||
fun is_claimed(index: int): bool {
|
||||
var claim_bit_index: int = index % 256;
|
||||
var mask: int = 1 << claim_bit_index;
|
||||
return (255 & mask) == mask;
|
||||
}
|
||||
|
||||
@method_id(12)
|
||||
fun bit_not(i: int, b: bool): (int, bool, bool, bool, int, bool) {
|
||||
var i2 = ~i;
|
||||
var b2 = !b;
|
||||
var (i3: int, b3: bool) = (i2, b2);
|
||||
return (i3, b3, !i, !b, ~~~i, !!!b);
|
||||
}
|
||||
|
||||
@method_id(13)
|
||||
fun boolWithBitwiseConst() {
|
||||
var found = true;
|
||||
return (found & false, found | true, found ^ true, found & found);
|
||||
}
|
||||
|
||||
global g14: int;
|
||||
fun getBool() { return (g14 += 1) > 2; }
|
||||
|
||||
@method_id(14)
|
||||
fun boolWithBitwise(b: bool) {
|
||||
g14 = 0;
|
||||
return (b & getBool(), !b & getBool(), b | getBool(), !b | getBool(), b ^ getBool(), !b & getBool(), g14);
|
||||
}
|
||||
|
||||
@method_id(15)
|
||||
fun boolWithBitwiseSet(b1: bool, b2: bool) {
|
||||
b1 &= b2;
|
||||
b2 |= true;
|
||||
b1 |= b1 == false;
|
||||
b2 ^= (b1 ^= b2);
|
||||
return (b1, b2);
|
||||
}
|
||||
|
||||
@method_id(16)
|
||||
fun testDoUntilCodegen(i: bool, n: int) {
|
||||
var cnt = 0;
|
||||
do { cnt += 1; } while (i);
|
||||
do { cnt += 1; } while (!!i);
|
||||
do { cnt += 1; } while (n);
|
||||
return (cnt, !i, !n);
|
||||
}
|
||||
|
||||
@method_id(17)
|
||||
fun testConstNegateCodegen() {
|
||||
return (!0, !1, !true, !false, !!true, !!false);
|
||||
}
|
||||
|
||||
@method_id(18)
|
||||
fun testBoolNegateOptimized(x: bool) {
|
||||
return (x, !x, !!x, !!!x, !!!!true);
|
||||
}
|
||||
|
||||
fun eqX(x: bool) { return x; }
|
||||
|
||||
@method_id(19)
|
||||
fun testBoolCompareOptimized(x: bool) {
|
||||
return (x == true, x != true, eqX(x) == false, eqX(x) != false, !!(x == !false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
method_id | in | out
|
||||
|
@ -50,4 +109,96 @@ fun is_claimed(index: int): int {
|
|||
@testcase | 11 | 1 | -1
|
||||
@testcase | 11 | 256 | -1
|
||||
@testcase | 11 | 8 | 0
|
||||
@testcase | 12 | 0 0 | -1 -1 -1 -1 -1 -1
|
||||
@testcase | 12 | -1 -1 | 0 0 0 0 0 0
|
||||
@testcase | 12 | 7 0 | -8 -1 0 -1 -8 -1
|
||||
@testcase | 14 | -1 | 0 0 -1 -1 0 0 6
|
||||
@testcase | 14 | 0 | 0 0 -1 -1 -1 -1 6
|
||||
@testcase | 15 | -1 -1 | 0 -1
|
||||
@testcase | 15 | -1 0 | 0 -1
|
||||
@testcase | 16 | 0 0 | 3 -1 -1
|
||||
@testcase | 17 | | -1 0 0 -1 -1 0
|
||||
@testcase | 18 | 0 | 0 -1 0 -1 -1
|
||||
@testcase | 18 | -1 | -1 0 -1 0 -1
|
||||
@testcase | 19 | 0 | 0 -1 -1 0 0
|
||||
@testcase | 19 | -1 | -1 0 0 -1 -1
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
boolWithBitwiseConst PROC:<{
|
||||
//
|
||||
0 PUSHINT // _3
|
||||
-1 PUSHINT // _3 _5
|
||||
0 PUSHINT // _3 _5 _7
|
||||
-1 PUSHINT // _3 _5 _7 _8
|
||||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
testDoUntilCodegen PROC:<{
|
||||
// i n
|
||||
0 PUSHINT // i n cnt=0
|
||||
UNTIL:<{
|
||||
INC // i n cnt
|
||||
s2 PUSH // i n cnt i
|
||||
NOT // i n cnt _6
|
||||
}> // i n cnt
|
||||
UNTIL:<{
|
||||
INC // i n cnt
|
||||
s2 PUSH // i n cnt i
|
||||
NOT // i n cnt _9
|
||||
}> // i n cnt
|
||||
UNTIL:<{
|
||||
INC // i n cnt
|
||||
OVER // i n cnt n
|
||||
0 EQINT // i n cnt _12
|
||||
}> // i n cnt
|
||||
s0 s2 XCHG // cnt n i
|
||||
NOT // cnt n _13
|
||||
SWAP // cnt _13 n
|
||||
0 EQINT // cnt _13 _14
|
||||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
testConstNegateCodegen PROC:<{
|
||||
//
|
||||
TRUE // _0
|
||||
FALSE // _0 _1
|
||||
FALSE // _0 _1 _2
|
||||
TRUE // _0 _1 _2 _3
|
||||
TRUE // _0 _1 _2 _3 _4
|
||||
FALSE // _0 _1 _2 _3 _4 _5
|
||||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
testBoolNegateOptimized PROC:<{
|
||||
// x
|
||||
DUP // x x
|
||||
NOT // x _1
|
||||
OVER // x _1 x
|
||||
NOT // x _1 _2
|
||||
s2 s(-1) PUXC
|
||||
TRUE // x _1 x _2 _3
|
||||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
testBoolCompareOptimized PROC:<{
|
||||
// x
|
||||
DUP // x x
|
||||
NOT // x _1
|
||||
OVER // x _1 x
|
||||
eqX CALLDICT // x _1 _2
|
||||
NOT // x _1 _3
|
||||
s2 PUSH // x _1 _3 x
|
||||
eqX CALLDICT // x _1 _3 _4
|
||||
s3 PUSH // x _1 _3 _4 x
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
global op: (int, int) -> int;
|
||||
|
||||
fun check_assoc(a: int, b: int, c: int): int {
|
||||
fun check_assoc(a: int, b: int, c: int): bool {
|
||||
return op(op(a, b), c) == op(a, op(b, c));
|
||||
}
|
||||
|
||||
fun unnamed_args(_: int, _: slice, _: auto): auto {
|
||||
fun unnamed_args(_: int, _: slice, _: int) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fun main(x: int, y: int, z: int): int {
|
||||
fun main(x: int, y: int, z: int): bool {
|
||||
op = `_+_`;
|
||||
if (0) { return null; }
|
||||
return check_assoc(x, y, z);
|
||||
}
|
||||
|
||||
@method_id(101)
|
||||
fun test101(x: int, z: int): auto {
|
||||
fun test101(x: int, z: int) {
|
||||
return unnamed_args(x, "asdf", z);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
fun check_assoc(op: auto, a: int, b: int, c: int) {
|
||||
fun check_assoc(op: (int, int) -> int, a: int, b: int, c: int) {
|
||||
return op(op(a, b), c) == op(a, op(b, c));
|
||||
}
|
||||
|
||||
fun main(x: int, y: int, z: int): int {
|
||||
fun main(x: int, y: int, z: int): bool {
|
||||
return check_assoc(`_+_`, x, y, z);
|
||||
}
|
||||
|
||||
|
|
|
@ -162,8 +162,8 @@ fun test13() {
|
|||
}
|
||||
|
||||
@method_id(114)
|
||||
fun test110(x: int) {
|
||||
var s = beginCell().storeBool(x < 0).storeBool(0).storeBool(x).endCell().beginParse();
|
||||
fun test110(x: bool) {
|
||||
var s = beginCell().storeBool(x == true).storeBool(false).storeBool(x).endCell().beginParse();
|
||||
return (s.loadBool(), s.loadBool(), s.loadBool());
|
||||
}
|
||||
|
||||
|
@ -179,15 +179,15 @@ fun test111() {
|
|||
if (s.addressIsNone()) {
|
||||
s.skipBits(2);
|
||||
}
|
||||
if (s.loadBool() == 0) {
|
||||
assert(s.loadBool() == 0) throw 444;
|
||||
if (s.loadBool() == false) {
|
||||
assert(!s.loadBool()) throw 444;
|
||||
s.skipBouncedPrefix();
|
||||
}
|
||||
var op2 = s.loadMessageOp();
|
||||
var q2 = s.loadMessageQueryId();
|
||||
s.skipBits(64);
|
||||
s.assertEndOfSlice();
|
||||
assert(isMessageBounced(0x001)) throw 444;
|
||||
assert(isMessageBounced(0x001) && !isMessageBounced(0x002)) throw 444;
|
||||
return (op1, q1, op2, q2);
|
||||
}
|
||||
|
||||
|
@ -216,15 +216,15 @@ Note, that since 'compute-asm-ltr' became on be default, chaining methods codege
|
|||
"""
|
||||
test6 PROC:<{
|
||||
//
|
||||
NEWC // _1
|
||||
1 PUSHINT // _1 _2=1
|
||||
SWAP // _2=1 _1
|
||||
NEWC // _0
|
||||
1 PUSHINT // _0 _1=1
|
||||
SWAP // _1=1 _0
|
||||
32 STU // _0
|
||||
2 PUSHINT // _0 _6=2
|
||||
SWAP // _6=2 _0
|
||||
2 PUSHINT // _0 _5=2
|
||||
SWAP // _5=2 _0
|
||||
32 STU // _0
|
||||
3 PUSHINT // _0 _10=3
|
||||
SWAP // _10=3 _0
|
||||
3 PUSHINT // _0 _9=3
|
||||
SWAP // _9=3 _0
|
||||
32 STU // _0
|
||||
}>
|
||||
"""
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@method_id(101)
|
||||
fun test1(): int {
|
||||
var x = false;
|
||||
if (x == true) {
|
||||
var x: int = false as int;
|
||||
if (x == true as int) {
|
||||
x= 100500;
|
||||
}
|
||||
return x;
|
||||
|
@ -35,7 +35,7 @@ Below, I just give examples of @fif_codegen tag:
|
|||
"""
|
||||
main PROC:<{
|
||||
// s
|
||||
17 PUSHINT // s _3=17
|
||||
17 PUSHINT // s _1=17
|
||||
OVER // s z=17 t
|
||||
WHILE:<{
|
||||
...
|
||||
|
|
150
tolk-tester/tests/generics-1.tolk
Normal file
150
tolk-tester/tests/generics-1.tolk
Normal file
|
@ -0,0 +1,150 @@
|
|||
fun eq1<X>(value: X): X { return value; }
|
||||
fun eq2<X>(value: X) { return value; }
|
||||
fun eq3<X>(value: X): X { var cp: [X] = [eq1(value)]; var ((([v: X]))) = cp; return v; }
|
||||
fun eq4<X>(value: X) { return eq1<X>(value); }
|
||||
|
||||
@method_id(101)
|
||||
fun test101(x: int) {
|
||||
var (a, b, c) = (x, (x,x), [x,x]);
|
||||
return (eq1(a), eq1(b), eq1(c), eq2(a), eq2(b), eq2(c), eq3(a), eq4(b), eq3(createEmptyTuple()));
|
||||
}
|
||||
|
||||
fun getTwo<X>(): X { return 2 as X; }
|
||||
|
||||
fun takeInt(a: int) { return a; }
|
||||
|
||||
@method_id(102)
|
||||
fun test102(): (int, int, int, [(int, int)]) {
|
||||
var a: int = getTwo();
|
||||
var _: int = getTwo();
|
||||
var b = getTwo() as int;
|
||||
var c: int = 1 ? getTwo() : getTwo();
|
||||
var c redef = getTwo();
|
||||
return (eq1<int>(a), eq2<int>(b), takeInt(getTwo()), [(getTwo(), getTwo())]);
|
||||
}
|
||||
|
||||
@method_id(103)
|
||||
fun test103(first: int): (int, int, int) {
|
||||
var t = createEmptyTuple();
|
||||
var cs = beginCell().storeInt(100, 32).endCell().beginParse();
|
||||
t.tuplePush(first);
|
||||
t.tuplePush(2);
|
||||
t.tuplePush(cs);
|
||||
cs = t.tupleAt(2);
|
||||
cs = t.tupleAt(2) as slice;
|
||||
return (t.tupleAt(0), cs.loadInt(32), t.tupleAt<slice>(2).loadInt(32));
|
||||
}
|
||||
|
||||
fun manyEq<T1, T2, T3>(a: T1, b: T2, c: T3): [T1, T2, T3] {
|
||||
return [a, b, c];
|
||||
}
|
||||
|
||||
@method_id(104)
|
||||
fun test104(f: int) {
|
||||
return (
|
||||
manyEq(1 ? 1 : 1, f ? 0 : null, !f ? getTwo() as int : null),
|
||||
manyEq((f ? null as int : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool()), 0, eq4(f))
|
||||
);
|
||||
}
|
||||
|
||||
fun calcSum<X>(x: X, y: X) { return x + y; }
|
||||
|
||||
@method_id(105)
|
||||
fun test105() {
|
||||
if (0) { calcSum(((0)), null); }
|
||||
return (calcSum(1, 2));
|
||||
}
|
||||
|
||||
fun calcYPlus1<Y>(value: Y) { return value + 1; }
|
||||
fun calcLoad32(cs: slice) { return cs.loadInt(32); }
|
||||
fun calcTensorPlus1(tens: (int, int)) { var (f, s) = tens; return (f + 1, s + 1); }
|
||||
fun calcTensorMul2(tens: (int, int)) { var (f, s) = tens; return (f * 2, s * 2); }
|
||||
fun cellToSlice(c: cell) { return c.beginParse(); }
|
||||
fun abstractTransform<X, Y, R>(xToY: (X) -> Y, yToR: (((Y))) -> R, initialX: X): R {
|
||||
var y = xToY(initialX);
|
||||
return yToR(y);
|
||||
}
|
||||
|
||||
@method_id(106)
|
||||
fun test106() {
|
||||
var c = beginCell().storeInt(106, 32).endCell();
|
||||
return [
|
||||
abstractTransform(cellToSlice, calcLoad32, c),
|
||||
abstractTransform(calcYPlus1<int>, calcYPlus1<int>, 0),
|
||||
abstractTransform(calcTensorPlus1, calcTensorMul2, (2, 2))
|
||||
];
|
||||
}
|
||||
|
||||
fun callTupleFirst<X, Y>(t: X): Y { return t.tupleFirst(); }
|
||||
fun callTuplePush<T, V>(mutate self: T, v1: V, v2: V): self { self.tuplePush(v1); tuplePush(mutate self, v2); return self; }
|
||||
fun getTupleLastInt(t: tuple) { return t.tupleLast<int>(); }
|
||||
fun getTupleSize(t: tuple) { return t.tupleSize(); }
|
||||
fun callAnyFn<TObj, TResult>(f: (TObj) -> TResult, arg: TObj) { return f(arg); }
|
||||
fun callAnyFn2<TCallback>(f: TCallback, arg: tuple) { return f(arg); }
|
||||
|
||||
global t107: tuple;
|
||||
|
||||
@method_id(107)
|
||||
fun test107() {
|
||||
t107 = createEmptyTuple();
|
||||
callTuplePush(mutate t107, 1, 2);
|
||||
t107.callTuplePush(3, 4).callTuplePush(5, 6);
|
||||
var first: int = t107.callTupleFirst();
|
||||
return (
|
||||
callAnyFn<tuple, int>(getTupleSize, t107),
|
||||
callAnyFn2(getTupleSize, t107),
|
||||
first,
|
||||
callTupleFirst(t107) as int,
|
||||
callAnyFn(getTupleLastInt, t107),
|
||||
callAnyFn2(getTupleLastInt, t107)
|
||||
);
|
||||
}
|
||||
|
||||
global g108: int;
|
||||
|
||||
fun inc108(by: int) { g108 += by; }
|
||||
fun getInc108() { return inc108; }
|
||||
fun returnResult<RetT>(f: () -> RetT): RetT { return f(); }
|
||||
fun applyAndReturn<ArgT, RetT>(f: () -> (ArgT) -> RetT, arg: ArgT): () -> ArgT -> RetT {
|
||||
f()(arg);
|
||||
return f;
|
||||
}
|
||||
|
||||
@method_id(108)
|
||||
fun test108() {
|
||||
g108 = 0;
|
||||
getInc108()(1);
|
||||
returnResult<(int) -> void>(getInc108)(2);
|
||||
applyAndReturn<int, void>(getInc108, 10)()(10);
|
||||
returnResult(getInc108)(2);
|
||||
applyAndReturn(getInc108, 10)()(10);
|
||||
return g108;
|
||||
}
|
||||
|
||||
fun main(x: int): (int, [[int, int]]) {
|
||||
try { if(x) { throw (1, x); } }
|
||||
catch (excNo, arg) { return (arg as int, [[eq2(arg as int), getTwo()]]); }
|
||||
return (0, [[x, 1]]);
|
||||
}
|
||||
|
||||
/**
|
||||
@testcase | 0 | 1 | 1 [ [ 1 2 ] ]
|
||||
@testcase | 101 | 0 | 0 0 0 [ 0 0 ] 0 0 0 [ 0 0 ] 0 0 0 []
|
||||
@testcase | 102 | | 2 2 2 [ 2 2 ]
|
||||
@testcase | 103 | 0 | 0 100 100
|
||||
@testcase | 104 | 0 | [ 1 (null) 2 ] [ 2 -1 0 0 ]
|
||||
@testcase | 105 | | 3
|
||||
@testcase | 106 | | [ 106 2 6 6 ]
|
||||
@testcase | 107 | | 6 6 1 1 6 6
|
||||
@testcase | 108 | | 45
|
||||
|
||||
@fif_codegen DECLPROC eq1<int>
|
||||
@fif_codegen DECLPROC eq1<tuple>
|
||||
@fif_codegen DECLPROC eq1<(int,int)>
|
||||
@fif_codegen DECLPROC eq1<[int,int]>
|
||||
@fif_codegen DECLPROC getTwo<int>
|
||||
|
||||
@fif_codegen_avoid DECLPROC eq1
|
||||
@fif_codegen_avoid DECLPROC eq2
|
||||
@fif_codegen_avoid DECLPROC eq3
|
||||
*/
|
|
@ -11,7 +11,7 @@ fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell {
|
|||
fun lookupIdxByValue(idict32: cell, value: int): int {
|
||||
var cur_key = -1;
|
||||
do {
|
||||
var (cur_key redef, cs: slice, found: int) = idict32.iDictGetNext(32, cur_key);
|
||||
var (cur_key redef, cs: slice, found: bool) = 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) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
fun main() {
|
||||
return true();
|
||||
const asdf = 1;
|
||||
|
||||
fun main(x: int) {
|
||||
return x.asdf();
|
||||
}
|
||||
|
||||
/**
|
||||
@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
|
||||
@stderr calling a non-function
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,6 @@ fun main() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr rvalue expected
|
||||
@stderr `_` can't be used as a value; it's a placeholder for a left side of assignment
|
||||
@stderr inc(_)
|
||||
*/
|
||||
|
|
10
tolk-tester/tests/invalid-call-9.tolk
Normal file
10
tolk-tester/tests/invalid-call-9.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun getOne() { return 1; }
|
||||
|
||||
fun main() {
|
||||
return getOne<int>();
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr calling a not generic function with generic T
|
||||
*/
|
8
tolk-tester/tests/invalid-const-1.tolk
Normal file
8
tolk-tester/tests/invalid-const-1.tolk
Normal file
|
@ -0,0 +1,8 @@
|
|||
fun main() {
|
||||
return 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr invalid integer constant
|
||||
*/
|
13
tolk-tester/tests/invalid-declaration-11.tolk
Normal file
13
tolk-tester/tests/invalid-declaration-11.tolk
Normal file
|
@ -0,0 +1,13 @@
|
|||
// this function is declared incorrectly,
|
||||
// since it should return 2 values onto a stack (1 for returned slice, 1 for mutated int)
|
||||
// but contains not 2 numbers in asm ret_order
|
||||
fun loadAddress2(mutate self: int): slice
|
||||
asm( -> 1 0 2) "LDMSGADDR";
|
||||
|
||||
fun main(){}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr ret_order (after ->) expected to contain 2 numbers
|
||||
@stderr asm( -> 1 0 2)
|
||||
*/
|
16
tolk-tester/tests/invalid-declaration-12.tolk
Normal file
16
tolk-tester/tests/invalid-declaration-12.tolk
Normal file
|
@ -0,0 +1,16 @@
|
|||
fun proxy(x: int) {
|
||||
return factorial(x);
|
||||
}
|
||||
|
||||
fun factorial(x: int) {
|
||||
if (x <= 0) {
|
||||
return 1;
|
||||
}
|
||||
return x * proxy(x-1);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr could not infer return type of `factorial`, because it appears in a recursive call chain
|
||||
@stderr fun factorial
|
||||
*/
|
7
tolk-tester/tests/invalid-declaration-13.tolk
Normal file
7
tolk-tester/tests/invalid-declaration-13.tolk
Normal file
|
@ -0,0 +1,7 @@
|
|||
const c: slice = 123 + 456;
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr expression type does not match declared type
|
||||
@stderr const c
|
||||
*/
|
10
tolk-tester/tests/invalid-generics-1.tolk
Normal file
10
tolk-tester/tests/invalid-generics-1.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun f<X>(v: int, x: X) {}
|
||||
|
||||
fun failCantDeduceWithoutArgument() {
|
||||
return f(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not deduce X for generic function `f<X>`
|
||||
*/
|
9
tolk-tester/tests/invalid-generics-10.tolk
Normal file
9
tolk-tester/tests/invalid-generics-10.tolk
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun invalidReferencingGenericMethodWithoutGeneric() {
|
||||
var t = createEmptyTuple();
|
||||
var cb = t.tupleLast;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not use a generic function `tupleLast<T>` as non-call
|
||||
*/
|
11
tolk-tester/tests/invalid-generics-11.tolk
Normal file
11
tolk-tester/tests/invalid-generics-11.tolk
Normal file
|
@ -0,0 +1,11 @@
|
|||
global gVar: int;
|
||||
|
||||
fun main() {
|
||||
var x = gVar<int>;
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr generic T not expected here
|
||||
*/
|
10
tolk-tester/tests/invalid-generics-2.tolk
Normal file
10
tolk-tester/tests/invalid-generics-2.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun f<T>(v: int, x: T) {}
|
||||
|
||||
fun failCantDeduceWithPlainNull() {
|
||||
return f(0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not deduce T for generic function `f<T>`
|
||||
*/
|
11
tolk-tester/tests/invalid-generics-3.tolk
Normal file
11
tolk-tester/tests/invalid-generics-3.tolk
Normal file
|
@ -0,0 +1,11 @@
|
|||
fun f<T>(x: T, y: T) {}
|
||||
|
||||
fun failIncompatibleTypesForT() {
|
||||
return f(32, "");
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr T is both int and slice for generic function `f<T>`
|
||||
@stderr f(32
|
||||
*/
|
10
tolk-tester/tests/invalid-generics-4.tolk
Normal file
10
tolk-tester/tests/invalid-generics-4.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun f<T>(x: T): void asm "NOP";
|
||||
|
||||
fun failInstantiatingAsmFunctionWithNon1Slot() {
|
||||
f((1, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not call `f<T>` with T=(int, int), because it occupies 2 stack slots in TVM, not 1
|
||||
*/
|
10
tolk-tester/tests/invalid-generics-5.tolk
Normal file
10
tolk-tester/tests/invalid-generics-5.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun f<T>(x: T): void asm "NOP";
|
||||
|
||||
fun failUsingGenericFunctionPartially() {
|
||||
var cb = f;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not use a generic function `f<T>` as non-call
|
||||
*/
|
10
tolk-tester/tests/invalid-generics-6.tolk
Normal file
10
tolk-tester/tests/invalid-generics-6.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun eq<X>(t: X) { return t; }
|
||||
|
||||
fun failUsingGenericFunctionPartially() {
|
||||
var cb = createEmptyTuple().eq().eq().tuplePush;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not use a generic function `tuplePush<T>` as non-call
|
||||
*/
|
18
tolk-tester/tests/invalid-generics-7.tolk
Normal file
18
tolk-tester/tests/invalid-generics-7.tolk
Normal file
|
@ -0,0 +1,18 @@
|
|||
fun failOnInstantiation(a: slice) {
|
||||
var b: slice = foo(a);
|
||||
}
|
||||
|
||||
fun bar<X>(value: X) : X {
|
||||
return 1;
|
||||
}
|
||||
fun foo<X>(value: X) : X {
|
||||
return bar(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr while instantiating generic function `foo<slice>`
|
||||
@stderr while instantiating generic function `bar<slice>`
|
||||
@stderr can not convert type `int` to return type `slice`
|
||||
@stderr return 1
|
||||
*/
|
11
tolk-tester/tests/invalid-generics-8.tolk
Normal file
11
tolk-tester/tests/invalid-generics-8.tolk
Normal file
|
@ -0,0 +1,11 @@
|
|||
fun withT1T2<T1, T2>(a: (T1, T2)) {}
|
||||
|
||||
fun wrongTCountPassed() {
|
||||
withT1T2<int>((5, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr wrong count of generic T: expected 2, got 1
|
||||
@stderr <int>
|
||||
*/
|
8
tolk-tester/tests/invalid-generics-9.tolk
Normal file
8
tolk-tester/tests/invalid-generics-9.tolk
Normal file
|
@ -0,0 +1,8 @@
|
|||
fun invalidProvidingGenericTsToNotGeneric() {
|
||||
beginCell<builder>();
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr calling a not generic function with generic T
|
||||
*/
|
|
@ -7,5 +7,5 @@ fun cantAssignToVal() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `x`
|
||||
@stderr modifying immutable variable `x`
|
||||
*/
|
||||
|
|
|
@ -4,5 +4,5 @@ fun load32(self: slice): int {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying `self` (call a mutating method), which is immutable by default
|
||||
@stderr modifying `self`, which is immutable by default
|
||||
*/
|
||||
|
|
9
tolk-tester/tests/invalid-mutate-16.tolk
Normal file
9
tolk-tester/tests/invalid-mutate-16.tolk
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun cantCallMutatingFunctionWithAssignmentLValue() {
|
||||
var t: tuple = createEmptyTuple();
|
||||
(t = createEmptyTuple()).tuplePush(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr assignment can not be used as lvalue
|
||||
*/
|
13
tolk-tester/tests/invalid-mutate-17.tolk
Normal file
13
tolk-tester/tests/invalid-mutate-17.tolk
Normal file
|
@ -0,0 +1,13 @@
|
|||
@pure
|
||||
fun tupleMut(mutate self: tuple): int
|
||||
asm "TLEN";
|
||||
|
||||
fun main() {
|
||||
var t = createEmptyTuple();
|
||||
return [[t.tupleMut]];
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr saving `tupleMut` into a variable is impossible, since it has `mutate` parameters
|
||||
*/
|
|
@ -6,5 +6,5 @@ fun cantAssignToVal() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `x`
|
||||
@stderr modifying immutable variable `x`
|
||||
*/
|
||||
|
|
|
@ -7,5 +7,5 @@ fun cantAssignToConst() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `op_increase`
|
||||
@stderr modifying immutable constant
|
||||
*/
|
||||
|
|
|
@ -10,5 +10,5 @@ fun cantPassToMutatingFunction() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `myVal`
|
||||
@stderr modifying immutable variable `myVal`
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,6 @@ fun cantCallMutatingMethod(c: cell) {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `s` (call a mutating method)
|
||||
@stderr modifying immutable variable `s`
|
||||
@stderr s.loadUint
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,6 @@ fun cantCallMutatingFunctionWithImmutable() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `op_increase` (call a mutating function)
|
||||
@stderr modifying immutable constant
|
||||
@stderr inc(mutate op_increase)
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,6 @@ fun cantCallMutatingFunctionWithRvalue() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr lvalue expected (call a mutating function)
|
||||
@stderr literal can not be used as lvalue
|
||||
@stderr incBoth(mutate x, mutate 30)
|
||||
*/
|
||||
|
|
|
@ -6,5 +6,5 @@ fun cantRedefImmutable() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying an immutable variable `x` (left side of assignment)
|
||||
@stderr `redef` for immutable variable
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,6 @@ fun increment(self: int) {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr modifying `self` (left side of assignment), which is immutable by default
|
||||
@stderr modifying `self`, which is immutable by default
|
||||
@stderr probably, you want to declare `mutate self`
|
||||
*/
|
||||
|
|
|
@ -4,5 +4,5 @@ fun load_u32(cs: slice): (slice, int) {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr expected `(`, got `32`
|
||||
@stderr expected `;`, got `32`
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,7 @@ fun f_pure(): int {
|
|||
return f_impure();
|
||||
}
|
||||
|
||||
fun f_impure(): int {}
|
||||
fun f_impure(): int { return 0; }
|
||||
|
||||
fun main(): int {
|
||||
return f_pure();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
fun validate_input(input: cell): (int, int) {
|
||||
var (x, y, z, correct) = calculateCellSize(input, 10);
|
||||
assert(correct) throw 102;
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
@pure
|
||||
|
|
10
tolk-tester/tests/invalid-redefinition-6.tolk
Normal file
10
tolk-tester/tests/invalid-redefinition-6.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
const s1 = "asdf";
|
||||
|
||||
fun main() {
|
||||
var s1 redef = "d";
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr `redef` for unknown variable
|
||||
*/
|
|
@ -4,6 +4,6 @@ fun cantReturnNothingFromSelf(mutate self: int): self {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr missing return; forgot `return self`?
|
||||
@stderr missing return
|
||||
@stderr }
|
||||
*/
|
||||
|
|
|
@ -4,5 +4,5 @@ fun main(x: int) {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr null is not a function: use `null`, not `null()`
|
||||
@stderr calling a non-function
|
||||
*/
|
||||
|
|
9
tolk-tester/tests/invalid-syntax-6.tolk
Normal file
9
tolk-tester/tests/invalid-syntax-6.tolk
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun main() {
|
||||
var a = 1;
|
||||
(a += 1) += 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr assignment can not be used as lvalue
|
||||
*/
|
9
tolk-tester/tests/invalid-syntax-7.tolk
Normal file
9
tolk-tester/tests/invalid-syntax-7.tolk
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun main() {
|
||||
var x = 1;
|
||||
x += (var y = 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr expected <expression>, got `var`
|
||||
*/
|
|
@ -6,5 +6,5 @@ fun main() {
|
|||
/**
|
||||
@compilation_should_fail
|
||||
@stderr .tolk:2
|
||||
@stderr expected <type>, got `scli`
|
||||
@stderr unknown type name `scli`
|
||||
*/
|
||||
|
|
8
tolk-tester/tests/invalid-typing-10.tolk
Normal file
8
tolk-tester/tests/invalid-typing-10.tolk
Normal file
|
@ -0,0 +1,8 @@
|
|||
fun failMathOnBoolean(c: cell) {
|
||||
return (null == c) * 10;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not apply operator `*` to `bool` and `int`
|
||||
*/
|
11
tolk-tester/tests/invalid-typing-11.tolk
Normal file
11
tolk-tester/tests/invalid-typing-11.tolk
Normal file
|
@ -0,0 +1,11 @@
|
|||
fun failBitwiseNotOnBool() {
|
||||
var eq = 1 == 0;
|
||||
if (~eq) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not apply operator `~` to `bool`
|
||||
*/
|
10
tolk-tester/tests/invalid-typing-12.tolk
Normal file
10
tolk-tester/tests/invalid-typing-12.tolk
Normal file
|
@ -0,0 +1,10 @@
|
|||
fun failAssignNullToTensor() {
|
||||
var ab = (1, 2);
|
||||
ab = null;
|
||||
return ab;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not assign `null` to variable of type `(int, int)`
|
||||
*/
|
|
@ -1,9 +1,9 @@
|
|||
fun main() {
|
||||
var tri: (int, bool) = (10, false);
|
||||
var tri: (int, int) = (10, false);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr bool type is not supported yet
|
||||
@stderr can not assign `(int, bool)` to variable of type `(int, int)`
|
||||
*/
|
||||
|
|
|
@ -15,5 +15,5 @@ fun cantMixDifferentThis() {
|
|||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr cannot apply function appendBuilder : builder -> (builder, ()) to arguments of type int: cannot unify type int with builder
|
||||
@stderr can not call method for `builder` with object of type `int`
|
||||
*/
|
||||
|
|
|
@ -7,8 +7,6 @@ fun cantCallNotChainedMethodsInAChain(x: int) {
|
|||
}
|
||||
|
||||
/**
|
||||
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
|
||||
@stderr can not call method for `int` with object of type `void`
|
||||
*/
|
||||
|
|
|
@ -7,8 +7,7 @@ fun failWhenReturnANotChainedValue(x: int): int {
|
|||
}
|
||||
|
||||
/**
|
||||
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
|
||||
@stderr x.incNotChained()
|
||||
@stderr can not convert type `void` to return type `int`
|
||||
*/
|
||||
|
|
8
tolk-tester/tests/invalid-typing-6.tolk
Normal file
8
tolk-tester/tests/invalid-typing-6.tolk
Normal file
|
@ -0,0 +1,8 @@
|
|||
fun failWhenTernaryConditionNotInt(cs: slice) {
|
||||
return cs ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not use `slice` as a boolean condition
|
||||
*/
|
9
tolk-tester/tests/invalid-typing-7.tolk
Normal file
9
tolk-tester/tests/invalid-typing-7.tolk
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun failAssignPlainNullToVariable() {
|
||||
var x = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not infer type of `x`, it's always null
|
||||
@stderr specify its type with `x: <type>` or use `null as <type>`
|
||||
*/
|
8
tolk-tester/tests/invalid-typing-8.tolk
Normal file
8
tolk-tester/tests/invalid-typing-8.tolk
Normal file
|
@ -0,0 +1,8 @@
|
|||
fun failExplicitCastIncompatible(c: cell) {
|
||||
return c as slice;
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr type `cell` can not be cast to `slice`
|
||||
*/
|
13
tolk-tester/tests/invalid-typing-9.tolk
Normal file
13
tolk-tester/tests/invalid-typing-9.tolk
Normal file
|
@ -0,0 +1,13 @@
|
|||
fun getTupleLastGetter<X>(): tuple -> X {
|
||||
return tupleLast<X>;
|
||||
}
|
||||
|
||||
fun failTypeMismatch() {
|
||||
var t = createEmptyTuple();
|
||||
var c: cell = getTupleLastGetter<int>()(t);
|
||||
}
|
||||
|
||||
/**
|
||||
@compilation_should_fail
|
||||
@stderr can not assign `int` to variable of type `cell`
|
||||
*/
|
|
@ -1,14 +1,14 @@
|
|||
import "imports/use-dicts.tolk"
|
||||
|
||||
fun simpleAllConst() {
|
||||
return (!0, !!0 & !false, !!!0, !1, !!1, !-1, !!-1, (!5 == 0) == !0, !0 == true);
|
||||
return (!0, !!0 & !false, !!!0, !1, !!1, !-1, !!-1, (!5 as int == 0) == !0, !0 == true);
|
||||
}
|
||||
|
||||
fun compileTimeEval1(x: int) {
|
||||
// todo now compiler doesn't understand that bool can't be equal to number other than 0/-1
|
||||
// (but understands that it can't be positive)
|
||||
// that's why for now, the last condition is evaluated at runtime
|
||||
return (!x, !x > 10, !x < 10, !!x == 5, !x == -10);
|
||||
return (!x, !x as int > 10, (!x as int) < 10, !!x as int == 5, !x as int == -10);
|
||||
}
|
||||
|
||||
@method_id(101)
|
||||
|
@ -23,13 +23,13 @@ fun withAndOr(x: int, y: int, z: int) {
|
|||
var return_at_end = -1;
|
||||
if (!x & !y) {
|
||||
if (!z & !y) { return 10; }
|
||||
else if (z | !!y) { return_at_end = 20; }
|
||||
else if ((z != 0) | !!y) { return_at_end = 20; }
|
||||
} else if (!!x & !!y & !z) {
|
||||
if (!z & (x > 10)) { return_at_end = 30; }
|
||||
if ((x != 11) & !z) { return 40; }
|
||||
return_at_end = 50;
|
||||
} else {
|
||||
return_at_end = !x ? !y : !z | 1;
|
||||
return_at_end = !x ? !y as int : (!z as int) | 1;
|
||||
}
|
||||
return return_at_end;
|
||||
}
|
||||
|
@ -54,7 +54,8 @@ fun testDict(last: int) {
|
|||
|
||||
@method_id(105)
|
||||
fun testNotNull(x: int) {
|
||||
return [x == null, null == x, !(x == null), null == null, +(null != null)];
|
||||
// return [x == null, null == x, !(x == null), null == null, +(null != null)];
|
||||
return [x == null, null == x, !(x == null)];
|
||||
}
|
||||
|
||||
@method_id(106)
|
||||
|
@ -123,6 +124,31 @@ fun testLogicalOps2(first: int) {
|
|||
return (s.getRemainingBitsCount(), sum);
|
||||
}
|
||||
|
||||
@method_id(112)
|
||||
fun mixLogicalIntsAndBools(first: int, cond: bool) {
|
||||
return (
|
||||
(first && cond) || (!first && cond),
|
||||
((first & -1) & cond as int) == ((first && true) && cond) as int,
|
||||
7 && cond,
|
||||
first || cond || !cond || alwaysThrows(),
|
||||
cond || first || !first || alwaysThrows()
|
||||
);
|
||||
}
|
||||
|
||||
@method_id(113)
|
||||
fun testConvertIfToIfnot(x: bool) {
|
||||
assert(!!(x == false), 100);
|
||||
assert(!x, 100);
|
||||
if (x == !!false) {
|
||||
return 1;
|
||||
}
|
||||
if (!!(x != !false)) {
|
||||
return 1;
|
||||
}
|
||||
assert(!!x, 100);
|
||||
return -4;
|
||||
}
|
||||
|
||||
fun main() {
|
||||
|
||||
}
|
||||
|
@ -144,8 +170,8 @@ fun main() {
|
|||
@testcase | 104 | 50 | 3 5 -1
|
||||
@testcase | 104 | 100 | 3 5 5
|
||||
@testcase | 104 | 0 | 3 -1 5
|
||||
@testcase | 105 | 0 | [ 0 0 -1 -1 0 ]
|
||||
@testcase | 105 | null | [ -1 -1 0 -1 0 ]
|
||||
@testcase | 105 | 0 | [ 0 0 -1 ]
|
||||
@testcase | 105 | null | [ -1 -1 0 ]
|
||||
@testcase | 106 | | [ 0 0 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ 0 -1 ]
|
||||
@testcase | 107 | | [ -1 -1 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ -1 0 ]
|
||||
@testcase | 108 | 1 2 | -1
|
||||
|
@ -159,18 +185,21 @@ fun main() {
|
|||
@testcase | 110 | 500 | -1 -1 0 -1 -1 3
|
||||
@testcase | 111 | 0 | 32 4
|
||||
@testcase | 111 | -1 | 0 8
|
||||
@testcase | 112 | 5 0 | 0 -1 0 -1 -1
|
||||
@testcase | 112 | 0 -1 | -1 -1 -1 -1 -1
|
||||
@testcase | 113 | 0 | 1
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
simpleAllConst PROC:<{
|
||||
//
|
||||
-1 PUSHINT
|
||||
TRUE
|
||||
0 PUSHINT
|
||||
-1 PUSHINT
|
||||
0 PUSHINT
|
||||
-1 PUSHINT
|
||||
0 PUSHINT
|
||||
-1 PUSHINT
|
||||
TRUE
|
||||
FALSE
|
||||
TRUE
|
||||
FALSE
|
||||
TRUE
|
||||
TRUE
|
||||
TRUE
|
||||
}>
|
||||
|
@ -292,4 +321,27 @@ These are moments of future optimizations. For now, it's more than enough.
|
|||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
testConvertIfToIfnot PROC:<{
|
||||
// x
|
||||
DUP // x x
|
||||
100 THROWIF
|
||||
DUP // x x
|
||||
100 THROWIF
|
||||
DUP // x x
|
||||
IFNOTJMP:<{ // x
|
||||
DROP //
|
||||
1 PUSHINT // _7=1
|
||||
}> // x
|
||||
DUP // x x
|
||||
IFNOTJMP:<{ // x
|
||||
DROP //
|
||||
1 PUSHINT // _8=1
|
||||
}> // x
|
||||
100 THROWIFNOT
|
||||
-4 PUSHINT // _12=-4
|
||||
}>
|
||||
"""
|
||||
|
||||
*/
|
||||
|
|
|
@ -118,12 +118,19 @@ fun updateTwoItems(mutate self: (int, int), byValue: int) {
|
|||
self = (first + byValue, second + byValue);
|
||||
}
|
||||
|
||||
global t107_1: int;
|
||||
global t107_2: int;
|
||||
|
||||
@method_id(107)
|
||||
fun testMutableTensor() {
|
||||
var t = (40, 50);
|
||||
t.updateTwoItems(10);
|
||||
updateTwoItems(mutate t, 10);
|
||||
return t;
|
||||
t107_1 = 1;
|
||||
t107_2 = 2;
|
||||
(t107_1, t107_2).updateTwoItems(10);
|
||||
updateTwoItems(mutate (t107_1, t107_2), 10);
|
||||
return (t, t107_1, t107_2);
|
||||
}
|
||||
|
||||
@pure
|
||||
|
@ -147,7 +154,7 @@ fun getSumOfNumbersInCell(c: cell): int {
|
|||
|
||||
@method_id(110)
|
||||
fun testStoreChaining() {
|
||||
var b = beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32);
|
||||
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);
|
||||
|
@ -191,7 +198,7 @@ fun testStoreAndMutateBoth() {
|
|||
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));
|
||||
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];
|
||||
|
@ -278,7 +285,7 @@ fun main(){}
|
|||
@testcase | 104 | | 1 2 3
|
||||
@testcase | 105 | | 5 5 110
|
||||
@testcase | 106 | | 160 110
|
||||
@testcase | 107 | | 60 70
|
||||
@testcase | 107 | | 60 70 21 22
|
||||
@testcase | 110 | | 320
|
||||
@testcase | 111 | | 55 55
|
||||
@testcase | 112 | | [ 1 13 3 23 33 ]
|
||||
|
@ -300,7 +307,7 @@ fun main(){}
|
|||
...
|
||||
incrementTwoInPlace CALLDICT // x y sum1
|
||||
-ROT
|
||||
10 PUSHINT // sum1 x y _9=10
|
||||
10 PUSHINT // sum1 x y _8=10
|
||||
incrementTwoInPlace CALLDICT // sum1 x y sum2
|
||||
s1 s3 s0 XCHG3 // x y sum1 sum2
|
||||
}>
|
||||
|
@ -310,8 +317,8 @@ fun main(){}
|
|||
"""
|
||||
load_next PROC:<{
|
||||
// cs
|
||||
32 LDI // _1 cs
|
||||
SWAP // cs _1
|
||||
32 LDI // _3 cs
|
||||
SWAP // cs _3
|
||||
}>
|
||||
"""
|
||||
|
||||
|
@ -319,7 +326,7 @@ fun main(){}
|
|||
"""
|
||||
testStoreUintPureUnusedResult PROC:<{
|
||||
//
|
||||
0 PUSHINT // _12=0
|
||||
0 PUSHINT // _11=0
|
||||
}>
|
||||
"""
|
||||
|
||||
|
@ -330,7 +337,7 @@ fun main(){}
|
|||
NEWC // b
|
||||
STIX // _2
|
||||
DROP //
|
||||
0 PUSHINT // _12=0
|
||||
0 PUSHINT // _11=0
|
||||
}>
|
||||
"""
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ global `some()var`:int;
|
|||
return `a`*-1*-(1)*---(1)*+just10()+-`just10`()*m1*-m1+-eq(m1)----0x1;
|
||||
}
|
||||
|
||||
@method_id(112) fun `bitwise~ops`(flags:int):[int,int] {
|
||||
@method_id(112) fun `bitwise~ops`(flags:int):[bool,bool] {
|
||||
return[
|
||||
(just10()-3==just10()-(4)--1)|((2==2)&(eq(eq(10)) -3==just10()--13)),
|
||||
((flags&0xFF)!=0)
|
||||
|
|
|
@ -7,12 +7,14 @@ fun test1() {
|
|||
numbers = listPrepend(2, numbers);
|
||||
numbers = listPrepend(3, numbers);
|
||||
numbers = listPrepend(4, numbers);
|
||||
var (h, numbers redef) = listSplit(numbers);
|
||||
var (h: int, numbers redef) = listSplit(numbers);
|
||||
h += listGetHead(numbers);
|
||||
|
||||
_ = null;
|
||||
(_, _) = (null, null);
|
||||
var t = createEmptyTuple();
|
||||
do {
|
||||
var num = numbers.listNext();
|
||||
var num: int = numbers.listNext();
|
||||
t.tuplePush(num);
|
||||
} while (numbers != null);
|
||||
|
||||
|
@ -44,7 +46,7 @@ fun test3(x: int) {
|
|||
}
|
||||
|
||||
fun getUntypedNull() {
|
||||
var untyped = null;
|
||||
var untyped: null = null;
|
||||
if (true) {
|
||||
return untyped;
|
||||
}
|
||||
|
@ -52,8 +54,8 @@ fun getUntypedNull() {
|
|||
}
|
||||
|
||||
@method_id(104)
|
||||
fun test4() {
|
||||
var (_, (_, untyped)) = (3, (createEmptyTuple, null));
|
||||
fun test4(): null {
|
||||
var (_, (_, untyped: null)) = (3, (createEmptyTuple, null));
|
||||
if (true) {
|
||||
return untyped;
|
||||
}
|
||||
|
@ -62,21 +64,16 @@ fun test4() {
|
|||
|
||||
@method_id(105)
|
||||
fun test5() {
|
||||
var n = getUntypedNull();
|
||||
var n: slice = getUntypedNull();
|
||||
return !(null == n) ? n.loadInt(32) : 100;
|
||||
}
|
||||
|
||||
@method_id(106)
|
||||
fun test6(x: int) {
|
||||
return x > null; // this compiles (for now), but fails at runtime
|
||||
}
|
||||
|
||||
@method_id(107)
|
||||
fun test7() {
|
||||
var b = beginCell().storeMaybeRef(null);
|
||||
var s = b.endCell().beginParse();
|
||||
var c = s.loadMaybeRef();
|
||||
return (null == c) * 10 + (b != null);
|
||||
return (null == c) as int * 10 + (b != null) as int;
|
||||
}
|
||||
|
||||
fun main() {
|
||||
|
@ -132,27 +129,18 @@ fun main() {
|
|||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
test6 PROC:<{
|
||||
// x
|
||||
PUSHNULL // x _1
|
||||
GREATER // _2
|
||||
}>
|
||||
"""
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
test7 PROC:<{
|
||||
...
|
||||
LDOPTREF // b _20 _19
|
||||
LDOPTREF // b _18 _17
|
||||
DROP // b c
|
||||
ISNULL // b _13
|
||||
10 MULCONST // b _15
|
||||
SWAP // _15 b
|
||||
ISNULL // _15 _16
|
||||
0 EQINT // _15 _17
|
||||
ADD // _18
|
||||
ISNULL // b _11
|
||||
10 MULCONST // b _13
|
||||
SWAP // _13 b
|
||||
ISNULL // _13 _14
|
||||
NOT // _13 _15
|
||||
ADD // _16
|
||||
}>
|
||||
"""
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
fun justTrue(): int { return true; }
|
||||
fun justTrue(): bool { return true; }
|
||||
|
||||
fun unary_minus_1(a: int, b: int, c: int): int{return -(a+b) *c;}
|
||||
fun unary_minus_2(a: int, b: int, c: int): int{return(-(a+b))*c;}
|
||||
|
@ -6,17 +6,17 @@ fun unary_minus_3(a: int, b: int, c: int): int{return-((a+b) *c);}
|
|||
|
||||
|
||||
@method_id(101)
|
||||
fun test1(x: int, y: int, z: int): int {
|
||||
fun test1(x: int, y: int, z: int): bool {
|
||||
return (x > 0) & (y > 0) & (z > 0);
|
||||
}
|
||||
|
||||
@method_id(102)
|
||||
fun test2(x: int, y: int, z: int): int {
|
||||
return x > (0 & (y > 0) & (z > 0));
|
||||
fun test2(x: int, y: int, z: int): bool {
|
||||
return x > (0 & (y > 0) as int & (z > 0) as int);
|
||||
}
|
||||
|
||||
@method_id(103)
|
||||
fun test3(x: int, y: int, z: int): int {
|
||||
fun test3(x: int, y: int, z: int): bool {
|
||||
if ((x < 0) | (y < 0)) {
|
||||
return z < 0;
|
||||
}
|
||||
|
@ -24,29 +24,29 @@ fun test3(x: int, y: int, z: int): int {
|
|||
}
|
||||
|
||||
@method_id(104)
|
||||
fun test4(x: int, y: int, mode: int): int {
|
||||
fun test4(x: int, y: int, mode: int): bool {
|
||||
if (mode == 1) {
|
||||
return (x == 10) | (y == 20);
|
||||
} if (mode == 2) {
|
||||
return (x == 10) | (y == 20);
|
||||
} else {
|
||||
return x == (10 | (y == 20));
|
||||
return x == (10 | (y == 20) as int);
|
||||
}
|
||||
}
|
||||
|
||||
@method_id(105)
|
||||
fun test5(status: int): int {
|
||||
return justTrue() & (status == 1) & ((justTrue() & status) == 1);
|
||||
fun test5(status: int): bool {
|
||||
return justTrue() & (status == 1) & ((justTrue() as int & status) == 1);
|
||||
}
|
||||
|
||||
@method_id(106)
|
||||
fun test6(a: int, b: int, c: int): int {
|
||||
fun test6(a: int, b: int, c: int): bool {
|
||||
return (unary_minus_1(a,b,c) == unary_minus_2(a,b,c)) & (unary_minus_1(a,b,c) == unary_minus_3(a,b,c));
|
||||
}
|
||||
|
||||
@method_id(107)
|
||||
fun test7(b: int): int {
|
||||
var a = b == 3 ? 3 : b == 4 ? 4 : (b == 5) & 1 ? 5 : 100;
|
||||
var a = b == 3 ? 3 : b == 4 ? 4 : (b == 5) & true ? 5 : 100;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
@ -56,14 +56,14 @@ fun test8(b: int): int {
|
|||
return a;
|
||||
}
|
||||
|
||||
fun `_<p`(a: auto, b: auto): int { return true; }
|
||||
fun `_<p`(a: int, b: int): bool { return true; }
|
||||
|
||||
fun main() {
|
||||
// ok to parse
|
||||
var c = [
|
||||
(3 & 3) > 0, 3 & (3 > 0), 3 & (`_<_`(3, 0)),
|
||||
3 & `_<p`(3, 0), (1 & 2) ^ (3 | 4),
|
||||
1 & ((1) == 1)
|
||||
(3 & 3) > 0, 3 & (3 > 0) as int, 3 & (`_<_`(3, 0)),
|
||||
3 & `_<p`(3, 0) as int, (1 & 2) ^ (3 | 4),
|
||||
true & ((1) == 1)
|
||||
];
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ fun test1(): int {
|
|||
@method_id(102)
|
||||
fun test2(value: int): int {
|
||||
save_contract_data(value);
|
||||
var (_, restored: auto) = get_contract_data();
|
||||
var (_, restored) = get_contract_data();
|
||||
return restored;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ fun used_from_noncall2(): int { return int20; }
|
|||
fun used_as_noncall2(): int { return 0 * 0 + used_from_noncall2() + (0 << 0); }
|
||||
|
||||
global unused_gv: int;
|
||||
global used_gv: auto;
|
||||
global used_gv: int;
|
||||
|
||||
fun receiveGetter(): (() -> int) { return used_as_noncall2; }
|
||||
fun receiveGetter(): () -> int { return used_as_noncall2; }
|
||||
|
||||
@pure
|
||||
fun usedButOptimizedOut(x: int): int { return x + 2; }
|
||||
|
|
|
@ -158,6 +158,44 @@ fun testNotMutatingChainableSelfMutateAnother(initial: int) {
|
|||
return (arg, c108, c109, x);
|
||||
}
|
||||
|
||||
fun pickG110(mutate self: int, mutate pushTo: tuple): self {
|
||||
self += 10;
|
||||
pushTo.tuplePush(c110);
|
||||
return self;
|
||||
}
|
||||
|
||||
global tup110: tuple;
|
||||
global c110: int;
|
||||
|
||||
@method_id(110)
|
||||
fun testMutateGlobalsLValue(init: int) {
|
||||
c110 = init;
|
||||
tup110 = createEmptyTuple();
|
||||
c110.incChained().incChained().pickG110(mutate tup110).incChained().pickG110(mutate tup110).incChained();
|
||||
return (c110, tup110);
|
||||
}
|
||||
|
||||
fun myTuplePush<T>(mutate self: tuple, value: T): self {
|
||||
self.tuplePush(value);
|
||||
return self;
|
||||
}
|
||||
|
||||
fun myTupleAt<T>(self: tuple, idx: int): T {
|
||||
return self.tupleAt(idx);
|
||||
}
|
||||
|
||||
global tup111: tuple;
|
||||
|
||||
@method_id(111)
|
||||
fun testForallFunctionsWithSelf(): (int, int, tuple) {
|
||||
var t = createEmptyTuple();
|
||||
tup111 = createEmptyTuple();
|
||||
t.myTuplePush(10);
|
||||
tup111.myTuplePush(1).myTuplePush(2).myTuplePush(3);
|
||||
return (t.myTupleAt(0), tup111.myTupleAt(tup111.tupleSize() - 1), tup111);
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun main() { }
|
||||
|
||||
|
@ -179,6 +217,8 @@ fun main() { }
|
|||
@testcase | 109 | 200 | 200 3 1 2
|
||||
@testcase | 109 | 100 | 100 0 0 1
|
||||
@testcase | 109 | 102 | 102 2 1 2
|
||||
@testcase | 110 | 0 | 24 [ 2 13 ]
|
||||
@testcase | 111 | | 10 3 [ 1 2 3 ]
|
||||
|
||||
@fif_codegen
|
||||
"""
|
||||
|
|
|
@ -218,7 +218,7 @@ fun fixed248_log2_const(): int {
|
|||
@pure
|
||||
@inline
|
||||
fun Pi_const_f254(): int {
|
||||
var (c: auto, _) = Pi_xconst_f254();
|
||||
var (c, _) = Pi_xconst_f254();
|
||||
return c;
|
||||
}
|
||||
|
||||
|
@ -661,7 +661,7 @@ fun fixed248_pow(x: int, y: int): int {
|
|||
return 1 << 248; // x^0 = 1
|
||||
}
|
||||
if (x <= 0) {
|
||||
var bad: int = (x | y) < 0;
|
||||
var bad: int = ((x | y) < 0) as int;
|
||||
return 0 >> bad; // 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise
|
||||
}
|
||||
var (l, s) = log2_aux_f256(x);
|
||||
|
@ -677,7 +677,7 @@ fun fixed248_pow(x: int, y: int): int {
|
|||
// now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2
|
||||
var sq: int = q + 248;
|
||||
if (sq <= 0) {
|
||||
return -(sq == 0); // underflow
|
||||
return -((sq == 0) as int); // underflow
|
||||
}
|
||||
y = expm1_f257(mulrshiftr256(ll, log2_const_f256()));
|
||||
return (y ~>> (9 - q)) - (-1 << sq);
|
||||
|
@ -986,7 +986,7 @@ fun tset<X>(mutate self: tuple, idx: int, value: X): void
|
|||
// fixed256 acos_prepare_slow(fixed255 x);
|
||||
@inline
|
||||
fun acos_prepare_slow_f255(x: int): int {
|
||||
x -= (x == 0);
|
||||
x -= (x == 0) as int;
|
||||
var t: int = 1;
|
||||
repeat (255) {
|
||||
t = t * sign(x) * 2 + 1; // decode Gray code (sign(x_0), sign(x_1), ...)
|
||||
|
@ -1019,7 +1019,8 @@ fun test_nrand(n: int): tuple {
|
|||
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);
|
||||
var at_bucket: int = t.tupleAt(bucket);
|
||||
t.tset(bucket, at_bucket + 1);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
fun unsafeGetInt<X>(any: X): int
|
||||
asm "NOP";
|
||||
|
||||
@method_id(11)
|
||||
fun foo(x: int): int {
|
||||
try {
|
||||
if (x == 7) {
|
||||
|
@ -14,7 +10,6 @@ fun foo(x: int): int {
|
|||
}
|
||||
|
||||
@inline
|
||||
@method_id(12)
|
||||
fun foo_inline(x: int): int {
|
||||
try {
|
||||
assert(!(x == 7)) throw 44;
|
||||
|
@ -25,36 +20,34 @@ fun foo_inline(x: int): int {
|
|||
}
|
||||
|
||||
@inline_ref
|
||||
@method_id(13)
|
||||
fun foo_inlineref(x: int): int {
|
||||
try {
|
||||
if (x == 7) { throw (44, 2); }
|
||||
return x;
|
||||
} catch (_, arg) {
|
||||
return unsafeGetInt(arg);
|
||||
return arg as int;
|
||||
}
|
||||
}
|
||||
|
||||
@method_id(1)
|
||||
@method_id(101)
|
||||
fun test(x: int, y: int, z: int): int {
|
||||
y = foo(y);
|
||||
return x * 100 + y * 10 + z;
|
||||
}
|
||||
|
||||
@method_id(2)
|
||||
@method_id(102)
|
||||
fun test_inline(x: int, y: int, z: int): int {
|
||||
y = foo_inline(y);
|
||||
return x * 100 + y * 10 + z;
|
||||
}
|
||||
|
||||
@method_id(3)
|
||||
@method_id(103)
|
||||
fun test_inlineref(x: int, y: int, z: int): int {
|
||||
y = foo_inlineref(y);
|
||||
return x * 100 + y * 10 + z;
|
||||
}
|
||||
|
||||
@inline
|
||||
@method_id(14)
|
||||
fun foo_inline_big(
|
||||
x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int, x8: int, x9: int, x10: int,
|
||||
x11: int, x12: int, x13: int, x14: int, x15: int, x16: int, x17: int, x18: int, x19: int, x20: int
|
||||
|
@ -69,7 +62,7 @@ fun foo_inline_big(
|
|||
}
|
||||
}
|
||||
|
||||
@method_id(4)
|
||||
@method_id(104)
|
||||
fun test_inline_big(x: int, y: int, z: int): int {
|
||||
y = foo_inline_big(
|
||||
y, y + 1, y + 2, y + 3, y + 4, y + 5, y + 6, y + 7, y + 8, y + 9,
|
||||
|
@ -77,7 +70,6 @@ fun test_inline_big(x: int, y: int, z: int): int {
|
|||
return x * 1000000 + y * 1000 + z;
|
||||
}
|
||||
|
||||
@method_id(15)
|
||||
fun foo_big(
|
||||
x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int, x8: int, x9: int, x10: int,
|
||||
x11: int, x12: int, x13: int, x14: int, x15: int, x16: int, x17: int, x18: int, x19: int, x20: int
|
||||
|
@ -88,11 +80,11 @@ fun foo_big(
|
|||
}
|
||||
return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20;
|
||||
} catch (code, arg) {
|
||||
return unsafeGetInt(arg);
|
||||
return arg as int;
|
||||
}
|
||||
}
|
||||
|
||||
@method_id(5)
|
||||
@method_id(105)
|
||||
fun test_big(x: int, y: int, z: int): int {
|
||||
y = foo_big(
|
||||
y, y + 1, y + 2, y + 3, y + 4, y + 5, y + 6, y + 7, y + 8, y + 9,
|
||||
|
@ -100,7 +92,7 @@ fun test_big(x: int, y: int, z: int): int {
|
|||
return x * 1000000 + y * 1000 + z;
|
||||
}
|
||||
|
||||
@method_id(16)
|
||||
@method_id(106)
|
||||
fun test_catch_into_same(x: int): int {
|
||||
var code = x;
|
||||
try {
|
||||
|
@ -112,7 +104,7 @@ fun test_catch_into_same(x: int): int {
|
|||
}
|
||||
|
||||
|
||||
@method_id(17)
|
||||
@method_id(107)
|
||||
fun test_catch_into_same_2(x: int): int {
|
||||
var code = x;
|
||||
try {
|
||||
|
@ -124,28 +116,77 @@ fun test_catch_into_same_2(x: int): int {
|
|||
return code;
|
||||
}
|
||||
|
||||
global after046: int;
|
||||
|
||||
// this bug existed in FunC and is fixed in v0.4.6
|
||||
fun bug_046_internal(op: int) {
|
||||
if (op == 1) {
|
||||
return;
|
||||
} else if (op == 2) {
|
||||
return;
|
||||
} else {
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
fun bug_046_called() {
|
||||
after046 = 0;
|
||||
try {
|
||||
bug_046_internal(1337);
|
||||
after046 = 1; // shouldn't be called
|
||||
} catch(n) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@method_id(108)
|
||||
fun bug_046_entrypoint() {
|
||||
bug_046_called();
|
||||
return after046;
|
||||
}
|
||||
|
||||
global g_reg: int;
|
||||
|
||||
@method_id(109)
|
||||
fun test109(): (int, int) {
|
||||
var l_reg = 10;
|
||||
g_reg = 10;
|
||||
try {
|
||||
// note, that regardless of assignment, an exception RESTORES them to previous (to 10)
|
||||
// it's very unexpected, but is considered to be a TVM feature, not a bug
|
||||
g_reg = 999;
|
||||
l_reg = 999;
|
||||
bug_046_internal(999); // throws
|
||||
} catch {
|
||||
}
|
||||
// returns (10,10) because of an exception, see a comment above
|
||||
return (g_reg, l_reg);
|
||||
}
|
||||
|
||||
fun main() {
|
||||
}
|
||||
|
||||
/**
|
||||
method_id | in | out
|
||||
@testcase | 1 | 1 2 3 | 123
|
||||
@testcase | 1 | 3 8 9 | 389
|
||||
@testcase | 1 | 3 7 9 | 329
|
||||
@testcase | 2 | 1 2 3 | 123
|
||||
@testcase | 2 | 3 8 9 | 389
|
||||
@testcase | 2 | 3 7 9 | 329
|
||||
@testcase | 3 | 1 2 3 | 123
|
||||
@testcase | 3 | 3 8 9 | 389
|
||||
@testcase | 3 | 3 7 9 | 329
|
||||
@testcase | 4 | 4 8 9 | 4350009
|
||||
@testcase | 4 | 4 7 9 | 4001009
|
||||
@testcase | 5 | 4 8 9 | 4350009
|
||||
@testcase | 5 | 4 7 9 | 4001009
|
||||
@testcase | 16 | 5 | 5
|
||||
@testcase | 16 | 20 | 44
|
||||
@testcase | 17 | 5 | 5
|
||||
@testcase | 17 | 20 | 20
|
||||
@testcase | 101 | 1 2 3 | 123
|
||||
@testcase | 101 | 3 8 9 | 389
|
||||
@testcase | 101 | 3 7 9 | 329
|
||||
@testcase | 102 | 1 2 3 | 123
|
||||
@testcase | 102 | 3 8 9 | 389
|
||||
@testcase | 102 | 3 7 9 | 329
|
||||
@testcase | 103 | 1 2 3 | 123
|
||||
@testcase | 103 | 3 8 9 | 389
|
||||
@testcase | 103 | 3 7 9 | 329
|
||||
@testcase | 104 | 4 8 9 | 4350009
|
||||
@testcase | 104 | 4 7 9 | 4001009
|
||||
@testcase | 105 | 4 8 9 | 4350009
|
||||
@testcase | 105 | 4 7 9 | 4001009
|
||||
@testcase | 106 | 5 | 5
|
||||
@testcase | 106 | 20 | 44
|
||||
@testcase | 107 | 5 | 5
|
||||
@testcase | 107 | 20 | 20
|
||||
@testcase | 108 | | 0
|
||||
|
||||
@code_hash 73240939343624734070640372352271282883450660826541545137654364443860257436623
|
||||
@code_hash 39307974281105539319288356721945232226028429128341177951717392648324358675585
|
||||
*/
|
||||
|
|
|
@ -38,7 +38,7 @@ fun foo_until(x: int): int {
|
|||
}
|
||||
|
||||
@method_id(4)
|
||||
fun test4(x: int): (int, int) {
|
||||
fun test4(x: int): (int, bool) {
|
||||
var s = 0;
|
||||
var reached = false;
|
||||
do {
|
||||
|
|
14
tolk-tester/tests/unreachable-1.tolk
Normal file
14
tolk-tester/tests/unreachable-1.tolk
Normal file
|
@ -0,0 +1,14 @@
|
|||
fun main(x: int) {
|
||||
if (x) {
|
||||
x = 10;;;;;
|
||||
return x;;;
|
||||
x = 20;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@testcase | 0 | 1 | 10
|
||||
@stderr warning: unreachable code
|
||||
@stderr x = 20;
|
||||
*/
|
22
tolk-tester/tests/unreachable-2.tolk
Normal file
22
tolk-tester/tests/unreachable-2.tolk
Normal file
|
@ -0,0 +1,22 @@
|
|||
fun main(x: int) {
|
||||
if (x) {
|
||||
if (x > 10) {
|
||||
return 1; // throw 1;
|
||||
} else if (true) {
|
||||
return -1;
|
||||
} else {
|
||||
return 2; // throw 2;
|
||||
}
|
||||
} else {
|
||||
{{return 1;}
|
||||
x = 30;}
|
||||
}
|
||||
assert(false, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
@testcase | 0 | 1 | -1
|
||||
@stderr warning: unreachable code
|
||||
@stderr assert(false, 10)
|
||||
@stderr x = 30
|
||||
*/
|
|
@ -15,8 +15,127 @@ fun testVarApply1() {
|
|||
return (s.loadInt(32), s.loadInt(32));
|
||||
}
|
||||
|
||||
@inline
|
||||
fun my_throw_always() {
|
||||
throw 1000;
|
||||
}
|
||||
|
||||
@inline
|
||||
fun get_raiser() {
|
||||
return my_throw_always;
|
||||
}
|
||||
|
||||
@method_id(102)
|
||||
fun testVarApplyWithoutSavingResult() {
|
||||
try {
|
||||
var raiser = get_raiser();
|
||||
raiser(); // `some_var()` is always impure, the compiler has no considerations about its runtime value
|
||||
return 0;
|
||||
} catch (code) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
@inline
|
||||
fun sum(a: int, b: int) {
|
||||
assert(a + b < 24, 1000);
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@inline
|
||||
fun mul(a: int, b: int) {
|
||||
assert(a * b < 24, 1001);
|
||||
return a * b;
|
||||
}
|
||||
|
||||
fun demo_handler(op: int, query_id: int, a: int, b: int): int {
|
||||
if (op == 0xF2) {
|
||||
val func = query_id % 2 == 0 ? sum : mul;
|
||||
val result = func(a, b);
|
||||
return 0; // result not used, we test that func is nevertheless called
|
||||
}
|
||||
if (op == 0xF4) {
|
||||
val func = query_id % 2 == 0 ? sum : mul;
|
||||
val result = func(a, b);
|
||||
return result;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@method_id(103)
|
||||
fun testVarApplyInTernary() {
|
||||
var t: tuple = createEmptyTuple();
|
||||
try {
|
||||
t.tuplePush(demo_handler(0xF2, 122, 100, 200));
|
||||
} catch(code) {
|
||||
t.tuplePush(code);
|
||||
}
|
||||
try {
|
||||
t.tuplePush(demo_handler(0xF4, 122, 100, 200));
|
||||
} catch(code) {
|
||||
t.tuplePush(code);
|
||||
}
|
||||
try {
|
||||
t.tuplePush(demo_handler(0xF2, 122, 10, 10));
|
||||
} catch(code) {
|
||||
t.tuplePush(code);
|
||||
}
|
||||
try {
|
||||
t.tuplePush(demo_handler(0xF2, 123, 10, 10));
|
||||
} catch(code) {
|
||||
t.tuplePush(code);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
fun always_throw2(x: int) {
|
||||
throw 239 + x;
|
||||
}
|
||||
|
||||
global global_f: int -> void;
|
||||
|
||||
@method_id(104)
|
||||
fun testGlobalVarApply() {
|
||||
try {
|
||||
global_f = always_throw2;
|
||||
global_f(1);
|
||||
return 0;
|
||||
} catch (code) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
@method_id(105)
|
||||
fun testVarApply2() {
|
||||
var creator = createEmptyTuple;
|
||||
var t = creator();
|
||||
t.tuplePush(1);
|
||||
var sizer = t.tupleSize;
|
||||
return sizer(t);
|
||||
}
|
||||
|
||||
fun getTupleLastGetter<X>(): (tuple) -> X {
|
||||
return tupleLast<X>;
|
||||
}
|
||||
|
||||
@method_id(106)
|
||||
fun testVarApply3() {
|
||||
var t = createEmptyTuple();
|
||||
t.tuplePush(1);
|
||||
t.tuplePush([2]);
|
||||
var getIntAt = t.tupleAt<int>;
|
||||
var getTupleFirstInt = createEmptyTuple().tupleFirst<int>;
|
||||
var getTupleLastTuple = getTupleLastGetter<tuple>();
|
||||
return (getIntAt(t, 0), getTupleFirstInt(t), getTupleLastTuple(t), getTupleLastGetter<tuple>()(t));
|
||||
}
|
||||
|
||||
fun main() {}
|
||||
|
||||
/**
|
||||
@testcase | 101 | | 1 2
|
||||
@testcase | 102 | | 1000
|
||||
@testcase | 103 | | [ 1000 1000 0 1001 ]
|
||||
@testcase | 104 | | 240
|
||||
@testcase | 105 | | 1
|
||||
@testcase | 106 | | 1 1 [ 2 ] [ 2 ]
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,7 @@ fun main(x: int): int {
|
|||
if (i > 5) {
|
||||
return 1;
|
||||
}
|
||||
var f: int = (i * i == 64);
|
||||
var f: bool = (i * i == 64);
|
||||
} while (!f);
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ fun test(y: int): int {
|
|||
if (y > 0) {
|
||||
return 1;
|
||||
}
|
||||
return x > 0;
|
||||
return x > 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
@method_id(2)
|
||||
|
|
|
@ -347,11 +347,11 @@ class TolkTestFile {
|
|||
if (exit_code === 0 && this.compilation_should_fail)
|
||||
throw new TolkCompilationSucceededError("compilation succeeded, but it should have failed")
|
||||
|
||||
if (exit_code !== 0 && this.compilation_should_fail) {
|
||||
for (let should_include of this.stderr_includes)
|
||||
for (let should_include of this.stderr_includes) // @stderr is used to check errors and warnings
|
||||
should_include.check(stderr)
|
||||
|
||||
if (exit_code !== 0 && this.compilation_should_fail)
|
||||
return
|
||||
}
|
||||
|
||||
if (exit_code !== 0 && !this.compilation_should_fail)
|
||||
throw new TolkCompilationFailedError(`tolk exit_code = ${exit_code}`, stderr)
|
||||
|
|
|
@ -327,9 +327,10 @@ class TolkTestFile:
|
|||
if exit_code == 0 and self.compilation_should_fail:
|
||||
raise TolkCompilationSucceededError("compilation succeeded, but it should have failed")
|
||||
|
||||
if exit_code != 0 and self.compilation_should_fail:
|
||||
for should_include in self.stderr_includes:
|
||||
for should_include in self.stderr_includes: # @stderr is used to check errors and warnings
|
||||
should_include.check(stderr)
|
||||
|
||||
if exit_code != 0 and self.compilation_should_fail:
|
||||
return
|
||||
|
||||
if exit_code != 0 and not self.compilation_should_fail:
|
||||
|
|
|
@ -7,14 +7,24 @@ set(TOLK_SOURCE
|
|||
compiler-state.cpp
|
||||
ast.cpp
|
||||
ast-from-tokens.cpp
|
||||
constant-evaluator.cpp
|
||||
pipe-discover-parse-sources.cpp
|
||||
pipe-register-symbols.cpp
|
||||
pipe-resolve-identifiers.cpp
|
||||
pipe-calc-rvalue-lvalue.cpp
|
||||
pipe-detect-unreachable.cpp
|
||||
pipe-infer-types-and-calls.cpp
|
||||
pipe-refine-lvalue-for-mutate.cpp
|
||||
pipe-check-rvalue-lvalue.cpp
|
||||
pipe-check-pure-impure.cpp
|
||||
pipe-constant-folding.cpp
|
||||
pipe-optimize-boolean-expr.cpp
|
||||
pipe-ast-to-legacy.cpp
|
||||
pipe-find-unused-symbols.cpp
|
||||
pipe-generate-fif-output.cpp
|
||||
unify-types.cpp
|
||||
type-system.cpp
|
||||
generics-helpers.cpp
|
||||
abscode.cpp
|
||||
gen-abscode.cpp
|
||||
analyzer.cpp
|
||||
asmops.cpp
|
||||
builtins.cpp
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
#include "tolk.h"
|
||||
#include "compiler-state.h"
|
||||
#include "type-system.h"
|
||||
|
||||
namespace tolk {
|
||||
|
||||
|
@ -25,21 +26,10 @@ namespace tolk {
|
|||
*
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void TmpVar::set_location(SrcLocation loc) {
|
||||
where = loc;
|
||||
}
|
||||
|
||||
void TmpVar::dump(std::ostream& os) const {
|
||||
show(os);
|
||||
os << " : " << v_type << " (width ";
|
||||
v_type->show_width(os);
|
||||
os << v_type->calc_width_on_stack();
|
||||
os << ")";
|
||||
if (coord > 0) {
|
||||
os << " = _" << (coord >> 8) << '.' << (coord & 255);
|
||||
|
@ -55,8 +45,8 @@ void TmpVar::dump(std::ostream& os) const {
|
|||
}
|
||||
|
||||
void TmpVar::show(std::ostream& os, int omit_idx) const {
|
||||
if (!is_unnamed()) {
|
||||
os << G.symbols.get_name(sym_idx);
|
||||
if (v_sym) {
|
||||
os << v_sym->name;
|
||||
if (omit_idx >= 2) {
|
||||
return;
|
||||
}
|
||||
|
@ -149,10 +139,6 @@ void VarDescr::set_const(std::string value) {
|
|||
val = _Const;
|
||||
}
|
||||
|
||||
void VarDescr::set_const_nan() {
|
||||
set_const(td::make_refint());
|
||||
}
|
||||
|
||||
void VarDescr::operator|=(const VarDescr& y) {
|
||||
val &= y.val;
|
||||
if (is_int_const() && y.is_int_const() && cmp(int_const, y.int_const) != 0) {
|
||||
|
@ -273,7 +259,7 @@ void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx
|
|||
case _Call:
|
||||
os << pfx << dis << "CALL: ";
|
||||
show_var_list(os, left, vars);
|
||||
os << " := " << (fun_ref ? fun_ref->name() : "(null)") << " ";
|
||||
os << " := " << (f_sym ? f_sym->name : "(null)") << " ";
|
||||
if ((mode & 4) && args.size() == right.size()) {
|
||||
show_var_list(os, args, vars);
|
||||
} else {
|
||||
|
@ -332,11 +318,11 @@ void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx
|
|||
case _GlobVar:
|
||||
os << pfx << dis << "GLOBVAR ";
|
||||
show_var_list(os, left, vars);
|
||||
os << " := " << (fun_ref ? fun_ref->name() : "(null)") << std::endl;
|
||||
os << " := " << (g_sym ? g_sym->name : "(null)") << std::endl;
|
||||
break;
|
||||
case _SetGlob:
|
||||
os << pfx << dis << "SETGLOB ";
|
||||
os << (fun_ref ? fun_ref->name() : "(null)") << " := ";
|
||||
os << (g_sym ? g_sym->name : "(null)") << " := ";
|
||||
show_var_list(os, right, vars);
|
||||
os << std::endl;
|
||||
break;
|
||||
|
@ -458,22 +444,22 @@ void CodeBlob::print(std::ostream& os, int flags) const {
|
|||
os << "-------- END ---------\n\n";
|
||||
}
|
||||
|
||||
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);
|
||||
var_idx_t CodeBlob::create_var(TypePtr var_type, const LocalVarData* v_sym, SrcLocation location) {
|
||||
vars.emplace_back(var_cnt, var_type, v_sym, location);
|
||||
return var_cnt++;
|
||||
}
|
||||
|
||||
bool CodeBlob::import_params(FormalArgList arg_list) {
|
||||
bool CodeBlob::import_params(FormalArgList&& arg_list) {
|
||||
if (var_cnt || in_var_cnt) {
|
||||
return false;
|
||||
}
|
||||
std::vector<var_idx_t> list;
|
||||
for (const auto& par : arg_list) {
|
||||
TypeExpr* arg_type;
|
||||
SymDef* arg_sym;
|
||||
TypePtr arg_type;
|
||||
const LocalVarData* arg_sym;
|
||||
SrcLocation arg_loc;
|
||||
std::tie(arg_type, arg_sym, arg_loc) = par;
|
||||
list.push_back(create_var(arg_type, arg_sym ? arg_sym->sym_idx : 0, arg_loc));
|
||||
list.push_back(create_var(arg_type, arg_sym, arg_loc));
|
||||
}
|
||||
emplace_back(loc, Op::_Import, list);
|
||||
in_var_cnt = var_cnt;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
#include "tolk.h"
|
||||
#include "compiler-state.h"
|
||||
#include "type-system.h"
|
||||
|
||||
namespace tolk {
|
||||
|
||||
|
@ -25,38 +26,30 @@ namespace tolk {
|
|||
*
|
||||
*/
|
||||
|
||||
void CodeBlob::simplify_var_types() {
|
||||
for (TmpVar& var : vars) {
|
||||
TypeExpr::remove_indirect(var.v_type);
|
||||
var.v_type->recompute_width();
|
||||
}
|
||||
}
|
||||
|
||||
int CodeBlob::split_vars(bool strict) {
|
||||
int n = var_cnt, changes = 0;
|
||||
for (int j = 0; j < var_cnt; j++) {
|
||||
TmpVar& var = vars[j];
|
||||
if (strict && var.v_type->minw != var.v_type->maxw) {
|
||||
int width_j = var.v_type->calc_width_on_stack();
|
||||
if (strict && width_j < 0) {
|
||||
throw ParseError{var.where, "variable does not have fixed width, cannot manipulate it"};
|
||||
}
|
||||
std::vector<TypeExpr*> comp_types;
|
||||
int k = var.v_type->extract_components(comp_types);
|
||||
tolk_assert(k <= 254 && n <= 0x7fff00);
|
||||
tolk_assert((unsigned)k == comp_types.size());
|
||||
if (k != 1) {
|
||||
var.coord = ~((n << 8) + k);
|
||||
for (int i = 0; i < k; i++) {
|
||||
auto v = create_var(comp_types[i], vars[j].sym_idx, vars[j].where);
|
||||
if (width_j == 1) {
|
||||
continue;
|
||||
}
|
||||
std::vector<TypePtr> comp_types;
|
||||
var.v_type->extract_components(comp_types);
|
||||
tolk_assert(width_j <= 254 && n <= 0x7fff00);
|
||||
tolk_assert((unsigned)width_j == comp_types.size());
|
||||
var.coord = ~((n << 8) + width_j);
|
||||
for (int i = 0; i < width_j; i++) {
|
||||
auto v = create_var(comp_types[i], vars[j].v_sym, vars[j].where);
|
||||
tolk_assert(v == n + i);
|
||||
tolk_assert(vars[v].idx == v);
|
||||
vars[v].coord = ((int)j << 8) + i + 1;
|
||||
}
|
||||
n += k;
|
||||
n += width_j;
|
||||
++changes;
|
||||
} else if (strict && var.v_type->minw != 1) {
|
||||
throw ParseError{var.where,
|
||||
"cannot work with variable or variable component of width greater than one"};
|
||||
}
|
||||
}
|
||||
if (!changes) {
|
||||
return 0;
|
||||
|
@ -687,7 +680,7 @@ void CodeBlob::fwd_analyze() {
|
|||
tolk_assert(ops && ops->cl == Op::_Import);
|
||||
for (var_idx_t i : ops->left) {
|
||||
values += i;
|
||||
if (vars[i].v_type->is_int()) {
|
||||
if (vars[i].v_type == TypeDataInt::create()) {
|
||||
values[i]->val |= VarDescr::_Int;
|
||||
}
|
||||
}
|
||||
|
@ -732,15 +725,18 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
|
|||
}
|
||||
case _Call: {
|
||||
prepare_args(values);
|
||||
auto func = dynamic_cast<const SymValAsmFunc*>(fun_ref->value);
|
||||
if (func) {
|
||||
if (!f_sym->is_code_function()) {
|
||||
std::vector<VarDescr> res;
|
||||
res.reserve(left.size());
|
||||
for (var_idx_t i : left) {
|
||||
res.emplace_back(i);
|
||||
}
|
||||
AsmOpList tmp;
|
||||
func->compile(tmp, res, args, where); // abstract interpretation of res := f (args)
|
||||
if (f_sym->is_asm_function()) {
|
||||
std::get<FunctionBodyAsm*>(f_sym->body)->compile(tmp); // abstract interpretation of res := f (args)
|
||||
} else {
|
||||
std::get<FunctionBodyBuiltin*>(f_sym->body)->compile(tmp, res, args, where);
|
||||
}
|
||||
int j = 0;
|
||||
for (var_idx_t i : left) {
|
||||
values.add_newval(i).set_value(res[j++]);
|
||||
|
@ -878,27 +874,10 @@ bool Op::set_noreturn(bool flag) {
|
|||
return flag;
|
||||
}
|
||||
|
||||
void Op::set_impure(const CodeBlob &code) {
|
||||
// todo calling this function with `code` is a bad design (flags are assigned after Op is constructed)
|
||||
// later it's better to check this somewhere in code.emplace_back()
|
||||
if (code.flags & CodeBlob::_ForbidImpure) {
|
||||
throw ParseError(where, "an impure operation in a pure function");
|
||||
}
|
||||
void Op::set_impure_flag() {
|
||||
flags |= _Impure;
|
||||
}
|
||||
|
||||
void Op::set_impure(const CodeBlob &code, bool flag) {
|
||||
if (flag) {
|
||||
if (code.flags & CodeBlob::_ForbidImpure) {
|
||||
throw ParseError(where, "an impure operation in a pure function");
|
||||
}
|
||||
flags |= _Impure;
|
||||
} else {
|
||||
flags &= ~_Impure;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Op::mark_noreturn() {
|
||||
switch (cl) {
|
||||
case _Nop:
|
||||
|
|
|
@ -52,10 +52,10 @@ std::ostream& operator<<(std::ostream& os, AsmOp::SReg stack_reg) {
|
|||
}
|
||||
}
|
||||
|
||||
AsmOp AsmOp::Const(int arg, std::string push_op, td::RefInt256 origin) {
|
||||
AsmOp AsmOp::Const(int arg, const std::string& push_op) {
|
||||
std::ostringstream os;
|
||||
os << arg << ' ' << push_op;
|
||||
return AsmOp::Const(os.str(), origin);
|
||||
return AsmOp::Const(os.str());
|
||||
}
|
||||
|
||||
AsmOp AsmOp::make_stk2(int a, int b, const char* str, int delta) {
|
||||
|
@ -161,36 +161,36 @@ AsmOp AsmOp::UnTuple(int a) {
|
|||
return AsmOp::Custom(os.str(), 1, a);
|
||||
}
|
||||
|
||||
AsmOp AsmOp::IntConst(td::RefInt256 x) {
|
||||
AsmOp AsmOp::IntConst(const td::RefInt256& x) {
|
||||
if (x->signed_fits_bits(8)) {
|
||||
return AsmOp::Const(dec_string(x) + " PUSHINT", x);
|
||||
return AsmOp::Const(dec_string(x) + " PUSHINT");
|
||||
}
|
||||
if (!x->is_valid()) {
|
||||
return AsmOp::Const("PUSHNAN", x);
|
||||
return AsmOp::Const("PUSHNAN");
|
||||
}
|
||||
int k = is_pos_pow2(x);
|
||||
if (k >= 0) {
|
||||
return AsmOp::Const(k, "PUSHPOW2", x);
|
||||
return AsmOp::Const(k, "PUSHPOW2");
|
||||
}
|
||||
k = is_pos_pow2(x + 1);
|
||||
if (k >= 0) {
|
||||
return AsmOp::Const(k, "PUSHPOW2DEC", x);
|
||||
return AsmOp::Const(k, "PUSHPOW2DEC");
|
||||
}
|
||||
k = is_pos_pow2(-x);
|
||||
if (k >= 0) {
|
||||
return AsmOp::Const(k, "PUSHNEGPOW2", x);
|
||||
return AsmOp::Const(k, "PUSHNEGPOW2");
|
||||
}
|
||||
if (!x->mod_pow2_short(23)) {
|
||||
return AsmOp::Const(dec_string(x) + " PUSHINTX", x);
|
||||
return AsmOp::Const(dec_string(x) + " PUSHINTX");
|
||||
}
|
||||
return AsmOp::Const(dec_string(x) + " PUSHINT", x);
|
||||
return AsmOp::Const(dec_string(x) + " PUSHINT");
|
||||
}
|
||||
|
||||
AsmOp AsmOp::BoolConst(bool f) {
|
||||
return AsmOp::Const(f ? "TRUE" : "FALSE");
|
||||
}
|
||||
|
||||
AsmOp AsmOp::Parse(std::string custom_op) {
|
||||
AsmOp AsmOp::Parse(const std::string& custom_op) {
|
||||
if (custom_op == "NOP") {
|
||||
return AsmOp::Nop();
|
||||
} else if (custom_op == "SWAP") {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,12 +16,10 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "src-file.h"
|
||||
#include "fwd-declarations.h"
|
||||
|
||||
namespace tolk {
|
||||
|
||||
struct ASTNodeBase;
|
||||
|
||||
const ASTNodeBase* parse_src_file_to_ast(const SrcFile* file);
|
||||
AnyV parse_src_file_to_ast(const SrcFile* file);
|
||||
|
||||
} // namespace tolk
|
||||
|
|
|
@ -35,25 +35,39 @@ namespace tolk {
|
|||
|
||||
class ASTReplacer {
|
||||
protected:
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE static AnyV replace_children(const ASTNodeLeaf* v) {
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE static AnyExprV replace_children(const ASTExprLeaf* v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyV replace_children(const ASTNodeUnary* v) {
|
||||
auto* v_mutable = const_cast<ASTNodeUnary*>(v);
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyExprV replace_children(const ASTExprUnary* v) {
|
||||
auto* v_mutable = const_cast<ASTExprUnary*>(v);
|
||||
v_mutable->child = replace(v_mutable->child);
|
||||
return v_mutable;
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyV replace_children(const ASTNodeBinary* v) {
|
||||
auto* v_mutable = const_cast<ASTNodeBinary*>(v);
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyExprV replace_children(const ASTExprBinary* v) {
|
||||
auto* v_mutable = const_cast<ASTExprBinary*>(v);
|
||||
v_mutable->lhs = replace(v->lhs);
|
||||
v_mutable->rhs = replace(v->rhs);
|
||||
return v_mutable;
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyV replace_children(const ASTNodeVararg* v) {
|
||||
auto* v_mutable = const_cast<ASTNodeVararg*>(v);
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyExprV replace_children(const ASTExprVararg* v) {
|
||||
auto* v_mutable = const_cast<ASTExprVararg*>(v);
|
||||
for (AnyExprV& child : v_mutable->children) {
|
||||
child = replace(child);
|
||||
}
|
||||
return v_mutable;
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyV replace_children(const ASTStatementUnary* v) {
|
||||
auto* v_mutable = const_cast<ASTStatementUnary*>(v);
|
||||
v_mutable->child = replace(v_mutable->child);
|
||||
return v_mutable;
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE AnyV replace_children(const ASTStatementVararg* v) {
|
||||
auto* v_mutable = const_cast<ASTStatementVararg*>(v);
|
||||
for (AnyV& child : v_mutable->children) {
|
||||
child = replace(child);
|
||||
}
|
||||
|
@ -64,97 +78,120 @@ public:
|
|||
virtual ~ASTReplacer() = default;
|
||||
|
||||
virtual AnyV replace(AnyV v) = 0;
|
||||
virtual AnyExprV replace(AnyExprV v) = 0;
|
||||
};
|
||||
|
||||
class ASTReplacerInFunctionBody : public ASTReplacer {
|
||||
protected:
|
||||
using parent = ASTReplacerInFunctionBody;
|
||||
|
||||
virtual AnyV replace(V<ast_empty> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_parenthesized_expr> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_tensor> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_tensor_square> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_identifier> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_int_const> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_string_const> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_bool_const> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_null_keyword> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_function_call> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_dot_method_call> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_underscore> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_unary_operator> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_binary_operator> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_ternary_operator> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_return_statement> v) { return replace_children(v); }
|
||||
// expressions
|
||||
virtual AnyExprV replace(V<ast_empty_expression> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_parenthesized_expression> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_tensor> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_typed_tuple> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_reference> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_local_var_lhs> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_local_vars_declaration> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_int_const> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_string_const> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_bool_const> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_null_keyword> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_argument> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_argument_list> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_dot_access> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_function_call> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_underscore> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_assign> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_set_assign> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_unary_operator> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_binary_operator> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_ternary_operator> v) { return replace_children(v); }
|
||||
virtual AnyExprV replace(V<ast_cast_as_operator> v) { return replace_children(v); }
|
||||
// statements
|
||||
virtual AnyV replace(V<ast_empty_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_sequence> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_return_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_if_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_repeat_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_while_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_do_while_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_throw_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_assert_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_try_catch_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_if_statement> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_local_var> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_local_vars_declaration> v) { return replace_children(v); }
|
||||
virtual AnyV replace(V<ast_asm_body> v) { return replace_children(v); }
|
||||
|
||||
AnyV replace(AnyV v) final {
|
||||
AnyExprV replace(AnyExprV v) final {
|
||||
switch (v->type) {
|
||||
case ast_empty: return replace(v->as<ast_empty>());
|
||||
case ast_parenthesized_expr: return replace(v->as<ast_parenthesized_expr>());
|
||||
case ast_empty_expression: return replace(v->as<ast_empty_expression>());
|
||||
case ast_parenthesized_expression: return replace(v->as<ast_parenthesized_expression>());
|
||||
case ast_tensor: return replace(v->as<ast_tensor>());
|
||||
case ast_tensor_square: return replace(v->as<ast_tensor_square>());
|
||||
case ast_identifier: return replace(v->as<ast_identifier>());
|
||||
case ast_typed_tuple: return replace(v->as<ast_typed_tuple>());
|
||||
case ast_reference: return replace(v->as<ast_reference>());
|
||||
case ast_local_var_lhs: return replace(v->as<ast_local_var_lhs>());
|
||||
case ast_local_vars_declaration: return replace(v->as<ast_local_vars_declaration>());
|
||||
case ast_int_const: return replace(v->as<ast_int_const>());
|
||||
case ast_string_const: return replace(v->as<ast_string_const>());
|
||||
case ast_bool_const: return replace(v->as<ast_bool_const>());
|
||||
case ast_null_keyword: return replace(v->as<ast_null_keyword>());
|
||||
case ast_self_keyword: return replace(v->as<ast_self_keyword>());
|
||||
case ast_argument: return replace(v->as<ast_argument>());
|
||||
case ast_argument_list: return replace(v->as<ast_argument_list>());
|
||||
case ast_dot_access: return replace(v->as<ast_dot_access>());
|
||||
case ast_function_call: return replace(v->as<ast_function_call>());
|
||||
case ast_dot_method_call: return replace(v->as<ast_dot_method_call>());
|
||||
case ast_underscore: return replace(v->as<ast_underscore>());
|
||||
case ast_assign: return replace(v->as<ast_assign>());
|
||||
case ast_set_assign: return replace(v->as<ast_set_assign>());
|
||||
case ast_unary_operator: return replace(v->as<ast_unary_operator>());
|
||||
case ast_binary_operator: return replace(v->as<ast_binary_operator>());
|
||||
case ast_ternary_operator: return replace(v->as<ast_ternary_operator>());
|
||||
case ast_return_statement: return replace(v->as<ast_return_statement>());
|
||||
case ast_cast_as_operator: return replace(v->as<ast_cast_as_operator>());
|
||||
default:
|
||||
throw UnexpectedASTNodeType(v, "ASTReplacerInFunctionBody::replace");
|
||||
}
|
||||
}
|
||||
|
||||
AnyV replace(AnyV v) final {
|
||||
switch (v->type) {
|
||||
case ast_empty_statement: return replace(v->as<ast_empty_statement>());
|
||||
case ast_sequence: return replace(v->as<ast_sequence>());
|
||||
case ast_return_statement: return replace(v->as<ast_return_statement>());
|
||||
case ast_if_statement: return replace(v->as<ast_if_statement>());
|
||||
case ast_repeat_statement: return replace(v->as<ast_repeat_statement>());
|
||||
case ast_while_statement: return replace(v->as<ast_while_statement>());
|
||||
case ast_do_while_statement: return replace(v->as<ast_do_while_statement>());
|
||||
case ast_throw_statement: return replace(v->as<ast_throw_statement>());
|
||||
case ast_assert_statement: return replace(v->as<ast_assert_statement>());
|
||||
case ast_try_catch_statement: return replace(v->as<ast_try_catch_statement>());
|
||||
case ast_if_statement: return replace(v->as<ast_if_statement>());
|
||||
case ast_local_var: return replace(v->as<ast_local_var>());
|
||||
case ast_local_vars_declaration: return replace(v->as<ast_local_vars_declaration>());
|
||||
case ast_asm_body: return replace(v->as<ast_asm_body>());
|
||||
default:
|
||||
throw UnexpectedASTNodeType(v, "ASTReplacerInFunctionBody::visit");
|
||||
#ifdef TOLK_DEBUG
|
||||
case ast_asm_body:
|
||||
throw UnexpectedASTNodeType(v, "ASTReplacer::replace");
|
||||
#endif
|
||||
default: {
|
||||
// be very careful, don't forget to handle all statements (not expressions) above!
|
||||
AnyExprV as_expr = reinterpret_cast<const ASTNodeExpressionBase*>(v);
|
||||
return replace(as_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void start_replacing_in_function(V<ast_function_declaration> v) {
|
||||
replace(v->get_body());
|
||||
}
|
||||
};
|
||||
virtual bool should_visit_function(const FunctionData* fun_ref) = 0;
|
||||
|
||||
class ASTReplacerAllFunctionsInFile : public ASTReplacerInFunctionBody {
|
||||
protected:
|
||||
using parent = ASTReplacerAllFunctionsInFile;
|
||||
|
||||
virtual bool should_enter_function(V<ast_function_declaration> v) = 0;
|
||||
|
||||
public:
|
||||
void start_replacing_in_file(V<ast_tolk_file> v_file) {
|
||||
for (AnyV v : v_file->get_toplevel_declarations()) {
|
||||
if (auto v_function = v->try_as<ast_function_declaration>()) {
|
||||
if (should_enter_function(v_function)) {
|
||||
void start_replacing_in_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
|
||||
replace(v_function->get_body());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const std::vector<const FunctionData*>& get_all_not_builtin_functions();
|
||||
|
||||
template<class BodyReplacerT>
|
||||
void replace_ast_of_all_functions() {
|
||||
BodyReplacerT visitor;
|
||||
for (const FunctionData* fun_ref : get_all_not_builtin_functions()) {
|
||||
if (visitor.should_visit_function(fun_ref)) {
|
||||
visitor.start_replacing_in_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tolk
|
||||
|
|
255
tolk/ast-replicator.h
Normal file
255
tolk/ast-replicator.h
Normal file
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ast.h"
|
||||
#include "platform-utils.h"
|
||||
|
||||
namespace tolk {
|
||||
|
||||
class ASTReplicator {
|
||||
protected:
|
||||
virtual AnyV clone(AnyV v) = 0;
|
||||
virtual AnyExprV clone(AnyExprV v) = 0;
|
||||
virtual TypePtr clone(TypePtr) = 0;
|
||||
|
||||
public:
|
||||
virtual ~ASTReplicator() = default;
|
||||
};
|
||||
|
||||
class ASTReplicatorFunction : public ASTReplicator {
|
||||
protected:
|
||||
using parent = ASTReplicatorFunction;
|
||||
|
||||
std::vector<AnyV> clone(const std::vector<AnyV>& items) {
|
||||
std::vector<AnyV> result;
|
||||
result.reserve(items.size());
|
||||
for (AnyV item : items) {
|
||||
result.push_back(clone(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AnyExprV> clone(const std::vector<AnyExprV>& items) {
|
||||
std::vector<AnyExprV> result;
|
||||
result.reserve(items.size());
|
||||
for (AnyExprV item : items) {
|
||||
result.push_back(clone(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// expressions
|
||||
|
||||
virtual V<ast_empty_expression> clone(V<ast_empty_expression> v) {
|
||||
return createV<ast_empty_expression>(v->loc);
|
||||
}
|
||||
virtual V<ast_parenthesized_expression> clone(V<ast_parenthesized_expression> v) {
|
||||
return createV<ast_parenthesized_expression>(v->loc, clone(v->get_expr()));
|
||||
}
|
||||
virtual V<ast_tensor> clone(V<ast_tensor> v) {
|
||||
return createV<ast_tensor>(v->loc, clone(v->get_items()));
|
||||
}
|
||||
virtual V<ast_typed_tuple> clone(V<ast_typed_tuple> v) {
|
||||
return createV<ast_typed_tuple>(v->loc, clone(v->get_items()));
|
||||
}
|
||||
virtual V<ast_reference> clone(V<ast_reference> v) {
|
||||
return createV<ast_reference>(v->loc, clone(v->get_identifier()), v->has_instantiationTs() ? clone(v->get_instantiationTs()) : nullptr);
|
||||
}
|
||||
virtual V<ast_local_var_lhs> clone(V<ast_local_var_lhs> v) {
|
||||
return createV<ast_local_var_lhs>(v->loc, clone(v->get_identifier()), clone(v->declared_type), v->is_immutable, v->marked_as_redef);
|
||||
}
|
||||
virtual V<ast_local_vars_declaration> clone(V<ast_local_vars_declaration> v) {
|
||||
return createV<ast_local_vars_declaration>(v->loc, clone(v->get_expr()));
|
||||
}
|
||||
virtual V<ast_int_const> clone(V<ast_int_const> v) {
|
||||
return createV<ast_int_const>(v->loc, v->intval, v->orig_str);
|
||||
}
|
||||
virtual V<ast_string_const> clone(V<ast_string_const> v) {
|
||||
return createV<ast_string_const>(v->loc, v->str_val, v->modifier);
|
||||
}
|
||||
virtual V<ast_bool_const> clone(V<ast_bool_const> v) {
|
||||
return createV<ast_bool_const>(v->loc, v->bool_val);
|
||||
}
|
||||
virtual V<ast_null_keyword> clone(V<ast_null_keyword> v) {
|
||||
return createV<ast_null_keyword>(v->loc);
|
||||
}
|
||||
virtual V<ast_argument> clone(V<ast_argument> v) {
|
||||
return createV<ast_argument>(v->loc, clone(v->get_expr()), v->passed_as_mutate);
|
||||
}
|
||||
virtual V<ast_argument_list> clone(V<ast_argument_list> v) {
|
||||
return createV<ast_argument_list>(v->loc, clone(v->get_arguments()));
|
||||
}
|
||||
virtual V<ast_dot_access> clone(V<ast_dot_access> v) {
|
||||
return createV<ast_dot_access>(v->loc, clone(v->get_obj()), clone(v->get_identifier()), v->has_instantiationTs() ? clone(v->get_instantiationTs()) : nullptr);
|
||||
}
|
||||
virtual V<ast_function_call> clone(V<ast_function_call> v) {
|
||||
return createV<ast_function_call>(v->loc, clone(v->get_callee()), clone(v->get_arg_list()));
|
||||
}
|
||||
virtual V<ast_underscore> clone(V<ast_underscore> v) {
|
||||
return createV<ast_underscore>(v->loc);
|
||||
}
|
||||
virtual V<ast_assign> clone(V<ast_assign> v) {
|
||||
return createV<ast_assign>(v->loc, clone(v->get_lhs()), clone(v->get_rhs()));
|
||||
}
|
||||
virtual V<ast_set_assign> clone(V<ast_set_assign> v) {
|
||||
return createV<ast_set_assign>(v->loc, v->operator_name, v->tok, clone(v->get_lhs()), clone(v->get_rhs()));
|
||||
}
|
||||
virtual V<ast_unary_operator> clone(V<ast_unary_operator> v) {
|
||||
return createV<ast_unary_operator>(v->loc, v->operator_name, v->tok, clone(v->get_rhs()));
|
||||
}
|
||||
virtual V<ast_binary_operator> clone(V<ast_binary_operator> v) {
|
||||
return createV<ast_binary_operator>(v->loc, v->operator_name, v->tok, clone(v->get_lhs()), clone(v->get_rhs()));
|
||||
}
|
||||
virtual V<ast_ternary_operator> clone(V<ast_ternary_operator> v) {
|
||||
return createV<ast_ternary_operator>(v->loc, clone(v->get_cond()), clone(v->get_when_true()), clone(v->get_when_false()));
|
||||
}
|
||||
virtual V<ast_cast_as_operator> clone(V<ast_cast_as_operator> v) {
|
||||
return createV<ast_cast_as_operator>(v->loc, clone(v->get_expr()), clone(v->cast_to_type));
|
||||
}
|
||||
|
||||
// statements
|
||||
|
||||
virtual V<ast_empty_statement> clone(V<ast_empty_statement> v) {
|
||||
return createV<ast_empty_statement>(v->loc);
|
||||
}
|
||||
virtual V<ast_sequence> clone(V<ast_sequence> v) {
|
||||
return createV<ast_sequence>(v->loc, v->loc_end, clone(v->get_items()));
|
||||
}
|
||||
virtual V<ast_return_statement> clone(V<ast_return_statement> v) {
|
||||
return createV<ast_return_statement>(v->loc, clone(v->get_return_value()));
|
||||
}
|
||||
virtual V<ast_if_statement> clone(V<ast_if_statement> v) {
|
||||
return createV<ast_if_statement>(v->loc, v->is_ifnot, clone(v->get_cond()), clone(v->get_if_body()), clone(v->get_else_body()));
|
||||
}
|
||||
virtual V<ast_repeat_statement> clone(V<ast_repeat_statement> v) {
|
||||
return createV<ast_repeat_statement>(v->loc, clone(v->get_cond()), clone(v->get_body()));
|
||||
}
|
||||
virtual V<ast_while_statement> clone(V<ast_while_statement> v) {
|
||||
return createV<ast_while_statement>(v->loc, clone(v->get_cond()), clone(v->get_body()));
|
||||
}
|
||||
virtual V<ast_do_while_statement> clone(V<ast_do_while_statement> v) {
|
||||
return createV<ast_do_while_statement>(v->loc, clone(v->get_body()), clone(v->get_cond()));
|
||||
}
|
||||
virtual V<ast_throw_statement> clone(V<ast_throw_statement> v) {
|
||||
return createV<ast_throw_statement>(v->loc, clone(v->get_thrown_code()), clone(v->get_thrown_arg()));
|
||||
}
|
||||
virtual V<ast_assert_statement> clone(V<ast_assert_statement> v) {
|
||||
return createV<ast_assert_statement>(v->loc, clone(v->get_cond()), clone(v->get_thrown_code()));
|
||||
}
|
||||
virtual V<ast_try_catch_statement> clone(V<ast_try_catch_statement> v) {
|
||||
return createV<ast_try_catch_statement>(v->loc, clone(v->get_try_body()), clone(v->get_catch_expr()), clone(v->get_catch_body()));
|
||||
}
|
||||
virtual V<ast_asm_body> clone(V<ast_asm_body> v) {
|
||||
return createV<ast_asm_body>(v->loc, v->arg_order, v->ret_order, clone(v->get_asm_commands()));
|
||||
}
|
||||
|
||||
// other
|
||||
|
||||
virtual V<ast_identifier> clone(V<ast_identifier> v) {
|
||||
return createV<ast_identifier>(v->loc, v->name);
|
||||
}
|
||||
virtual V<ast_instantiationT_item> clone(V<ast_instantiationT_item> v) {
|
||||
return createV<ast_instantiationT_item>(v->loc, clone(v->substituted_type));
|
||||
}
|
||||
virtual V<ast_instantiationT_list> clone(V<ast_instantiationT_list> v) {
|
||||
return createV<ast_instantiationT_list>(v->loc, clone(v->get_items()));
|
||||
}
|
||||
virtual V<ast_parameter> clone(V<ast_parameter> v) {
|
||||
return createV<ast_parameter>(v->loc, v->param_name, clone(v->declared_type), v->declared_as_mutate);
|
||||
}
|
||||
virtual V<ast_parameter_list> clone(V<ast_parameter_list> v) {
|
||||
return createV<ast_parameter_list>(v->loc, clone(v->get_params()));
|
||||
}
|
||||
|
||||
AnyExprV clone(AnyExprV v) final {
|
||||
switch (v->type) {
|
||||
case ast_empty_expression: return clone(v->as<ast_empty_expression>());
|
||||
case ast_parenthesized_expression: return clone(v->as<ast_parenthesized_expression>());
|
||||
case ast_tensor: return clone(v->as<ast_tensor>());
|
||||
case ast_typed_tuple: return clone(v->as<ast_typed_tuple>());
|
||||
case ast_reference: return clone(v->as<ast_reference>());
|
||||
case ast_local_var_lhs: return clone(v->as<ast_local_var_lhs>());
|
||||
case ast_local_vars_declaration: return clone(v->as<ast_local_vars_declaration>());
|
||||
case ast_int_const: return clone(v->as<ast_int_const>());
|
||||
case ast_string_const: return clone(v->as<ast_string_const>());
|
||||
case ast_bool_const: return clone(v->as<ast_bool_const>());
|
||||
case ast_null_keyword: return clone(v->as<ast_null_keyword>());
|
||||
case ast_argument: return clone(v->as<ast_argument>());
|
||||
case ast_argument_list: return clone(v->as<ast_argument_list>());
|
||||
case ast_dot_access: return clone(v->as<ast_dot_access>());
|
||||
case ast_function_call: return clone(v->as<ast_function_call>());
|
||||
case ast_underscore: return clone(v->as<ast_underscore>());
|
||||
case ast_assign: return clone(v->as<ast_assign>());
|
||||
case ast_set_assign: return clone(v->as<ast_set_assign>());
|
||||
case ast_unary_operator: return clone(v->as<ast_unary_operator>());
|
||||
case ast_binary_operator: return clone(v->as<ast_binary_operator>());
|
||||
case ast_ternary_operator: return clone(v->as<ast_ternary_operator>());
|
||||
case ast_cast_as_operator: return clone(v->as<ast_cast_as_operator>());
|
||||
default:
|
||||
throw UnexpectedASTNodeType(v, "ASTReplicatorFunction::clone");
|
||||
}
|
||||
}
|
||||
|
||||
AnyV clone(AnyV v) final {
|
||||
switch (v->type) {
|
||||
case ast_empty_statement: return clone(v->as<ast_empty_statement>());
|
||||
case ast_sequence: return clone(v->as<ast_sequence>());
|
||||
case ast_return_statement: return clone(v->as<ast_return_statement>());
|
||||
case ast_if_statement: return clone(v->as<ast_if_statement>());
|
||||
case ast_repeat_statement: return clone(v->as<ast_repeat_statement>());
|
||||
case ast_while_statement: return clone(v->as<ast_while_statement>());
|
||||
case ast_do_while_statement: return clone(v->as<ast_do_while_statement>());
|
||||
case ast_throw_statement: return clone(v->as<ast_throw_statement>());
|
||||
case ast_assert_statement: return clone(v->as<ast_assert_statement>());
|
||||
case ast_try_catch_statement: return clone(v->as<ast_try_catch_statement>());
|
||||
case ast_asm_body: return clone(v->as<ast_asm_body>());
|
||||
// other AST nodes that can be children of ast nodes of function body
|
||||
case ast_identifier: return clone(v->as<ast_identifier>());
|
||||
case ast_instantiationT_item: return clone(v->as<ast_instantiationT_item>());
|
||||
case ast_instantiationT_list: return clone(v->as<ast_instantiationT_list>());
|
||||
case ast_parameter: return clone(v->as<ast_parameter>());
|
||||
case ast_parameter_list: return clone(v->as<ast_parameter_list>());
|
||||
|
||||
default: {
|
||||
// be very careful, don't forget to handle all statements/other (not expressions) above!
|
||||
AnyExprV as_expr = reinterpret_cast<const ASTNodeExpressionBase*>(v);
|
||||
return clone(as_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypePtr clone(TypePtr t) override {
|
||||
return t;
|
||||
}
|
||||
|
||||
public:
|
||||
virtual V<ast_function_declaration> clone_function_body(V<ast_function_declaration> v_function) {
|
||||
return createV<ast_function_declaration>(
|
||||
v_function->loc,
|
||||
clone(v_function->get_identifier()),
|
||||
clone(v_function->get_param_list()),
|
||||
clone(v_function->get_body()->as<ast_sequence>()),
|
||||
clone(v_function->declared_return_type),
|
||||
v_function->genericsT_list,
|
||||
v_function->method_id,
|
||||
v_function->flags
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tolk
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "ast.h"
|
||||
#include "ast-visitor.h"
|
||||
#include "type-system.h"
|
||||
#include <sstream>
|
||||
|
||||
/*
|
||||
|
@ -31,46 +32,55 @@ namespace tolk {
|
|||
|
||||
class ASTStringifier final : public ASTVisitor {
|
||||
constexpr static std::pair<ASTNodeType, const char*> name_pairs[] = {
|
||||
{ast_empty, "ast_empty"},
|
||||
{ast_parenthesized_expr, "ast_parenthesized_expr"},
|
||||
{ast_tensor, "ast_tensor"},
|
||||
{ast_tensor_square, "ast_tensor_square"},
|
||||
{ast_identifier, "ast_identifier"},
|
||||
// expressions
|
||||
{ast_empty_expression, "ast_empty_expression"},
|
||||
{ast_parenthesized_expression, "ast_parenthesized_expression"},
|
||||
{ast_tensor, "ast_tensor"},
|
||||
{ast_typed_tuple, "ast_typed_tuple"},
|
||||
{ast_reference, "ast_reference"},
|
||||
{ast_local_var_lhs, "ast_local_var_lhs"},
|
||||
{ast_local_vars_declaration, "ast_local_vars_declaration"},
|
||||
{ast_int_const, "ast_int_const"},
|
||||
{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_dot_access, "ast_dot_access"},
|
||||
{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_assign, "ast_assign"},
|
||||
{ast_set_assign, "ast_set_assign"},
|
||||
{ast_unary_operator, "ast_unary_operator"},
|
||||
{ast_binary_operator, "ast_binary_operator"},
|
||||
{ast_ternary_operator, "ast_ternary_operator"},
|
||||
{ast_return_statement, "ast_return_statement"},
|
||||
{ast_cast_as_operator, "ast_cast_as_operator"},
|
||||
// statements
|
||||
{ast_empty_statement, "ast_empty_statement"},
|
||||
{ast_sequence, "ast_sequence"},
|
||||
{ast_return_statement, "ast_return_statement"},
|
||||
{ast_if_statement, "ast_if_statement"},
|
||||
{ast_repeat_statement, "ast_repeat_statement"},
|
||||
{ast_while_statement, "ast_while_statement"},
|
||||
{ast_do_while_statement, "ast_do_while_statement"},
|
||||
{ast_throw_statement, "ast_throw_statement"},
|
||||
{ast_assert_statement, "ast_assert_statement"},
|
||||
{ast_try_catch_statement, "ast_try_catch_statement"},
|
||||
{ast_if_statement, "ast_if_statement"},
|
||||
{ast_asm_body, "ast_asm_body"},
|
||||
// other
|
||||
{ast_genericsT_item, "ast_genericsT_item"},
|
||||
{ast_genericsT_list, "ast_genericsT_list"},
|
||||
{ast_instantiationT_item, "ast_instantiationT_item"},
|
||||
{ast_instantiationT_list, "ast_instantiationT_list"},
|
||||
{ast_parameter, "ast_parameter"},
|
||||
{ast_parameter_list, "ast_parameter_list"},
|
||||
{ast_asm_body, "ast_asm_body"},
|
||||
{ast_annotation, "ast_annotation"},
|
||||
{ast_function_declaration, "ast_function_declaration"},
|
||||
{ast_local_var, "ast_local_var"},
|
||||
{ast_local_vars_declaration, "ast_local_vars_declaration"},
|
||||
{ast_global_var_declaration, "ast_global_var_declaration"},
|
||||
{ast_constant_declaration, "ast_constant_declaration"},
|
||||
{ast_tolk_required_version, "ast_tolk_required_version"},
|
||||
{ast_import_statement, "ast_import_statement"},
|
||||
{ast_import_directive, "ast_import_directive"},
|
||||
{ast_tolk_file, "ast_tolk_file"},
|
||||
};
|
||||
|
||||
|
@ -114,62 +124,94 @@ class ASTStringifier final : public ASTVisitor {
|
|||
switch (v->type) {
|
||||
case ast_identifier:
|
||||
return static_cast<std::string>(v->as<ast_identifier>()->name);
|
||||
case ast_reference: {
|
||||
std::string result(v->as<ast_reference>()->get_name());
|
||||
if (v->as<ast_reference>()->has_instantiationTs()) {
|
||||
result += specific_str(v->as<ast_reference>()->get_instantiationTs());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case ast_int_const:
|
||||
return static_cast<std::string>(v->as<ast_int_const>()->int_val);
|
||||
return static_cast<std::string>(v->as<ast_int_const>()->orig_str);
|
||||
case ast_string_const:
|
||||
if (char modifier = v->as<ast_string_const>()->modifier) {
|
||||
return "\"" + static_cast<std::string>(v->as<ast_string_const>()->str_val) + "\"" + std::string(1, modifier);
|
||||
} else {
|
||||
return "\"" + static_cast<std::string>(v->as<ast_string_const>()->str_val) + "\"";
|
||||
}
|
||||
case ast_bool_const:
|
||||
return v->as<ast_bool_const>()->bool_val ? "true" : "false";
|
||||
case ast_dot_access: {
|
||||
std::string result = "." + static_cast<std::string>(v->as<ast_dot_access>()->get_field_name());
|
||||
if (v->as<ast_dot_access>()->has_instantiationTs()) {
|
||||
result += specific_str(v->as<ast_dot_access>()->get_instantiationTs());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case ast_function_call: {
|
||||
if (auto v_lhs = v->as<ast_function_call>()->get_called_f()->try_as<ast_identifier>()) {
|
||||
return static_cast<std::string>(v_lhs->name) + "()";
|
||||
std::string inner = specific_str(v->as<ast_function_call>()->get_callee());
|
||||
if (int n_args = v->as<ast_function_call>()->get_num_args()) {
|
||||
return inner + "(..." + std::to_string(n_args) + ")";
|
||||
}
|
||||
return {};
|
||||
return inner + "()";
|
||||
}
|
||||
case ast_dot_method_call:
|
||||
return static_cast<std::string>(v->as<ast_dot_method_call>()->method_name);
|
||||
case ast_global_var_declaration:
|
||||
return static_cast<std::string>(v->as<ast_global_var_declaration>()->get_identifier()->name);
|
||||
case ast_constant_declaration:
|
||||
return static_cast<std::string>(v->as<ast_constant_declaration>()->get_identifier()->name);
|
||||
case ast_assign:
|
||||
return "=";
|
||||
case ast_set_assign:
|
||||
return static_cast<std::string>(v->as<ast_set_assign>()->operator_name) + "=";
|
||||
case ast_unary_operator:
|
||||
return static_cast<std::string>(v->as<ast_unary_operator>()->operator_name);
|
||||
case ast_binary_operator:
|
||||
return static_cast<std::string>(v->as<ast_binary_operator>()->operator_name);
|
||||
case ast_cast_as_operator:
|
||||
return v->as<ast_cast_as_operator>()->cast_to_type->as_human_readable();
|
||||
case ast_sequence:
|
||||
return "↓" + std::to_string(v->as<ast_sequence>()->get_items().size());
|
||||
case ast_instantiationT_item:
|
||||
return v->as<ast_instantiationT_item>()->substituted_type->as_human_readable();
|
||||
case ast_if_statement:
|
||||
return v->as<ast_if_statement>()->is_ifnot ? "ifnot" : "";
|
||||
case ast_annotation:
|
||||
return annotation_kinds[static_cast<int>(v->as<ast_annotation>()->kind)].second;
|
||||
case ast_parameter: {
|
||||
std::ostringstream os;
|
||||
os << v->as<ast_parameter>()->param_type;
|
||||
return static_cast<std::string>(v->as<ast_parameter>()->get_identifier()->name) + ": " + os.str();
|
||||
os << v->as<ast_parameter>()->declared_type;
|
||||
return static_cast<std::string>(v->as<ast_parameter>()->param_name) + ": " + os.str();
|
||||
}
|
||||
case ast_function_declaration: {
|
||||
std::string param_names;
|
||||
for (int i = 0; i < v->as<ast_function_declaration>()->get_num_params(); i++) {
|
||||
if (!param_names.empty())
|
||||
param_names += ",";
|
||||
param_names += v->as<ast_function_declaration>()->get_param(i)->get_identifier()->name;
|
||||
param_names += v->as<ast_function_declaration>()->get_param(i)->param_name;
|
||||
}
|
||||
return "fun " + static_cast<std::string>(v->as<ast_function_declaration>()->get_identifier()->name) + "(" + param_names + ")";
|
||||
}
|
||||
case ast_local_var: {
|
||||
case ast_local_var_lhs: {
|
||||
std::ostringstream os;
|
||||
os << v->as<ast_local_var>()->declared_type;
|
||||
if (auto v_ident = v->as<ast_local_var>()->get_identifier()->try_as<ast_identifier>()) {
|
||||
return static_cast<std::string>(v_ident->name) + ":" + os.str();
|
||||
}
|
||||
os << (v->as<ast_local_var_lhs>()->inferred_type ? v->as<ast_local_var_lhs>()->inferred_type : v->as<ast_local_var_lhs>()->declared_type);
|
||||
if (v->as<ast_local_var_lhs>()->get_name().empty()) {
|
||||
return "_: " + os.str();
|
||||
}
|
||||
return static_cast<std::string>(v->as<ast_local_var_lhs>()->get_name()) + ":" + os.str();
|
||||
}
|
||||
case ast_instantiationT_list: {
|
||||
std::string result = "<";
|
||||
for (AnyV item : v->as<ast_instantiationT_list>()->get_items()) {
|
||||
if (result.size() > 1)
|
||||
result += ",";
|
||||
result += item->as<ast_instantiationT_item>()->substituted_type->as_human_readable();
|
||||
}
|
||||
return result + ">";
|
||||
}
|
||||
case ast_tolk_required_version:
|
||||
return static_cast<std::string>(v->as<ast_tolk_required_version>()->semver);
|
||||
case ast_import_statement:
|
||||
return static_cast<std::string>(v->as<ast_import_statement>()->get_file_leaf()->str_val);
|
||||
case ast_import_directive:
|
||||
return static_cast<std::string>(v->as<ast_import_directive>()->get_file_leaf()->str_val);
|
||||
case ast_tolk_file:
|
||||
return v->as<ast_tolk_file>()->file->rel_filename;
|
||||
default:
|
||||
|
@ -202,46 +244,55 @@ public:
|
|||
|
||||
void visit(AnyV v) override {
|
||||
switch (v->type) {
|
||||
case ast_empty: return handle_vertex(v->as<ast_empty>());
|
||||
case ast_parenthesized_expr: return handle_vertex(v->as<ast_parenthesized_expr>());
|
||||
case ast_tensor: return handle_vertex(v->as<ast_tensor>());
|
||||
case ast_tensor_square: return handle_vertex(v->as<ast_tensor_square>());
|
||||
case ast_identifier: return handle_vertex(v->as<ast_identifier>());
|
||||
// expressions
|
||||
case ast_empty_expression: return handle_vertex(v->as<ast_empty_expression>());
|
||||
case ast_parenthesized_expression: return handle_vertex(v->as<ast_parenthesized_expression>());
|
||||
case ast_tensor: return handle_vertex(v->as<ast_tensor>());
|
||||
case ast_typed_tuple: return handle_vertex(v->as<ast_typed_tuple>());
|
||||
case ast_reference: return handle_vertex(v->as<ast_reference>());
|
||||
case ast_local_var_lhs: return handle_vertex(v->as<ast_local_var_lhs>());
|
||||
case ast_local_vars_declaration: return handle_vertex(v->as<ast_local_vars_declaration>());
|
||||
case ast_int_const: return handle_vertex(v->as<ast_int_const>());
|
||||
case ast_string_const: return handle_vertex(v->as<ast_string_const>());
|
||||
case ast_bool_const: return handle_vertex(v->as<ast_bool_const>());
|
||||
case ast_null_keyword: return handle_vertex(v->as<ast_null_keyword>());
|
||||
case ast_self_keyword: return handle_vertex(v->as<ast_self_keyword>());
|
||||
case ast_argument: return handle_vertex(v->as<ast_argument>());
|
||||
case ast_argument_list: return handle_vertex(v->as<ast_argument_list>());
|
||||
case ast_dot_access: return handle_vertex(v->as<ast_dot_access>());
|
||||
case ast_function_call: return handle_vertex(v->as<ast_function_call>());
|
||||
case ast_dot_method_call: return handle_vertex(v->as<ast_dot_method_call>());
|
||||
case ast_global_var_declaration: return handle_vertex(v->as<ast_global_var_declaration>());
|
||||
case ast_constant_declaration: return handle_vertex(v->as<ast_constant_declaration>());
|
||||
case ast_underscore: return handle_vertex(v->as<ast_underscore>());
|
||||
case ast_assign: return handle_vertex(v->as<ast_assign>());
|
||||
case ast_set_assign: return handle_vertex(v->as<ast_set_assign>());
|
||||
case ast_unary_operator: return handle_vertex(v->as<ast_unary_operator>());
|
||||
case ast_binary_operator: return handle_vertex(v->as<ast_binary_operator>());
|
||||
case ast_ternary_operator: return handle_vertex(v->as<ast_ternary_operator>());
|
||||
case ast_return_statement: return handle_vertex(v->as<ast_return_statement>());
|
||||
case ast_cast_as_operator: return handle_vertex(v->as<ast_cast_as_operator>());
|
||||
// statements
|
||||
case ast_empty_statement: return handle_vertex(v->as<ast_empty_statement>());
|
||||
case ast_sequence: return handle_vertex(v->as<ast_sequence>());
|
||||
case ast_return_statement: return handle_vertex(v->as<ast_return_statement>());
|
||||
case ast_if_statement: return handle_vertex(v->as<ast_if_statement>());
|
||||
case ast_repeat_statement: return handle_vertex(v->as<ast_repeat_statement>());
|
||||
case ast_while_statement: return handle_vertex(v->as<ast_while_statement>());
|
||||
case ast_do_while_statement: return handle_vertex(v->as<ast_do_while_statement>());
|
||||
case ast_throw_statement: return handle_vertex(v->as<ast_throw_statement>());
|
||||
case ast_assert_statement: return handle_vertex(v->as<ast_assert_statement>());
|
||||
case ast_try_catch_statement: return handle_vertex(v->as<ast_try_catch_statement>());
|
||||
case ast_if_statement: return handle_vertex(v->as<ast_if_statement>());
|
||||
case ast_asm_body: return handle_vertex(v->as<ast_asm_body>());
|
||||
// other
|
||||
case ast_genericsT_item: return handle_vertex(v->as<ast_genericsT_item>());
|
||||
case ast_genericsT_list: return handle_vertex(v->as<ast_genericsT_list>());
|
||||
case ast_instantiationT_item: return handle_vertex(v->as<ast_instantiationT_item>());
|
||||
case ast_instantiationT_list: return handle_vertex(v->as<ast_instantiationT_list>());
|
||||
case ast_parameter: return handle_vertex(v->as<ast_parameter>());
|
||||
case ast_parameter_list: return handle_vertex(v->as<ast_parameter_list>());
|
||||
case ast_asm_body: return handle_vertex(v->as<ast_asm_body>());
|
||||
case ast_annotation: return handle_vertex(v->as<ast_annotation>());
|
||||
case ast_function_declaration: return handle_vertex(v->as<ast_function_declaration>());
|
||||
case ast_local_var: return handle_vertex(v->as<ast_local_var>());
|
||||
case ast_local_vars_declaration: return handle_vertex(v->as<ast_local_vars_declaration>());
|
||||
case ast_global_var_declaration: return handle_vertex(v->as<ast_global_var_declaration>());
|
||||
case ast_constant_declaration: return handle_vertex(v->as<ast_constant_declaration>());
|
||||
case ast_tolk_required_version: return handle_vertex(v->as<ast_tolk_required_version>());
|
||||
case ast_import_statement: return handle_vertex(v->as<ast_import_statement>());
|
||||
case ast_import_directive: return handle_vertex(v->as<ast_import_directive>());
|
||||
case ast_tolk_file: return handle_vertex(v->as<ast_tolk_file>());
|
||||
default:
|
||||
throw UnexpectedASTNodeType(v, "ASTStringifier::visit");
|
||||
|
|
|
@ -37,20 +37,40 @@ namespace tolk {
|
|||
|
||||
class ASTVisitor {
|
||||
protected:
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE static void visit_children(const ASTNodeLeaf* v) {
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE static void visit_children(const ASTExprLeaf* v) {
|
||||
static_cast<void>(v);
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTNodeUnary* v) {
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTExprUnary* v) {
|
||||
visit(v->child);
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTNodeBinary* v) {
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTExprBinary* v) {
|
||||
visit(v->lhs);
|
||||
visit(v->rhs);
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTNodeVararg* v) {
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTExprVararg* v) {
|
||||
for (AnyExprV child : v->children) {
|
||||
visit(child);
|
||||
}
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTStatementUnary* v) {
|
||||
visit(v->child);
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTStatementVararg* v) {
|
||||
for (AnyV child : v->children) {
|
||||
visit(child);
|
||||
}
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE static void visit_children(const ASTOtherLeaf* v) {
|
||||
static_cast<void>(v);
|
||||
}
|
||||
|
||||
GNU_ATTRIBUTE_ALWAYS_INLINE void visit_children(const ASTOtherVararg* v) {
|
||||
for (AnyV child : v->children) {
|
||||
visit(child);
|
||||
}
|
||||
|
@ -66,90 +86,105 @@ class ASTVisitorFunctionBody : public ASTVisitor {
|
|||
protected:
|
||||
using parent = ASTVisitorFunctionBody;
|
||||
|
||||
virtual void visit(V<ast_empty> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_parenthesized_expr> v) { return visit_children(v); }
|
||||
// expressions
|
||||
virtual void visit(V<ast_empty_expression> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_parenthesized_expression> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_tensor> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_tensor_square> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_identifier> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_typed_tuple> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_reference> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_local_var_lhs> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_local_vars_declaration> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_int_const> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_string_const> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_bool_const> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_null_keyword> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_self_keyword> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_argument> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_argument_list> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_dot_access> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_function_call> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_dot_method_call> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_underscore> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_assign> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_set_assign> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_unary_operator> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_binary_operator> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_ternary_operator> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_return_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_cast_as_operator> v) { return visit_children(v); }
|
||||
// statements
|
||||
virtual void visit(V<ast_empty_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_sequence> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_return_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_if_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_repeat_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_while_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_do_while_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_throw_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_assert_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_try_catch_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_if_statement> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_local_var> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_local_vars_declaration> v) { return visit_children(v); }
|
||||
virtual void visit(V<ast_asm_body> v) { return visit_children(v); }
|
||||
|
||||
void visit(AnyV v) final {
|
||||
switch (v->type) {
|
||||
case ast_empty: return visit(v->as<ast_empty>());
|
||||
case ast_parenthesized_expr: return visit(v->as<ast_parenthesized_expr>());
|
||||
// expressions
|
||||
case ast_empty_expression: return visit(v->as<ast_empty_expression>());
|
||||
case ast_parenthesized_expression: return visit(v->as<ast_parenthesized_expression>());
|
||||
case ast_tensor: return visit(v->as<ast_tensor>());
|
||||
case ast_tensor_square: return visit(v->as<ast_tensor_square>());
|
||||
case ast_identifier: return visit(v->as<ast_identifier>());
|
||||
case ast_typed_tuple: return visit(v->as<ast_typed_tuple>());
|
||||
case ast_reference: return visit(v->as<ast_reference>());
|
||||
case ast_local_var_lhs: return visit(v->as<ast_local_var_lhs>());
|
||||
case ast_local_vars_declaration: return visit(v->as<ast_local_vars_declaration>());
|
||||
case ast_int_const: return visit(v->as<ast_int_const>());
|
||||
case ast_string_const: return visit(v->as<ast_string_const>());
|
||||
case ast_bool_const: return visit(v->as<ast_bool_const>());
|
||||
case ast_null_keyword: return visit(v->as<ast_null_keyword>());
|
||||
case ast_self_keyword: return visit(v->as<ast_self_keyword>());
|
||||
case ast_argument: return visit(v->as<ast_argument>());
|
||||
case ast_argument_list: return visit(v->as<ast_argument_list>());
|
||||
case ast_dot_access: return visit(v->as<ast_dot_access>());
|
||||
case ast_function_call: return visit(v->as<ast_function_call>());
|
||||
case ast_dot_method_call: return visit(v->as<ast_dot_method_call>());
|
||||
case ast_underscore: return visit(v->as<ast_underscore>());
|
||||
case ast_assign: return visit(v->as<ast_assign>());
|
||||
case ast_set_assign: return visit(v->as<ast_set_assign>());
|
||||
case ast_unary_operator: return visit(v->as<ast_unary_operator>());
|
||||
case ast_binary_operator: return visit(v->as<ast_binary_operator>());
|
||||
case ast_ternary_operator: return visit(v->as<ast_ternary_operator>());
|
||||
case ast_return_statement: return visit(v->as<ast_return_statement>());
|
||||
case ast_cast_as_operator: return visit(v->as<ast_cast_as_operator>());
|
||||
// statements
|
||||
case ast_empty_statement: return visit(v->as<ast_empty_statement>());
|
||||
case ast_sequence: return visit(v->as<ast_sequence>());
|
||||
case ast_return_statement: return visit(v->as<ast_return_statement>());
|
||||
case ast_if_statement: return visit(v->as<ast_if_statement>());
|
||||
case ast_repeat_statement: return visit(v->as<ast_repeat_statement>());
|
||||
case ast_while_statement: return visit(v->as<ast_while_statement>());
|
||||
case ast_do_while_statement: return visit(v->as<ast_do_while_statement>());
|
||||
case ast_throw_statement: return visit(v->as<ast_throw_statement>());
|
||||
case ast_assert_statement: return visit(v->as<ast_assert_statement>());
|
||||
case ast_try_catch_statement: return visit(v->as<ast_try_catch_statement>());
|
||||
case ast_if_statement: return visit(v->as<ast_if_statement>());
|
||||
case ast_local_var: return visit(v->as<ast_local_var>());
|
||||
case ast_local_vars_declaration: return visit(v->as<ast_local_vars_declaration>());
|
||||
case ast_asm_body: return visit(v->as<ast_asm_body>());
|
||||
#ifdef TOLK_DEBUG
|
||||
case ast_asm_body:
|
||||
throw UnexpectedASTNodeType(v, "ASTVisitor; forgot to filter out asm functions in should_visit_function()?");
|
||||
#endif
|
||||
default:
|
||||
throw UnexpectedASTNodeType(v, "ASTVisitorFunctionBody::visit");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void start_visiting_function(V<ast_function_declaration> v_function) {
|
||||
virtual bool should_visit_function(const FunctionData* fun_ref) = 0;
|
||||
|
||||
virtual void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v_function) {
|
||||
visit(v_function->get_body());
|
||||
}
|
||||
};
|
||||
|
||||
class ASTVisitorAllFunctionsInFile : public ASTVisitorFunctionBody {
|
||||
protected:
|
||||
using parent = ASTVisitorAllFunctionsInFile;
|
||||
|
||||
virtual bool should_enter_function(V<ast_function_declaration> v) = 0;
|
||||
const std::vector<const FunctionData*>& get_all_not_builtin_functions();
|
||||
|
||||
public:
|
||||
void start_visiting_file(V<ast_tolk_file> v_file) {
|
||||
for (AnyV v : v_file->get_toplevel_declarations()) {
|
||||
if (auto v_func = v->try_as<ast_function_declaration>()) {
|
||||
if (should_enter_function(v_func)) {
|
||||
visit(v_func->get_body());
|
||||
template<class BodyVisitorT>
|
||||
void visit_ast_of_all_functions() {
|
||||
BodyVisitorT visitor;
|
||||
for (const FunctionData* fun_ref : get_all_not_builtin_functions()) {
|
||||
if (visitor.should_visit_function(fun_ref)) {
|
||||
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tolk
|
||||
|
|
101
tolk/ast.cpp
101
tolk/ast.cpp
|
@ -15,8 +15,9 @@
|
|||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ast.h"
|
||||
#ifdef TOLK_DEBUG
|
||||
#include "ast-stringifier.h"
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
namespace tolk {
|
||||
|
||||
|
@ -79,7 +80,7 @@ int Vertex<ast_genericsT_list>::lookup_idx(std::string_view nameT) const {
|
|||
|
||||
int Vertex<ast_parameter_list>::lookup_idx(std::string_view param_name) const {
|
||||
for (size_t idx = 0; idx < children.size(); ++idx) {
|
||||
if (children[idx] && children[idx]->as<ast_parameter>()->get_identifier()->name == param_name) {
|
||||
if (children[idx] && children[idx]->as<ast_parameter>()->param_name == param_name) {
|
||||
return static_cast<int>(idx);
|
||||
}
|
||||
}
|
||||
|
@ -96,8 +97,100 @@ int Vertex<ast_parameter_list>::get_mutate_params_count() const {
|
|||
return n;
|
||||
}
|
||||
|
||||
void Vertex<ast_import_statement>::mutate_set_src_file(const SrcFile* file) const {
|
||||
const_cast<Vertex*>(this)->file = file;
|
||||
// ---------------------------------------------------------
|
||||
// "assign" methods
|
||||
//
|
||||
// From the user's point of view, all AST vertices are constant, fields are public, but can't be modified.
|
||||
// The only way to modify a field is to call "mutate()" and then use these "assign_*" methods.
|
||||
// Therefore, there is a guarantee, that all AST mutations are done via these methods,
|
||||
// easily searched by usages, and there is no another way to modify any other field.
|
||||
|
||||
void ASTNodeExpressionBase::assign_inferred_type(TypePtr type) {
|
||||
this->inferred_type = type;
|
||||
}
|
||||
|
||||
void ASTNodeExpressionBase::assign_rvalue_true() {
|
||||
this->is_rvalue = true;
|
||||
}
|
||||
|
||||
void ASTNodeExpressionBase::assign_lvalue_true() {
|
||||
this->is_lvalue = true;
|
||||
}
|
||||
|
||||
void Vertex<ast_reference>::assign_sym(const Symbol* sym) {
|
||||
this->sym = sym;
|
||||
}
|
||||
|
||||
void Vertex<ast_function_call>::assign_fun_ref(const FunctionData* fun_ref) {
|
||||
this->fun_maybe = fun_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_cast_as_operator>::assign_resolved_type(TypePtr cast_to_type) {
|
||||
this->cast_to_type = cast_to_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_global_var_declaration>::assign_var_ref(const GlobalVarData* var_ref) {
|
||||
this->var_ref = var_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_global_var_declaration>::assign_resolved_type(TypePtr declared_type) {
|
||||
this->declared_type = declared_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_constant_declaration>::assign_const_ref(const GlobalConstData* const_ref) {
|
||||
this->const_ref = const_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_constant_declaration>::assign_resolved_type(TypePtr declared_type) {
|
||||
this->declared_type = declared_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_instantiationT_item>::assign_resolved_type(TypePtr substituted_type) {
|
||||
this->substituted_type = substituted_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_parameter>::assign_param_ref(const LocalVarData* param_ref) {
|
||||
this->param_ref = param_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_parameter>::assign_resolved_type(TypePtr declared_type) {
|
||||
this->declared_type = declared_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_set_assign>::assign_fun_ref(const FunctionData* fun_ref) {
|
||||
this->fun_ref = fun_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_unary_operator>::assign_fun_ref(const FunctionData* fun_ref) {
|
||||
this->fun_ref = fun_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_binary_operator>::assign_fun_ref(const FunctionData* fun_ref) {
|
||||
this->fun_ref = fun_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_dot_access>::assign_target(const DotTarget& target) {
|
||||
this->target = target;
|
||||
}
|
||||
|
||||
void Vertex<ast_function_declaration>::assign_fun_ref(const FunctionData* fun_ref) {
|
||||
this->fun_ref = fun_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_function_declaration>::assign_resolved_type(TypePtr declared_return_type) {
|
||||
this->declared_return_type = declared_return_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_local_var_lhs>::assign_var_ref(const LocalVarData* var_ref) {
|
||||
this->var_ref = var_ref;
|
||||
}
|
||||
|
||||
void Vertex<ast_local_var_lhs>::assign_resolved_type(TypePtr declared_type) {
|
||||
this->declared_type = declared_type;
|
||||
}
|
||||
|
||||
void Vertex<ast_import_directive>::assign_src_file(const SrcFile* file) {
|
||||
this->file = file;
|
||||
}
|
||||
|
||||
} // namespace tolk
|
||||
|
|
957
tolk/ast.h
957
tolk/ast.h
File diff suppressed because it is too large
Load diff
|
@ -16,86 +16,57 @@
|
|||
*/
|
||||
#include "tolk.h"
|
||||
#include "compiler-state.h"
|
||||
#include "type-system.h"
|
||||
#include "generics-helpers.h"
|
||||
|
||||
namespace tolk {
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
/*
|
||||
*
|
||||
* SYMBOL VALUES
|
||||
*
|
||||
*/
|
||||
|
||||
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);
|
||||
tolk_assert(!def->value);
|
||||
|
||||
def->value = func_val;
|
||||
#ifdef TOLK_DEBUG
|
||||
def->value->sym_name = name;
|
||||
#endif
|
||||
return def;
|
||||
}
|
||||
|
||||
// given func_type = `(slice, int) -> slice` and func flags, create SymDef for parameters
|
||||
// given func_type = `(slice, int) -> slice` and func flags, create SymLocalVarOrParameter
|
||||
// currently (see at the bottom) parameters of built-in functions are unnamed:
|
||||
// built-in functions are created using a resulting type
|
||||
static std::vector<SymDef*> define_builtin_parameters(const TypeExpr* func_type, int func_flags) {
|
||||
static std::vector<LocalVarData> define_builtin_parameters(const std::vector<TypePtr>& params_types, 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 <T>)
|
||||
TypeExpr* params_type = func_type->constr == TypeExpr::te_ForAll ? func_type->args[0]->args[0] : func_type->args[0];
|
||||
std::vector<SymDef*> parameters;
|
||||
bool is_mutate_self = func_flags & FunctionData::flagHasMutateParams;
|
||||
std::vector<LocalVarData> parameters;
|
||||
parameters.reserve(params_types.size());
|
||||
|
||||
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<int>(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);
|
||||
for (int i = 0; i < static_cast<int>(params_types.size()); ++i) {
|
||||
LocalVarData p_sym("", {}, params_types[i], (i == 0 && is_mutate_self) * LocalVarData::flagMutateParameter, i);
|
||||
parameters.push_back(std::move(p_sym));
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
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));
|
||||
static void define_builtin_func(const std::string& name, const std::vector<TypePtr>& params_types, TypePtr return_type, const GenericsDeclaration* genericTs, const simple_compile_func_t& func, int flags) {
|
||||
auto* f_sym = new FunctionData(name, {}, return_type, define_builtin_parameters(params_types, flags), flags, genericTs, nullptr, new FunctionBodyBuiltin(func), nullptr);
|
||||
G.symtable.add_function(f_sym);
|
||||
}
|
||||
|
||||
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));
|
||||
static void define_builtin_func(const std::string& name, const std::vector<TypePtr>& params_types, TypePtr return_type, const GenericsDeclaration* genericTs, const AsmOp& macro, int flags) {
|
||||
auto* f_sym = new FunctionData(name, {}, return_type, define_builtin_parameters(params_types, flags), flags, genericTs, nullptr, new FunctionBodyBuiltin(make_simple_compile(macro)), nullptr);
|
||||
G.symtable.add_function(f_sym);
|
||||
}
|
||||
|
||||
static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, int flags,
|
||||
static void define_builtin_func(const std::string& name, const std::vector<TypePtr>& params_types, TypePtr return_type, const GenericsDeclaration* genericTs, const simple_compile_func_t& func, int flags,
|
||||
std::initializer_list<int> arg_order, std::initializer_list<int> 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));
|
||||
auto* f_sym = new FunctionData(name, {}, return_type, define_builtin_parameters(params_types, flags), flags, genericTs, nullptr, new FunctionBodyBuiltin(func), nullptr);
|
||||
f_sym->arg_order = arg_order;
|
||||
f_sym->ret_order = ret_order;
|
||||
G.symtable.add_function(f_sym);
|
||||
}
|
||||
|
||||
bool SymValAsmFunc::compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in,
|
||||
void FunctionBodyBuiltin::compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in,
|
||||
SrcLocation where) const {
|
||||
if (simple_compile) {
|
||||
return dest.append(simple_compile(out, in, where));
|
||||
} else if (ext_compile) {
|
||||
return ext_compile(dest, out, in);
|
||||
} else {
|
||||
return false;
|
||||
dest.append(simple_compile(out, in, where));
|
||||
}
|
||||
|
||||
void FunctionBodyAsm::compile(AsmOpList& dest) const {
|
||||
dest.append(ops);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* DEFINE BUILT-IN FUNCTIONS
|
||||
|
@ -504,7 +475,7 @@ AsmOp compile_unary_plus(std::vector<VarDescr>& res, std::vector<VarDescr>& args
|
|||
return AsmOp::Nop();
|
||||
}
|
||||
|
||||
AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
|
||||
AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where, bool for_int_arg) {
|
||||
tolk_assert(res.size() == 1 && args.size() == 1);
|
||||
VarDescr &r = res[0], &x = args[0];
|
||||
if (x.is_int_const()) {
|
||||
|
@ -513,7 +484,9 @@ AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
|
|||
return push_const(r.int_const);
|
||||
}
|
||||
r.val = VarDescr::ValBool;
|
||||
return exec_op("0 EQINT", 1);
|
||||
// for integers, `!var` is `var != 0`
|
||||
// for booleans, `!var` can be shortened to `~var` (works the same for 0/-1 but consumes less)
|
||||
return for_int_arg ? exec_op("0 EQINT", 1) : exec_op("NOT", 1);
|
||||
}
|
||||
|
||||
AsmOp compile_bitwise_and(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
|
||||
|
@ -1076,7 +1049,7 @@ AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
|
|||
return exec_op(fetch ? "LDSLICEX" : "PLDSLICEX", 2, 1 + (unsigned)fetch);
|
||||
}
|
||||
|
||||
// fun at<X>(t: tuple, index: int): X asm "INDEXVAR";
|
||||
// fun tupleAt<X>(t: tuple, index: int): X asm "INDEXVAR";
|
||||
AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
|
||||
tolk_assert(args.size() == 2 && res.size() == 1);
|
||||
auto& y = args[1];
|
||||
|
@ -1087,7 +1060,7 @@ AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args,
|
|||
return exec_op("INDEXVAR", 2, 1);
|
||||
}
|
||||
|
||||
// fun __isNull<X>(X arg): int
|
||||
// fun __isNull<X>(X arg): bool
|
||||
AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
|
||||
tolk_assert(args.size() == 1 && res.size() == 1);
|
||||
res[0].val = VarDescr::ValBool;
|
||||
|
@ -1098,143 +1071,188 @@ AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, S
|
|||
void define_builtins() {
|
||||
using namespace std::placeholders;
|
||||
|
||||
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));
|
||||
TypePtr Unit = TypeDataVoid::create();
|
||||
TypePtr Int = TypeDataInt::create();
|
||||
TypePtr Bool = TypeDataBool::create();
|
||||
TypePtr Slice = TypeDataSlice::create();
|
||||
TypePtr Builder = TypeDataBuilder::create();
|
||||
TypePtr Tuple = TypeDataTuple::create();
|
||||
|
||||
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,
|
||||
std::vector<GenericsDeclaration::GenericsItem> itemsT;
|
||||
itemsT.emplace_back("T");
|
||||
TypePtr typeT = TypeDataGenericT::create("T");
|
||||
const GenericsDeclaration* declGenericT = new GenericsDeclaration(std::move(itemsT));
|
||||
|
||||
std::vector ParamsInt1 = {Int};
|
||||
std::vector ParamsInt2 = {Int, Int};
|
||||
std::vector ParamsInt3 = {Int, Int, Int};
|
||||
std::vector ParamsSliceInt = {Slice, Int};
|
||||
|
||||
// builtin operators
|
||||
// they are internally stored as functions, because at IR level, there is no difference
|
||||
// between calling `userAdd(a,b)` and `_+_(a,b)`
|
||||
// since they are registered in a global symtable, technically, they can even be referenced from Tolk code,
|
||||
// though it's a "hidden feature" and won't work well for overloads (`==` for int and bool, for example)
|
||||
|
||||
// unary operators
|
||||
define_builtin_func("-_", ParamsInt1, Int, nullptr,
|
||||
compile_unary_minus,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("+_", ParamsInt1, Int, nullptr,
|
||||
compile_unary_plus,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("!_", ParamsInt1, Bool, nullptr,
|
||||
std::bind(compile_logical_not, _1, _2, _3, true),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("!b_", {Bool}, Bool, nullptr, // "overloaded" separate version for bool
|
||||
std::bind(compile_logical_not, _1, _2, _3, false),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("~_", ParamsInt1, Int, nullptr,
|
||||
compile_bitwise_not,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
|
||||
// binary operators
|
||||
define_builtin_func("_+_", ParamsInt2, Int, nullptr,
|
||||
compile_add,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_-_", ParamsInt2, Int, nullptr,
|
||||
compile_sub,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_*_", ParamsInt2, Int, nullptr,
|
||||
compile_mul,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_/_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_div, _1, _2, _3, -1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_~/_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_div, _1, _2, _3, 0),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_^/_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_div, _1, _2, _3, 1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_%_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_mod, _1, _2, _3, -1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_<<_", ParamsInt2, Int, nullptr,
|
||||
compile_lshift,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_>>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_rshift, _1, _2, _3, -1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_~>>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_rshift, _1, _2, _3, 0),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_^>>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_rshift, _1, _2, _3, 1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_&_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
compile_bitwise_and,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_|_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
compile_bitwise_or,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_^_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
compile_bitwise_xor,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_==_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
std::bind(compile_cmp_int, _1, _2, 2),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_!=_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
std::bind(compile_cmp_int, _1, _2, 5),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_<_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_cmp_int, _1, _2, 4),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_cmp_int, _1, _2, 1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_<=_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_cmp_int, _1, _2, 6),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_>=_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_cmp_int, _1, _2, 3),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_<=>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_cmp_int, _1, _2, 7),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
|
||||
// special function used for internal compilation of some lexical constructs
|
||||
// for example, `throw 123;` is actually calling `__throw(123)`
|
||||
define_builtin_func("__true", {}, Bool, nullptr, /* AsmOp::Const("TRUE") */
|
||||
std::bind(compile_bool_const, _1, _2, true),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__false", {}, Bool, nullptr, /* AsmOp::Const("FALSE") */
|
||||
std::bind(compile_bool_const, _1, _2, false),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__null", {}, typeT, declGenericT,
|
||||
AsmOp::Const("PUSHNULL"),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__isNull", {typeT}, Bool, declGenericT,
|
||||
compile_is_null,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
|
||||
compile_throw,
|
||||
0);
|
||||
define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg,
|
||||
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
|
||||
compile_throw_arg,
|
||||
0);
|
||||
define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), compile_throw_if_unless,
|
||||
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
|
||||
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)),
|
||||
|
||||
// functions from stdlib marked as `builtin`, implemented at compiler level for optimizations
|
||||
// (for example, `loadInt(1)` is `1 LDI`, but `loadInt(n)` for non-constant requires it be on a stack and `LDIX`)
|
||||
define_builtin_func("mulDivFloor", ParamsInt3, Int, nullptr,
|
||||
std::bind(compile_muldiv, _1, _2, _3, -1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("mulDivRound", ParamsInt3, Int, nullptr,
|
||||
std::bind(compile_muldiv, _1, _2, _3, 0),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("mulDivCeil", ParamsInt3, Int, nullptr,
|
||||
std::bind(compile_muldiv, _1, _2, _3, 1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("mulDivMod", ParamsInt3, TypeDataTensor::create({Int, Int}), nullptr,
|
||||
AsmOp::Custom("MULDIVMOD", 3, 2),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("loadInt", ParamsSliceInt, Int, nullptr,
|
||||
std::bind(compile_fetch_int, _1, _2, true, true),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf,
|
||||
{}, {1, 0});
|
||||
define_builtin_func("loadUint", ParamsSliceInt, Int, nullptr,
|
||||
std::bind(compile_fetch_int, _1, _2, true, false),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf,
|
||||
{}, {1, 0});
|
||||
define_builtin_func("loadBits", ParamsSliceInt, Slice, nullptr,
|
||||
std::bind(compile_fetch_slice, _1, _2, true),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf,
|
||||
{}, {1, 0});
|
||||
define_builtin_func("preloadInt", ParamsSliceInt, Int, nullptr,
|
||||
std::bind(compile_fetch_int, _1, _2, false, true),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf);
|
||||
define_builtin_func("preloadUint", ParamsSliceInt, Int, nullptr,
|
||||
std::bind(compile_fetch_int, _1, _2, false, false),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf);
|
||||
define_builtin_func("preloadBits", ParamsSliceInt, Slice, nullptr,
|
||||
std::bind(compile_fetch_slice, _1, _2, false),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf);
|
||||
define_builtin_func("storeInt", {Builder, Int, Int}, Unit, nullptr,
|
||||
std::bind(compile_store_int, _1, _2, true),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf | FunctionData::flagReturnsSelf,
|
||||
{1, 0, 2}, {});
|
||||
define_builtin_func("storeUint", {Builder, Int, Int}, Unit, nullptr,
|
||||
std::bind(compile_store_int, _1, _2, false),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf | FunctionData::flagReturnsSelf,
|
||||
{1, 0, 2}, {});
|
||||
define_builtin_func("tupleAt", {Tuple, Int}, typeT, declGenericT,
|
||||
compile_tuple_at,
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagAcceptsSelf);
|
||||
define_builtin_func("debugPrint", {typeT}, Unit, declGenericT,
|
||||
AsmOp::Custom("s0 DUMP DROP", 1, 1),
|
||||
0);
|
||||
define_builtin_func("debugPrintString", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)),
|
||||
define_builtin_func("debugPrintString", {typeT}, Unit, declGenericT,
|
||||
AsmOp::Custom("STRDUMP DROP", 1, 1),
|
||||
0);
|
||||
define_builtin_func("debugDumpStack", TypeExpr::new_map(Unit, Unit),
|
||||
define_builtin_func("debugDumpStack", {}, Unit, nullptr,
|
||||
AsmOp::Custom("DUMPSTK", 0, 0),
|
||||
0);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
#include "tolk.h"
|
||||
#include "compiler-state.h"
|
||||
#include "type-system.h"
|
||||
|
||||
namespace tolk {
|
||||
|
||||
|
@ -314,7 +315,7 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
return true;
|
||||
}
|
||||
case _GlobVar:
|
||||
if (dynamic_cast<const SymValGlobVar*>(fun_ref->value)) {
|
||||
if (g_sym) {
|
||||
bool used = false;
|
||||
for (auto i : left) {
|
||||
auto p = next->var_info[i];
|
||||
|
@ -325,8 +326,7 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
if (!used || disabled()) {
|
||||
return true;
|
||||
}
|
||||
std::string name = G.symbols.get_name(fun_ref->sym_idx);
|
||||
stack.o << AsmOp::Custom(name + " GETGLOB", 0, 1);
|
||||
stack.o << AsmOp::Custom(g_sym->name + " GETGLOB", 0, 1);
|
||||
if (left.size() != 1) {
|
||||
tolk_assert(left.size() <= 15);
|
||||
stack.o << AsmOp::UnTuple((int)left.size());
|
||||
|
@ -343,25 +343,28 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
}
|
||||
stack.o << "CONT:<{";
|
||||
stack.o.indent();
|
||||
auto func = dynamic_cast<SymValAsmFunc*>(fun_ref->value);
|
||||
if (func) {
|
||||
if (f_sym->is_asm_function() || f_sym->is_builtin_function()) {
|
||||
// TODO: create and compile a true lambda instead of this (so that arg_order and ret_order would work correctly)
|
||||
std::vector<VarDescr> args0, res;
|
||||
TypeExpr::remove_indirect(func->sym_type);
|
||||
tolk_assert(func->get_type()->is_map());
|
||||
auto wr = func->get_type()->args.at(0)->get_width();
|
||||
auto wl = func->get_type()->args.at(1)->get_width();
|
||||
tolk_assert(wl >= 0 && wr >= 0);
|
||||
for (int i = 0; i < wl; i++) {
|
||||
int w_arg = 0;
|
||||
for (const LocalVarData& param : f_sym->parameters) {
|
||||
w_arg += param.declared_type->calc_width_on_stack();
|
||||
}
|
||||
int w_ret = f_sym->inferred_return_type->calc_width_on_stack();
|
||||
tolk_assert(w_ret >= 0 && w_arg >= 0);
|
||||
for (int i = 0; i < w_ret; i++) {
|
||||
res.emplace_back(0);
|
||||
}
|
||||
for (int i = 0; i < wr; i++) {
|
||||
for (int i = 0; i < w_arg; i++) {
|
||||
args0.emplace_back(0);
|
||||
}
|
||||
func->compile(stack.o, res, args0, where); // compile res := f (args0)
|
||||
if (f_sym->is_asm_function()) {
|
||||
std::get<FunctionBodyAsm*>(f_sym->body)->compile(stack.o); // compile res := f (args0)
|
||||
} else {
|
||||
std::string name = G.symbols.get_name(fun_ref->sym_idx);
|
||||
stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size());
|
||||
std::get<FunctionBodyBuiltin*>(f_sym->body)->compile(stack.o, res, args0, where); // compile res := f (args0)
|
||||
}
|
||||
} else {
|
||||
stack.o << AsmOp::Custom(f_sym->name + " CALLDICT", (int)right.size(), (int)left.size());
|
||||
}
|
||||
stack.o.undent();
|
||||
stack.o << "}>";
|
||||
|
@ -438,10 +441,9 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
if (disabled()) {
|
||||
return true;
|
||||
}
|
||||
// fun_ref can be nullptr for Op::_CallInd (invoke a variable, not a function)
|
||||
SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr);
|
||||
auto arg_order = (func ? func->get_arg_order() : nullptr);
|
||||
auto ret_order = (func ? func->get_ret_order() : nullptr);
|
||||
// f_sym can be nullptr for Op::_CallInd (invoke a variable, not a function)
|
||||
const std::vector<int>* arg_order = f_sym ? f_sym->get_arg_order() : nullptr;
|
||||
const std::vector<int>* ret_order = f_sym ? f_sym->get_ret_order() : nullptr;
|
||||
tolk_assert(!arg_order || arg_order->size() == right.size());
|
||||
tolk_assert(!ret_order || ret_order->size() == left.size());
|
||||
std::vector<var_idx_t> right1;
|
||||
|
@ -455,14 +457,12 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
right1.push_back(arg.idx);
|
||||
}
|
||||
}
|
||||
} else if (arg_order) {
|
||||
for (int i = 0; i < (int)right.size(); i++) {
|
||||
right1.push_back(right.at(arg_order->at(i)));
|
||||
}
|
||||
} else {
|
||||
tolk_assert(!arg_order);
|
||||
right1 = right;
|
||||
}
|
||||
std::vector<bool> last;
|
||||
last.reserve(right1.size());
|
||||
for (var_idx_t x : right1) {
|
||||
last.push_back(var_info[x] && var_info[x]->is_last());
|
||||
}
|
||||
|
@ -488,23 +488,25 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
};
|
||||
if (cl == _CallInd) {
|
||||
exec_callxargs((int)right.size() - 1, (int)left.size());
|
||||
} else if (auto asm_fv = dynamic_cast<const SymValAsmFunc*>(fun_ref->value)) {
|
||||
} else if (!f_sym->is_code_function()) {
|
||||
std::vector<VarDescr> res;
|
||||
res.reserve(left.size());
|
||||
for (var_idx_t i : left) {
|
||||
res.emplace_back(i);
|
||||
}
|
||||
asm_fv->compile(stack.o, res, args, where); // compile res := f (args)
|
||||
if (f_sym->is_asm_function()) {
|
||||
std::get<FunctionBodyAsm*>(f_sym->body)->compile(stack.o); // compile res := f (args)
|
||||
} else {
|
||||
auto fv = dynamic_cast<const SymValCodeFunc*>(fun_ref->value);
|
||||
std::string name = G.symbols.get_name(fun_ref->sym_idx);
|
||||
if (fv->is_inline() || fv->is_inline_ref()) {
|
||||
stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size());
|
||||
} else if (fv->code && fv->code->require_callxargs) {
|
||||
stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2);
|
||||
std::get<FunctionBodyBuiltin*>(f_sym->body)->compile(stack.o, res, args, where); // compile res := f (args)
|
||||
}
|
||||
} else {
|
||||
if (f_sym->is_inline() || f_sym->is_inline_ref()) {
|
||||
stack.o << AsmOp::Custom(f_sym->name + " INLINECALLDICT", (int)right.size(), (int)left.size());
|
||||
} else if (f_sym->is_code_function() && std::get<FunctionBodyCode*>(f_sym->body)->code->require_callxargs) {
|
||||
stack.o << AsmOp::Custom(f_sym->name + (" PREPAREDICT"), 0, 2);
|
||||
exec_callxargs((int)right.size() + 1, (int)left.size());
|
||||
} else {
|
||||
stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size());
|
||||
stack.o << AsmOp::Custom(f_sym->name + " CALLDICT", (int)right.size(), (int)left.size());
|
||||
}
|
||||
}
|
||||
stack.s.resize(k);
|
||||
|
@ -515,7 +517,7 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
return true;
|
||||
}
|
||||
case _SetGlob: {
|
||||
tolk_assert(fun_ref && dynamic_cast<const SymValGlobVar*>(fun_ref->value));
|
||||
tolk_assert(g_sym);
|
||||
std::vector<bool> last;
|
||||
for (var_idx_t x : right) {
|
||||
last.push_back(var_info[x] && var_info[x]->is_last());
|
||||
|
@ -534,8 +536,7 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
stack.o << AsmOp::Tuple((int)right.size());
|
||||
}
|
||||
if (!right.empty()) {
|
||||
std::string name = G.symbols.get_name(fun_ref->sym_idx);
|
||||
stack.o << AsmOp::Custom(name + " SETGLOB", 1, 0);
|
||||
stack.o << AsmOp::Custom(g_sym->name + " SETGLOB", 1, 0);
|
||||
}
|
||||
stack.s.resize(k);
|
||||
return true;
|
||||
|
@ -826,6 +827,8 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
catch_stack.push_new_var(left[1]);
|
||||
stack.rearrange_top(catch_vars, catch_last);
|
||||
stack.opt_show();
|
||||
stack.o << "c1 PUSH";
|
||||
stack.o << "c3 PUSH";
|
||||
stack.o << "c4 PUSH";
|
||||
stack.o << "c5 PUSH";
|
||||
stack.o << "c7 PUSH";
|
||||
|
@ -842,6 +845,8 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
stack.o << "c7 SETCONT";
|
||||
stack.o << "c5 SETCONT";
|
||||
stack.o << "c4 SETCONT";
|
||||
stack.o << "c3 SETCONT";
|
||||
stack.o << "c1 SETCONT";
|
||||
for (size_t begin = catch_vars.size(), end = begin; end > 0; end = begin) {
|
||||
begin = end >= block_size ? end - block_size : 0;
|
||||
stack.o << std::to_string(end - begin) + " PUSHINT";
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue