1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[Tolk] v0.6 syntax: fun, import, var, types on the right, etc.

Lots of changes, actually. Most noticeable are:
- traditional //comments
- #include -> import
- a rule "import what you use"
- ~ found -> !found (for -1/0)
- null() -> null
- is_null?(v) -> v == null
- throw is a keyword
- catch with swapped arguments
- throw_if, throw_unless -> assert
- do until -> do while
- elseif -> else if
- drop ifnot, elseifnot
- drop rarely used operators

A testing framework also appears here. All tests existed earlier,
but due to significant syntax changes, their history is useless.
This commit is contained in:
tolk-vm 2024-10-31 11:11:41 +04:00
parent 5a3e3595d6
commit e2edadba92
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
133 changed files with 8196 additions and 2605 deletions

View file

@ -0,0 +1,42 @@
fun one(dummy: tuple) {
return 1;
}
fun main(a: int, x: int) {
var y: int = 0;
var z: int = 0;
while ((y = x * x) > a) {
x -= 1;
z = one(null);
}
return (y, z);
}
fun throwIfLt10(x: int): void {
if (x > 10) {
return;
}
throw 234;
return;
}
@method_id(88)
fun test88(x: int) {
try {
var x: void = throwIfLt10(x);
return 0;
} catch(code) {
return code;
}
}
/**
method_id | in | out
@testcase | 0 | 101 15 | 100 1
@testcase | 0 | 101 14 | 100 1
@testcase | 0 | 101 10 | 100 0
@testcase | 0 | 100 10 | 100 0
@testcase | 0 | 100 10 | 100 0
@testcase | 88 | 5 | 234
@testcase | 88 | 50 | 0
*/

85
tolk-tester/tests/a6.tolk Normal file
View file

@ -0,0 +1,85 @@
fun f(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) {
// solve a 2x2 linear equation
var D: int = a*d - b*c;;;; var Dx: int = e*d-b*f ;;;; var Dy: int = a * f - e * c;
return (Dx/D,Dy/D);
};;;;
fun mulDivR(x: int, y: int, z: int): int { return muldivr(x, y, z); }
fun calc_phi(): int {
var n = 1;
repeat (70) { n*=10; };
var p= 1;
var `q`=1;
do {
(p,q)=(q,p+q);
} while (q <= n); //;;
return mulDivR(p, n, q);
}
fun calc_sqrt2(): int {
var n = 1;
repeat (70) { n *= 10; }
var p = 1;
var q = 1;
do {
var t = p + q;
(p, q) = (q, t + q);
} while (q <= n);
return mulDivR(p, n, q);
}
fun calc_root(m: auto): auto {
var base: int=1;
repeat(70) { base *= 10; }
var (a, b, c) = (1,0,-m);
var (p1, q1, p2, q2) = (1, 0, 0, 1);
do {
var k: int=-1;
var (a1, b1, c1) = (0, 0, 0);
do {
k+=1;
(a1, b1, c1) = (a, b, c);
c+=b;
c += b += a;
} while (c <= 0);
(a, b, c) = (-c1, -b1, -a1);
(p1, q1) = (k * p1+q1, p1);
(p2, q2) = (k * p2+q2, p2);
} while (p1 <= base);
return (p1, q1, p2, q2);
}
fun ataninv(base: int, q: int): int { // computes base*atan(1/q)
base=base~/q;
q*=-q;
var sum: int = 0;
var n: int = 1;
do {
sum += base~/n;
base = base~/q;
n += 2;
} while (base != 0);
return sum;
}
fun arctanInv(base: int, q: int): int { return ataninv(base, q); }
fun calc_pi(): int {
var base: int = 64;
repeat (70) { base *= 10; }
return (arctanInv(base << 2, 5) - arctanInv(base, 239))~>>4;
}
fun calcPi(): int { return calc_pi(); }
fun main(): int {
return calcPi();
}
/**
method_id | in | out
@testcase | 0 | | 31415926535897932384626433832795028841971693993751058209749445923078164
@code_hash 84337043972311674339187056298873613816389434478842780265748859098303774481976
*/

View file

@ -0,0 +1,16 @@
fun main(a: int, b: int, c: int, d: int, e: int, f: int): (int, int) {
var D: int = a * d - b * c;
var Dx: int = e * d - b * f;
var Dy: int = a * f - e * c;
return (Dx / D, Dy / D);
}
/**
method_id | in | out
@testcase | 0 | 1 1 1 -1 10 6 | 8 2
@testcase | 0 | 817 -31 624 -241 132272 272276 | 132 -788
@testcase | 0 | -886 562 498 -212 -36452 -68958 | -505 -861
@testcase | 0 | 448 -433 -444 792 150012 -356232 | -218 -572
@testcase | 0 | -40 -821 433 -734 -721629 -741724 | -206 889
@testcase | 0 | -261 -98 -494 868 -166153 733738 | 263 995
*/

View file

@ -0,0 +1,26 @@
@deprecated
fun twice(f: auto, x: auto): auto {
return f (f (x));
}
fun sqr(x: int) {
return x * x;
}
fun main(x: int): int {
var f = sqr;
return twice(f, x) * f(x);
}
@method_id(4)
fun pow6(x: int): int {
return twice(sqr, x) * sqr(x);
}
/**
method_id | in | out
@testcase | 0 | 3 | 729
@testcase | 0 | 10 | 1000000
@testcase | 4 | 3 | 729
@testcase | 4 | 10 | 1000000
*/

24
tolk-tester/tests/a7.tolk Normal file
View file

@ -0,0 +1,24 @@
fun main() { }
@method_id(1)
fun steps(x: int): int {
var n = 0;
while (x > 1) {
n += 1;
if (x & 1) {
x = 3 * x + 1;
} else {
x >>= 1;
}
}
return n;
}
/**
method_id | in | out
@testcase | 1 | 1 | 0
@testcase | 1 | 2 | 1
@testcase | 1 | 5 | 5
@testcase | 1 | 19 | 20
@testcase | 1 | 27 | 111
@testcase | 1 | 100 | 25
*/

View file

@ -0,0 +1,108 @@
fun unsafe_tuple<X>(x: X): tuple
asm "NOP";
fun inc(x: int, y: int): (int, int) {
return (x + y, y * 10);
}
fun ~inc(x: int, y: int): (int, int) {
(x, y) = inc(x, y);
return (x, y);
}
fun ~incWrap(x: int, y: int): (int, int) {
return ~inc(x, y);
}
@method_id(11)
fun test_return(x: int): (int, int, int, int, int, int, int) {
return (x, x~incWrap(x / 20), x, x = x * 2, x, x += 1, x);
}
@method_id(12)
fun test_assign(x: int): (int, int, int, int, int, int, int) {
var (x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int) = (x, x~inc(x / 20), x, x=x*2, x, x+=1, x);
return (x1, x2, x3, x4, x5, x6, x7);
}
@method_id(13)
fun test_tuple(x: int): tuple {
var t: tuple = unsafe_tuple([x, x~incWrap(x / 20), x, x = x * 2, x, x += 1, x]);
return t;
}
@method_id(14)
fun test_tuple_assign(x: int): (int, int, int, int, int, int, int) {
var [x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int] = [x, x~inc(x / 20), x, x = x * 2, x, x += 1, x];
return (x1, x2, x3, x4, x5, x6, x7);
}
fun foo1(x1: int, x2: int, x3: int, x4: int, x5: int, x6: int, x7: int): (int, int, int, int, int, int, int) {
return (x1, x2, x3, x4, x5, x6, x7);
}
@method_id(15)
fun test_call_1(x: int): (int, int, int, int, int, int, int) {
return foo1(x, x~inc(x / 20), x, x = x * 2, x, x += 1, x);
}
fun foo2(x1: int, x2: int, x3456: (int, int, int, int), x7: int): (int, int, int, int, int, int, int) {
var (x3: int, x4: int, x5: int, x6: int) = x3456;
return (x1, x2, x3, x4, x5, x6, x7);
}
@method_id(16)
fun test_call_2(x: int): (int, int, int, int, int, int, int) {
return foo2(x, x~incWrap(x / 20), (x, x = x * 2, x, x += 1), x);
}
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";
@method_id(17)
fun test_call_asm_old(x: int): (int, int, int, int, int, int, int) {
return asm_func(x, x += 1, x, x, x~inc(x / 20), x, x = x * 2);
}
@method_id(18)
fun test_call_asm_new(x: int): (int, int, int, int, int, int, int) {
return asm_func(x, x~incWrap(x / 20), x, x = x * 2, x, x += 1, x);
}
global xx: int;
@method_id(19)
fun test_global(x: int): (int, int, int, int, int, int, int) {
xx = x;
return (xx, xx~incWrap(xx / 20), xx, xx = xx * 2, xx, xx += 1, xx);
}
@method_id(20)
fun test_if_else(x: int): (int, int, int, int, int) {
if (x > 10) {
return (x~inc(8), x + 1, x = 1, x <<= 3, x);
} else {
xx = 9;
return (x, x~inc(-4), x~inc(-1), x >= 1, x = x + xx);
}
}
fun main() {
}
/**
method_id | in | out
@testcase | 11 | 100 | 100 50 105 210 210 211 211
@testcase | 12 | 100 | 100 50 105 210 210 211 211
@testcase | 13 | 100 | [ 100 50 105 210 210 211 211 ]
@testcase | 14 | 100 | 100 50 105 210 210 211 211
@testcase | 15 | 100 | 100 50 105 210 210 211 211
@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 | 20 | 80 | 80 89 1 8 8
@testcase | 20 | 9 | 9 -40 -10 -1 13
@fif_codegen_avoid ~incWrap
@code_hash 97139400653362069936987769894397430077752335662822462908581556703209313861576
*/

View file

@ -0,0 +1,145 @@
@pure
fun empty_tuple2(): tuple
asm "NIL";
@pure
fun tpush2<X>(t: tuple, x: X): (tuple, ())
asm "TPUSH";
fun emptyTuple(): tuple { return empty_tuple2(); }
fun tuplePush<X>(t: tuple, value: X): (tuple, ()) { return tpush2(t, value); }
@pure
fun asm_func_1(x: int, y: int, z: int): tuple
asm "3 TUPLE";
@pure
fun asm_func_2(x: int, y: int, z: int): tuple
asm (z y x -> 0) "3 TUPLE";
@pure
fun asm_func_3(x: int, y: int, z: int): tuple
asm (y z x -> 0) "3 TUPLE";
@pure
fun asm_func_4(a: int, b: (int, (int, int)), c: int): tuple
asm (b a c -> 0) "5 TUPLE";
fun asmFunc1(x: int, y: int, z: int): tuple { return asm_func_1(x, y, z); }
fun asmFunc3(x: int, y: int, z: int): tuple { return asm_func_3(x, y, z); }
@pure
fun asm_func_modify(a: tuple, b: int, c: int): (tuple, ())
asm (c b a -> 0) "SWAP TPUSH SWAP TPUSH";
fun asmFuncModify(a: tuple, b: int, c: int): (tuple, ()) { return asm_func_modify(a, b, c); }
global t: tuple;
fun foo(x: int): int {
t~tuplePush(x);
return x * 10;
}
@method_id(11)
fun test_old_1(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = asmFunc1(foo(11), foo(22), foo(33));
return (t, t2);
}
@method_id(12)
fun test_old_2(): (tuple, tuple) {
t = emptyTuple();
var t2: tuple = asm_func_2(foo(11), foo(22), foo(33));
return (t, t2);
}
@method_id(13)
fun test_old_3(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = asm_func_3(foo(11), foo(22), foo(33));
return (t, t2);
}
@method_id(14)
fun test_old_4(): (tuple, tuple) {
t = emptyTuple();
var t2: tuple = empty_tuple2();
// This actually computes left-to-right even without compute-asm-ltr
t2 = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55));
return (t, t2);
}
@method_id(15)
fun test_old_modify(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = empty_tuple2();
t2~asmFuncModify(foo(22), foo(33));
return (t, t2);
}
@method_id(16)
fun test_old_dot(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = foo(11).asmFunc3(foo(22), foo(33));
return (t, t2);
}
@method_id(21)
fun test_new_1(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = asmFunc1(foo(11), foo(22), foo(33));
return (t, t2);
}
@method_id(22)
fun test_new_2(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = asm_func_2(foo(11), foo(22), foo(33));
return (t, t2);
}
@method_id(23)
fun test_new_3(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = asm_func_3(foo(11), foo(22), foo(33));
return (t, t2);
}
@method_id(24)
fun test_new_4(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = asm_func_4(foo(11), (foo(22), (foo(33), foo(44))), foo(55));
return (t, t2);
}
@method_id(25)
fun test_new_modify(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = empty_tuple2();
t2~asm_func_modify(foo(22), foo(33));
return (t, t2);
}
@method_id(26)
fun test_new_dot(): (tuple, tuple) {
t = empty_tuple2();
var t2: tuple = foo(11).asm_func_3(foo(22), foo(33));
return (t, t2);
}
fun main() {
}
/**
method_id | in | out
@testcase | 11 | | [ 11 22 33 ] [ 110 220 330 ]
@testcase | 12 | | [ 11 22 33 ] [ 330 220 110 ]
@testcase | 13 | | [ 11 22 33 ] [ 220 330 110 ]
@testcase | 14 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ]
@testcase | 15 | | [ 22 33 ] [ 220 330 ]
@testcase | 16 | | [ 11 22 33 ] [ 220 330 110 ]
@testcase | 21 | | [ 11 22 33 ] [ 110 220 330 ]
@testcase | 22 | | [ 11 22 33 ] [ 330 220 110 ]
@testcase | 23 | | [ 11 22 33 ] [ 220 330 110 ]
@testcase | 24 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ]
@testcase | 25 | | [ 22 33 ] [ 220 330 ]
@testcase | 26 | | [ 11 22 33 ] [ 220 330 110 ]
@code_hash 93068291567112337250118419287631047120002003622184251973082208096953112184588
*/

View file

@ -0,0 +1,53 @@
fun lshift(): int {
return (1 << 0) == 1;
}
fun rshift(): int {
return (1 >> 0) == 1;
}
fun lshift_var(i: int): int {
return (1 << i) == 1;
}
fun rshift_var(i: int): int {
return (1 >> i) == 1;
}
fun main(x: int): int {
if (x == 0) {
return lshift();
} else if (x == 1) {
return rshift();
} else if (x == 2) {
return lshift_var(0);
} else if (x == 3) {
return rshift_var(0);
} else if (x == 4) {
return lshift_var(1);
} else {
return rshift_var(1);
}
}
@method_id(11)
fun is_claimed(index: int): int {
var claim_bit_index: int = index % 256;
var mask: int = 1 << claim_bit_index;
return (255 & mask) == mask;
}
/**
method_id | in | out
@testcase | 0 | 0 | -1
@testcase | 0 | 1 | -1
@testcase | 0 | 2 | -1
@testcase | 0 | 3 | -1
@testcase | 0 | 4 | 0
@testcase | 0 | 5 | 0
@testcase | 11 | 0 | -1
@testcase | 11 | 1 | -1
@testcase | 11 | 256 | -1
@testcase | 11 | 8 | 0
*/

27
tolk-tester/tests/c2.tolk Normal file
View file

@ -0,0 +1,27 @@
global op: (int, int) -> int;
fun check_assoc(a: int, b: int, c: int): int {
return op(op(a, b), c) == op(a, op(b, c));
}
fun unnamed_args(_: int, _: slice, _: auto): auto {
return true;
}
fun main(x: int, y: int, z: int): int {
op = `_+_`;
return check_assoc(x, y, z);
}
@method_id(101)
fun test101(x: int, z: int): auto {
return unnamed_args(x, "asdf", z);
}
/**
method_id | in | out
@testcase | 0 | 2 3 9 | -1
@testcase | 0 | 11 22 44 | -1
@testcase | 0 | -1 -10 -20 | -1
@testcase | 101 | 1 10 | -1
*/

View file

@ -0,0 +1,14 @@
fun check_assoc(op: auto, 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 {
return check_assoc(`_+_`, x, y, z);
}
/**
method_id | in | out
@testcase | 0 | 2 3 9 | -1
@testcase | 0 | 11 22 44 | -1
@testcase | 0 | -1 -10 -20 | -1
*/

View file

@ -0,0 +1,245 @@
// Here we test "functions that just wrap other functions" (camelCase in particular):
// > builder beginCell() { return begin_cell(); }
// Such functions, when called, are explicitly inlined during code generation (even without `inline` modifier).
// It means, that `beginCell()` is replaced to `begin_cell()` (and effectively to `NEWC`).
// Moreover, body of `beginCell` is NOT codegenerated at all.
// Hence, we can write camelCase wrappers (as well as more intelligible namings around stdlib functions)
// without affecting performance and even bytecode hashes.
// This works with ~functions also. And even works with wrappers of wrappers.
// Moreover, such wrappers can reorder input parameters, see a separate test camel2.tolk.
fun myBeginCell(): builder { return begin_cell(); }
fun myEndCell(b: builder): cell { return end_cell(b); }
fun myStoreRef(b: builder, c: cell): builder { return store_ref(b, c); }
fun myStoreUint(b: builder, i: int, bw: int): builder { return store_uint(b, i, bw); }
// 'inline' is not needed actually, but if it exists, it's just ignored
@inline
@pure
fun myBeginParse(c: cell): slice { return begin_parse(c); }
@inline
@pure
fun mySkipBits(s: slice, len: int): slice { return skip_bits(s, len); }
@inline
@pure
fun ~mySkipBits(s: slice, len: int): (slice, ()) { return ~skip_bits(s, len); }
@inline
@pure
fun ~myLoadUint(s: slice, len: int): (slice, int) { return load_uint(s, len); }
fun myComputeDataSize(c: cell, maxCells: int): (int, int, int) { return compute_data_size(c, maxCells); }
fun dict__new(): cell { return new_dict(); }
fun dict__iset(dict: cell, keyLen: int, index: int, value: slice): cell { return idict_set(dict, keyLen, index, value); }
fun ~dict__iset(dict: cell, keyLen: int, index: int, value: slice): (cell, ()) { return ~idict_set(dict, keyLen, index, value); }
fun dict__tryIGet(dict: cell, keyLen: int, index: int): (slice, int) { return idict_get?(dict, keyLen, index); }
fun dict__tryIGetMin(dict: cell, keyLen: int): (int, slice, int) { return idict_get_min?(dict, keyLen); }
fun myEmptyTuple(): tuple { return empty_tuple(); }
fun emptyTuple1(): tuple { return myEmptyTuple(); }
fun emptyTuple11(): tuple { return emptyTuple1(); }
fun myTuplePush<X>(t: tuple, value: X): tuple { return tpush(t, value); }
fun ~myTuplePush<X>(t: tuple, value: X): (tuple, ()) { return ~tpush(t, value); }
fun myTupleAt<X>(t: tuple, index: int): X { return at(t, index); }
fun tripleSecond<X1, Y2, Z3>(p: [X1, Y2, Z3]): Y2 { return triple_second(p); }
@pure
fun nullValue<X>(): X
asm "PUSHNULL";
fun initial1(x: tuple): tuple { return x; }
fun initial2(x: tuple): tuple { return initial1(x); }
// int add(int x, int y) { return x + y; } // this is also a wrapper, as its body is _+_(x,y)
fun fake1(a: int, b: int, c: int): void
asm(a b c) "DROP DROP DROP";
fun fake2(a: int, b: int, c: int): void
asm(b c a) "DROP DROP DROP";
fun fake3(a: int, b: int, c: int): ()
asm(c a b) "DROP DROP DROP";
fun fake4(a: int, b: int, c: int): ()
asm(c b a) "DROP DROP DROP";
fun fake1Wrapper(a: int, b: int, c: int) { return fake1(a, b, c); }
fun fake2Wrapper(a: int, b: int, c: int) { return fake2(a, b, c); }
fun fake3Wrapper(a: int, b: int, c: int) { return fake3(a, b, c); }
fun fake4Wrapper(a: int, b: int, c: int) { return fake4(a, b, c); }
@method_id(101)
fun test1(): [int, int, int] {
var x: int = 1;
var y: int = 1;
var to_be_ref: cell = myBeginCell().myEndCell();
var in_c: builder = myBeginCell().myStoreUint(123, 8);
in_c = myStoreRef(in_c, to_be_ref);
var (a, b, c) = myComputeDataSize(in_c.myEndCell(), 10);
assert(!(b != 8)) throw 101;
assert(!(c != 1), 101);
return [a, b + x, c + y];
}
@method_id(102)
fun test2(): [[int, int, int], int, int, int] {
var dict: cell = dict__new();
dict = dict__iset(dict, 32, 456, myBeginCell().myStoreUint(4560, 32).myEndCell().myBeginParse());
dict.dict__iset(32, 789, myBeginCell().myStoreUint(7890, 32).myEndCell().myBeginParse());
dict~dict__iset(32, 123, myBeginCell().myStoreUint(0, 64).myStoreUint(1230, 32).myStoreUint(1231, 32).myStoreUint(1232, 32).myEndCell().myBeginParse());
var (mink, minv, _) = dict__tryIGetMin(dict, 32);
// skip 64 bits
minv~mySkipBits(16);
minv = minv.mySkipBits(16);
minv.mySkipBits(11); // does nothing
(minv, _) = ~mySkipBits(minv, 16);
mySkipBits(minv, 11); // does nothing
minv~mySkipBits(16);
// load 3*32
var minv1 = minv~myLoadUint(32);
var minv2 = minv~myLoadUint(32);
var minv3 = minv~myLoadUint(32);
var (_, found123) = dict__tryIGet(dict, 32, 123);
var (_, found456) = dict__tryIGet(dict, 32, 456);
var (_, found789) = dict__tryIGet(dict, 32, 789);
return [[minv1, minv2, minv3], found123, found456, found789];
}
@method_id(103)
fun test3(): tuple {
var with34: tuple = initial2(emptyTuple1());
with34~myTuplePush(34);
var t: tuple = emptyTuple11();
t = myTuplePush(t, 12);
myTuplePush(t, emptyTuple11()); // does nothing
t~myTuplePush(emptyTuple1());
t~myTuplePush(with34.myTupleAt(0));
t.myTuplePush("123"s); // does nothing
var tri: [cell, int, cell] = [nullValue(), 90 + 1, null];
var f: int = tripleSecond(tri);
(t, _) = ~myTuplePush(t, f);
return t;
}
@method_id(104)
fun test4(a: int, b: int, c: int): int {
fake1Wrapper(a, b, c);
fake2Wrapper(a, b, c);
fake3Wrapper(a, b, c);
fake4Wrapper(a, b, c);
return 10;
}
fun main(): int {
var x: int = now();
return 30;
}
/**
method_id | in | out
@testcase | 101 | | [ 2 9 2 ]
@testcase | 102 | | [ [ 1230 1231 1232 ] -1 -1 0 ]
@testcase | 103 | | [ 12 [] 34 91 ]
@fif_codegen
"""
main PROC:<{
//
30 PUSHINT
}>
"""
@fif_codegen
"""
test1 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _9=123
SWAP // to_be_ref _9=123 _8
8 STU // to_be_ref in_c
STREF // in_c
ENDC // _16
10 PUSHINT // _16 _17=10
CDATASIZE // a b c
OVER // a b c b
8 NEQINT // a b c _21
101 THROWIF
DUP // a b c c
1 NEQINT // a b c _26
101 THROWIF
SWAP // a c b
INC // a c _30
SWAP // a _30 c
INC // a _30 _31
TRIPLE // _29
}>
"""
@fif_codegen
"""
test2 PROC:<{
...
16 PUSHINT // dict minv _45=16
SDSKIPFIRST // dict minv
16 PUSHINT // dict minv _47=16
SDSKIPFIRST // dict minv
16 PUSHINT // dict minv _52=16
SDSKIPFIRST // dict minv
16 PUSHINT // dict minv _57=16
SDSKIPFIRST // dict minv
...
32 PUSHINT // dict minv1 minv2 minv3 found123 found456 _83=32
789 PUSHINT // dict minv1 minv2 minv3 found123 found456 _83=32 _84=789
s0 s7 s7 XCHG3 // found456 minv1 minv2 minv3 found123 _84=789 dict _83=32
DICTIGET
NULLSWAPIFNOT // found456 minv1 minv2 minv3 found123 _101 _102
NIP // found456 minv1 minv2 minv3 found123 found789
...
4 TUPLE // _86
}>
"""
@fif_codegen
"""
test3 PROC:<{
//
NIL // _1
initial1 CALLDICT // with34
...
TRIPLE // t tri
SECOND // t f
TPUSH // t
}>
"""
@fif_codegen
"""
test4 PROC:<{
// a b c
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s1 s0 s2 PUSH3 // a b c b c a
DROP DROP DROP
s0 s2 s1 PUSH3 // a b c c a b
DROP DROP DROP
s0 s2 XCHG // c b a
DROP DROP DROP
10 PUSHINT // _7=10
}>
"""
@fif_codegen_avoid DECLPROC myBeginCell
@fif_codegen_avoid DECLPROC myStoreUint
@fif_codegen_avoid DECLPROC myStoreRef
@fif_codegen_avoid DECLPROC myComputeDataSize
@fif_codegen_avoid DECLPROC tryIdictGet
@fif_codegen_avoid DECLPROC myEmptyTuple
@fif_codegen_avoid DECLPROC myStoreUint
@fif_codegen_avoid DECLPROC initial2
@fif_codegen_avoid DECLPROC add
@fif_codegen_avoid DECLPROC increase
*/

View file

@ -0,0 +1,204 @@
// Here we also test "functions that just wrap other functions" like in camel1.tolk,
// but when they reorder arguments, e.g.
// > T f(x,y) { return anotherF(y,x); }
// This also works, even for wrappers of wrappers, even if anotherF is asm(with reorder).
// But swapping arguments may sometimes lead to bytecode changes (see test2),
// both with compute-asm-ltr and without it.
fun myBeginCell(): builder { return begin_cell(); }
fun myEndCell(b: builder): cell { return end_cell(b); }
fun myStoreRef1(b: builder, c: cell): builder { return store_ref(b, c); }
fun myStoreRef2(c: cell, b: builder): builder { return store_ref(b, c); }
fun myStoreUint1(b: builder, x: int, bw: int): builder { return store_uint(b, x, bw); }
fun myStoreUint2(b: builder, bw: int, x: int): builder { return store_uint(b, x, bw); }
fun computeDataSize1(c: cell, maxCells: int): (int, int, int) { return compute_data_size(c, maxCells); }
fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return compute_data_size(c, maxCells); }
fun fake(a: int, b: int, c: int): void
asm "DROP DROP DROP";
fun fake2(b: int, c: int, a: int) { return fake(a,b,c); }
fun fake3(c: int, a: int, b: int) { return fake(a,b,c); }
fun fake4(c: int, b: int, a: int) { return fake(a,b,c); }
@method_id(101)
fun test1(): (int, int, int) {
var x: int = 1;
var y: int = 1;
var to_be_ref: cell = myBeginCell().myEndCell();
var in_c: builder = myBeginCell().myStoreUint1(123, 8);
in_c = myStoreRef1(in_c, to_be_ref);
var (a, b, c) = computeDataSize1(in_c.myEndCell(), 10);
assert(!0, 101);
return (a, b + x, c + y);
}
@method_id(102)
fun test2(): (int, int, int) {
var x: int = 1;
var y: int = 1;
var to_be_ref: cell = myBeginCell().myEndCell();
var in_c: builder = myBeginCell().myStoreUint2(8, 123);
in_c = myStoreRef2(to_be_ref, in_c);
var (a, b, c) = computeDataSize2(10, in_c.myEndCell());
return (a, b + x, c + y);
}
@method_id(103)
fun test3(): (int, int, int) {
var x: int = 1;
var y: int = 1;
var to_be_ref: cell = begin_cell().end_cell();
var in_c: builder = begin_cell().store_uint(123, 8);
in_c = store_ref(in_c, to_be_ref);
var (a, b, c) = compute_data_size(in_c.end_cell(), 10);
return (a, b + x, c + y);
}
fun beginCell1(): builder { return begin_cell(); }
fun beginCell11(): builder { return beginCell1(); }
fun beginCell111(): builder { return beginCell11(); }
fun endCell1(b: builder): cell { return end_cell(b); }
fun endCell11(b: builder): cell { return endCell1(b); }
fun beginParse1(c: cell): slice { return begin_parse(c); }
fun beginParse11(c: cell): slice { return beginParse1(c); }
fun storeInt1(b: builder, bw: int, x: int): builder { return store_int(b, x, bw); }
fun storeInt11(bw: int, x: int, b: builder): builder { return storeInt1(b, bw, x); }
fun storeInt111(b: builder, x: int, bw: int): builder { return storeInt11(bw, x, b); }
@method_id(104)
fun test4(): slice {
var b: builder = beginCell111();
b = storeInt11(32, 1, b);
b = storeInt111(b, 2, 32).storeInt111(3, 32);
return b.endCell11().beginParse11();
}
@method_id(105)
fun test5(a: int, b: int, c: int): int {
fake(a, b, c);
fake2(b, c, a);
fake3(c, a, b);
fake4(c, b, a);
return a;
}
fun main() {
throw 0;
}
/**
method_id | in | out
@testcase | 101 | | 2 9 2
@testcase | 102 | | 2 9 2
@testcase | 103 | | 2 9 2
@testcase | 104 | | CS{Cell{0018000000010000000200000003} bits: 0..96; refs: 0..0}
test1 and test3 fif code is absolutely identical, test2 (due to reorder) is a bit different:
@fif_codegen
"""
test1 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _9=123
SWAP // to_be_ref _9=123 _8
8 STU // to_be_ref in_c
STREF // in_c
ENDC // _16
10 PUSHINT // _16 _17=10
CDATASIZE // a b c
SWAP // a c b
INC // a c _23
SWAP // a _23 c
INC // a _23 _24
}>
"""
@fif_codegen
"""
test2 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _10=123
SWAP // to_be_ref _10=123 _8
8 STU // to_be_ref in_c
STREF // in_c
10 PUSHINT
SWAP
ENDC
SWAP
CDATASIZE // a b c
SWAP // a c b
INC // a c _19
SWAP // a _19 c
INC // a _19 _20
}>
"""
@fif_codegen
"""
test3 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _9=123
SWAP // to_be_ref _9=123 _8
8 STU // to_be_ref in_c
STREF // in_c
ENDC // _16
10 PUSHINT // _16 _17=10
CDATASIZE // a b c
SWAP // a c b
INC // a c _19
SWAP // a _19 c
INC // a _19 _20
}>
"""
@fif_codegen
"""
test4 PROC:<{
//
NEWC // b
1 PUSHINT // b _3=1
SWAP // _3=1 b
32 STI // b
2 PUSHINT
SWAP // _5=2 b
32 STI
3 PUSHINT
SWAP
32 STI // b
ENDC // _11
CTOS // _12
}>
"""
@fif_codegen
"""
test5 PROC:<{
// a b c
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s2 PUSH
-ROT // a a b c
DROP DROP DROP
}>
"""
@fif_codegen_avoid myStoreUint1
@fif_codegen_avoid myStoreUint2
*/

View file

@ -0,0 +1,95 @@
// Here we test that if you declare a wrapper like
// > builder beginCell() { return begin_cell(); }
// but use it NOT only as a direct call, BUT as a 1-st class function
// (save to a variable, return from a function, etc.)
// it also works, since a function becomes codegenerated (though direct calls are expectedly inlined).
fun myBeginCell(): builder { return begin_cell(); }
fun myEndCell(b: builder): cell { return end_cell(b); }
fun myStoreRef(b: builder, c: cell): builder { return store_ref(b, c); }
fun myStoreUint3(i: int, bw: int, b: builder): builder { return store_uint(b, i, bw); }
fun computeDataSize2(maxCells: int, c: cell): (int, int, int) { return compute_data_size(c, maxCells); }
fun myEmptyTuple(): tuple { return empty_tuple(); }
fun myTuplePush<X>(t: tuple, value: X): tuple { return tpush(t, value); }
fun ~myTuplePush<X>(t: tuple, value: X): (tuple, ()) { return ~tpush(t, value); }
fun tupleGetFirst<X>(t: tuple): X { return first(t); }
@inline
fun getBeginEnd(): (auto, auto) {
return (myBeginCell, myEndCell);
}
fun begAndStore(beg: auto, store: auto, x: int): builder {
return store(x, 8, beg());
}
fun test1(): (int, int, int) {
var (_, computer) = (0, computeDataSize2);
var (beg, end) = getBeginEnd();
var t: tuple = myEmptyTuple();
t~myTuplePush(myStoreRef);
var refStorer = tupleGetFirst(t);
var x: int = 1;
var y: int = 1;
var to_be_ref: cell = myBeginCell().myEndCell();
var in_c: builder = begAndStore(beg, myStoreUint3, 123);
in_c = refStorer(in_c, to_be_ref);
var (a, b, c) = computer(10, end(in_c));
return (a, b + x, c + y);
}
fun main(): (int, int, int) {
return test1();
}
/**
method_id | in | out
@testcase | 0 | | 2 9 2
@fif_codegen DECLPROC myBeginCell
@fif_codegen DECLPROC computeDataSize2
@fif_codegen
"""
myStoreUint3 PROC:<{
// i bw b
SWAP // i b bw
STUX // _3
}>
"""
@fif_codegen
"""
myStoreRef PROC:<{
// b c
SWAP // c b
STREF // _2
}>
"""
@fif_codegen
"""
CONT:<{
computeDataSize2 CALLDICT
}> // computer
getBeginEnd INLINECALLDICT // computer beg end
NIL // computer beg end t
...
NEWC // computer beg end refStorer _19
ENDC // computer beg end refStorer to_be_ref
...
CONT:<{
myStoreUint3 CALLDICT
}>
...
begAndStore CALLDICT // computer to_be_ref end refStorer in_c
"""
@fif_codegen_avoid myEmptyTuple
@fif_codegen_avoid myTuplePush
*/

View file

@ -0,0 +1,145 @@
// Here we test that a just-return function is not a valid wrapper, it will not be inlined.
// (doesn't use all arguments, has different pureness, has method_id, etc.)
fun myStoreUint(b: builder, x: int, unused: int): builder { return store_uint(b, x, x); }
fun throwIf(excNo: int, cond: int) { assert(!cond) throw excNo; }
fun initial1(x: auto) { return x; }
fun initial2(x: auto) { return initial1(x); }
@pure
fun asm_func_4(a: int, b: (int, (int, int)), c: int): tuple
asm (b a c -> 0) "5 TUPLE";
fun asmFunc4(a: int, b: (int, (int, int)), c: int): tuple { return asm_func_4(a, b, c); }
fun postpone_elections(): int {
return false;
}
fun setAndGetData(ret: int): int {
var c: cell = begin_cell().store_uint(ret, 8).end_cell();
set_data(c);
var s: slice = get_data().begin_parse();
throwIf(101, 0);
return s~load_uint(8);
}
fun setAndGetDataWrapper(ret: int): int {
return setAndGetData(ret);
}
@method_id(101)
fun test1(): int {
var c: cell = begin_cell().myStoreUint(32, 10000000).end_cell();
var s: slice = c.begin_parse();
return s~load_uint(32);
}
get fun test2(ret: int): int {
return setAndGetDataWrapper(ret);
}
@method_id(103)
fun test3(): int {
return initial2(10);
}
global t: tuple;
fun foo(x: int): int {
t~tpush(x);
return x * 10;
}
@method_id(104)
fun test4(): (tuple, tuple) {
t = empty_tuple();
var t2: tuple = asmFunc4(foo(11), (foo(22), (foo(33), foo(44))), foo(55));
return (t, t2);
}
@method_id(105)
fun test5(): int {
if (1) {
return postpone_elections();
}
return 123;
}
@method_id(106)
fun test6(): int {
return add2(1, 2); // doesn't inline since declared below
}
fun main(ret: int): int {
return setAndGetDataWrapper(ret);
}
fun onExternalMessage(ret: int): int {
return setAndGetData(ret);
}
// currently, functions implemented after usage, can't be inlined, since inlining is legacy, not AST
fun add2(x: int, y: int): int { return x + y; }
/**
method_id | in | out
@testcase | 101 | | 32
@testcase | 103 | | 10
@testcase | 104 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ]
@testcase | 105 | | 0
@testcase | 106 | | 3
@testcase | 74435 | 99 | 99
@testcase | 0 | 98 | 98
@testcase | -1 | 97 | 97
@fif_codegen DECLPROC myStoreUint
@fif_codegen DECLPROC throwIf
@fif_codegen DECLPROC postpone_elections
@fif_codegen DECLPROC add2
@fif_codegen 74435 DECLMETHOD test2
@fif_codegen
"""
test3 PROC:<{
//
10 PUSHINT // _0=10
initial2 CALLDICT // _1
}>
"""
@fif_codegen
"""
test2 PROC:<{
// ret
setAndGetData CALLDICT // _1
}>
"""
@fif_codegen
"""
11 PUSHINT
foo CALLDICT
22 PUSHINT
foo CALLDICT
33 PUSHINT
foo CALLDICT
44 PUSHINT
foo CALLDICT
55 PUSHINT
foo CALLDICT
asmFunc4 CALLDICT // t2
"""
@fif_codegen
"""
test6 PROC:<{
//
1 PUSHINT // _0=1
2 PUSHINT // _0=1 _1=2
add2 CALLDICT // _2
}>
"""
@fif_codegen_avoid setAndGetDataWrapper
*/

View file

@ -0,0 +1,163 @@
fun store_u32(b: builder, value: int): builder {
return b.store_uint(value, 32);
}
fun ~store_u32(b: builder, value: int): (builder, ()) {
return ~store_uint(b, value, 32);
}
fun load_u32(cs: slice): (slice, int) {
return cs.load_uint(32);
}
fun my_load_int(s: slice, len: int): (slice, int)
asm(s len -> 1 0) "LDIX"; // top is "value slice"
fun my_store_int(b: builder, x: int, len: int): builder
asm(x b len) "STIX";
fun ~my_store_int(b: builder, x: int, len: int): (builder, ())
asm(x b len) "STIX";
@method_id(101)
fun test1(): [int,int,int,int,int] {
var b: builder = begin_cell().store_uint(1, 32);
b = b.store_uint(2, 32);
b~store_uint(3, 32);
b = b.store_u32(4);
b~store_u32(5);
var cs: slice = b.end_cell().begin_parse();
var (cs redef, one: int) = cs.load_uint(32);
var (two: int, three: int) = (cs~load_uint(32), cs~load_u32());
var (cs redef, four: int) = cs.load_u32();
var five: int = cs~load_u32();
return [one,two,three,four,five];
}
@method_id(102)
fun test2(): [int,int,int] {
var b: builder = begin_cell().my_store_int(1, 32);
b = b.my_store_int(2, 32);
b~my_store_int(3, 32);
var cs: slice = b.end_cell().begin_parse();
var (cs redef, one: int) = cs.my_load_int(32);
var (two: int, three: int) = (cs~my_load_int(32), cs~my_load_int(32));
return [one,two,three];
}
@method_id(103)
fun test3(ret: int): int {
var (_, same: int) = begin_cell().store_uint(ret,32).end_cell().begin_parse().load_uint(32);
return same;
}
@method_id(104)
fun test4(): [int,int] {
var b: builder = my_store_int(begin_cell(), 1, 32);
b = store_int(store_int(b, 2, 32), 3, 32);
var cs: slice = b.end_cell().begin_parse();
var cs32: slice = cs.first_bits(32); // todo s.first_bits()~load_uint() doesn't work, 'lvalue expected'
var (one, _, three) = (cs32~load_int(32), cs~skip_bits(64), cs~load_u32());
return [one,three];
}
@method_id(105)
fun test5(): [int,int] {
var cref: cell = end_cell(store_u32(begin_cell(), 105));
var c: cell = begin_cell().store_ref(cref).store_ref(cref).store_u32(1).end_cell();
var cs: slice = begin_parse(c);
// todo I want cs~load_ref().begin_parse()~load_u32(), but 'lvalue expected'
var ref1 = cs~load_ref().begin_parse();
var ref2 = cs~load_ref().begin_parse();
var sto5x2: int = ref1~load_u32() + ref2~load_uint(32);
return [sto5x2, cs~load_u32()];
}
fun ~sumNumbersInSlice(s: slice): (slice, int) {
var result = 0;
while (!slice_data_empty?(s)) {
result += s~load_uint(32);
}
return (s, result);
}
@method_id(106)
fun test6() {
var ref = begin_cell().store_int(100, 32).end_cell();
var s: slice = begin_cell().store_int(1, 32).store_int(2, 32).store_ref(ref).end_cell().begin_parse();
var result = (slice_bits(s), s~sumNumbersInSlice(), slice_bits(s), slice_empty?(s), slice_data_empty?(s), slice_refs_empty?(s));
var ref2: cell = s~load_ref();
var s2: slice = ref2.begin_parse();
s.end_parse();
return (result, s2~load_int(32), s2.slice_empty?());
}
@method_id(107)
fun test7() {
var s: slice = begin_cell().store_int(1, 32).store_int(2, 32).store_int(3, 32).store_int(4, 32).store_int(5, 32).store_int(6, 32).store_int(7, 32).end_cell().begin_parse();
var size1 = slice_bits(s);
s~skip_bits(32);
var s1: slice = s.first_bits(64);
var n1 = s1~load_int(32);
var size2 = slice_bits(s);
s~load_int(32);
var size3 = slice_bits(s);
s~skip_last_bits(32);
var size4 = slice_bits(s);
var n2 = s~load_int(32);
var size5 = slice_bits(s);
return (n1, n2, size1, size2, size3, size4, size5);
}
@method_id(108)
fun test108() {
var (result1, result2) = (0, 0);
try {
begin_cell().store_ref(begin_cell().end_cell()).end_cell().begin_parse().end_parse();
result1 = 100;
} catch (code) {
result1 = code;
}
try {
begin_cell().end_cell().begin_parse().end_parse();
result2 = 100;
} catch (code) {
result2 = code;
}
return (result1, result2);
}
@method_id(109)
fun test109() {
var ref2 = begin_cell().store_int(1, 32).end_cell();
var ref1 = begin_cell().store_int(1, 32).store_ref(ref2).end_cell();
var c = begin_cell().store_int(444, 32).store_ref(ref1).store_ref(ref1).store_ref(ref1).store_ref(ref2).store_int(4, 32).end_cell();
var (n_cells1, n_bits1, n_refs1) = c.compute_data_size(10);
var s = c.begin_parse();
s~load_ref();
s~load_ref();
var n = s~load_int(32);
var (n_cells2, n_bits2, n_refs2) = s.slice_compute_data_size(10);
return ([n_cells1, n_bits1, n_refs1], [n_cells2, n_bits2, n_refs2], n);
}
fun main(): int {
return 0;
}
/**
@testcase | 101 | | [ 1 2 3 4 5 ]
@testcase | 102 | | [ 1 2 3 ]
@testcase | 103 | 103 | 103
@testcase | 104 | | [ 1 3 ]
@testcase | 105 | | [ 210 1 ]
@testcase | 106 | | 64 3 0 0 -1 0 100 -1
@testcase | 107 | | 2 3 224 192 160 128 96
@testcase | 108 | | 9 100
@testcase | 109 | | [ 3 128 5 ] [ 2 96 3 ] 444
*/

View file

@ -0,0 +1,75 @@
const int1 = 1;
const int2 = 2;
const int101: int = 101;
const int111: int = 111;
const int1r = int1;
const str1 = "const1";
const str2 = "aabbcc"s;
const str2r: slice = str2;
const str1int = 0x636f6e737431;
const str2int = 0xAABBCC;
const nibbles: int = 4;
fun iget1(): int { return int1; }
fun iget2(): int { return int2; }
fun iget3(): int { return int1+int2; }
fun iget1r(): int { return int1r; }
fun sget1(): slice { return str1; }
fun sget2(): slice { return str2; }
fun sget2r(): slice { return str2r; }
const int240: int = ((int1+int2)*10)<<3;
fun iget240(): int { return int240; }
@pure
fun newc(): builder
asm "NEWC";
@pure
fun endcs(b: builder): slice
asm "ENDC" "CTOS";
@pure
fun sdeq(s1: slice, s2: slice): int
asm "SDEQ";
@pure
fun stslicer(b: builder, s: slice): builder
asm "STSLICER";
fun storeUint(b: builder, x: int, len: int): builder { return store_uint(b, x, len); }
fun endSlice(b: builder): slice { return endcs(b); }
fun main() {
var i1: int = iget1();
var i2: int = iget2();
var i3: int = iget3();
assert(i1 == 1) throw int101;
assert(i2 == 2) throw 102;
assert(i3 == 3) throw 103;
var s1: slice = sget1();
var s2: slice = sget2();
var s3: slice = newc().stslicer(str1).stslicer(str2r).endcs();
assert(sdeq(s1, newc().storeUint(str1int, 12 * nibbles).endcs())) throw int111;
assert(sdeq(s2, newc().store_uint(str2int, 6 * nibbles).endSlice())) throw 112;
assert(sdeq(s3, newc().store_uint(0x636f6e737431AABBCC, 18 * nibbles).endcs())) throw 113;
var i4: int = iget240();
assert(i4 == 240) throw ((104));
return 0;
}
/**
@testcase | 0 | | 0
@code_hash 61273295789179921867241079778489100375537711211918844448475493726205774530743
*/

View file

@ -0,0 +1,41 @@
fun elseif(cond: int) {
if (cond > 0) {
throw(cond);
}
}
@inline
@method_id(101)
fun foo(x: int): int {
if (x==1) {
return 111;
} else {
x *= 2;
}
return x + 1;
}
fun main(x: int): (int, int) {
return (foo(x), 222);
}
@method_id(102)
fun test2(x: int) {
try {
if (x < 0) { return -1; }
elseif (x);
} catch(excNo) {
return excNo * 1000;
}
return 0;
}
/**
method_id | in | out
@testcase | 0 | 1 | 111 222
@testcase | 0 | 3 | 7 222
@testcase | 101 | 1 | 111
@testcase | 101 | 3 | 7
@testcase | 102 | -5 | -1
@testcase | 102 | 5 | 5000
*/

View file

@ -0,0 +1,96 @@
@method_id(101)
fun test1(): int {
var x = false;
if (x == true) {
x= 100500;
}
return x;
}
fun main(s: int) {
var (z, t) = (17, s);
while (z > 0) {
t = s;
z -= 1;
}
return ~ t;
}
/**
method_id | in | out
@testcase | 0 | 1 | -2
@testcase | 0 | 5 | -6
@testcase | 101 | | 0
Below, I just give examples of @fif_codegen tag:
* a pattern can be single-line (after the tag), or multi-line, surrounded with """
* there may be multiple @fif_codegen, they all will be checked
* identation (spaces) is not checked intentionally
* "..." means any number of any lines
* lines not divided with "..." are expected to be consecutive in fif output
* //comments can be omitted, but if present, they are also expected to be equal
* there is also a tag @fif_codegen_avoid to check a pattern does not occur
@fif_codegen
"""
main PROC:<{
// s
17 PUSHINT // s _3=17
OVER // s z=17 t
WHILE:<{
...
}>DO<{ // s z t
...
s1 s(-1) PUXC // s t z
...
2 1 BLKDROP2
...
}>
"""
@fif_codegen
"""
main PROC:<{
...
WHILE:<{
...
}>DO<{
...
}>
}END>c
"""
@fif_codegen
"""
OVER
0 GTINT // s z t _5
"""
@fif_codegen
"""
"Asm.fif" include
...
PROGRAM{
...
}END>c
"""
@fif_codegen
"""
test1 PROC:<{
//
FALSE
}>
"""
@fif_codegen NOT // _8
@fif_codegen main PROC:<{
@fif_codegen_avoid PROCINLINE
@fif_codegen_avoid END c
@fif_codegen_avoid
"""
multiline
can also be
"""
*/

View file

@ -0,0 +1,31 @@
fun main(): int
// inside a comment, /* doesn't start a new one
/* but if // is inside, a comment may end at this line*/ {
var cc = "a string may contain /* or // or /*, not parsed";
// return 1;
return get10() + /*
traditional comment /* may not be nested
// line comment
// ends */1 +
1;
/* moreover, different comment styles
may be used for opening and closing
*/
}
/***
first line
//two-lined*/
@method_id(10)
fun get10(): int {
return 10;
}
/**
@testcase | 0 | | 12
@testcase | 10 | | 10
*/

View file

@ -0,0 +1,66 @@
@method_id(101)
fun test1(x: int): int {
if (x > 200) {
return 200;
} else if (x > 100) {
return 100;
} else if (!(x <= 50)) {
if (!(x > 90)) {
return x;
} else {
return 90;
}
} else {
return 0;
}
}
@method_id(102)
fun test2(x: int) {
if (x == 20) { return 20; }
if (x != 50) { return 50; }
if (x == 0) { return 0; }
return -1;
}
@method_id(103)
fun test3(x: int) {
if (!(x != 20)) { return 20; }
if (!(x == 50)) { return 50; }
if (!x) { return 0; }
return -1;
}
fun main() {
}
/**
@testcase | 101 | 0 | 0
@testcase | 101 | 1000 | 200
@testcase | 101 | 150 | 100
@testcase | 101 | -1 | 0
@testcase | 101 | 87 | 87
@testcase | 101 | 94 | 90
@testcase | 102 | 20 | 20
@testcase | 102 | 40 | 50
@testcase | 102 | 50 | -1
@testcase | 103 | 20 | 20
@testcase | 103 | 40 | 50
@testcase | 103 | 50 | -1
@fif_codegen
"""
test3 PROC:<{
// x
DUP // x x
20 NEQINT // x _2
IFNOTJMP:<{ // x
DROP //
20 PUSHINT // _3=20
}> // x
DUP // x x
50 EQINT // x _5
IFNOTJMP:<{ // x
"""
*/

View file

@ -0,0 +1,4 @@
fun demoOfInvalid(): (int) {
var f = someAdd;
return f(1, 2);
}

View file

@ -0,0 +1,3 @@
fun someAdd(a: int, b: int): int {
return a + b + 0;
}

View file

@ -0,0 +1,62 @@
@inline
fun foo(x: int): int {
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
x = x * 10 + 1;
return x;
}
fun main(x: int): int {
return foo(x) * 10 + 5;
}
/**
method_id | in | out
@testcase | 0 | 9 | 9111111111111111111111111111111111111111111111111115
*/

View file

@ -0,0 +1,28 @@
fun foo1(x: int): int {
if (x == 1) {
return 1;
}
return 2;
}
@inline
fun foo2(x: int): int {
if (x == 1) {
return 11;
}
return 22;
}
@inline_ref
fun foo3(x: int): int {
if (x == 1) {
return 111;
}
return 222;
}
fun main(x: int): (int, int, int) {
return (foo1(x)+1, foo2(x)+1, foo3(x)+1);
}
/**
method_id | in | out
@testcase | 0 | 1 | 2 12 112
@testcase | 0 | 2 | 3 23 223
*/

View file

@ -0,0 +1,48 @@
global g: int;
@inline
fun foo_repeat() {
g = 1;
repeat(5) {
g *= 2;
}
}
@inline
fun foo_until(): int {
g = 1;
var i: int = 0;
do {
g *= 2;
i += 1;
} while (i < 8);
return i;
}
@inline
fun foo_while(): int {
g = 1;
var i: int = 0;
while (i < 10) {
g *= 2;
i += 1;
}
return i;
}
fun main() {
foo_repeat();
var x: int = g;
foo_until();
var y: int = g;
foo_while();
var z: int = g;
return (x, y, z);
}
/**
method_id | in | out
@testcase | 0 | | 32 256 1024
@code_hash 102749806552989901976653997041637095139193406161777448419603700344770997608788
*/

View file

@ -0,0 +1,9 @@
fun main(flags: int): int {
return flags&0xFF!=0;
}
/**
@compilation_should_fail
@stderr & has lower precedence than !=
@stderr Use parenthesis
*/

View file

@ -0,0 +1,8 @@
fun justTrue(): int { return true; }
const a = justTrue() | 1 < 9;
/**
@compilation_should_fail
@stderr | has lower precedence than <
*/

View file

@ -0,0 +1,8 @@
fun justTrue(): int { return true; }
const a = justTrue() | (1 < 9) | justTrue() != true;
/**
@compilation_should_fail
@stderr | has lower precedence than !=
*/

View file

@ -0,0 +1,6 @@
const a = (1) <=> (0) ^ 8;
/**
@compilation_should_fail
@stderr ^ has lower precedence than <=>
*/

View file

@ -0,0 +1,11 @@
const MAX_SLIPAGE = 100;
fun main(jetton_amount: int, msg_value: int, slippage: int) {
if ((0 == jetton_amount) | (msg_value == 0) | true | false | slippage > MAX_SLIPAGE) {
}
}
/**
@compilation_should_fail
@stderr | has lower precedence than >
*/

View file

@ -0,0 +1,9 @@
fun main() {
if ((1==1)|(2==2)&(3==3)) {
}
}
/**
@compilation_should_fail
@stderr mixing | with & without parenthesis
*/

View file

@ -0,0 +1,8 @@
fun main() {
var c = x && y || x && y;
}
/**
@compilation_should_fail
@stderr mixing && with || without parenthesis
*/

View file

@ -0,0 +1,10 @@
fun moddiv2(x: int, y: int): (int, int) builtin;
/**
@compilation_should_fail
@stderr
"""
`builtin` used for non-builtin function
fun moddiv2
"""
*/

View file

@ -0,0 +1,12 @@
fun main() {
try {
} catch(int, arg) {}
return 0;
}
/**
@compilation_should_fail
@stderr expected identifier, got `int`
@stderr catch(int
*/

View file

@ -0,0 +1,9 @@
fun main() {
try {}
catch(err, arg, more) {}
}
/**
@compilation_should_fail
@stderr expected `)`, got `,`
*/

View file

@ -0,0 +1,11 @@
/*
in tolk we decided to drop nested comments support
/*
not nested
*/
*/
/**
@compilation_should_fail
@stderr error: expected fun or get, got `*`
*/

View file

@ -0,0 +1,8 @@
fun main(): int {
;; this is not a comment
}
/**
@compilation_should_fail
@stderr error: expected `;`, got `is`
*/

View file

@ -0,0 +1,8 @@
const ONE = TWO - 1;
const TWO = ONE + 1;
/**
@compilation_should_fail
@stderr const ONE
@stderr undefined symbol `TWO`
*/

View file

@ -0,0 +1,6 @@
const a = 10, b = 20;
/**
@compilation_should_fail
@stderr multiple declarations are not allowed
*/

View file

@ -0,0 +1,8 @@
get fun onInternalMessage() {
return 0;
}
/**
@compilation_should_fail
@stderr invalid declaration of a reserved function
*/

View file

@ -0,0 +1,8 @@
fun main(int): int {
}
/**
@compilation_should_fail
@stderr expected parameter name, got `int`
*/

View file

@ -0,0 +1,8 @@
int main() {
}
/**
@compilation_should_fail
@stderr expected fun or get, got `int`
*/

View file

@ -0,0 +1,8 @@
fun main() {
int x = 0;
}
/**
@compilation_should_fail
@stderr probably, you use FunC-like declarations; valid syntax is `var x: int = ...`
*/

View file

@ -0,0 +1,6 @@
enum MyKind { }
/**
@compilation_should_fail
@stderr `enum` is not supported yet
*/

View file

@ -0,0 +1,8 @@
fun main() {
val imm = 10;
}
/**
@compilation_should_fail
@stderr immutable variables are not supported yet
*/

View file

@ -0,0 +1,8 @@
fun main() {
var a = 10, b = 20;
}
/**
@compilation_should_fail
@stderr multiple declarations are not allowed
*/

View file

@ -0,0 +1,8 @@
fun someDemo() {
return 0;
}
/**
@compilation_should_fail
@stderr the contract has no entrypoint
*/

View file

@ -0,0 +1,9 @@
fun recv_internal() {
return 0;
}
/**
@compilation_should_fail
@stderr this is a reserved FunC/Fift identifier
@stderr you need `onInternalMessage`
*/

View file

@ -0,0 +1,9 @@
@method_id(123)
get fun hello(x: int, y: int): (int, int) {
return (x, y);
}
/**
@compilation_should_fail
@stderr @method_id can be specified only for regular functions
*/

View file

@ -0,0 +1,17 @@
@pure
get fun secret(): int {
return 0;
}
@pure
get fun balanced(): int {
return 1;
}
fun main(): int {
return secret() + balanced();
}
/**
@compilation_should_fail
@stderr GET methods hash collision: `secret` and `balanced` produce the same hash
*/

View file

@ -0,0 +1,9 @@
// line1
/* */ import "unexisting.tolk";
// line3
/**
@compilation_should_fail
@stderr invalid-import.tolk:2:7: error: Failed to import: cannot find file
@stderr import "unexisting.tolk";
*/

View file

@ -0,0 +1,8 @@
fun main() {
return 1 && 2;
}
/**
@compilation_should_fail
@stderr logical operators are not supported yet
*/

View file

@ -0,0 +1,8 @@
import "imports/some-math.tolk";
import "imports/invalid-no-import.tolk";
/**
@compilation_should_fail
@stderr imports/invalid-no-import.tolk:2:13
@stderr Using a non-imported symbol `someAdd`
*/

View file

@ -0,0 +1,12 @@
fun eq(x: int): int {
return x;
}
fun main(x: int): int {
return eq x;
}
/**
@compilation_should_fail
@stderr expected `;`, got `x`
*/

View file

@ -0,0 +1,12 @@
fun main(x: int): int {
if x {
return 10;
}
return 0;
}
/**
@compilation_should_fail
@stderr expected `(`, got `x`
*/

View file

@ -0,0 +1,12 @@
fun main(x: int): int {
if (x, 1) {
return 10;
}
return 0;
}
/**
@compilation_should_fail
@stderr expected `)`, got `,`
*/

View file

@ -0,0 +1,8 @@
fun load_u32(cs: slice): (slice, int) {
return cs.load_uint 32;
}
/**
@compilation_should_fail
@stderr expected `(`, got `32`
*/

View file

@ -0,0 +1,20 @@
@pure
fun f_pure(): int {
return f_impure();
}
fun f_impure(): int {}
fun main(): int {
return f_pure();
}
/**
@compilation_should_fail
@stderr
"""
an impure operation in a pure function
return f_impure();
"""
*/

View file

@ -0,0 +1,23 @@
global g: int;
@pure
fun f_pure(): builder {
var b: builder = begin_cell();
g = g + 1;
return b;
}
fun main(): int {
g = 0;
f_pure();
return g;
}
/**
@compilation_should_fail
@stderr
"""
an impure operation in a pure function
g = g + 1;
"""
*/

View file

@ -0,0 +1,23 @@
@pure
fun validate_input(input: cell): (int, int) {
var (x, y, z, correct) = compute_data_size?(input, 10);
assert(correct) throw 102;
}
@pure
fun someF(): int {
var c: cell = begin_cell().end_cell();
validate_input(c);
return 0;
}
fun main() {}
/**
@compilation_should_fail
@stderr
"""
an impure operation in a pure function
assert(correct)
"""
*/

View file

@ -0,0 +1,7 @@
global moddiv: int;
/**
@compilation_should_fail
@stderr global moddiv: int;
@stderr redefinition of built-in symbol
*/

View file

@ -0,0 +1,12 @@
global hello: int;
fun hello(): int {
}
/**
@compilation_should_fail
@stderr fun hello()
@stderr redefinition of symbol, previous was at
@stderr invalid-redefinition-2.tolk:1:1
*/

View file

@ -0,0 +1,8 @@
fun main(): int {
var demo_10: int = demo_10;
}
/**
@compilation_should_fail
@stderr undefined symbol `demo_10`
*/

View file

@ -0,0 +1,9 @@
fun main(): int {
var (a: int, b: int) = (10, 20);
var (a, b: int) = (10, 20);
}
/**
@compilation_should_fail
@stderr redeclaration of local variable `a`
*/

View file

@ -0,0 +1,9 @@
fun main(x: int): int {
var (a: int, b: int) = (10, 20);
var (a redef, x: int) = (10, 20);
}
/**
@compilation_should_fail
@stderr redeclaration of local variable `x`
*/

View file

@ -0,0 +1,8 @@
fun main(flags: int) {
return flags << 1 + 32;
}
/**
@compilation_should_fail
@stderr << has lower precedence than +
*/

View file

@ -0,0 +1,14 @@
fun main(x: int): int {
if (x > 0) {
var y: int = 10;
} else {
var y: slice = "20";
}
~dump(y);
}
/**
@compilation_should_fail
@stderr ~dump(y);
@stderr undefined symbol `y`
*/

View file

@ -0,0 +1,12 @@
fun main(x: int): int {
try {
if (x > 10) { throw(44); }
} catch(code) {}
return code;
}
/**
@compilation_should_fail
@stderr return code;
@stderr undefined symbol `code`
*/

View file

@ -0,0 +1,15 @@
fun main(x: int): int {
if (x > 0) {
return 1;
}
// 'elseif' doesn't exist anymore, it's treated as 'someFunction(arg)'
elseif(x < 0) {
return -1;
}
return x;
}
/**
@compilation_should_fail
@stderr expected `;`, got `{`
*/

View file

@ -0,0 +1,13 @@
fun main(x: int) {
while (x > 0) {
if (x == 10) {
break;
}
x = x -1;
}
}
/**
@compilation_should_fail
@stderr break/continue from loops are not supported yet
*/

View file

@ -0,0 +1,8 @@
fun main(x: int) {
return null();
}
/**
@compilation_should_fail
@stderr null is not a function: use `null`, not `null()`
*/

View file

@ -0,0 +1,8 @@
fun main(x: int) {
assert(x > 0);
}
/**
@compilation_should_fail
@stderr expected `throw excNo` after assert, got `;`
*/

View file

@ -0,0 +1,7 @@
tolk asdf;
/**
@compilation_should_fail
@stderr semver expected
@stderr tolk asdf;
*/

View file

@ -0,0 +1,10 @@
fun main() {
var tri: [int, scli] = [10, null()];
return;
}
/**
@compilation_should_fail
@stderr .tolk:2
@stderr expected <type>, got `scli`
*/

View file

@ -0,0 +1,9 @@
fun main() {
var tri: (int, bool) = (10, false);
return;
}
/**
@compilation_should_fail
@stderr bool type is not supported yet
*/

View file

@ -0,0 +1,8 @@
fun main(s: auto) {
var (z, t) = ;
/**
@compilation_should_fail
@stderr expected <expression>, got `;`
@stderr var (z, t) = ;
*/

View file

@ -0,0 +1,154 @@
fun simpleAllConst() {
return (!0, !!0 & !false, !!!0, !1, !!1, !-1, !!-1, (!5 == 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);
}
@method_id(101)
fun withIfNot(x: int, y: int) {
if (!x) { return 10; }
else if (!y) { return 20; }
return x+y;
}
@method_id(102)
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 (!!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 return_at_end;
}
@method_id(103)
fun someSum(upto: int) {
var x = 0;
var should_break = false;
while (!x & !should_break) {
if (upto < 10) { x = upto; should_break = true; }
else { upto = upto - 1; }
}
return x;
}
fun lookupIdxByValue(idict32: cell, value: int) {
var cur_key = -1;
do {
var (cur_key redef, cs: slice, found: int) = idict32.idict_get_next?(32, cur_key);
// todo one-line condition (via &) doesn't work, since right side is calculated immediately
if (found) {
if (cs~load_int(32) == value) {
return cur_key;
}
}
} while (found);
return -1;
}
@method_id(104)
fun testDict(last: int) {
// prepare dict: [3 => 30, 4 => 40, 5 => 50]
var dict: cell = new_dict();
dict~idict_set_builder(32, 3, begin_cell().store_int(30, 32));
dict~idict_set_builder(32, 4, begin_cell().store_int(40, 32));
dict~idict_set_builder(32, 5, begin_cell().store_int(!last ? 100 : last, 32));
return (lookupIdxByValue(dict, 30), lookupIdxByValue(dict, last), lookupIdxByValue(dict, 100));
}
@method_id(105)
fun testNotNull(x: int) {
return [x == null, null == x, !(x == null), null == null, +(null != null)];
}
fun main() {
}
/**
@testcase | 101 | 0 0 | 10
@testcase | 101 | 5 0 | 20
@testcase | 101 | 5 8 | 13
@testcase | 102 | 0 0 0 | 10
@testcase | 102 | 0 0 5 | 20
@testcase | 102 | 1 2 0 | 40
@testcase | 102 | 11 2 0 | 50
@testcase | 102 | 1 0 0 | -1
@testcase | 102 | 0 1 0 | 0
@testcase | 102 | 1 0 1 | 1
@testcase | 103 | 15 | 9
@testcase | 103 | 6 | 6
@testcase | 103 | -1 | -1
@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 ]
@fif_codegen
"""
simpleAllConst PROC:<{
//
-1 PUSHINT
0 PUSHINT
-1 PUSHINT
0 PUSHINT
-1 PUSHINT
0 PUSHINT
-1 PUSHINT
TRUE
TRUE
}>
"""
@fif_codegen
"""
compileTimeEval1 PROC:<{
// x
DUP // x x
0 EQINT // x _1
FALSE // x _1 _4
TRUE // x _1 _4 _7
FALSE // x _1 _4 _7 _11
s0 s4 XCHG // _11 _1 _4 _7 x
0 EQINT // _11 _1 _4 _7 _12
-10 EQINT // _11 _1 _4 _7 _14
s3 s4 XCHG
s1 s3 s0 XCHG3 // _1 _4 _7 _11 _14
}>
"""
@fif_codegen
"""
withIfNot PROC:<{
c2 SAVE
SAMEALTSAVE // x y
OVER // x y x
IFNOTJMP:<{ // x y
2DROP //
10 PUSHINT // _2=10
}> // x y
DUP // x y y
IFNOTJMP:<{ // x y
2DROP //
20 PUSHINT // _3=20
RETALT
}> // x y
ADD // _4
}>
"""
*/

View file

@ -0,0 +1,15 @@
@method_id(1)
fun foo1(): int { return 111; }
@method_id(3)
fun foo2(): int { return 222; }
@method_id(10)
fun foo3(): int { return 333; }
fun main(): int { return 999; }
/**
method_id | in | out
@testcase | 1 | | 111
@testcase | 3 | | 222
@testcase | 10 | | 333
@testcase | 0 | | 999
*/

View file

@ -0,0 +1,117 @@
const int10:int=10;
fun just10(): int { return int10; }
fun eq(v: int): int { return`v`; }
@method_id(101) fun `get_-1` (): int {return-1;}
@method_id(102) fun `get_--1` (): int {return--1;}
@method_id(103) fun `get_---1`(): int {return---1;}
@method_id(104) fun `get_+++1`(): int {return+++1;}
@method_id(105) fun `get_+-+1`(): int {return+-+1;}
global `some()var`:int;
@method_id(110) fun `some_math`(): int {
`some()var`=--6;
return 1*-2*-3*-4*just10()*-5+-`some()var`+--`some()var`---`some()var`;
}
@method_id(111) fun `negative_nums`(a:int):int {
var m$0:int=1;
var m1:int=-(+0x1)*m$0;
return `a`*-1*-(1)*---(1)*+just10()+-`just10`()*m1*-m1+-eq(m1)----0x1;
}
@method_id(112) fun `bitwise~ops`(flags:int):[int,int] {
return[
(just10()-3==just10()-(4)--1)|((2==2)&(eq(eq(10)) -3==just10()--13)),
((flags&0xFF)!=0)
];
}
@method_id(113)fun`unary+bitwise-constant`():[int,int,int]{
// todo spaces are still not allowed before ~
return [~-~~+-3, ~+3-~ 9, -(-~+-20-~ 10+3+~ 38&39)];
}
@method_id(114)fun`unary+bitwize-parametrized`(c3:int, c9:int, c20:int, c10:int, c38:int):[int,int,int]{
// todo spaces are still not allowed before ~
return [~-~~+-c3, ~+c3-~ `c9`, -(-~+-c20-~ c10+c3+~ c38&39)];
}
fun add3(a: int, b: int, c: int) { return a+b+c; }
@method_id(115) fun unary_const_check(): [int,int] {
var fst1: int=-1;
var snd1: int=-1;
var trd1: int=+2;
var (fst2,snd2,trd2)=(-1,-1,+2);
return [add3(fst2,snd2,trd2),add3(fst1,snd1,trd1)];
}
fun `load:u32`(cs: slice): (slice, int) {
return cs.load_uint(32);
}
@method_id(116) fun `call_~_via_backticks`():[int,int,int,int] {
var b:builder = begin_cell().store_uint(1, 32).store_uint(2, 32).store_uint(3, 32).store_uint(4, 32);
var `cs`:slice = b.end_cell().begin_parse();
var (`cs` redef,one:int) = `cs`.`load_uint`(32);
var (two:int,three:int) = (`cs`~`load_uint`(32), cs~`load:u32`());
var (cs redef,four:int) = cs.`load:u32`();
return [one,two,three,four];
}
fun`main`(){}
/**
method_id | in | out
@testcase | 101 | | -1
@testcase | 102 | | 1
@testcase | 103 | | -1
@testcase | 104 | | 1
@testcase | 105 | | -1
@testcase | 110 | | 1194
@testcase | 111 | -1 | 22
@testcase | 112 | 0 | [ -1 0 ]
@testcase | 113 | | [ -4 6 -4 ]
@testcase | 114 | 3 9 20 10 38 | [ -4 6 -4 ]
@testcase | 115 | | [ 0 0 ]
@testcase | 116 | | [ 1 2 3 4 ]
@fif_codegen
"""
get_+-+1 PROC:<{
//
-1 PUSHINT
}>
"""
@fif_codegen
"""
unary+bitwise-constant PROC:<{
//
-4 PUSHINT
6 PUSHINT
-4 PUSHINT
TRIPLE
}>
"""
@fif_codegen
"""
unary_const_check PROC:<{
//
-1 PUSHINT // fst1=-1
DUP // fst1=-1 snd1=-1
2 PUSHINT // fst1=-1 snd1=-1 trd1=2
s1 s1 s0 PUSH3 // fst1=-1 snd1=-1 trd1=2 fst2=-1 snd2=-1 trd2=2
add3 CALLDICT // fst1=-1 snd1=-1 trd1=2 _13
3 -ROLL // _13 fst1=-1 snd1=-1 trd1=2
add3 CALLDICT // _13 _14
PAIR // _12
}>
"""
*/

View file

@ -0,0 +1,157 @@
import "../../crypto/smartcont/stdlib.tolk"
@method_id(101)
fun test1() {
var numbers: tuple = null;
numbers = cons(1, numbers);
numbers = cons(2, numbers);
numbers = cons(3, numbers);
numbers = cons(4, numbers);
var (h, numbers redef) = uncons(numbers);
h += car(numbers);
var t = empty_tuple();
do {
var num = numbers~list_next();
t~tpush(num);
} while (numbers != null);
return (h, numbers == null, t);
}
@method_id(102)
fun test2(x: int) {
if (null != x) {
var y: int = null;
if (y != null) { return 10; }
return y;
}
try {
return x + 10; // will throw, since not a number
} catch {
return -1;
}
return 100;
}
fun myIsNull(x: int): int {
return x == null ? -1 : x;
}
@method_id(103)
fun test3(x: int) {
return myIsNull(x > 10 ? null : x);
}
fun getUntypedNull() {
var untyped = null;
if (true) {
return untyped;
}
return untyped;
}
@method_id(104)
fun test4() {
var (_, (_, untyped)) = (3, (empty_tuple, null));
if (true) {
return untyped;
}
return untyped;
}
@method_id(105)
fun test5() {
var n = getUntypedNull();
return !(null == n) ? n~load_int(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 = begin_cell().store_maybe_ref(null);
var s = b.end_cell().begin_parse();
var c = s~load_maybe_ref();
return (null == c) * 10 + (b != null);
}
fun main() {
// now, the compiler doesn't optimize this at compile-time, fif codegen contains ifs
var i: int = null;
if (i == null) {
return 1;
}
return 10;
}
/**
@testcase | 101 | | 7 -1 [ 3 2 1 ]
@testcase | 102 | 5 | (null)
@testcase | 102 | null | -1
@testcase | 103 | 5 | 5
@testcase | 103 | 15 | -1
@testcase | 104 | | (null)
@testcase | 105 | | 100
@testcase | 107 | | -11
@fif_codegen
"""
test1 PROC:<{
//
PUSHNULL // numbers
1 PUSHINT // numbers _2=1
SWAP // _2=1 numbers
CONS // numbers
2 PUSHINT // numbers _4=2
SWAP // _4=2 numbers
CONS // numbers
3 PUSHINT // numbers _6=3
SWAP // _6=3 numbers
CONS // numbers
4 PUSHINT // numbers _8=4
SWAP // _8=4 numbers
CONS // numbers
UNCONS // h numbers
DUP // h numbers numbers
CAR // h numbers _12
"""
@fif_codegen
"""
main PROC:<{
//
PUSHNULL // i
ISNULL // _2
IFJMP:<{ //
1 PUSHINT // _3=1
}> //
10 PUSHINT // _4=10
}>
"""
@fif_codegen
"""
test6 PROC:<{
// x
PUSHNULL // x _1
GREATER // _2
}>
"""
@fif_codegen
"""
test7 PROC:<{
...
LDOPTREF // b _17 _16
DROP // b c
ISNULL // b _10
10 MULCONST // b _12
SWAP // _12 b
ISNULL // _12 _13
0 EQINT // _12 _14
ADD // _15
}>
"""
*/

View file

@ -0,0 +1,121 @@
fun justTrue(): int { 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;}
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 {
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));
}
@method_id(103)
fun test3(x: int, y: int, z: int): int {
if ((x < 0) | (y < 0)) {
return z < 0;
}
return (x > 0) & (y > 0);
}
@method_id(104)
fun test4(x: int, y: int, mode: int): int {
if (mode == 1) {
return (x == 10) | (y == 20);
} if (mode == 2) {
return (x == 10) | (y == 20);
} else {
return x == (10 | (y == 20));
}
}
@method_id(105)
fun test5(status: int): int {
return justTrue() & (status == 1) & ((justTrue() & status) == 1);
}
@method_id(106)
fun test6(a: int, b: int, c: int): int {
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;
return a;
}
@method_id(108)
fun test8(b: int): int {
var a = b == 3 ? 3 : b == 4 ? 4 : b = 5 ? 5 : 100;
return a;
}
fun `_<p`(a: auto, b: auto): int { 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)
];
}
/**
@testcase | 101 | 1 2 3 | -1
@testcase | 101 | 1 0 3 | 0
@testcase | 101 | 1 2 -1 | 0
@testcase | 102 | 1 0 0 | -1
@testcase | 103 | -1 -2 -3 | -1
@testcase | 103 | -1 -2 0 | 0
@testcase | 103 | 1 2 0 | -1
@testcase | 103 | 1 0 2 | 0
@testcase | 104 | 10 20 1 | -1
@testcase | 104 | 10 20 2 | -1
@testcase | 104 | 10 20 3 | 0
@testcase | 105 | 1 | -1
@testcase | 105 | 0 | 0
@testcase | 106 | 1 2 3 | -1
@testcase | 107 | 3 | 3
@testcase | 107 | 4 | 4
@testcase | 107 | 5 | 5
@testcase | 107 | 6 | 100
@testcase | 108 | 3 | 3
@testcase | 108 | 4 | 4
@testcase | 108 | 6 | 5
@fif_codegen
"""
unary_minus_1 PROC:<{
// a b c
-ROT // c a b
ADD // c _3
NEGATE // c _4
SWAP // _4 c
MUL // _5
}>
unary_minus_2 PROC:<{
// a b c
-ROT // c a b
ADD // c _3
NEGATE // c _4
SWAP // _4 c
MUL // _5
}>
unary_minus_3 PROC:<{
// a b c
-ROT // c a b
ADD // c _3
SWAP // _3 c
MUL // _4
NEGATE // _5
}>
"""
*/

View file

@ -0,0 +1,46 @@
@pure
fun f_pure1(): int {
return f_pure2();
}
@pure
fun f_pure2(): int {
return 2;
}
@pure
fun get_contract_data(): (int, int) {
var c: cell = get_data();
var cs: slice = c.begin_parse();
cs~load_bits(32);
var value: int = cs~load_uint(16);
return (1, value);
}
fun save_contract_data(value: int) {
var b: builder = begin_cell().store_int(1, 32).store_uint(value, 16);
set_data(b.end_cell());
}
@pure
@method_id(101)
fun test1(): int {
return f_pure1();
}
@method_id(102)
fun test2(value: int): int {
save_contract_data(value);
var (_, restored: auto) = get_contract_data();
return restored;
}
fun main() { return; }
/**
@testcase | 101 | | 2
@testcase | 102 | 44 | 44
*/

View file

@ -0,0 +1,48 @@
fun unused1(): int { return 2; }
fun unused2(): int { return unused1(); }
fun unused3(x: int): int { return x * 2+unused2(); }
fun used_from_noncall1(): int { return 10; }
fun used_as_noncall1(): int { return used_from_noncall1(); }
const int20: int = 20;
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;
fun receiveGetter(): (() -> int) { return used_as_noncall2; }
@pure
fun usedButOptimizedOut(x: int): int { return x + 2; }
fun main(): (int, int, int) {
used_gv = 1;
used_gv = used_gv + 2;
var getter1 = used_as_noncall1;
var getter2 = receiveGetter();
usedButOptimizedOut(used_gv);
return (used_gv, getter1(), getter2());
}
/**
@experimental_options remove-unused-functions
@testcase | 0 | | 3 10 20
@fif_codegen DECLPROC used_as_noncall1
@fif_codegen DECLGLOBVAR used_gv
@fif_codegen_avoid DECLPROC unused1
@fif_codegen_avoid DECLPROC unused2
@fif_codegen_avoid DECLPROC unused3
@fif_codegen_avoid DECLGLOBVAR unused_gv
Note, that `usedButOptimizedOut()` (a pure function which result is unused)
is currently codegenerated, since it's formally reachable.
This is because optimizing code is a moment of codegen for now (later than marking unused symbols).
@fif_codegen DECLPROC usedButOptimizedOut
@fif_codegen_avoid usedButOptimizedOut CALLDICT
*/

61
tolk-tester/tests/s1.tolk Normal file
View file

@ -0,0 +1,61 @@
get ascii_slice(): slice {
return"string";
}
get raw_slice(): slice {
return "abcdef"s;
}
get addr_slice(): slice {
return "Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"a;
}
get string_hex(): int {
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"u;
}
get fun string_minihash(): int { // 'get' and 'get fun' both possible
return "transfer(slice, int)"h;
}
get fun string_maxihash(): int {
return "transfer(slice, int)"H;
}
get fun string_crc32(): int {
return "transfer(slice, int)"c;
}
@pure
fun newc(): builder
asm "NEWC";
fun endcs(b: builder): slice
asm "ENDC" "CTOS";
@pure
fun sdeq(s1: slice, s2: slice): int
asm "SDEQ";
fun main() {
var s_ascii: slice = ascii_slice();
var s_raw: slice = raw_slice();
var s_addr: slice = addr_slice();
var i_hex: int = string_hex();
var i_mini: int = string_minihash();
var i_maxi: int = string_maxihash();
var i_crc: int = string_crc32();
assert(sdeq(s_ascii, newc().store_uint(0x737472696E67, 12 * 4).endcs())) throw 101;
assert(sdeq(s_raw, newc().store_uint(0xABCDEF, 6 * 4).endcs())) throw 102;
assert(sdeq(s_addr, newc().store_uint(4, 3).store_int(-1, 8)
.store_uint(0x3333333333333333333333333333333333333333333333333333333333333333, 256).endcs()), 103);
assert(i_hex == 0x4142434445464748494A4B4C4D4E4F505152535455565758595A303132333435) throw 104;
assert(i_mini == 0x7a62e8a8) throw 105;
assert(i_maxi == 0x7a62e8a8ebac41bd6de16c65e7be363bc2d2cbc6a0873778dead4795c13db979) throw 106;
assert(i_crc == 2235694568) throw 107;
return 0;
}
/**
@testcase | 0 | | 0
@code_hash 13830542019509784148027107880226447201604257839069192762244575629978154217223
*/

View file

@ -0,0 +1,24 @@
fun onInternalMessage() { return 0; }
fun onExternalMessage() { return -1; }
fun onRunTickTock() { return -2; }
fun onSplitPrepare() { return -3; }
fun onSplitInstall() { return -4; }
/**
@experimental_options remove-unused-functions
@testcase | 0 | | 0
@testcase | -1 | | -1
@testcase | -2 | | -2
@testcase | -3 | | -3
@testcase | -4 | | -4
@fif_codegen
"""
0 DECLMETHOD onInternalMessage
-1 DECLMETHOD onExternalMessage
-2 DECLMETHOD onRunTickTock
-3 DECLMETHOD onSplitPrepare
-4 DECLMETHOD onSplitInstall
"""
*/

View file

@ -0,0 +1,309 @@
import "../../crypto/smartcont/mathlib.tolk";
@pure
fun ~tset<X>(t: tuple, idx: int, value: X): (tuple, ())
asm(t value idx) "SETINDEXVAR";
// computes 1-acos(x)/Pi by a very simple, extremely slow (~70k gas) and imprecise method
// fixed256 acos_prepare_slow(fixed255 x);
@inline
fun acos_prepare_slow_f255(x: int): int {
x -= (x == 0);
var t: int = 1;
repeat (255) {
t = t * sgn(x) * 2 + 1; // decode Gray code (sgn(x_0), sgn(x_1), ...)
x = (-1 << 255) - muldivr(x, - x, 1 << 254); // iterate x := 2*x^2 - 1 = cos(2*acos(x))
}
return abs(t);
}
// extremely slow (~70k gas) and somewhat imprecise (very imprecise when x is small), for testing only
// fixed254 acos_slow(fixed255 x);
@inline_ref
fun acos_slow_f255(x: int): int {
var t: int = acos_prepare_slow_f255(x);
return - mulrshiftr256(t + (-1<<256), Pi_const_f254());
}
// fixed255 asin_slow(fixed255 x);
@inline_ref
fun asin_slow_f255(x: int): int {
var t: int = acos_prepare_slow_f255(abs(x)) % (1 << 255);
return muldivr(t, Pi_const_f254(), 1 << 255) * sgn(x);
}
@inline_ref
fun test_nrand(n: int): tuple {
var t: tuple = empty_tuple();
repeat (255) {
t~tpush(0);
}
repeat (n) {
var x: int = fixed248_nrand();
var bucket: int = (abs(x) >> 243); // 255 buckets starting from x=0, each 1/32 wide
t~tset(bucket, t.at(bucket) + 1);
}
return t;
}
@method_id(10000)
fun geom_mean_test(x: int, y: int): int {
return geom_mean(x, y);
}
@method_id(10001)
fun tan_f260_test(x: int): int {
return tan_f260(x);
}
@method_id(10002)
fun sincosm1_f259_test(x: int): (int, int) {
return sincosm1_f259(x);
}
@method_id(10003)
fun sincosn_f256_test(x: int, y: int): (int, int) {
return sincosn_f256(x, y);
}
@method_id(10004)
fun sincosm1_f256_test(x: int): (int, int) {
return sincosm1_f256(x);
}
@method_id(10005)
fun tan_aux_f256_test(x: int): (int, int) {
return tan_aux_f256(x);
}
@method_id(10006)
fun fixed248_tan_test(x: int): int {
return fixed248_tan(x);
}
/*
(int) atanh_alt_f258_test(x) method_id(10007) {
return atanh_alt_f258(x);
}
*/
@method_id(10008)
fun atanh_f258_test(x:int, y:int): int {
return atanh_f258(x, y);
}
@method_id(10009)
fun atanh_f261_test(x:int, y:int): int {
return atanh_f261(x, y);
}
@method_id(10010)
fun log2_aux_f256_test(x:int): (int, int) {
return log2_aux_f256(x);
}
@method_id(10011)
fun log_aux_f256_test(x:int): (int, int) {
return log_aux_f256(x);
}
@method_id(10012)
fun fixed248_pow_test(x:int, y:int): int {
return fixed248_pow(x, y);
}
@method_id(10013)
fun exp_log_div(x:int, y:int): int {
return fixed248_exp(fixed248_log(x << 248) ~/ y);
}
@method_id(10014)
fun fixed248_log_test(x:int): int {
return fixed248_log(x);
}
@method_id(10015)
fun log_aux_f257_test(x:int): (int,int) {
return log_aux_f257(x);
}
@method_id(10016)
fun fixed248_sincos_test(x:int): (int,int) {
return fixed248_sincos(x);
}
@method_id(10017)
fun fixed248_exp_test(x:int): int {
return fixed248_exp(x);
}
@method_id(10018)
fun fixed248_exp2_test(x:int): int {
return fixed248_exp2(x);
}
@method_id(10019)
fun expm1_f257_test(x:int): int {
return expm1_f257(x);
}
@method_id(10020)
fun atan_f255_test(x:int): int {
return atan_f255(x);
}
@method_id(10021)
fun atan_f259_test(x:int, n:int): int {
return atan_f259(x, n);
}
@method_id(10022)
fun atan_aux_f256_test(x:int): (int, int) {
return atan_aux_f256(x);
}
@method_id(10023)
fun asin_f255_test(x:int): int {
return asin_f255(x);
}
@method_id(10024)
fun asin_slow_f255_test(x:int): int {
return asin_slow_f255(x);
}
@method_id(10025)
fun acos_f255_test(x:int): int {
return acos_f255(x);
}
@method_id(10026)
fun acos_slow_f255_test(x:int): int {
return acos_slow_f255(x);
}
@method_id(10027)
fun fixed248_atan_test(x:int): int {
return fixed248_atan(x);
}
@method_id(10028)
fun fixed248_acot_test(x:int): int {
return fixed248_acot(x);
}
fun main() {
var One: int = 1;
// repeat(76 / 4) { One *= 10000; }
var sqrt2: int = geom_mean(One, 2 * One);
var sqrt3: int = geom_mean(One, 3 * One);
// return geom_mean(-1 - (-1 << 256), -1 - (-1 << 256));
// return geom_mean(-1 - (-1 << 256), -2 - (-1 << 256));
// return geom_mean(-1 - (-1 << 256), 1 << 255);
// return (sqrt2, geom_mean(sqrt2, One)); // (sqrt(2), 2^(1/4))
// return (sqrt3, geom_mean(sqrt3, One)); // (sqrt(3), 3^(1/4))
// return geom_mean(3 << 254, 1 << 254);
// return geom_mean(3, 5);
// return tan_f260(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1);
// return tan_f260(15 << 252); // tan(15/256) * 2^260
// return sincosm1_f259(1 << 255); // (sin,1-cos)(1/16) * 2^259
// return sincosm1_f259(115641670674223639132965820642403718536242645001775371762318060545014644837101 - 1);
// return sincosm1_f256((1 << 255) - 1 + (1 << 255)); // (sin,1-cos)(1-2^(-256))
// return sincosm1_f256(Pi_const_f254()); // (sin,1-cos)(Pi/4)
// return sincosn_f256(Pi_const_f254(), 0); // (sin,-cos)(Pi/4)
// return sincosn_f256((1 << 255) + 1, 0); // (sin,-cos)(1/2+1/2^256)
// return sincosn_f256(1 << 254, 0);
// return sincosn_f256(touch(15) << 252, 0); // (sin,-cos)(15/16)
// return sincosm1_f256(touch(15) << 252); // (sin,1-cos)(15/16)
// return sincosn_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698, 0); // (sin,-cos)(Pi/6)
// return sincosm1_f256(60628596148627720713372490462954977108898896221398738326462025186323149077698); // (sin,1-cos)(Pi/6)
// return tan_aux_f256(1899 << 245); // (p,q) such that p/q=tan(1899/2048)
// return fixed248_tan(11 << 248); // tan(11)
// return atanh_alt_f258(1 << 252); // atanh(1/64) * 2^258
// return atanh_f258(1 << 252, 18); // atanh(1/64) * 2^258
// return atanh_f261(muldivr(64, 1 << 255, 55), 18); // atanh(1/55) * 2^261
// return log2_aux_f256(1 << 255);
// return log2_aux_f256(-1 - (-1 << 256)); // log2(2-1/2^255))*2^256 ~ 2^256 - 1.43
// return log_aux_f256(-1 - (-1 << 256));
// return log_aux_f256(3); // log(3/2)*2^256
// return fixed248_pow(3 << 248, 3 << 248); // 3^3
// return fixed248_exp(fixed248_log(5 << 248) ~/ 7); // exp(log(5)/7) = 5^(1/7)
// return fixed248_log(Pi_const_f254() ~>> 6); // log(Pi)
// return atanh_alt_f258(1 << 255); // atanh(1/8) * 2^258
// return atanh_f258(1 << 255, 37); // atanh(1/8) * 2^258
// return atanh_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485, 36); // atanh(sqrt(2)/8) * 2^258
// return log_aux_f257(Pi_const_f254()); // log(Pi/4)
// return log_aux_f257(3 << 254); // log(3)
// return atanh_alt_f258(81877371507464127617551201542979628307507432471243237061821853600756754782485); // atanh(sqrt(2)/8) * 2^258
// return fixed248_sincos(Pi_const_f254() ~/ (64 * 3)); // (sin,cos)(Pi/3)
// return fixed248_exp(3 << 248); // exp(3)*2^248
// return fixed248_exp2((1 << 248) ~/ 5); // 2^(1/5)*2^248
// return fixed248_pow(3 << 248, -3 << 247); // 3^(-1.5)
// return fixed248_pow(10 << 248, -70 << 248); // 10^(-70)
// return fixed248_pow(fixed248_Pi_const(), touch(3) << 248); // Pi^3 ~ 31.006, computed more precisely
// return fixed248_pow(fixed248_Pi_const(), fixed248_Pi_const()); // Pi^Pi, more precisely
// return fixed248_exp(fixed248_log(fixed248_Pi_const()) * 3); // Pi^3 ~ 31.006
// return fixed248_exp(muldivr(fixed248_log(fixed248_Pi_const()), fixed248_Pi_const(), 1 << 248)); // Pi^Pi
// return fixed248_sin(fixed248_log(fixed248_exp(fixed248_Pi_const()))); // sin(log(e^Pi))
// return expm1_f257(1 << 255); // (exp(1/4)-1)*2^256
// return expm1_f257(-1 << 256); // (exp(-1/2)-1)*2^256 (argument out of range, will overflow)
// return expm1_f257(log2_const_f256()); // (exp(log(2)/2)-1)*2^256
// return expm1_f257(- log2_const_f256()); // (exp(-log(2)/2)-1)*2^256
// return tanh_f258(log2_const_f256(), 17); // tanh(log(2)/4)*2^258
// return atan_f255(0xa0 << 247);
// return atan_f259(1 << 255, 26); // atan(1/16)
// return atan_f259(touch(2273) << 244, 26); // atan(2273/2^15)
// return atan_aux_f256(0xa0 << 248);
// return atan_aux_f256(-1 - (-1 << 256));
// return atan_aux_f256(-1 << 256);
// return atan_aux_f256(1); // atan(1/2^256)*2^261 = 32
//return fixed248_nrand();
// return test_nrand(100000);
var One2: int = touch(1 << 255);
// return asin_f255(One);
// return asin_f255(-2 * One ~/ -3);
var arg: int = muldivr(12, One2, 17); // 12/17
// return [ asin_slow_f255(arg), asin_f255(arg) ];
// return [ acos_slow_f255(arg), acos_f255(arg) ];
// return 4 * atan_f255(One ~/ 5) - atan_f255(One ~/ 239); // 4 * atan(1/5) - atan(1/239) = Pi/4 as fixed255
var One3: int = touch(1 << 248);
// return fixed248_atan(One) ~/ 5); // atan(1/5)
// return fixed248_acot(One ~/ 239); // atan(1/5)
}
/**
method_id | in | out
@testcase | 10000 | -1-(-1<<256) -1-(-1<<256) | 115792089237316195423570985008687907853269984665640564039457584007913129639935
@testcase | 10000 | -1-(-1<<256) -2-(-1<<256) | 115792089237316195423570985008687907853269984665640564039457584007913129639934
@testcase | 10000 | -1-(-1<<256) 1<<255 | 81877371507464127617551201542979628307507432471243237061821853600756754782485
@testcase | 10000 | 1 2 | 1
@testcase | 10000 | 1 3 | 2
@testcase | 10000 | 3<<254 1<<254 | 50139445418395255283694704271811692336355250894665672355503583528635147053497
@testcase | 10000 | 3 5 | 4
@testcase | 10001 | 115641670674223639132965820642403718536242645001775371762318060545014644837101-1 | 115792089237316195423570985008687907853269984665640564039457584007913129639935
@testcase | 10001 | 15<<252 | 108679485937549714997960660780289583146059954551846264494610741505469565211201
@testcase | 10002 | 1<<255 | 57858359242454268843682786479537198006144860419130642837770554273561536355094 28938600351875109040123440645416448095273333920390487381363947585666516031269
@testcase | 10002 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 | 90796875678616203090520439851979829600860326752181983760731669850687818036503 71369031536005973567205947792557760023823761636922618688720973932041901854510
@testcase | 10002 | 115641670674223639132965820642403718536242645001775371762318060545014644837100 | 115341536360906404779899502576747487978354537254490211650198994186870666100480 115341536360906404779899502576747487978354537254490211650198994186870666100479
@testcase | 10003 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 0 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 -81877371507464127617551201542979628307507432471243237061821853600756754782486
@testcase | 10003 | (1<<255)+1 0 | 55513684748706254392157395574451324146997108788015526773113170656738693667657 -101617118319522600545601981648807607350213579319835970884288805016705398675944
@testcase | 10003 | 1<<254 0 | 28647421327665059059430596260119789787021370826354543144805343654507971817712 -112192393597863122712065585177748900737784171216163716639418346853706594800924
@testcase | 10003 | 15<<252 0 | 93337815620236900315136494926097782162348358704087992554326802765553037216157 -68526346066204767396483080633934170508153877799043171682610011603005473885083
@testcase | 10004 | 15<<252 | 93337815620236900315136494926097782162348358704087992554326802765553037216158 94531486342222856054175808749507474690232213733194784713695144809815311509707
@testcase | 10003 | 60628596148627720713372490462954977108898896221398738326462025186323149077698 0 | 57896044618658097711785492504343953926634992332820282019728792003956564819968 -100278890836790510567389408543623384672710501789331344711007167057270294106993
@testcase | 10004 | 60628596148627720713372490462954977108898896221398738326462025186323149077698 | 57896044618658097711785492504343953926634992332820282019728792003956564819968 31026396801051369712363152930129046361118965752618438656900833901285671065886
@testcase | 10005 | 1899<<245 | -115784979074977116522606932816046735344768048129666123117516779696532375620701 -86847621900007587791673148476644866514014227467564880140262768165345715058771
@testcase | 10006 | 11<<248 | -102200470999497240398685962406597118965525125432278008915850368651878945159221
@testcase | 10008 | 1<<252 18 | 7237594612640731814076778712183932891481921212865048737772958953246047977071
@testcase! | 10009 | 64*(1<<255)//55 18 | 67377367986958444187782963285047188951340314639925508148698906136973510008513
@testcase | 10010 | 1<<255 | 0 255
@testcase | 10011 | -1-(-1<<256) | 80260960185991308862233904206310070533990667611589946606122867505419956976171 255
@testcase | 10012 | 3<<248 3<<248 | 12212446911748192486079752325135052781399568695204278238536542063334587891712
@testcase | 10013 | 5 7 | 569235245303856216139605450142923208167703167128528666640203654338408315932
@testcase | 10014 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 | 517776035526939558040896860590142614178014859368681705591403663865964112176
@testcase | 10008 | 1<<255 37 | 58200445412255555045265806996802932280233368707362818578692888102488340124094
@testcase | 10008 | 81877371507464127617551201542979628307507432471243237061821853600756754782485 36 | 82746618329032515754939514227666784789465120373484337368014239356561508382845
@testcase | 10015 | 90942894222941581070058735694432465663348344332098107489693037779484723616546 | -55942510554172181731996424203087263676819062449594753161692794122306202470292 256
@testcase | 10015 | 3<<254 | -66622616410625360568738677407433830899150908037353507097280251369610028875158 256
@testcase | 10016 | 90942894222941581070058735694432465663348344332098107489693037779484723616546//(64*3) | 391714417331212931903864877123528846377775397614575565277371746317462086355 226156424291633194186662080095093570025917938800079226639565593765455331328
@testcase | 10017 | 3<<248 | 9084946421051389814103830025729847734065792062362132089390904679466687950835
@testcase | 10018 | (1<<248)//5 | 519571025111621076330285524602776985448579272766894385941850747946908706857
@testcase | 10012 | 3<<248 -3<<247 | 87047648295825095978636639360784188083950088358794570061638165848324908079
@testcase | 10012 | 10<<248 -70<<248 | 45231
@testcase | 10012 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 3<<248 | 14024537329227316173680050897643053638073167245065581681188087336877135047241
@testcase | 10012 | 1420982722233462204219667745225507275989817880189032929526453715304448806508 1420982722233462204219667745225507275989817880189032929526453715304448806508 | 16492303277433924047657446877966346821161732581471802839855102123372676002295
@testcase | 10019 | 1<<255 | 65775792789545756849501669218806308540691279864498696756901136302101823231959
@testcase | 10019 | -1<<255 | -51226238931640701466578648374135745377468902266335737558089915608594425303282
@testcase | 10020 | 160<<247 | 32340690885082755723307749066376646841771751777398167772823878380310576779097
@testcase | 10021 | 1<<255 26 | 57820835337111819566482910321201859268121322500887685881159030272507322418551
@testcase | 10021 | 2273<<244 26 | 64153929153128256059565403901040178355488584937372975321150754259394300105908
@testcase | 10022 | 160<<248 | 18 -13775317617017974742132028403521581424991093186766868001115299479309514610238
@testcase | 10022 | -1-(-1<<256) | 25 16312150880916231694896252427912541090503675654570543195394548083530005073282
@testcase | 10022 | -1<<256 | -25 -16312150880916231694896252427912541090503675654570543195394548083530005073298
@testcase | 10022 | 1 | 0 32
@testcase | 10023 | 1<<255 | 90942894222941581070058735694432465663348344332098107489693037779484723616546
@testcase | 10023 | (1-(1<<255))//-3 | 19675212872822715586637341573564384553677006914302429002469838095945333339604
@testcase | 10023 | 12*(1<<255)//17 | 45371280744427205854111943101074857545572584208710061167826656461897302968384
@testcase | 10024 | 12*(1<<255)//17 | 45371280744427205854111943101074857545572584208710061167826656461897302968387
@testcase | 10025 | 12*(1<<255)//17 | 22785806739257187607973396296678804058887880061694023160933190658793710324081
@testcase | 10026 | 12*(1<<255)//17 | 22785806739257187607973396296678804058887880061694023160933190658793710324080
@testcase | 10027 | (1<<248)//5 | 89284547973388213553327350968415123522888028497458323165947767504203347189
@testcase | 10028 | (1<<248)//239 | 708598849781543798951441405045469962900811296151941404481049216461523216127
*/

View file

@ -0,0 +1,151 @@
fun unsafeGetInt<X>(any: X): int
asm "NOP";
@method_id(11)
fun foo(x: int): int {
try {
if (x == 7) {
throw 44;
}
return x;
} catch {
return 2;
}
}
@inline
@method_id(12)
fun foo_inline(x: int): int {
try {
assert(!(x == 7)) throw 44;
return x;
} catch {
return 2;
}
}
@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);
}
}
@method_id(1)
fun test(x: int, y: int, z: int): int {
y = foo(y);
return x * 100 + y * 10 + z;
}
@method_id(2)
fun test_inline(x: int, y: int, z: int): int {
y = foo_inline(y);
return x * 100 + y * 10 + z;
}
@method_id(3)
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
): int {
try {
if (x1 == 7) {
throw 44;
}
return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20;
} catch {
return 1;
}
}
@method_id(4)
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,
y + 10, y + 11, y + 12, y + 13, y + 14, y + 15, y + 16, y + 17, y + 18, y + 19);
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
): int {
try {
if (x1 == 7) {
throw (44, 1);
}
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);
}
}
@method_id(5)
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,
y + 10, y + 11, y + 12, y + 13, y + 14, y + 15, y + 16, y + 17, y + 18, y + 19);
return x * 1000000 + y * 1000 + z;
}
@method_id(16)
fun test_catch_into_same(x: int): int {
var code = x;
try {
assert(x <= 10, 44);
} catch(code) {
return code;
}
return code;
}
@method_id(17)
fun test_catch_into_same_2(x: int): int {
var code = x;
try {
if (x > 10) {
throw 44;
}
} catch(code) {
}
return code;
}
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
@code_hash 73240939343624734070640372352271282883450660826541545137654364443860257436623
*/

View file

@ -0,0 +1,17 @@
fun main(x: int): (int, int) {
var y: int = 5;
if (x < 0) {
x *= 2;
y += 1;
if (x == -10) {
return (111, 0);
}
}
return (x + 1, y);
}
/**
method_id | in | out
@testcase | 0 | 10 | 11 5
@testcase | 0 | -5 | 111 0
@testcase | 0 | -4 | -7 6
*/

View file

@ -0,0 +1,19 @@
@inline
fun foo(x: int): int {
if (x < 0) {
x *= 2;
if (x == -10) {
return 111;
}
}
return x + 1;
}
fun main(x: int): int {
return foo(x) * 10;
}
/**
method_id | in | out
@testcase | 0 | 10 | 110
@testcase | 0 | -5 | 1110
@testcase | 0 | -4 | -70
*/

View file

@ -0,0 +1,68 @@
fun main() { }
@method_id(1)
fun foo_repeat(x: int): int {
repeat(10) {
x += 10;
if (x >= 100) {
return x;
}
}
return -1;
}
@method_id(2)
fun foo_while(x: int): int {
var i: int = 0;
while (i < 10) {
x += 10;
if (x >= 100) {
return x;
}
i += 1;
}
return -1;
}
@method_id(3)
fun foo_until(x: int): int {
var i: int = 0;
do {
x += 10;
if (x >= 100) {
return x;
}
i += 1;
} while (i < 10);
return -1;
}
@method_id(4)
fun test4(x: int): (int, int) {
var s = 0;
var reached = false;
do {
x = x - 1;
s = s + 1;
if (x < 10) {
reached = true;
}
} while (!reached);
return (s, reached);
}
/**
method_id | in | out
@testcase | 1 | 40 | 100
@testcase | 1 | 33 | 103
@testcase | 1 | -5 | -1
@testcase | 2 | 40 | 100
@testcase | 2 | 33 | 103
@testcase | 2 | -5 | -1
@testcase | 3 | 40 | 100
@testcase | 3 | 33 | 103
@testcase | 3 | -5 | -1
@testcase | 4 | 18 | 9 -1
@code_hash 12359153928622198176298534554187062238616102949658930329300859312625793323482
*/

View file

@ -0,0 +1,40 @@
fun foo(y: int): int {
if (y < 0) {
y *= 2;
if (y == -10) {
return 111;
}
}
return y + 1;
}
fun bar(x: int, y: int): (int, int) {
if (x < 0) {
y = foo(y);
x *= 2;
if (x == -10) {
return (111, y);
}
}
return (x + 1, y);
}
fun bar2(x: int, y: int): (int,int) {
return bar(x, y);
}
fun main(x: int, y: int): (int, int) {
(x, y) = bar2(x, y);
return (x, y * 10);
}
/**
method_id | in | out
@testcase | 0 | 3 3 | 4 30
@testcase | 0 | 3 -5 | 4 -50
@testcase | 0 | 3 -4 | 4 -40
@testcase | 0 | -5 3 | 111 40
@testcase | 0 | -5 -5 | 111 1110
@testcase | 0 | -5 -4 | 111 -70
@testcase | 0 | -4 3 | -7 40
@testcase | 0 | -4 -5 | -7 1110
@testcase | 0 | -4 -4 | -7 -70
@code_hash 68625253347714662162648433047986779710161195298061582217368558479961252943991
*/

View file

@ -0,0 +1,49 @@
fun main(): int {
var c: cell = my_begin_cell().store_int(demo_10, 32).my_end_cell();
var cs: slice = my_begin_parse(c);
var ten: int = cs~load_int(32);
return 1 + demo1(ten) + demo_var;
}
@pure
fun my_begin_cell(): builder
asm "NEWC";
@pure
fun my_end_cell(b: builder): cell
asm "ENDC";
@pure
fun my_begin_parse(c: cell): slice
asm "CTOS";
fun demo1(v: int): int {
demo_var = 23;
return v;
}
global demo_var: int;
const demo_10: int = 10;
fun test1(): int {
var demo_var: int = demo_10;
var demo_slice: int = demo_20;
if (demo_var > 0) {
var demo_var: tuple = null;
var demo_slice: tuple = null;
}
return demo_var + demo_slice;
}
global demo_slice: slice;
const demo_20: int = 20;
/**
@testcase | 0 | | 34
@fif_codegen
"""
test1 PROC:<{
//
30 PUSHINT // _10
}>
"""
*/

14
tolk-tester/tests/w1.tolk Normal file
View file

@ -0,0 +1,14 @@
fun main(id: int): (int, int) {
if (id > 0) {
if (id > 10) {
return (2 * id, 3 * id);
}
}
return (5, 6);
}
/**
method_id | in | out
@testcase | 0 | 0 | 5 6
@testcase | 0 | 4 | 5 6
@testcase | 0 | 11 | 22 33
*/

34
tolk-tester/tests/w2.tolk Normal file
View file

@ -0,0 +1,34 @@
@method_id(101)
fun test1(cs: slice) {
return cs~load_uint(8)+cs~load_uint(8)+cs~load_uint(8)+cs~load_uint(8);
}
@method_id(102)
fun test2(cs: slice) {
var (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10,
x11, x12, x13, x14, x15, x16, x17, x18, x19) = f(cs);
return x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9
+ x10+ x11+ x12+ x13+ x14+ x15+ x16+ x17+ x18+ x19;
}
fun main(cs: slice) {
return (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8));
}
fun f(cs: slice) {
return (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8),
cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8),
cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8),
cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8),
cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8));
}
/**
method_id | in | out
@testcase | 102 | x{000102030405060708090a0b0c0d0e0f10111213} | 190
@testcase | 101 | x{000102030405060708090a0b0c0d0e0f10111213} | 6
@testcase | 0 | x{000102030405060708090a0b0c0d0e0f10111213} | 0 1 2 3
@code_hash 58474889199998908444151060994149070836199913191952040273624197630531731101157
*/

19
tolk-tester/tests/w6.tolk Normal file
View file

@ -0,0 +1,19 @@
fun main(x: int): int {
var i: int = 0;
// int f = false;
do {
i = i + 1;
if (i > 5) {
return 1;
}
var f: int = (i * i == 64);
} while (!f);
return -1;
}
/**
method_id | in | out
@testcase | 0 | 0 | 1
@code_hash 36599880583276393028571473830850694081778552118303309411432666239740650614479
*/

26
tolk-tester/tests/w7.tolk Normal file
View file

@ -0,0 +1,26 @@
@method_id(1)
fun test(y: int): int {
var x: int = 1;
if (y > 0) {
return 1;
}
return x > 0;
}
@method_id(2)
fun f(y: int): int {
if (y > 0) {
return 1;
}
return 2;
}
fun main() { }
/**
method_id | in | out
@testcase | 1 | 10 | 1
@testcase | 1 | -5 | -1
@testcase | 2 | 10 | 1
@testcase | 2 | -5 | 2
*/

14
tolk-tester/tests/w9.tolk Normal file
View file

@ -0,0 +1,14 @@
fun main(s: int) {
var (z, t) = (17, s);
while (z > 0) {
t = s;
z -= 1;
}
return ~ t;
}
/**
method_id | in | out
@testcase | 0 | 1 | -2
@testcase | 0 | 5 | -6
*/

525
tolk-tester/tolk-tester.js Normal file
View file

@ -0,0 +1,525 @@
// Usage: `node tolk-tester.js tests_dir` OR `node tolk-tester.js test_file.tolk`
// from current dir, providing some env (see getenv() calls).
// This is a JS version of tolk-tester.py to test Tolk compiled to WASM.
// Don't forget to keep it identical to Python version!
const fs = require('fs');
const os = require('os');
const path = require('path');
const child_process = require('child_process');
function print(...args) {
console.log(...args)
}
/** @return {string} */
function getenv(name, def = null) {
if (name in process.env)
return process.env[name]
if (def === null) {
print(`Environment variable ${name} is not set`)
process.exit(1)
}
return def
}
const TOLKFIFTLIB_MODULE = getenv('TOLKFIFTLIB_MODULE')
const TOLKFIFTLIB_WASM = getenv('TOLKFIFTLIB_WASM')
const FIFT_EXECUTABLE = getenv('FIFT_EXECUTABLE')
const FIFT_LIBS_FOLDER = getenv('FIFTPATH') // this env is needed for fift to work properly
const TMP_DIR = os.tmpdir()
class CmdLineOptions {
constructor(/**string[]*/ argv) {
if (argv.length !== 3) {
print("Usage: node tolk-tester.js tests_dir OR node tolk-tester.js test_file.tolk")
process.exit(1)
}
if (!fs.existsSync(argv[2])) {
print(`Input '${argv[2]}' doesn't exist`)
process.exit(1)
}
if (fs.lstatSync(argv[2]).isDirectory()) {
this.tests_dir = argv[2]
this.test_file = null
} else {
this.tests_dir = path.dirname(argv[2])
this.test_file = argv[2]
}
}
/** @return {string[]} */
find_tests() {
if (this.test_file) // an option to run (debug) a single test
return [this.test_file]
let tests = fs.readdirSync(this.tests_dir).filter(f => f.endsWith('.tolk') || f.endsWith('.ton'))
tests.sort()
return tests.map(f => path.join(this.tests_dir, f))
}
}
class ParseInputError extends Error {
}
class TolkCompilationFailedError extends Error {
constructor(/**string*/ message, /**string*/ stderr) {
super(message);
this.stderr = stderr
}
}
class TolkCompilationSucceededError extends Error {
}
class FiftExecutionFailedError extends Error {
constructor(/**string*/ message, /**string*/ stderr) {
super(message);
this.stderr = stderr
}
}
class CompareOutputError extends Error {
constructor(/**string*/ message, /**string*/ output) {
super(message);
this.output = output
}
}
class CompareFifCodegenError extends Error {
}
class CompareCodeHashError extends Error {
}
/*
* In positive tests, there are several testcases "input X should produce output Y".
*/
class TolkTestCaseInputOutput {
static reJustNumber = /^[-+]?\d+$/
static reMathExpr = /^[0x123456789()+\-*/<>]*$/
constructor(/**string*/ method_id_str, /**string*/ input_str, /**string*/ output_str) {
let processed_inputs = []
for (let in_arg of input_str.split(' ')) {
if (in_arg.length === 0)
continue
else if (in_arg.startsWith("x{") || TolkTestCaseInputOutput.reJustNumber.test(in_arg))
processed_inputs.push(in_arg)
else if (TolkTestCaseInputOutput.reMathExpr.test(in_arg))
// replace "3<<254" with "3n<<254n" (big number) before eval (in Python we don't need this)
processed_inputs.push(eval(in_arg.replace('//', '/').replace(/(\d)($|\D)/gmi, '$1n$2')).toString())
else if (in_arg === "null")
processed_inputs.push("null")
else
throw new ParseInputError(`'${in_arg}' can't be evaluated`)
}
this.method_id = +method_id_str
this.input = processed_inputs.join(' ')
this.expected_output = output_str
}
check(/**string[]*/ stdout_lines, /**number*/ line_idx) {
if (stdout_lines[line_idx] !== this.expected_output)
throw new CompareOutputError(`error on case #${line_idx + 1} (${this.method_id} | ${this.input}): expected '${this.expected_output}', found '${stdout_lines[line_idx]}'`, stdout_lines.join("\n"))
}
}
/*
* @stderr checks, when compilation fails, that stderr (compilation error) is expected.
* If it's multiline, all lines must be present in specified order.
*/
class TolkTestCaseStderr {
constructor(/**string[]*/ stderr_pattern, /**boolean*/ avoid) {
this.stderr_pattern = stderr_pattern
this.avoid = avoid
}
check(/**string*/ stderr) {
const line_match = this.find_pattern_in_stderr(stderr.split(/\n/))
if (line_match === -1 && !this.avoid)
throw new CompareOutputError("pattern not found in stderr:\n" +
this.stderr_pattern.map(x => " " + x).join("\n"), stderr)
else if (line_match !== -1 && this.avoid)
throw new CompareOutputError(`pattern found (line ${line_match + 1}), but not expected to be:\n` +
this.stderr_pattern.map(x => " " + x).join("\n"), stderr)
}
find_pattern_in_stderr(/**string[]*/ stderr) {
for (let line_start = 0; line_start < stderr.length; ++line_start)
if (this.try_match_pattern(0, stderr, line_start))
return line_start
return -1
}
try_match_pattern(/**number*/ pattern_offset, /**string[]*/ stderr, /**number*/ offset) {
if (pattern_offset >= this.stderr_pattern.length)
return true
if (offset >= stderr.length)
return false
const line_pattern = this.stderr_pattern[pattern_offset]
const line_output = stderr[offset]
return line_output.includes(line_pattern) && this.try_match_pattern(pattern_offset + 1, stderr, offset + 1)
}
}
/*
* @fif_codegen checks that contents of compiled.fif matches the expected pattern.
* @fif_codegen_avoid checks that is does not match the pattern.
* See comments in run_tests.py.
*/
class TolkTestCaseFifCodegen {
constructor(/**string[]*/ fif_pattern, /**boolean*/ avoid) {
/** @type {string[]} */
this.fif_pattern = fif_pattern.map(s => s.trim())
this.avoid = avoid
}
check(/**string[]*/ fif_output) {
const line_match = this.find_pattern_in_fif_output(fif_output)
if (line_match === -1 && !this.avoid)
throw new CompareFifCodegenError("pattern not found:\n" +
this.fif_pattern.map(x => " " + x).join("\n"))
else if (line_match !== -1 && this.avoid)
throw new CompareFifCodegenError(`pattern found (line ${line_match + 1}), but not expected to be:\n` +
this.fif_pattern.map(x => " " + x).join("\n"))
}
find_pattern_in_fif_output(/**string[]*/ fif_output) {
for (let line_start = 0; line_start < fif_output.length; ++line_start)
if (this.try_match_pattern(0, fif_output, line_start))
return line_start
return -1
}
try_match_pattern(/**number*/ pattern_offset, /**string[]*/ fif_output, /**number*/ offset) {
if (pattern_offset >= this.fif_pattern.length)
return true
if (offset >= fif_output.length)
return false
const line_pattern = this.fif_pattern[pattern_offset]
const line_output = fif_output[offset]
if (line_pattern !== "...") {
if (!TolkTestCaseFifCodegen.does_line_match(line_pattern, line_output))
return false
return this.try_match_pattern(pattern_offset + 1, fif_output, offset + 1)
}
while (offset < fif_output.length) {
if (this.try_match_pattern(pattern_offset + 1, fif_output, offset))
return true
offset = offset + 1
}
return false
}
static split_line_to_cmd_and_comment(/**string*/ trimmed_line) {
const pos = trimmed_line.indexOf("//")
if (pos === -1)
return [trimmed_line, null]
else
return [trimmed_line.substring(0, pos).trimEnd(), trimmed_line.substring(pos + 2).trimStart()]
}
static does_line_match(/**string*/ line_pattern, /**string*/ line_output) {
const [cmd_pattern, comment_pattern] = TolkTestCaseFifCodegen.split_line_to_cmd_and_comment(line_pattern)
const [cmd_output, comment_output] = TolkTestCaseFifCodegen.split_line_to_cmd_and_comment(line_output.trim())
return cmd_pattern === cmd_output && (comment_pattern === null || comment_pattern === comment_output)
}
}
/*
* @code_hash checks that hash of compiled output.fif matches the provided value.
* It's used to "record" code boc hash and to check that it remains the same on compiler modifications.
* Being much less flexible than @fif_codegen, it nevertheless gives a guarantee of bytecode stability.
*/
class TolkTestCaseExpectedHash {
constructor(/**string*/ expected_hash) {
this.code_hash = expected_hash
}
check(/**string*/ fif_code_hash) {
if (this.code_hash !== fif_code_hash)
throw new CompareCodeHashError(`expected ${this.code_hash}, actual ${fif_code_hash}`)
}
}
class TolkTestFile {
constructor(/**string*/ tolk_filename, /**string*/ artifacts_folder) {
this.line_idx = 0
this.tolk_filename = tolk_filename
this.artifacts_folder = artifacts_folder
this.compilation_should_fail = false
/** @type {TolkTestCaseStderr[]} */
this.stderr_includes = []
/** @type {TolkTestCaseInputOutput[]} */
this.input_output = []
/** @type {TolkTestCaseFifCodegen[]} */
this.fif_codegen = []
/** @type {TolkTestCaseExpectedHash | null} */
this.expected_hash = null
/** @type {string | null} */
this.experimental_options = null
}
parse_input_from_tolk_file() {
const lines = fs.readFileSync(this.tolk_filename, 'utf-8').split(/\r?\n/)
this.line_idx = 0
while (this.line_idx < lines.length) {
const line = lines[this.line_idx]
if (line.startsWith('@testcase')) {
let s = line.split("|").map(p => p.trim())
if (s.length !== 4)
throw new ParseInputError(`incorrect format of @testcase: ${line}`)
this.input_output.push(new TolkTestCaseInputOutput(s[1], s[2], s[3]))
} else if (line.startsWith('@compilation_should_fail')) {
this.compilation_should_fail = true
} else if (line.startsWith('@stderr')) {
this.stderr_includes.push(new TolkTestCaseStderr(this.parse_string_value(lines), false))
} else if (line.startsWith("@fif_codegen_avoid")) {
this.fif_codegen.push(new TolkTestCaseFifCodegen(this.parse_string_value(lines), true))
} else if (line.startsWith("@fif_codegen")) {
this.fif_codegen.push(new TolkTestCaseFifCodegen(this.parse_string_value(lines), false))
} else if (line.startsWith("@code_hash")) {
this.expected_hash = new TolkTestCaseExpectedHash(this.parse_string_value(lines, false)[0])
} else if (line.startsWith("@experimental_options")) {
this.experimental_options = line.substring(22)
}
this.line_idx++
}
if (this.input_output.length === 0 && !this.compilation_should_fail)
throw new ParseInputError("no @testcase present")
if (this.input_output.length !== 0 && this.compilation_should_fail)
throw new ParseInputError("@testcase present, but compilation_should_fail")
}
/** @return {string[]} */
parse_string_value(/**string[]*/ lines, allow_multiline = true) {
// a tag must be followed by a space (single-line), e.g. '@stderr some text'
// or be a multi-line value, surrounded by """
const line = lines[this.line_idx]
const pos_sp = line.indexOf(' ')
const is_multi_line = lines[this.line_idx + 1] === '"""'
const is_single_line = pos_sp !== -1
if (!is_single_line && !is_multi_line)
throw new ParseInputError(`${line} value is empty (not followed by a string or a multiline """)`)
if (is_single_line && is_multi_line)
throw new ParseInputError(`${line.substring(0, pos_sp)} value is both single-line and followed by """`)
if (is_multi_line && !allow_multiline)
throw new ParseInputError(`${line} value should be single-line`);
if (is_single_line)
return [line.substring(pos_sp + 1).trim()]
this.line_idx += 2
let s_multiline = []
while (this.line_idx < lines.length && lines[this.line_idx] !== '"""') {
s_multiline.push(lines[this.line_idx])
this.line_idx = this.line_idx + 1
}
return s_multiline
}
get_compiled_fif_filename() {
return this.artifacts_folder + "/compiled.fif"
}
get_runner_fif_filename() {
return this.artifacts_folder + "/runner.fif"
}
async run_and_check() {
const wasmModule = await compileWasm(TOLKFIFTLIB_MODULE, TOLKFIFTLIB_WASM)
let res = compileFile(wasmModule, this.tolk_filename, this.experimental_options)
let exit_code = res.status === 'ok' ? 0 : 1
let stderr = res.message
let stdout = ''
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)
should_include.check(stderr)
return
}
if (exit_code !== 0 && !this.compilation_should_fail)
throw new TolkCompilationFailedError(`tolk exit_code = ${exit_code}`, stderr)
fs.writeFileSync(this.get_compiled_fif_filename(), `"Asm.fif" include\n${res.fiftCode}`)
{
let runner = `"${this.get_compiled_fif_filename()}" include <s constant code\n`
for (let t of this.input_output)
runner += `${t.input} ${t.method_id} code 1 runvmx abort"exitcode is not 0" .s cr { drop } depth 1- times\n`
if (this.expected_hash !== null)
runner += `"${this.get_compiled_fif_filename()}" include hash .s\n`
fs.writeFileSync(this.get_runner_fif_filename(), runner)
}
res = child_process.spawnSync(FIFT_EXECUTABLE, [this.get_runner_fif_filename()])
exit_code = res.status
stderr = (res.stderr || res.error).toString()
stdout = (res.stdout || '').toString()
if (exit_code)
throw new FiftExecutionFailedError(`fift exit_code = ${exit_code}`, stderr)
let stdout_lines = stdout.split("\n").map(x => x.trim()).filter(s => s.length > 0)
let fif_code_hash = null
if (this.expected_hash !== null) { // then the last stdout line is a hash
fif_code_hash = stdout_lines[stdout_lines.length - 1]
stdout_lines = stdout_lines.slice(0, stdout_lines.length - 1)
}
if (stdout_lines.length !== this.input_output.length)
throw new CompareOutputError(`unexpected number of fift output: ${stdout_lines.length} lines, but ${this.input_output.length} testcases`, stdout)
for (let i = 0; i < stdout_lines.length; ++i)
this.input_output[i].check(stdout_lines, i)
if (this.fif_codegen.length) {
const fif_output = fs.readFileSync(this.get_compiled_fif_filename(), 'utf-8').split(/\r?\n/)
for (let fif_codegen of this.fif_codegen)
fif_codegen.check(fif_output)
}
if (this.expected_hash !== null)
this.expected_hash.check(fif_code_hash)
}
}
async function run_all_tests(/**string[]*/ tests) {
for (let ti = 0; ti < tests.length; ++ti) {
let tolk_filename = tests[ti]
print(`Running test ${ti + 1}/${tests.length}: ${tolk_filename}`)
let artifacts_folder = path.join(TMP_DIR, tolk_filename)
let testcase = new TolkTestFile(tolk_filename, artifacts_folder)
try {
if (!fs.existsSync(artifacts_folder))
fs.mkdirSync(artifacts_folder, {recursive: true})
testcase.parse_input_from_tolk_file()
await testcase.run_and_check()
fs.rmSync(artifacts_folder, {recursive: true})
if (testcase.compilation_should_fail)
print(" OK, compilation failed as it should")
else
print(` OK, ${testcase.input_output.length} cases`)
} catch (e) {
if (e instanceof ParseInputError) {
print(` Error parsing input (cur line #${testcase.line_idx + 1}):`, e.message)
process.exit(2)
} else if (e instanceof TolkCompilationFailedError) {
print(" Error compiling tolk:", e.message)
print(" stderr:")
print(e.stderr.trimEnd())
process.exit(2)
} else if (e instanceof FiftExecutionFailedError) {
print(" Error executing fift:", e.message)
print(" stderr:")
print(e.stderr.trimEnd())
print(" compiled.fif at:", testcase.get_compiled_fif_filename())
process.exit(2)
} else if (e instanceof CompareOutputError) {
print(" Mismatch in output:", e.message)
print(" Full output:")
print(e.output.trimEnd())
print(" Was compiled to:", testcase.get_compiled_fif_filename())
process.exit(2)
} else if (e instanceof CompareFifCodegenError) {
print(" Mismatch in fif codegen:", e.message)
print(" Was compiled to:", testcase.get_compiled_fif_filename())
print(fs.readFileSync(testcase.get_compiled_fif_filename(), 'utf-8'))
process.exit(2)
} else if (e instanceof CompareCodeHashError) {
print(" Mismatch in code hash:", e.message)
print(" Was compiled to:", testcase.get_compiled_fif_filename())
process.exit(2)
}
throw e
}
}
}
const tests = new CmdLineOptions(process.argv).find_tests()
print(`Found ${tests.length} tests`)
run_all_tests(tests).then(
() => print(`Done, ${tests.length} tests`),
console.error
)
// below are WASM helpers, which don't exist in Python version
process.setMaxListeners(0);
function copyToCString(mod, str) {
const len = mod.lengthBytesUTF8(str) + 1;
const ptr = mod._malloc(len);
mod.stringToUTF8(str, ptr, len);
return ptr;
}
function copyToCStringPtr(mod, str, ptr) {
const allocated = copyToCString(mod, str);
mod.setValue(ptr, allocated, '*');
return allocated;
}
function copyFromCString(mod, ptr) {
return mod.UTF8ToString(ptr);
}
/** @return {{status: string, message: string, fiftCode: string, codeBoc: string, codeHashHex: string}} */
function compileFile(mod, filename, experimentalOptions) {
// see tolk-wasm.cpp: typedef void (*CStyleReadFileCallback)(int, char const*, char**, char**)
const callbackPtr = mod.addFunction((kind, dataPtr, destContents, destError) => {
if (kind === 0) { // realpath
try {
const relativeFilename = copyFromCString(mod, dataPtr)
copyToCStringPtr(mod, fs.realpathSync(relativeFilename), destContents);
} catch (err) {
copyToCStringPtr(mod, 'cannot find file', destError);
}
} else if (kind === 1) { // read file
try {
const filename = copyFromCString(mod, dataPtr) // already normalized (as returned above)
copyToCStringPtr(mod, fs.readFileSync(filename).toString('utf-8'), destContents);
} catch (err) {
copyToCStringPtr(mod, err.message || err.toString(), destError);
}
} else {
copyToCStringPtr(mod, 'Unknown callback kind=' + kind, destError);
}
}, 'viiii');
const config = {
optimizationLevel: 2,
withStackComments: true,
experimentalOptions: experimentalOptions || undefined,
stdlibLocation: __dirname + '/../crypto/smartcont/stdlib.tolk',
entrypointFileName: filename
};
const configPtr = copyToCString(mod, JSON.stringify(config));
const responsePtr = mod._tolk_compile(configPtr, callbackPtr);
return JSON.parse(copyFromCString(mod, responsePtr));
}
async function compileWasm(tolkFiftLibJsFileName, tolkFiftLibWasmFileName) {
const wasmModule = require(tolkFiftLibJsFileName)
const wasmBinary = new Uint8Array(fs.readFileSync(tolkFiftLibWasmFileName))
return await wasmModule({ wasmBinary })
}

430
tolk-tester/tolk-tester.py Normal file
View file

@ -0,0 +1,430 @@
# Usage: `tolk-tester.py tests_dir` OR `tolk-tester.py test_file.tolk`
# from current dir, providing some env (see getenv() calls).
# Every .tolk file should provide /* testcase description in a comment */, consider tests/ folder.
#
# Tests for Tolk can be
# * positive (compiled to .fif, run with fift, compared output with the one expected)
# * negative (compilation fails, and it's expected; patterns in stderr can be specified)
#
# Note, that there is also tolk-tester.js to test Tolk compiled to WASM.
# Don't forget to keep it identical to Python version!
import os
import os.path
import re
import shutil
import subprocess
import sys
import tempfile
from typing import List
def getenv(name, default=None):
if name in os.environ:
return os.environ[name]
if default is None:
print("Environment variable", name, "is not set", file=sys.stderr)
exit(1)
return default
TOLK_EXECUTABLE = getenv("TOLK_EXECUTABLE", "tolk")
FIFT_EXECUTABLE = getenv("FIFT_EXECUTABLE", "fift")
FIFT_LIBS_FOLDER = getenv("FIFTPATH") # this env is needed for fift to work properly
TMP_DIR = tempfile.mkdtemp()
class CmdLineOptions:
def __init__(self, argv: List[str]):
if len(argv) != 2:
print("Usage: tolk-tester.py tests_dir OR tolk-tester.py test_file.tolk", file=sys.stderr)
exit(1)
if not os.path.exists(argv[1]):
print("Input '%s' doesn't exist" % argv[1], file=sys.stderr)
exit(1)
if os.path.isdir(argv[1]):
self.tests_dir = argv[1]
self.test_file = None
else:
self.tests_dir = os.path.dirname(argv[1])
self.test_file = argv[1]
def find_tests(self) -> List[str]:
if self.test_file is not None: # an option to run (debug) a single test
return [self.test_file]
tests = [f for f in os.listdir(self.tests_dir) if f.endswith(".tolk") or f.endswith(".ton")]
tests.sort()
return [os.path.join(self.tests_dir, f) for f in tests]
class ParseInputError(Exception):
pass
class TolkCompilationFailedError(Exception):
def __init__(self, message: str, stderr: str):
super().__init__(message)
self.stderr = stderr
class TolkCompilationSucceededError(Exception):
pass
class FiftExecutionFailedError(Exception):
def __init__(self, message: str, stderr: str):
super().__init__(message)
self.stderr = stderr
class CompareOutputError(Exception):
def __init__(self, message: str, output: str):
super().__init__(message)
self.output = output
class CompareFifCodegenError(Exception):
pass
class CompareCodeHashError(Exception):
pass
class TolkTestCaseInputOutput:
"""
In positive tests, there are several testcases "input X should produce output Y".
They are written as a table:
@testcase | method_id | input (one or several) | output
"""
reJustNumber = re.compile(r"[-+]?\d+")
reMathExpr = re.compile(r"[0x123456789()+\-*/<>]+")
def __init__(self, method_id_str: str, input_str: str, output_str: str):
processed_inputs = []
for in_arg in input_str.split(" "):
if len(in_arg) == 0:
continue
elif in_arg.startswith("x{") or TolkTestCaseInputOutput.reJustNumber.fullmatch(in_arg):
processed_inputs.append(in_arg)
elif TolkTestCaseInputOutput.reMathExpr.fullmatch(in_arg):
processed_inputs.append(str(eval(in_arg)))
elif in_arg == "null":
processed_inputs.append("null")
else:
raise ParseInputError("'%s' can't be evaluated" % in_arg)
self.method_id = int(method_id_str)
self.input = " ".join(processed_inputs)
self.expected_output = output_str
def check(self, stdout_lines: List[str], line_idx: int):
if stdout_lines[line_idx] != self.expected_output:
raise CompareOutputError("error on case #%d (%d | %s): expected '%s', found '%s'" % (line_idx + 1, self.method_id, self.input, self.expected_output, stdout_lines[line_idx]), "\n".join(stdout_lines))
class TolkTestCaseStderr:
"""
@stderr checks, when compilation fails, that stderr (compilation error) is expected.
If it's multiline, all lines must be present in specified order.
"""
def __init__(self, stderr_pattern: List[str], avoid: bool):
self.stderr_pattern = stderr_pattern
self.avoid = avoid
def check(self, stderr: str):
line_match = self.find_pattern_in_stderr(stderr.splitlines())
if line_match == -1 and not self.avoid:
raise CompareOutputError("pattern not found in stderr:\n%s" %
"\n".join(map(lambda x: " " + x, self.stderr_pattern)), stderr)
elif line_match != -1 and self.avoid:
raise CompareOutputError("pattern found (line %d), but not expected to be:\n%s" %
(line_match + 1, "\n".join(map(lambda x: " " + x, self.stderr_pattern))), stderr)
def find_pattern_in_stderr(self, stderr: List[str]) -> int:
for line_start in range(len(stderr)):
if self.try_match_pattern(0, stderr, line_start):
return line_start
return -1
def try_match_pattern(self, pattern_offset: int, stderr: List[str], offset: int) -> bool:
if pattern_offset >= len(self.stderr_pattern):
return True
if offset >= len(stderr):
return False
line_pattern = self.stderr_pattern[pattern_offset]
line_output = stderr[offset]
return line_output.find(line_pattern) != -1 and self.try_match_pattern(pattern_offset + 1, stderr, offset + 1)
class TolkTestCaseFifCodegen:
"""
@fif_codegen checks that contents of compiled.fif matches the expected pattern.
@fif_codegen_avoid checks that is does not match the pattern.
The pattern is a multiline piece of fift code, optionally with "..." meaning "any lines here".
See tests/codegen_check_demo.tolk of how it looks.
A notable thing about indentations (spaces at line starts):
Taking them into account will complicate the code without reasonable profit,
that's why we just trim every string.
And one more word about //comments. Tolk inserts them into fift output.
If a line in the pattern contains a //comment, it's expected to be equal.
If a line does not, we just compare a command.
"""
def __init__(self, fif_pattern: List[str], avoid: bool):
self.fif_pattern = [s.strip() for s in fif_pattern]
self.avoid = avoid
def check(self, fif_output: List[str]):
line_match = self.find_pattern_in_fif_output(fif_output)
if line_match == -1 and not self.avoid:
raise CompareFifCodegenError("pattern not found:\n%s" %
"\n".join(map(lambda x: " " + x, self.fif_pattern)))
elif line_match != -1 and self.avoid:
raise CompareFifCodegenError("pattern found (line %d), but not expected to be:\n%s" %
(line_match + 1, "\n".join(map(lambda x: " " + x, self.fif_pattern))))
def find_pattern_in_fif_output(self, fif_output: List[str]) -> int:
for line_start in range(len(fif_output)):
if self.try_match_pattern(0, fif_output, line_start):
return line_start
return -1
def try_match_pattern(self, pattern_offset: int, fif_output: List[str], offset: int) -> bool:
if pattern_offset >= len(self.fif_pattern):
return True
if offset >= len(fif_output):
return False
line_pattern = self.fif_pattern[pattern_offset]
line_output = fif_output[offset]
if line_pattern != "...":
if not TolkTestCaseFifCodegen.does_line_match(line_pattern, line_output):
return False
return self.try_match_pattern(pattern_offset + 1, fif_output, offset + 1)
while offset < len(fif_output):
if self.try_match_pattern(pattern_offset + 1, fif_output, offset):
return True
offset = offset + 1
return False
@staticmethod
def split_line_to_cmd_and_comment(trimmed_line: str) -> tuple:
pos = trimmed_line.find("//")
if pos == -1:
return trimmed_line, None
else:
return trimmed_line[:pos].rstrip(), trimmed_line[pos + 2:].lstrip()
@staticmethod
def does_line_match(line_pattern: str, line_output: str) -> bool:
cmd_pattern, comment_pattern = TolkTestCaseFifCodegen.split_line_to_cmd_and_comment(line_pattern)
cmd_output, comment_output = TolkTestCaseFifCodegen.split_line_to_cmd_and_comment(line_output.strip())
return cmd_pattern == cmd_output and (comment_pattern is None or comment_pattern == comment_output)
class TolkTestCaseExpectedHash:
"""
@code_hash checks that hash of compiled output.fif matches the provided value.
It's used to "record" code boc hash and to check that it remains the same on compiler modifications.
Being much less flexible than @fif_codegen, it nevertheless gives a guarantee of bytecode stability.
"""
def __init__(self, expected_hash: str):
self.code_hash = expected_hash
def check(self, fif_code_hash: str):
if self.code_hash != fif_code_hash:
raise CompareCodeHashError("expected %s, actual %s" % (self.code_hash, fif_code_hash))
class TolkTestFile:
def __init__(self, tolk_filename: str, artifacts_folder: str):
self.line_idx = 0
self.tolk_filename = tolk_filename
self.artifacts_folder = artifacts_folder
self.compilation_should_fail = False
self.stderr_includes: List[TolkTestCaseStderr] = []
self.input_output: List[TolkTestCaseInputOutput] = []
self.fif_codegen: List[TolkTestCaseFifCodegen] = []
self.expected_hash: TolkTestCaseExpectedHash | None = None
self.experimental_options: str | None = None
def parse_input_from_tolk_file(self):
with open(self.tolk_filename, "r") as fd:
lines = fd.read().splitlines()
self.line_idx = 0
while self.line_idx < len(lines):
line = lines[self.line_idx]
if line.startswith("@testcase"):
s = [x.strip() for x in line.split("|")]
if len(s) != 4:
raise ParseInputError("incorrect format of @testcase: %s" % line)
self.input_output.append(TolkTestCaseInputOutput(s[1], s[2], s[3]))
elif line.startswith("@compilation_should_fail"):
self.compilation_should_fail = True
elif line.startswith("@stderr"):
self.stderr_includes.append(TolkTestCaseStderr(self.parse_string_value(lines), False))
elif line.startswith("@fif_codegen_avoid"):
self.fif_codegen.append(TolkTestCaseFifCodegen(self.parse_string_value(lines), True))
elif line.startswith("@fif_codegen"):
self.fif_codegen.append(TolkTestCaseFifCodegen(self.parse_string_value(lines), False))
elif line.startswith("@code_hash"):
self.expected_hash = TolkTestCaseExpectedHash(self.parse_string_value(lines, False)[0])
elif line.startswith("@experimental_options"):
self.experimental_options = line[22:]
self.line_idx = self.line_idx + 1
if len(self.input_output) == 0 and not self.compilation_should_fail:
raise ParseInputError("no @testcase present")
if len(self.input_output) != 0 and self.compilation_should_fail:
raise ParseInputError("@testcase present, but compilation_should_fail")
def parse_string_value(self, lines: List[str], allow_multiline = True) -> List[str]:
# a tag must be followed by a space (single-line), e.g. '@stderr some text'
# or be a multi-line value, surrounded by """
line = lines[self.line_idx]
pos_sp = line.find(' ')
is_multi_line = lines[self.line_idx + 1] == '"""'
is_single_line = pos_sp != -1
if not is_single_line and not is_multi_line:
raise ParseInputError('%s value is empty (not followed by a string or a multiline """)' % line)
if is_single_line and is_multi_line:
raise ParseInputError('%s value is both single-line and followed by """' % line[:pos_sp])
if is_multi_line and not allow_multiline:
raise ParseInputError("%s value should be single-line" % line)
if is_single_line:
return [line[pos_sp + 1:].strip()]
self.line_idx += 2
s_multiline = []
while self.line_idx < len(lines) and lines[self.line_idx] != '"""':
s_multiline.append(lines[self.line_idx])
self.line_idx = self.line_idx + 1
return s_multiline
def get_compiled_fif_filename(self):
return self.artifacts_folder + "/compiled.fif"
def get_runner_fif_filename(self):
return self.artifacts_folder + "/runner.fif"
def run_and_check(self):
cmd_args = [TOLK_EXECUTABLE, "-o", self.get_compiled_fif_filename()]
if self.experimental_options:
cmd_args = cmd_args + ["-x", self.experimental_options]
res = subprocess.run(cmd_args + [self.tolk_filename], capture_output=True, timeout=10)
exit_code = res.returncode
stderr = str(res.stderr, "utf-8")
stdout = str(res.stdout, "utf-8")
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:
should_include.check(stderr)
return
if exit_code != 0 and not self.compilation_should_fail:
raise TolkCompilationFailedError("tolk exit_code = %d" % exit_code, stderr)
with open(self.get_runner_fif_filename(), "w") as fd:
fd.write("\"%s\" include <s constant code\n" % self.get_compiled_fif_filename())
for t in self.input_output:
fd.write("%s %d code 1 runvmx abort\"exitcode is not 0\" .s cr { drop } depth 1- times\n" % (t.input, t.method_id))
if self.expected_hash is not None:
fd.write("\"%s\" include hash .s\n" % self.get_compiled_fif_filename())
res = subprocess.run([FIFT_EXECUTABLE, self.get_runner_fif_filename()], capture_output=True, timeout=10)
exit_code = res.returncode
stderr = str(res.stderr, "utf-8")
stdout = str(res.stdout, "utf-8")
if exit_code != 0:
raise FiftExecutionFailedError("fift exit_code = %d" % exit_code, stderr)
stdout_lines = [x.strip() for x in stdout.split("\n")]
stdout_lines = [x for x in stdout_lines if x != ""]
fif_code_hash = None
if self.expected_hash is not None: # then the last stdout line is a hash
fif_code_hash = stdout_lines[-1]
stdout_lines = stdout_lines[:-1]
if len(stdout_lines) != len(self.input_output):
raise CompareOutputError("unexpected number of fift output: %d lines, but %d testcases" % (len(stdout_lines), len(self.input_output)), stdout)
for i in range(len(stdout_lines)):
self.input_output[i].check(stdout_lines, i)
if len(self.fif_codegen):
with open(self.get_compiled_fif_filename()) as fd:
fif_output = fd.readlines()
for fif_codegen in self.fif_codegen:
fif_codegen.check(fif_output)
if self.expected_hash is not None:
self.expected_hash.check(fif_code_hash)
def run_all_tests(tests: List[str]):
for ti in range(len(tests)):
tolk_filename = tests[ti]
print("Running test %d/%d: %s" % (ti + 1, len(tests), tolk_filename), file=sys.stderr)
artifacts_folder = os.path.join(TMP_DIR, tolk_filename)
testcase = TolkTestFile(tolk_filename, artifacts_folder)
try:
if not os.path.exists(artifacts_folder):
os.makedirs(artifacts_folder)
testcase.parse_input_from_tolk_file()
testcase.run_and_check()
shutil.rmtree(artifacts_folder)
if testcase.compilation_should_fail:
print(" OK, compilation failed as it should", file=sys.stderr)
else:
print(" OK, %d cases" % len(testcase.input_output), file=sys.stderr)
except ParseInputError as e:
print(" Error parsing input (cur line #%d):" % (testcase.line_idx + 1), e, file=sys.stderr)
exit(2)
except TolkCompilationFailedError as e:
print(" Error compiling tolk:", e, file=sys.stderr)
print(" stderr:", file=sys.stderr)
print(e.stderr.rstrip(), file=sys.stderr)
exit(2)
except TolkCompilationSucceededError as e:
print(" Error:", e, file=sys.stderr)
exit(2)
except FiftExecutionFailedError as e:
print(" Error executing fift:", e, file=sys.stderr)
print(" stderr:", file=sys.stderr)
print(e.stderr.rstrip(), file=sys.stderr)
print(" compiled.fif at:", testcase.get_compiled_fif_filename(), file=sys.stderr)
exit(2)
except CompareOutputError as e:
print(" Mismatch in output:", e, file=sys.stderr)
print(" Full output:", file=sys.stderr)
print(e.output.rstrip(), file=sys.stderr)
print(" Was compiled to:", testcase.get_compiled_fif_filename(), file=sys.stderr)
exit(2)
except CompareFifCodegenError as e:
print(" Mismatch in fif codegen:", e, file=sys.stderr)
print(" Was compiled to:", testcase.get_compiled_fif_filename(), file=sys.stderr)
print(open(testcase.get_compiled_fif_filename()).read(), file=sys.stderr)
exit(2)
except CompareCodeHashError as e:
print(" Mismatch in code hash:", e, file=sys.stderr)
print(" Was compiled to:", testcase.get_compiled_fif_filename(), file=sys.stderr)
exit(2)
tests = CmdLineOptions(sys.argv).find_tests()
print("Found", len(tests), "tests", file=sys.stderr)
run_all_tests(tests)
print("Done, %d tests" % len(tests), file=sys.stderr)