diff --git a/CMakeLists.txt b/CMakeLists.txt index 885fcef7..7d5b8da5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -627,6 +627,30 @@ if (NOT NIX) endif() endif() +# Tolk tests +if (NOT NIX) + if (MSVC) + set(PYTHON_VER "python") + else() + set(PYTHON_VER "python3") + endif() + add_test( + NAME test-tolk + COMMAND ${PYTHON_VER} tolk-tester.py tests/ + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tolk-tester) + if (WIN32) + set_property(TEST test-tolk PROPERTY ENVIRONMENT + "TOLK_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/tolk/tolk.exe" + "FIFT_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/fift.exe" + "FIFTPATH=${CMAKE_CURRENT_SOURCE_DIR}/crypto/fift/lib/") + else() + set_property(TEST test-tolk PROPERTY ENVIRONMENT + "TOLK_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/tolk/tolk" + "FIFT_EXECUTABLE=${CMAKE_CURRENT_BINARY_DIR}/crypto/fift" + "FIFTPATH=${CMAKE_CURRENT_SOURCE_DIR}/crypto/fift/lib/") + endif() +endif() + #BEGIN internal if (NOT TON_ONLY_TONLIB) add_test(test-adnl test-adnl) diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 92ceab6d..964db441 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1589,6 +1589,9 @@ forget @proclist forget @proccnt { }END> b> } : }END>c { }END>c s +// This is the way how FunC assigns method_id for reserved functions. +// Note, that Tolk entrypoints have other names (`onInternalMessage`, etc.), +// but method_id is assigned not by Fift, but by Tolk code generation. 0 constant recv_internal -1 constant recv_external -2 constant run_ticktock diff --git a/crypto/smartcont/mathlib.tolk b/crypto/smartcont/mathlib.tolk index 74fdfdd5..1f7510a6 100644 --- a/crypto/smartcont/mathlib.tolk +++ b/crypto/smartcont/mathlib.tolk @@ -1,11 +1,12 @@ -{- +/* - - Tolk fixed-point mathematical library - (initially copied from mathlib.fc) - - -} + */ +tolk 0.6 -{- +/* This file is part of TON Tolk Standard Library. Tolk Standard Library is free software: you can redistribute it and/or modify @@ -18,920 +19,984 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. --} +*/ -{---------------- HIGH-LEVEL FUNCTION DECLARATIONS -----------------} -{- - Most functions declared here work either with integers or with fixed-point numbers of type `fixed248`. - `fixedNNN` informally denotes an alias for type `int` used to represent fixed-point numbers with scale 2^NNN. - Prefix `fixedNNN::` is prepended to the names of high-level functions that accept arguments and return values of type `fixedNNN`. --} +/*--------------- MISSING OPERATIONS AND BUILT-INS ----------------*/ -{- function declarations have been commented out, otherwise they are not inlined by the current Tolk compiler +@pure +fun sgn(x: int): int + asm "SGN"; -;; nearest integer to sqrt(a*b) for non-negative integers or fixed-point numbers a and b -int geom_mean(int a, int b) inline_ref; -;; integer square root -int sqrt(int a) inline; -;; fixed-point square root -;; fixed248 sqrt(fixed248 x) -int fixed248::sqrt(int x) inline; +/// compute floor(log2(x))+1 +@pure +fun log2_floor_p1(x: int): int + asm "UBITSIZE"; + +@pure +fun mulrshiftr(x: int, y: int, s: int): int + asm "MULRSHIFTR"; + +@pure +fun mulrshiftr256(x: int, y: int): int + asm "256 MULRSHIFTR#"; + +@pure +fun mulrshift256mod(x: int, y: int): (int, int) + asm "256 MULRSHIFT#MOD"; + +@pure +fun mulrshiftr256mod(x: int, y: int): (int, int) + asm "256 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr255mod(x: int, y: int): (int, int) + asm "255 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr248mod(x: int, y: int): (int, int) + asm "248 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr5mod(x: int, y: int): (int, int) + asm "5 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr6mod(x: int, y: int): (int, int) + asm "6 MULRSHIFTR#MOD"; + +@pure +fun mulrshiftr7mod(x: int, y: int): (int, int) + asm "7 MULRSHIFTR#MOD"; + +@pure +fun lshift256divr(x: int, y: int): int + asm "256 LSHIFT#DIVR"; + +@pure +fun lshift256divmodr(x: int, y: int): (int, int) + asm "256 LSHIFT#DIVMODR"; + +@pure +fun lshift255divmodr(x: int, y: int): (int, int) + asm "255 LSHIFT#DIVMODR"; + +@pure +fun lshift2divmodr(x: int, y: int): (int, int) + asm "2 LSHIFT#DIVMODR"; + +@pure +fun lshift7divmodr(x: int, y: int): (int, int) + asm "7 LSHIFT#DIVMODR"; + +@pure +fun lshiftdivmodr(x: int, y: int, s: int): (int, int) + asm "LSHIFTDIVMODR"; + +@pure +fun rshiftr256mod(x: int): (int, int) + asm "256 RSHIFTR#MOD"; + +@pure +fun rshiftr248mod(x: int): (int, int) + asm "248 RSHIFTR#MOD"; + +@pure +fun rshiftr4mod(x: int): (int, int) + asm "4 RSHIFTR#MOD"; + +@pure +fun rshift3mod(x: int): (int, int) + asm "3 RSHIFT#MOD"; + +/// computes y - x (Tolk compiler does not try to use this by itself) +@pure +fun sub_rev(x: int, y: int): int + asm "SUBR"; + +@pure +fun nan(): int + asm "PUSHNAN"; + +@pure +fun is_nan(x: int): int + asm "ISNAN"; -int fixed248::sqr(int x) inline; -const int fixed248::One; - -;; log(2) as fixed248 -int fixed248::log2_const() inline; -;; Pi as fixed248 -int fixed248::Pi_const() inline; - -;; fixed248 exp(fixed248 x) -int fixed248::exp(int x) inline_ref; -;; fixed248 exp2(fixed248 x) -int fixed248::exp2(int x) inline_ref; - -;; fixed248 log(fixed248 x) -int fixed248::log(int x) inline_ref; -;; fixed248 log2(fixed248 x) -int fixed248::log2(int x) inline; - -;; fixed248 pow(fixed248 x, fixed248 y) -int fixed248::pow(int x, int y) inline_ref; - -;; (fixed248, fixed248) sincos(fixed248 x); -(int, int) fixed248::sincos(int x) inline_ref; -;; fixed248 sin(fixed248 x); -int fixed248::sin(int x) inline; -;; fixed248 cos(fixed248 x); -int fixed248::cos(int x) inline; -;; fixed248 tan(fixed248 x); -int fixed248::tan(int x) inline_ref; -;; fixed248 cot(fixed248 x); -int fixed248::cot(int x) inline_ref; - - -;; fixed248 asin(fixed248 x); -int fixed248::asin(int x) inline; -;; fixed248 acos(fixed248 x); -int fixed248::acos(int x) inline; -;; fixed248 atan(fixed248 x); -int fixed248::atan(int x) inline_ref; -;; fixed248 acot(fixed248 x); -int fixed248::acot(int x) inline_ref; - -;; random number uniformly distributed in [0..1) -;; fixed248 random(); -int fixed248::random() inline; -;; random number with standard normal distribution (2100 gas on average) -;; fixed248 nrand(); -int fixed248::nrand() inline; -;; generates a random number approximately distributed according to the standard normal distribution (1200 gas) -;; (fails chi-squared test, but it is shorter and faster than fixed248::nrand()) -;; fixed248 nrand_fast(); -int fixed248::nrand_fast() inline; - --} ;; end (declarations) - -{-------------------- INTERMEDIATE FUNCTIONS -----------------------} - -{- - Intermediate functions are used in the implementations of high-level `fixedNNN::...` functions - if necessary, they can be used to define additional high-level functions for other fixed-point types, such as fixed128, outside this library. They can be also used in a hypothetical floating-point Tolk library. - For these reasons, the declarations of these functions are collected here. --} - -{- function declarations have been commented out, otherwise they are not inlined by the current Tolk compiler - -;; fixed258 tanh(fixed258 x, int steps); -int tanh_f258(int x, int n); - -;; computes exp(x)-1 for |x| <= log(2)/2. -;; fixed257 expm1(fixed257 x); -int expm1_f257(int x); - -;; computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small -;; this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) -;; (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) -(int, int) sincosn_f256(int x, int xe); - -;; compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 -;; (fixed256, fixed257) sincosm1_f256(fixed256 x); -;; slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter -(int, int) sincosm1_f256(int x); - -;; compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 -;; (int, int) tan_aux(fixed256 x); -(int, int) tan_aux_f256(int x); - -;; returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x -;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas -;; (fixed256, int) log_aux_f256(int x); -(int, int) log_aux_f256(int x); - -;; returns (y, s) such that log2(x) = y/2^256 + s for positive integer x -;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas -;; (fixed256, int) log2_aux_f256(int x); -(int, int) log2_aux_f256(int x); - -;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 -;; this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas -;; this is sufficient for most purposes -;; (int, fixed261) atan_aux(fixed256 x) -(int, int) atan_aux_f256(int x); - -;; fixed255 atan(fixed255 x); -int atan_f255(int x); - -;; for -1 <= x < 1 only -;; fixed256 atan_small(fixed256 x); -int atan_f256_small(int x); - -;; fixed255 asin(fixed255 x); -int asin_f255(int x); - -;; fixed254 acos(fixed255 x); -int acos_f255(int x); - -;; generates normally distributed pseudo-random number -;; fixed252 nrand(); -int nrand_f252(int x); - -;; a faster and shorter variant of nrand_f252() that fails chi-squared test -;; (should suffice for most purposes) -;; fixed252 nrand_fast(); -int nrand_fast_f252(int x); - --} ;; end (declarations) - -{---------------- MISSING OPERATIONS AND BUILT-INS -----------------} - -int sgn(int x) asm "SGN"; - -;; compute floor(log2(x))+1 -int log2_floor_p1(int x) asm "UBITSIZE"; - -int mulrshiftr(int x, int y, int s) asm "MULRSHIFTR"; -int mulrshiftr256(int x, int y) asm "256 MULRSHIFTR#"; -(int, int) mulrshift256mod(int x, int y) asm "256 MULRSHIFT#MOD"; -(int, int) mulrshiftr256mod(int x, int y) asm "256 MULRSHIFTR#MOD"; -(int, int) mulrshiftr255mod(int x, int y) asm "255 MULRSHIFTR#MOD"; -(int, int) mulrshiftr248mod(int x, int y) asm "248 MULRSHIFTR#MOD"; -(int, int) mulrshiftr5mod(int x, int y) asm "5 MULRSHIFTR#MOD"; -(int, int) mulrshiftr6mod(int x, int y) asm "6 MULRSHIFTR#MOD"; -(int, int) mulrshiftr7mod(int x, int y) asm "7 MULRSHIFTR#MOD"; - -int lshift256divr(int x, int y) asm "256 LSHIFT#DIVR"; -(int, int) lshift256divmodr(int x, int y) asm "256 LSHIFT#DIVMODR"; -(int, int) lshift255divmodr(int x, int y) asm "255 LSHIFT#DIVMODR"; -(int, int) lshift2divmodr(int x, int y) asm "2 LSHIFT#DIVMODR"; -(int, int) lshift7divmodr(int x, int y) asm "7 LSHIFT#DIVMODR"; -(int, int) lshiftdivmodr(int x, int y, int s) asm "LSHIFTDIVMODR"; - -(int, int) rshiftr256mod(int x) asm "256 RSHIFTR#MOD"; -(int, int) rshiftr248mod(int x) asm "248 RSHIFTR#MOD"; -(int, int) rshiftr4mod(int x) asm "4 RSHIFTR#MOD"; -(int, int) rshift3mod(int x) asm "3 RSHIFT#MOD"; - -;; computes y - x (Tolk compiler does not try to use this by itself) -int sub_rev(int x, int y) asm "SUBR"; - -int nan() asm "PUSHNAN"; -int is_nan(int x) asm "ISNAN"; - -{------------------------ SQUARE ROOTS ----------------------------} - -;; computes sqrt(a*b) exactly rounded to the nearest integer -;; for all 0 <= a, b <= 2^256-1 -;; may be used with b=1 or b=scale of fixed-point numbers -int geom_mean(int a, int b) inline_ref { - ifnot (min(a, b)) { - return 0; - } - int s = log2_floor_p1(a); ;; throws out of range error if a < 0 or b < 0 - int t = log2_floor_p1(b); - ;; NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b - int x = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); - do { - ;; if always used with b=2^const, may be optimized to "const LSHIFTDIVC#" - ;; it is important to use `muldivc` here, not `muldiv` or `muldivr` - int q = (muldivc(a, b, x) - x) / 2; - x += q; - } until (q == 0); - return x; -} - -;; integer square root, computes round(sqrt(a)) for all a>=0. -;; note: `inline` is better than `inline_ref` for such simple functions -int sqrt(int a) inline { - return geom_mean(a, 1); -} - -;; version for fixed248 = fixed-point numbers with scale 2^248 -;; fixed248 sqrt(fixed248 x) -int fixed248::sqrt(int x) inline { - return geom_mean(x, 1 << 248); -} - -;; fixed255 sqrt(fixed255 x) -int fixed255::sqrt(int x) inline { - return geom_mean(x, 1 << 255); -} - -;; fixed248 sqr(fixed248 x); -int fixed248::sqr(int x) inline { - return muldivr(x, x, 1 << 248); -} - -;; fixed255 sqr(fixed255 x); -int fixed255::sqr(int x) inline { - return muldivr(x, x, 1 << 255); -} - -const int fixed248::One = (1 << 248); -const int fixed255::One = (1 << 255); - -{-------------------- USEFUL CONSTANTS --------------------} - -;; store huge constants in inline_ref functions for reuse -;; (y,z) where y=round(log(2)*2^256), z=round((log(2)*2^256-y)*2^128) -;; then log(2) = y/2^256 + z/2^384 -(int, int) log2_xconst_f256() inline_ref { - return (80260960185991308862233904206310070533990667611589946606122867505419956976172, -32272921378999278490133606779486332143); -} - -;; (y,z) where Pi = y/2^254 + z/2^382 -(int, int) Pi_xconst_f254() inline_ref { - return (90942894222941581070058735694432465663348344332098107489693037779484723616546, 108051869516004014909778934258921521947); -} - -;; atan(1/16) as fixed260 -int Atan1_16_f260() inline_ref { - return 115641670674223639132965820642403718536242645001775371762318060545014644837101; ;; true value is ...101.0089... -} - -;; atan(1/8) as fixed259 -int Atan1_8_f259() inline_ref { - return 115194597005316551477397594802136977648153890007566736408151129975021336532841; ;; correction -0.1687... -} - -;; atan(1/32) as fixed261 -int Atan1_32_f261() inline_ref { - return 115754418570128574501879331591757054405465733718902755858991306434399246026247; ;; correction 0.395... -} - -;; inline is better than inline_ref for such very small functions -int log2_const_f256() inline { - (int c, _) = log2_xconst_f256(); - return c; -} - -int fixed248::log2_const() inline { - return log2_const_f256() ~>> 8; -} - -int Pi_const_f254() inline { - (int c, _) = Pi_xconst_f254(); - return c; -} - -int fixed248::Pi_const() inline { - return Pi_const_f254() ~>> 6; -} - -{--------------- HYPERBOLIC TANGENT AND EXPONENT -------------------} - -;; hyperbolic tangent of small x via n+2 terms of Lambert's continued fraction -;; n=17: good for |x| < log(2)/4 = 0.173 -;; fixed258 tanh_f258(fixed258 x, int n) -int tanh_f258(int x, int n) inline_ref { - int x2 = muldivr(x, x, 1 << 255); ;; x^2 as fixed261 - int c = int a = (2 * n + 5) << 250; ;; a=2n+5 as fixed250 - int Two = (1 << 251); ;; 2. as fixed250 - repeat (n) { - a = (c -= Two) + muldivr(x2, 1 << 239, a); ;; a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 - } - a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); ;; a := 3+x^2/a as fixed254 - ;; y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a - return x - (muldivr(x, x2, a + (x2 ~>> 7)) ~>> 7); -} - -;; fixed257 expm1_f257(fixed257 x) -;; computes exp(x)-1 for small x via 19 terms of Lambert's continued fraction for tanh(x/2) -;; good for |x| < log(2)/2 = 0.347 (n=17); consumes ~3500 gas -int expm1_f257(int x) inline_ref { - ;; (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 - int x2 = muldivr(x, x, 1 << 255); ;; x^2 as fixed261 - int Two = (1 << 251); ;; 2. as fixed250 - int c = int a = touch(39) << 250; ;; a=2n+5 as fixed250 - repeat (17) { - a = (c -= Two) + muldivr(x2, 1 << 239, a); ;; a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 - } - a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); ;; a := 3+x^2/a as fixed254 - ;; now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) - int t = (x ~>> 4) - a; ;; t:=x-a as fixed254 - return x - muldivr(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; ;; x - x^2 * (x-a) / (a + x*(x-a)) -} - -;; expm1_f257() may be used to implement specific fixed-point exponentials -;; example: -;; fixed248 exp(fixed248 x) -int fixed248::exp(int x) inline_ref { - var (l2c, l2d) = log2_xconst_f256(); - ;; divide x by log(2) and convert to fixed257 - ;; (int q, x) = muldivmodr(x, 256, l2c); ;; unfortunately, no such built-in - (int q, x) = lshiftdivmodr(x, l2c, 8); - x = 2 * x - muldivr(q, l2d, 1 << 127); - int y = expm1_f257(x); - ;; result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) - return (y ~>> (9 - q)) - (-1 << (248 + q)); - ;; note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 -} - -;; compute 2^x in fixed248 -;; fixed248 exp2(fixed248 x) -int fixed248::exp2(int x) inline_ref { - ;; (int q, x) = divmodr(x, 1 << 248); ;; no such built-in - (int q, x) = rshiftr248mod(x); - x = muldivr(x, log2_const_f256(), 1 << 247); - int y = expm1_f257(x); - return (y ~>> (9 - q)) - (-1 << (248 + q)); -} - -{--------------------- TRIGONOMETRIC FUNCTIONS -----------------------} - -;; fixed260 tan(fixed260 x); -;; computes tan(x) for small |x|> 10)) ~>> 9); -} - -;; fixed260 tan(fixed260 x); -int tan_f260(int x) inline_ref { - return tan_f260_inlined(x); -} - -;; fixed258 tan(fixed258 x); -;; computes tan(x) for small |x|> 6)) ~>> 5); -} - -;; fixed258 tan(fixed258 x); -int tan_f258(int x) inline_ref { - return tan_f258_inlined(x); -} - -;; (fixed259, fixed263) sincosm1(fixed259 x) -;; computes (sin(x), 1-cos(x)) for small |x|<2*atan(1/16) -(int, int) sincosm1_f259_inlined(int x) inline { - int t = tan_f260_inlined(x); ;; t=tan(x/2) as fixed260 - int tt = mulrshiftr256(t, t); ;; t^2 as fixed264 - int y = tt ~/ 512 + (1 << 255); ;; 1+t^2 as fixed255 - ;; 2*t/(1+t^2) as fixed259 and 2*t^2/(1+t^2) as fixed263 - ;; return (muldivr(t, 1 << 255, y), muldivr(tt, 1 << 255, y)); - return (t - muldivr(t / 2, tt, y) ~/ 256, tt - muldivr(tt / 2, tt, y) ~/ 256); -} - -(int, int) sincosm1_f259(int x) inline_ref { - return sincosm1_f259_inlined(x); -} - -;; computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small -;; this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) -;; (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) -(int, int) sincosn_f256(int x, int xe) inline_ref { - ;; var (q, x1) = muldivmodr(x, 8, Atan1_8_f259()); ;; no muldivmodr() builtin - var (q, x1) = lshift2divmodr(abs(x), Atan1_8_f259()); ;; reduce mod theta where theta=2*atan(1/8) - var (si, co) = sincosm1_f259(x1 * 2 + xe); - var (a, b, c) = (-1, 0, 1); - repeat (q) { ;; (a+b*I) *= (8+I)^2 = 63+16*I - (a, b, c) = (63 * a - 16 * b, 16 * a + 63 * b, 65 * c); - } - ;; now a/c = cos(q*theta), b/c = sin(q*theta) exactly(!) - ;; compute (a+b*I)*(1-co+si*I)/c - ;; (b, a) = (lshift256divr(b, c), lshift256divr(a, c)); - (b, int br) = lshift256divmodr(b, c); br = muldivr(br, 128, c); - (a, int ar) = lshift256divmodr(a, c); ar = muldivr(ar, 128, c); - return (sgn(x) * (((mulrshiftr256(b, co) - br) ~/ 16 - mulrshiftr256(a, si)) ~/ 8 - b), - a - ((mulrshiftr256(a, co) - ar) ~/ 16 + mulrshiftr256(b, si)) ~/ 8); -} - -;; compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 -;; (fixed256, fixed257) sincosm1_f256(fixed256 x); -;; slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter -(int, int) sincosm1_f256(int x) inline_ref { - var (si, co) = sincosm1_f259_inlined(x); ;; compute (sin,1-cos)(x/8) in (fixed259,fixed263) - int r = 7; - repeat (r / 2) { - ;; 1-cos(2*x) = 2*sin(x)^2, sin(2*x) = 2*sin(x)*cos(x) - (co, si) = (mulrshiftr256(si, si), si - (mulrshiftr256(si, co) ~>> r)); - r -= 2; - } - return (si, co); -} - -;; compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 -;; (int, int) tan_aux(fixed256 x); -(int, int) tan_aux_f256(int x) inline_ref { - int t = tan_f258_inlined(x); ;; t=tan(x/4) as fixed258 - ;; t:=2*t/(1-t^2)=2*(t-t^3/(t^2-1)) - int tt = mulrshiftr256(t, t); ;; t^2 as fixed260 - t = muldivr(t, tt, tt ~/ 16 + (-1 << 256)) ~/ 16 - t; ;; now t=-tan(x/2) as fixed259 - return (t, mulrshiftr256(t, t) ~/ 4 + (-1 << 256)); ;; return (2*t, t^2-1) as fixed256 -} - -;; sincosm1_f256() and sincosn_f256() may be used to implement trigonometric functions for different fixed-point types -;; example: -;; (fixed248, fixed248) sincos(fixed248 x); -(int, int) fixed248::sincos(int x) inline_ref { - var (Pic, Pid) = Pi_xconst_f254(); - ;; (int q, x) = muldivmodr(x, 128, Pic); ;; no muldivmodr() builtin - (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 - x = 2 * x - muldivr(q, Pid, 1 << 127); - (int si, int co) = sincosm1_f256(x); ;; doesn't make sense to use more accurate sincosn_f256() - co = (1 << 248) - (co ~>> 9); - si ~>>= 8; - repeat (q & 3) { - (si, co) = (co, - si); - } - return (si, co); -} - -;; fixed248 sin(fixed248 x); -;; inline is better than inline_ref for such simple functions -int fixed248::sin(int x) inline { - (int si, _) = fixed248::sincos(x); - return si; -} - -;; fixed248 cos(fixed248 x); -int fixed248::cos(int x) inline { - (_, int co) = fixed248::sincos(x); - return co; -} - -;; similarly, tan_aux_f256() may be used to implement tan() and cot() for specific fixed-point formats -;; fixed248 tan(fixed248 x); -;; not very accurate when |tan(x)| is very large (difficult to do better without floating-point numbers) -;; however, the relative accuracy is approximately 2^-247 in all cases, which is good enough for arguments given up to 2^-249 -int fixed248::tan(int x) inline_ref { - var (Pic, Pid) = Pi_xconst_f254(); - ;; (int q, x) = muldivmodr(x, 128, Pic); ;; no muldivmodr() builtin - (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 - x = 2 * x - muldivr(q, Pid, 1 << 127); - var (a, b) = tan_aux_f256(x); ;; now a/b = tan(x') - if (q & 1) { - (a, b) = (b, - a); - } - return muldivr(a, 1 << 248, b); ;; either -b/a or a/b as fixed248 -} - -;; fixed248 cot(fixed248 x); -int fixed248::cot(int x) inline_ref { - var (Pic, Pid) = Pi_xconst_f254(); - (int q, x) = lshift7divmodr(x, Pic); ;; reduce mod Pi/2 - x = 2 * x - muldivr(q, Pid, 1 << 127); - var (b, a) = tan_aux_f256(x); ;; now b/a = tan(x') - if (q & 1) { - (a, b) = (b, - a); - } - return muldivr(a, 1 << 248, b); ;; either -b/a or a/b as fixed248 -} - -{----------------- INVERSE HYPERBOLIC TANGENT AND LOGARITHMS -----------------} - -;; inverse hyperbolic tangent of small x, evaluated by means of n terms of the continued fraction -;; valid for |x| < 2^-2.5 ~ 0.18 if n=37 (slightly less accurate with n=36) -;; |x| < 1/8 if n=32; |x| < 2^-3.5 if n=28; |x| < 1/16 if n=25 -;; |x| < 2^-4.5 if n=23; |x| < 1/32 if n=21; |x| < 1/64 if n=18 -;; fixed258 atanh(fixed258 x); -int atanh_f258(int x, int n) inline_ref { - int x2 = mulrshiftr256(x, x); ;; x^2 as fixed260 - int One = (1 << 254); - int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 - repeat (n - 1) { - ;; a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 - int t = One - muldivr(x2, 1 << 248, a); ;; t := 1 - x^2 / a - a = muldivr(t, n, (int n1 = n - 1)) + One; - n = n1; - } - ;; x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a - ;; int d = muldivr(x2, 1 << 255, a - (x2 ~>> 6)); ;; d/(1-d) = x^2/(a-x^2) as fixed261 - ;; return x + (mulrshiftr256(x, d) ~>> 5); - return x + muldivr(x, x2 / 2, a - x2 ~/ 64) ~/ 32; -} - -;; number of terms n should be chosen as for atanh_f258() -;; fixed261 atanh(fixed261 x); -int atanh_f261_inlined(int x, int n) inline { - int x2 = mulrshiftr256(x, x); ;; x^2 as fixed266 - int One = (1 << 254); - int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 - repeat (n - 1) { - ;; a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 - int t = One - muldivr(x2, 1 << 242, a); ;; t := 1 - x^2 / a - a = muldivr(t, n, (int n1 = n - 1)) + One; - n = n1; - } - ;; x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a - ;; int d = muldivr(x2, 1 << 255, a - (x2 ~>> 12)); ;; d/(1-d) = x^2/(a-x^2) as fixed267 - ;; return x + (mulrshiftr256(x, d) ~>> 11); - return x + muldivr(x, x2, a - x2 ~/ 4096) ~/ 4096; -} - -;; fixed261 atanh(fixed261 x); -int atanh_f261(int x, int n) inline_ref { - return atanh_f261_inlined(x, n); -} - -;; returns (y, s) such that log(x) = y/2^257 + s*log(2) for positive integer x -;; (fixed257, int) log_aux(int x) -(int, int) log_aux_f257(int x) inline_ref { - int s = log2_floor_p1(x); - x <<= 256 - s; - int t = touch(-1 << 256); - if ((x >> 249) <= 90) { - ;; t~touch(); - t >>= 1; - s -= 1; - } - x += t; - int `2x` = 2 * x; - int y = lshift256divr(`2x`, (x >> 1) - t); - ;; y = `2x` - (mulrshiftr256(2x, y) ~>> 2); ;; this line could improve precision on very rare occasions - return (atanh_f258(y, 36), s); -} - -;; computes 33^m for small m -int pow33(int m) inline { - int t = 1; - repeat (m) { t *= 33; } - return t; -} - -;; computes 33^m for small 0<=m<=22 -;; slightly faster than pow33() -int pow33b(int m) inline { - (int mh, int ml) = m /% 5; - int t = 1; - repeat (ml) { t *= 33; } - repeat (mh) { t *= 33 * 33 * 33 * 33 * 33; } - return t; -} - -;; returns (s, q, y) such that log(x) = s*log(2) + q*log(33/32) + y/2^260 for positive integer x -;; (int, int, fixed260) log_auxx_f260(int x); -(int, int, int) log_auxx_f260(int x) inline_ref { - int s = log2_floor_p1(x) - 1; - x <<= 255 - s; ;; rescale to 1 <= x < 2 as fixed255 - int t = touch(2873) << 244; ;; ~ (33/32)^11 ~ sqrt(2) as fixed255 - int x1 = (x - t) >> 1; - int q = muldivr(x1, 65, x1 + t) + 11; ;; crude approximation to round(log(x)/log(33/32)) - ;; t = 1; repeat (q) { t *= 33; } ;; t:=33^q, 0<=q<=22 - t = pow33b(q); - t <<= (51 - q) * 5; ;; t:=(33/32)^q as fixed255, nearest power of 33/32 to x - x -= t; - int y = lshift256divr(x << 4, (x >> 1) + t); ;; y = (x-t)/(x+t) as fixed261 - y = atanh_f261(y, 18); ;; atanh((x-t)/(x+t)) as fixed261, or log(x/t) as fixed260 - return (s, q, y); -} - -;; returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x -;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas -;; (fixed256, int) log_aux_f256(int x); -(int, int) log_aux_f256(int x) inline_ref { - var (s, q, y) = log_auxx_f260(x); - var (yh, yl) = rshiftr4mod(y); ;; y ~/% 16 , but Tolk does not optimize this to RSHIFTR#MOD - ;; int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; ;; log(33/32) as fixed256 - ;; int Log33_32_l = -3769; ;; log(33/32) = Log33_32 / 2^256 + Log33_32_l / 2^269 - yh += (yl * 512 + q * -3769) ~>> 13; ;; compensation, may be removed if slightly worse accuracy is acceptable - int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; ;; log(33/32) as fixed256 - return (yh + q * Log33_32, s); -} - -;; returns (y, s) such that log2(x) = y/2^256 + s for positive integer x -;; this function is very precise (error less than 0.6 ulp) and consumes < 7k gas -;; (fixed256, int) log2_aux_f256(int x); -(int, int) log2_aux_f256(int x) inline_ref { - var (s, q, y) = log_auxx_f260(x); - y = lshift256divr(y, log2_const_f256()) ~>> 4; ;; y/log(2) as fixed256 - int Log33_32 = 5140487830366106860412008603913034462883915832139695448455767612111363481357; ;; log_2(33/32) as fixed256 - ;; Log33_32/2^256 happens to be a very precise approximation to log_2(33/32), no compensation required - return (y + q * Log33_32, s); -} - -;; functions log_aux_f256() and log2_aux_f256() may be used to implement specific fixed-point instances of log() and log2() - -;; fixed248 log(fixed248 x) -int fixed248::log(int x) inline_ref { - var (y, s) = log_aux_f256(x); - return muldivr(s - 248, log2_const_f256(), 1 << 8) + (y ~>> 8); - ;; return muldivr(s - 248, 80260960185991308862233904206310070533990667611589946606122867505419956976172, 1 << 8) + (y ~>> 8); -} - -;; fixed248 log2(fixed248 x) -int fixed248::log2(int x) inline { - var (y, s) = log2_aux_f256(x); - return ((s - 248) << 248) + (y ~>> 8); -} - -;; computes x^y as exp(y*log(x)), x >= 0 -;; fixed248 pow(fixed248 x, fixed248 y); -int fixed248::pow(int x, int y) inline_ref { - ifnot (y) { - return 1 << 248; ;; x^0 = 1 - } - if (x <= 0) { - int bad = (x | y) < 0; - return 0 >> bad; ;; 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise - } - var (l, s) = log2_aux_f256(x); - s -= 248; ;; log_2(x) = s+l, l is fixed256, 0<=l<1 - ;; compute (s+l)*y = q+ll - var (q1, r1) = mulrshiftr248mod(s, y); ;; muldivmodr(s, y, 1 << 248) - var (q2, r2) = mulrshift256mod(l, y); - r2 >>= 247; - var (q3, r3) = rshiftr248mod(q2); ;; divmodr(q2, 1 << 248); - var (q, ll) = rshiftr248mod(r1 + r3); - ll = 512 * ll + r2; - q += q1 + q3; - ;; now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2 - int sq = q + 248; - if (sq <= 0) { - return - (sq == 0); ;; underflow - } - y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); - return (y ~>> (9 - q)) - (-1 << sq); -} - -{--------------------- INVERSE TRIGONOMETRIC FUNCTIONS -------------------} - -;; number of terms n should be chosen as for atanh_f258() -;; fixed259 atan(fixed259 x); -int atan_f259(int x, int n) inline_ref { - int x2 = mulrshiftr256(x, x); ;; x^2 as fixed262 - int One = (1 << 254); - int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 - repeat (n - 1) { - ;; a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 - int t = One + muldivr(x2, 1 << 246, a); ;; t := 1 + x^2 / a - a = muldivr(t, n, (int n1 = n - 1)) + One; - n = n1; - } - ;; x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a - return x - muldivr(x, x2, a + x2 ~/ 256) ~/ 256; -} - -;; number of terms n should be chosen as for atanh_f261() -;; fixed261 atan(fixed261 x); -int atan_f261_inlined(int x, int n) inline { - int x2 = mulrshiftr256(x, x); ;; x^2 as fixed266 - int One = (1 << 254); - int a = One ~/ n + (1 << 255); ;; a := 2 + 1/n as fixed254 - repeat (n - 1) { - ;; a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 - int t = One + muldivr(x2, 1 << 242, a); ;; t := 1 + x^2 / a - a = muldivr(t, n, (int n1 = n - 1)) + One; - n = n1; - } - ;; x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a - return x - muldivr(x, x2, a + x2 ~/ 4096) ~/ 4096; -} - -;; fixed261 atan(fixed261 x); -int atan_f261(int x, int n) inline_ref { - return atan_f261_inlined(x, n); -} - -;; computes (q,a,b) such that q is approximately atan(x)/atan(1/32) and a+b*I=(1+I/32)^q as fixed255 -;; then b/a=atan(q*atan(1/32)) exactly, and (a,b) is almost a unit vector pointing in the direction of (1,x) -;; must have |x|<1.1, x is fixed24 -;; (int, fixed255, fixed255) atan_aux_prereduce(fixed24 x); -(int, int, int) atan_aux_prereduce(int x) inline_ref { - int xu = abs(x); - int tc = 7214596; ;; tan(13*theta) as fixed24 where theta=atan(1/32) - int t1 = muldivr(xu - tc, 1 << 88, xu * tc + (1 << 48)); ;; tan(x') as fixed64 where x'=atan(x)-13*theta - ;; t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta - int q = muldivr(t1 * 3073, 1 << 59, t1 * t1 + (touch(3) << 128)) + 13; ;; approximately round(atan(x)/theta), 0<=q<=25 - var (pa, pb) = (33226912, 5232641); ;; (32+I)^5 - var (qh, ql) = q /% 5; - var (a, b) = (1 << (5 * (51 - q)), 0); ;; (1/32^q, 0) as fixed255 - repeat (ql) { ;; a+b*I *= 32+I - (a, b) = (sub_rev(touch(b), 32 * a), a + 32 * b); ;; same as (32 * a - b, 32 * b + a), but more efficient - } - repeat (qh) { ;; a+b*I *= (32+I)^5 = pa + pb*I - (a, b) = (a * pa - b * pb, a * pb + b * pa); - } - int xs = sgn(x); - return (xs * q, a, xs * b); -} - -;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 -;; this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas -;; this is sufficient for most purposes -;; (int, fixed261) atan_aux(fixed256 x) -(int, int) atan_aux_f256(int x) inline_ref { - var (q, a, b) = atan_aux_prereduce(x ~>> 232); ;; convert x to fixed24 - ;; now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x - ;; compute y = u/v = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) - var (u, ul) = mulrshiftr256mod(a, x); - u = (ul ~>> 250) + ((u - b) << 6); ;; |u| < 1/32, convert fixed255 -> fixed261 - int v = a + mulrshiftr256(b, x); ;; v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 - int y = muldivr(u, 1 << 255, v); ;; y = u/v as fixed261 - int z = atan_f261_inlined(y, 18); ;; z = atan(x)-q*atan(1/32) - return (q, z); -} - -;; compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 -;; this function is very accurate (error < 2 ulp), but it consumes >7k gas -;; in most cases, faster function atan_aux_f256() should be used -;; (int, fixed261) atan_auxx(fixed256 x) -(int, int) atan_auxx_f256(int x) inline_ref { - var (q, a, b) = atan_aux_prereduce(x ~>> 232); ;; convert x to fixed24 - ;; now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x - ;; compute y = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) - ;; use sort of double precision arithmetic for this - var (u, ul) = mulrshiftr256mod(a, x); - ul /= 2; - u -= b; ;; |u| < 1/32 as fixed255 - var (v, vl) = mulrshiftr256mod(b, x); - vl /= 2; - v += a; ;; v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 - ;; y = (u + ul*eps) / (v + vl*eps) = u/v + (ul - vl * u/v)/v * eps where eps=1/2^255 - var (y, r) = lshift255divmodr(u, v); ;; y = u/v as fixed255 - int yl = muldivr(ul + r, 1 << 255, v) - muldivr(vl, y, v); ;; y/2^255 + yl/2^510 represent u/v - y = (yl ~>> 249) + (y << 6); ;; convert y to fixed261 - int z = atan_f261_inlined(y, 18); ;; z = atan(x)-q*atan(1/32) - return (q, z); -} - -;; consumes ~ 8k gas -;; fixed255 atan(fixed255 x); -int atan_f255(int x) inline_ref { - int s = (x ~>> 256); - touch(x); - if (s) { - x = lshift256divr(-1 << 255, x); ;; x:=-1/x as fixed256 - } else { - x *= 2; ;; convert to fixed256 - } - var (q, z) = atan_aux_f256(x); - ;; now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 - var (Pi_h, Pi_l) = Pi_xconst_f254(); ;; Pi/2 as fixed255 + fixed383 - var (qh, ql) = mulrshiftr6mod (q, Atan1_32_f261()); - return qh + s * Pi_h + (z + ql + muldivr(s, Pi_l, 1 << 122)) ~/ 64; -} - -;; computes atan(x) for -1 <= x < 1 only -;; fixed256 atan_small(fixed256 x); -int atan_f256_small(int x) inline_ref { - var (q, z) = atan_aux_f256(x); - ;; now atan(x) = z + q*atan(1/32), z is fixed261 - var (qh, ql) = mulrshiftr5mod (q, Atan1_32_f261()); - return qh + (z + ql) ~/ 32; -} - -;; fixed255 asin(fixed255 x); -int asin_f255(int x) inline_ref { - int a = fixed255::One - fixed255::sqr(x); ;; a:=1-x^2 - ifnot (a) { - return sgn(x) * Pi_const_f254(); ;; Pi/2 or -Pi/2 - } - int y = fixed255::sqrt(a); ;; sqrt(1-x^2) - int t = - lshift256divr(x, (-1 << 255) - y); ;; t = x/(1+sqrt(1-x^2)) avoiding overflow - return atan_f256_small(t); ;; asin(x)=2*atan(t) -} - -;; fixed254 acos(fixed255 x); -int acos_f255(int x) inline_ref { - int Pi = Pi_const_f254(); - if (x == (-1 << 255)) { - return Pi; ;; acos(-1) = Pi - } - Pi /= 2; - int y = fixed255::sqrt(fixed255::One - fixed255::sqr(x)); ;; sqrt(1-x^2) - int t = lshift256divr(x, (-1 << 255) - y); ;; t = -x/(1+sqrt(1-x^2)) avoiding overflow - return Pi + atan_f256_small(t) ~/ 2; ;; acos(x)=Pi/2 + 2*atan(t) -} - -;; consumes ~ 10k gas -;; fixed248 asin(fixed248 x) -int fixed248::asin(int x) inline { - return asin_f255(x << 7) ~>> 7; -} - -;; consumes ~ 10k gas -;; fixed248 acos(fixed248 x) -int fixed248::acos(int x) inline { - return acos_f255(x << 7) ~>> 6; -} - -;; consumes ~ 7500 gas -;; fixed248 atan(fixed248 x); -int fixed248::atan(int x) inline_ref { - int s = (x ~>> 249); - touch(x); - if (s) { - s = sgn(s); - x = lshift256divr(-1 << 248, x); ;; x:=-1/x as fixed256 - } else { - x <<= 8; ;; convert to fixed256 - } - var (q, z) = atan_aux_f256(x); - ;; now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 - return (z ~/ 64 + s * Pi_const_f254() + muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert -} - -;; fixed248 acot(fixed248 x); -int fixed248::acot(int x) inline_ref { - int s = (x ~>> 249); - touch(x); - if (s) { - x = lshift256divr(-1 << 248, x); ;; x:=-1/x as fixed256 - s = 0; - } else { - x <<= 8; ;; convert to fixed256 - s = sgn(x); - } - var (q, z) = atan_aux_f256(x); - ;; now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 - return (s * Pi_const_f254() - z ~/ 64 - muldivr(q, Atan1_32_f261(), 64)) ~/ 128; ;; compute in fixed255, then convert -} - -{--------------------- PSEUDO-RANDOM NUMBERS -------------------} - -;; random number with standard normal distribution N(0,1) -;; generated by Kinderman--Monahan ratio method modified by J.Leva -;; spends ~ 2k..3k gas on average -;; fixed252 nrand(); -int nrand_f252() inline_ref { - var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043); - ;; 4/sqrt(e*Pi) = 1.369 loop iterations on average - do { - var (u, v) = (random() / 16 + 1, muldivr(random() - (1 << 255), 7027, 1 << 16)); ;; fixed252; 7027=ceil(sqrt(8/e)*2^12) - int va = abs(v); - var (u1, v1) = (u - s, va - t); ;; (u - 29483/2^16, abs(v) + 3167/2^13) as fixed252 - ;; Q := u1^2 + v1 * (A*v1 - B*u1) as fixed252 where A=12845/2^16, B=16693/2^16 - int Q = muldivr(u1, u1, 1 << 252) + muldivr(v1, muldivr(v1, A, 1 << 16) - muldivr(u1, B, 1 << 16), 1 << 252); - ;; must have 9043 / 2^15 < Q < 9125 / 2^15, otherwise accept if smaller, reject if larger - int Qd = (Q >> 237) - r0; - if ((Qd < 9125 - 9043) & (va / u < 16)) { - x = muldivr(v, 1 << 252, u); ;; x:=v/u as fixed252; reject immediately if |v/u| >= 16 - if (Qd >= 0) { ;; immediately accept if Qd < 0 - ;; rarely taken branch - 0.012 times per call on average - ;; check condition v^2 < -4*u^2*log(u), or equivalent condition u < exp(-x^2/4) for x=v/u - int xx = mulrshiftr256(x, x) ~/ 4; ;; x^2/4 as fixed248 - int ex = fixed248::exp(- xx) * 16; ;; exp(-x^2/4) as fixed252 - if (u > ex) { - x = nan(); ;; condition false, reject - } - } +/*----------------------- SQUARE ROOTS ---------------------------*/ + +/// computes sqrt(a*b) exactly rounded to the nearest integer +/// for all 0 <= a, b <= 2^256-1 +/// may be used with b=1 or b=scale of fixed-point numbers +@pure +@inline_ref +fun geom_mean(a: int, b: int): int { + if (!min(a, b)) { + return 0; } - } until (~ is_nan(x)); - return x; + var s: int = log2_floor_p1(a); // throws out of range error if a < 0 or b < 0 + var t: int = log2_floor_p1(b); + // NB: (a-b)/2+b == (a+b)/2, but without overflow for large a and b + var x: int = (s == t ? (a - b) / 2 + b : 1 << ((s + t) / 2)); + do { + // if always used with b=2^const, may be optimized to "const LSHIFTDIVC#" + // it is important to use `muldivc` here, not `muldiv` or `muldivr` + var q: int = (muldivc(a, b, x) - x) / 2; + x += q; + } while (q); + return x; } -;; generates a random number approximately distributed according to the standard normal distribution -;; much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed -;; fixed252 nrand_fast(); -int nrand_fast_f252() inline_ref { - int t = touch(-3) << 253; ;; -6. as fixed252 - repeat (12) { - t += random() / 16; ;; add together 12 uniformly random numbers - } - return t; +/// integer square root, computes round(sqrt(a)) for all a>=0. +/// note: `inline` is better than `inline_ref` for such simple functions +@pure +@inline +fun sqrt(a: int): int { + return geom_mean(a, 1); } -;; random number uniformly distributed in [0..1) -;; fixed248 random(); -int fixed248::random() inline { - return random() >> 8; +/// version for fixed248 = fixed-point numbers with scale 2^248 +/// fixed248 sqrt(fixed248 x) +@pure +@inline +fun fixed248_sqrt(x: int): int { + return geom_mean(x, 1 << 248); } -;; random number with standard normal distribution -;; fixed248 nrand(); -int fixed248::nrand() inline { - return nrand_f252() ~>> 4; +/// fixed255 sqrt(fixed255 x) +@pure +@inline +fun fixed255_sqrt(x: int): int { + return geom_mean(x, 1 << 255); } -;; generates a random number approximately distributed according to the standard normal distribution -;; fixed248 nrand_fast(); -int fixed248::nrand_fast() inline { - return nrand_fast_f252() ~>> 4; +/// fixed248 sqr(fixed248 x); +@pure +@inline +fun fixed248_sqr(x: int): int { + return muldivr(x, x, 1 << 248); +} + +/// fixed255 sqr(fixed255 x); +@pure +@inline +fun fixed255_sqr(x: int): int { + return muldivr(x, x, 1 << 255); +} + +const fixed248_One: int = (1 << 248); +const fixed255_One: int = (1 << 255); + +/*------------------- USEFUL CONSTANTS -------------------*/ + +/// store huge constants in inline_ref functions for reuse +/// (y,z) where y=round(log(2)*2^256), z=round((log(2)*2^256-y)*2^128) +/// then log(2) = y/2^256 + z/2^384 +@pure +@inline_ref +fun log2_xconst_f256(): (int, int) { + return (80260960185991308862233904206310070533990667611589946606122867505419956976172, -32272921378999278490133606779486332143); +} + +/// (y,z) where Pi = y/2^254 + z/2^382 +@pure +@inline_ref +fun Pi_xconst_f254(): (int, int) { + return (90942894222941581070058735694432465663348344332098107489693037779484723616546, 108051869516004014909778934258921521947); +} + +/// atan(1/16) as fixed260 +@pure +@inline_ref +fun Atan1_16_f260(): int { + return 115641670674223639132965820642403718536242645001775371762318060545014644837101; // true value is ...101.0089... +} + +/// atan(1/8) as fixed259 +@pure +@inline_ref +fun Atan1_8_f259(): int { + return 115194597005316551477397594802136977648153890007566736408151129975021336532841; // correction -0.1687... +} + +/// atan(1/32) as fixed261 +@pure +@inline_ref +fun Atan1_32_f261(): int { + return 115754418570128574501879331591757054405465733718902755858991306434399246026247; // correction 0.395... +} + +/// inline is better than inline_ref for such very small functions +@pure +@inline +fun log2_const_f256(): int { + var (c: int, _) = log2_xconst_f256(); + return c; +} + +@pure +@inline +fun fixed248_log2_const(): int { + return log2_const_f256() ~>> 8; +} + +@pure +@inline +fun Pi_const_f254(): int { + var (c: auto, _) = Pi_xconst_f254(); + return c; +} + +@pure +@inline +fun fixed248_Pi_const(): int { + return Pi_const_f254() ~>> 6; +} + +/*-------------- HYPERBOLIC TANGENT AND EXPONENT ------------------*/ + +/// hyperbolic tangent of small x via n+2 terms of Lambert's continued fraction +/// n=17: good for |x| < log(2)/4 = 0.173 +/// fixed258 tanh_f258(fixed258 x, int n) +@pure +@inline_ref +fun tanh_f258(x: int, n: int): int { + var x2: int = muldivr(x, x, 1 << 255); // x^2 as fixed261 + var a: int = (2 * n + 5) << 250; // a=2n+5 as fixed250 + var c = a; + var Two: int = (1 << 251); // 2. as fixed250 + repeat (n) { + a = (c -= Two) + muldivr(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 + // y = x/(1+a') = x - x*a'/(1+a') = x - x*x^2/(a+x^2) where a' = x^2/a + return x - (muldivr(x, x2, a + (x2 ~>> 7)) ~>> 7); +} + +/// fixed257 expm1_f257(fixed257 x) +/// computes exp(x)-1 for small x via 19 terms of Lambert's continued fraction for tanh(x/2) +/// good for |x| < log(2)/2 = 0.347 (n=17); consumes ~3500 gas +@pure +@inline_ref +fun expm1_f257(x: int): int { + // (almost) compute tanh(x/2) first; x/2 as fixed258 = x as fixed257 + var x2: int = muldivr(x, x, 1 << 255); // x^2 as fixed261 + var Two: int = (1 << 251); // 2. as fixed250 + var a: int = touch(39) << 250; // a=2n+5 as fixed250 + var c = a; + repeat (17) { + a = (c -= Two) + muldivr(x2, 1 << 239, a); // a := 2k+1+x^2/a as fixed250, k=n+1,n,...,2 + } + a = (touch(3) << 254) + muldivr(x2, 1 << 243, a); // a := 3+x^2/a as fixed254 + // now tanh(x/2) = x/(1+a') where a'=x^2/a ; apply exp(x)-1=2*tanh(x/2)/(1-tanh(x/2)) + var t: int = (x ~>> 4) - a; // t:=x-a as fixed254 + return x - muldivr(x2, t / 2, a + mulrshiftr256(x, t) ~/ 4) ~/ 4; // x - x^2 * (x-a) / (a + x*(x-a)) +} + +/// expm1_f257() may be used to implement specific fixed-point exponentials +/// example: +/// fixed248 exp(fixed248 x) +@pure +@inline_ref +fun fixed248_exp(x: int): int { + var (l2c, l2d) = log2_xconst_f256(); + // divide x by log(2) and convert to fixed257 + // (int q, x) = muldivmodr(x, 256, l2c); // unfortunately, no such built-in + var (q: int, x redef) = lshiftdivmodr(x, l2c, 8); + x = 2 * x - muldivr(q, l2d, 1 << 127); + var y: int = expm1_f257(x); + // result is (1 + y) * (2^q) --> ((1 << 257) + y) >> (9 - q) + return (y ~>> (9 - q)) - (-1 << (248 + q)); + // note that (y ~>> (9 - q)) + (1 << (248 + q)) leads to overflow when q=8 +} + +/// compute 2^x in fixed248 +/// fixed248 exp2(fixed248 x) +@pure +@inline_ref +fun fixed248_exp2(x: int): int { + // (int q, x) = divmodr(x, 1 << 248); // no such built-in + var (q: int, x redef) = rshiftr248mod(x); + x = muldivr(x, log2_const_f256(), 1 << 247); + var y: int = expm1_f257(x); + return (y ~>> (9 - q)) - (-1 << (248 + q)); +} + +/*-------------------- TRIGONOMETRIC FUNCTIONS ----------------------*/ + +/// fixed260 tan(fixed260 x); +/// computes tan(x) for small |x|> 10)) ~>> 9); +} + +/// fixed260 tan(fixed260 x); +@pure +@inline_ref +fun tan_f260(x: int): int { + return tan_f260_inlined(x); +} + +/// fixed258 tan(fixed258 x); +/// computes tan(x) for small |x|> 6)) ~>> 5); +} + +/// fixed258 tan(fixed258 x); +@pure +@inline_ref +fun tan_f258(x: int): int { + return tan_f258_inlined(x); +} + +/// (fixed259, fixed263) sincosm1(fixed259 x) +/// computes (sin(x), 1-cos(x)) for small |x|<2*atan(1/16) +@pure +@inline +fun sincosm1_f259_inlined(x: int): (int, int) { + var t: int = tan_f260_inlined(x); // t=tan(x/2) as fixed260 + var tt: int = mulrshiftr256(t, t); // t^2 as fixed264 + var y: int = tt ~/ 512 + (1 << 255); // 1+t^2 as fixed255 + // 2*t/(1+t^2) as fixed259 and 2*t^2/(1+t^2) as fixed263 + // return (muldivr(t, 1 << 255, y), muldivr(tt, 1 << 255, y)); + return (t - muldivr(t / 2, tt, y) ~/ 256, tt - muldivr(tt / 2, tt, y) ~/ 256); +} + +@pure +@inline_ref +fun sincosm1_f259(x: int): (int, int) { + return sincosm1_f259_inlined(x); +} + +/// computes (sin(x+xe),-cos(x+xe)) for |x| <= Pi/4, xe very small +/// this function is very accurate, error less than 0.7 ulp (consumes ~ 5500 gas) +/// (fixed256, fixed256) sincosn(fixed256 x, fixed259 xe) +@pure +@inline_ref +fun sincosn_f256(x: int, xe: int): (int, int) { + // var (q, x1) = muldivmodr(x, 8, Atan1_8_f259()); // no muldivmodr() builtin + var (q, x1) = lshift2divmodr(abs(x), Atan1_8_f259()); // reduce mod theta where theta=2*atan(1/8) + var (si, co) = sincosm1_f259(x1 * 2 + xe); + var (a, b, c) = (-1, 0, 1); + repeat (q) { + // (a+b*I) *= (8+I)^2 = 63+16*I + (a, b, c) = (63 * a - 16 * b, 16 * a + 63 * b, 65 * c); + } + // now a/c = cos(q*theta), b/c = sin(q*theta) exactly(!) + // compute (a+b*I)*(1-co+si*I)/c + // (b, a) = (lshift256divr(b, c), lshift256divr(a, c)); + var (b redef, br: int) = lshift256divmodr(b, c); br = muldivr(br, 128, c); + var (a redef, ar: int) = lshift256divmodr(a, c); ar = muldivr(ar, 128, c); + return (sgn(x) * (((mulrshiftr256(b, co) - br) ~/ 16 - mulrshiftr256(a, si)) ~/ 8 - b), + a - ((mulrshiftr256(a, co) - ar) ~/ 16 + mulrshiftr256(b, si)) ~/ 8); +} + +/// compute (sin(x),1-cos(x)) in fixed256 for |x| < 16*atan(1/16) = 0.9987 +/// (fixed256, fixed257) sincosm1_f256(fixed256 x); +/// slightly less accurate than sincosn_f256() (error up to 3/2^256), but faster (~ 4k gas) and shorter +@pure +@inline_ref +fun sincosm1_f256(x: int): (int, int) { + var (si, co) = sincosm1_f259_inlined(x); // compute (sin,1-cos)(x/8) in (fixed259,fixed263) + var r: int = 7; + repeat (r / 2) { + // 1-cos(2*x) = 2*sin(x)^2, sin(2*x) = 2*sin(x)*cos(x) + (co, si) = (mulrshiftr256(si, si), si - (mulrshiftr256(si, co) ~>> r)); + r -= 2; + } + return (si, co); +} + +/// compute (p, q) such that p/q = tan(x) for |x|<2*atan(1/2)=1899/2048=0.927 +/// (int, int) tan_aux(fixed256 x); +@pure +@inline_ref +fun tan_aux_f256(x: int): (int, int) { + var t: int = tan_f258_inlined(x); // t=tan(x/4) as fixed258 + // t:=2*t/(1-t^2)=2*(t-t^3/(t^2-1)) + var tt: int = mulrshiftr256(t, t); // t^2 as fixed260 + t = muldivr(t, tt, tt ~/ 16 + (-1 << 256)) ~/ 16 - t; // now t=-tan(x/2) as fixed259 + return (t, mulrshiftr256(t, t) ~/ 4 + (-1 << 256)); // return (2*t, t^2-1) as fixed256 +} + +/// sincosm1_f256() and sincosn_f256() may be used to implement trigonometric functions for different fixed-point types +/// example: +/// (fixed248, fixed248) sincos(fixed248 x); +@pure +@inline_ref +fun fixed248_sincos(x: int): (int, int) { + var (Pic, Pid) = Pi_xconst_f254(); + // (int q, x) = muldivmodr(x, 128, Pic); // no muldivmodr() builtin + var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (si: int, co: int) = sincosm1_f256(x); // doesn't make sense to use more accurate sincosn_f256() + co = (1 << 248) - (co ~>> 9); + si = si ~>> 8; + repeat (q & 3) { + (si, co) = (co, -si); + } + return (si, co); +} + +/// fixed248 sin(fixed248 x); +/// inline is better than inline_ref for such simple functions +@pure +@inline +fun fixed248_sin(x: int): int { + var (si: int, _) = fixed248_sincos(x); + return si; +} + +/// fixed248 cos(fixed248 x); +@pure +@inline +fun fixed248_cos(x: int): int { + var (_, co: int) = fixed248_sincos(x); + return co; +} + +/// similarly, tan_aux_f256() may be used to implement tan() and cot() for specific fixed-point formats +/// fixed248 tan(fixed248 x); +/// not very accurate when |tan(x)| is very large (difficult to do better without floating-point numbers) +/// however, the relative accuracy is approximately 2^-247 in all cases, which is good enough for arguments given up to 2^-249 +@pure +@inline_ref +fun fixed248_tan(x: int): int { + var (Pic, Pid) = Pi_xconst_f254(); + // (int q, x) = muldivmodr(x, 128, Pic); // no muldivmodr() builtin + var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (a, b) = tan_aux_f256(x); // now a/b = tan(x') + if (q & 1) { + (a, b) = (b, -a); + } + return muldivr(a, 1 << 248, b); // either -b/a or a/b as fixed248 +} + +/// fixed248 cot(fixed248 x); +@pure +@inline_ref +fun fixed248_cot(x: int): int { + var (Pic, Pid) = Pi_xconst_f254(); + var (q: int, x redef) = lshift7divmodr(x, Pic); // reduce mod Pi/2 + x = 2 * x - muldivr(q, Pid, 1 << 127); + var (b, a) = tan_aux_f256(x); // now b/a = tan(x') + if (q & 1) { + (a, b) = (b, -a); + } + return muldivr(a, 1 << 248, b); // either -b/a or a/b as fixed248 +} + +/*---------------- INVERSE HYPERBOLIC TANGENT AND LOGARITHMS ----------------*/ + +/// inverse hyperbolic tangent of small x, evaluated by means of n terms of the continued fraction +/// valid for |x| < 2^-2.5 ~ 0.18 if n=37 (slightly less accurate with n=36) +/// |x| < 1/8 if n=32; |x| < 2^-3.5 if n=28; |x| < 1/16 if n=25 +/// |x| < 2^-4.5 if n=23; |x| < 1/32 if n=21; |x| < 1/64 if n=18 +/// fixed258 atanh(fixed258 x); +@pure +@inline_ref +fun atanh_f258(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed260 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + var t: int = One - muldivr(x2, 1 << 248, a); // t := 1 - x^2 / a + var n1: int = n - 1; + a = muldivr(t, n, n1) + One; + n = n1; + } + // x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + // int d = muldivr(x2, 1 << 255, a - (x2 ~>> 6)); // d/(1-d) = x^2/(a-x^2) as fixed261 + // return x + (mulrshiftr256(x, d) ~>> 5); + return x + muldivr(x, x2 / 2, a - x2 ~/ 64) ~/ 32; +} + +/// number of terms n should be chosen as for atanh_f258() +/// fixed261 atanh(fixed261 x); +@pure +@inline +fun atanh_f261_inlined(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed266 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 - x^2 / a)(1 + 1/n) as fixed254 + var t: int = One - muldivr(x2, 1 << 242, a); // t := 1 - x^2 / a + var n1: int = n - 1; + a = muldivr(t, n, n1) + One; + n = n1; + } + // x / (1 - x^2 / a) = x / (1 - d) = x + x * d / (1 - d) for d = x^2 / a + // int d = muldivr(x2, 1 << 255, a - (x2 ~>> 12)); // d/(1-d) = x^2/(a-x^2) as fixed267 + // return x + (mulrshiftr256(x, d) ~>> 11); + return x + muldivr(x, x2, a - x2 ~/ 4096) ~/ 4096; +} + +/// fixed261 atanh(fixed261 x); +@pure +@inline_ref +fun atanh_f261(x: int, n: int): int { + return atanh_f261_inlined(x, n); +} + +/// returns (y, s) such that log(x) = y/2^257 + s*log(2) for positive integer x +/// (fixed257, int) log_aux(int x) +@pure +@inline_ref +fun log_aux_f257(x: int): (int, int) { + var s: int = log2_floor_p1(x); + x <<= 256 - s; + var t: int = touch(-1 << 256); + if ((x >> 249) <= 90) { + // t~touch(); + t >>= 1; + s -= 1; + } + x += t; + var `2x`: int = 2 * x; + var y: int = lshift256divr(`2x`, (x >> 1) - t); + // y = `2x` - (mulrshiftr256(2x, y) ~>> 2); // this line could improve precision on very rare occasions + return (atanh_f258(y, 36), s); +} + +/// computes 33^m for small m +@pure +@inline +fun pow33(m: int): int { + var t: int = 1; + repeat (m) { + t *= 33; + } + return t; +} + +/// computes 33^m for small 0<=m<=22 +/// slightly faster than pow33() +@pure +@inline +fun pow33b(m: int): int { + var (mh: int, ml: int) = divmod(m, 5); + var t: int = 1; + repeat (ml) { + t *= 33; + } + repeat (mh) { + t *= 33 * 33 * 33 * 33 * 33; + } + return t; +} + +/// returns (s, q, y) such that log(x) = s*log(2) + q*log(33/32) + y/2^260 for positive integer x +/// (int, int, fixed260) log_auxx_f260(int x); +@pure +@inline_ref +fun log_auxx_f260(x: int): (int, int, int) { + var s: int = log2_floor_p1(x) - 1; + x <<= 255 - s; // rescale to 1 <= x < 2 as fixed255 + var t: int = touch(2873) << 244; // ~ (33/32)^11 ~ sqrt(2) as fixed255 + var x1: int = (x - t) >> 1; + var q: int = muldivr(x1, 65, x1 + t) + 11; // crude approximation to round(log(x)/log(33/32)) + // t = 1; repeat (q) { t *= 33; } // t:=33^q, 0<=q<=22 + t = pow33b(q); + t <<= (51 - q) * 5; // t:=(33/32)^q as fixed255, nearest power of 33/32 to x + x -= t; + var y: int = lshift256divr(x << 4, (x >> 1) + t); // y = (x-t)/(x+t) as fixed261 + y = atanh_f261(y, 18); // atanh((x-t)/(x+t)) as fixed261, or log(x/t) as fixed260 + return (s, q, y); +} + +/// returns (y, s) such that log(x) = y/2^256 + s*log(2) for positive integer x +/// this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +/// may be used to implement specific fixed-point instances of log() and log2() +/// (fixed256, int) log_aux_f256(int x); +@pure +@inline_ref +fun log_aux_f256(x: int): (int, int) { + var (s, q, y) = log_auxx_f260(x); + var (yh, yl) = rshiftr4mod(y); // y ~/% 16 , but Tolk does not optimize this to RSHIFTR#MOD + // int Log33_32 = 3563114646320977386603103333812068872452913448227778071188132859183498739150; // log(33/32) as fixed256 + // int Log33_32_l = -3769; // log(33/32) = Log33_32 / 2^256 + Log33_32_l / 2^269 + yh += (yl * 512 + q * -3769) ~>> 13; // compensation, may be removed if slightly worse accuracy is acceptable + var Log33_32: int = 3563114646320977386603103333812068872452913448227778071188132859183498739150; // log(33/32) as fixed256 + return (yh + q * Log33_32, s); +} + +/// returns (y, s) such that log2(x) = y/2^256 + s for positive integer x +/// this function is very precise (error less than 0.6 ulp) and consumes < 7k gas +/// may be used to implement specific fixed-point instances of log() and log2() +/// (fixed256, int) log2_aux_f256(int x); +@pure +@inline_ref +fun log2_aux_f256(x: int): (int, int) { + var (s, q, y) = log_auxx_f260(x); + y = lshift256divr(y, log2_const_f256()) ~>> 4; // y/log(2) as fixed256 + var Log33_32: int = 5140487830366106860412008603913034462883915832139695448455767612111363481357; // log_2(33/32) as fixed256 + // Log33_32/2^256 happens to be a very precise approximation to log_2(33/32), no compensation required + return (y + q * Log33_32, s); +} + + +/// fixed248 log(fixed248 x) +@pure +@inline_ref +fun fixed248_log(x: int): int { + var (y, s) = log_aux_f256(x); + return muldivr(s - 248, log2_const_f256(), 1 << 8) + (y ~>> 8); + // return muldivr(s - 248, 80260960185991308862233904206310070533990667611589946606122867505419956976172, 1 << 8) + (y ~>> 8); +} + +/// fixed248 log2(fixed248 x) +@pure +@inline +fun fixed248_log2(x: int): int { + var (y, s) = log2_aux_f256(x); + return ((s - 248) << 248) + (y ~>> 8); +} + +/// computes x^y as exp(y*log(x)), x >= 0 +/// fixed248 pow(fixed248 x, fixed248 y); +@pure +@inline_ref +fun fixed248_pow(x: int, y: int): int { + if (!y) { + return 1 << 248; // x^0 = 1 + } + if (x <= 0) { + var bad: int = (x | y) < 0; + return 0 >> bad; // 0^y = 0 if x=0 and y>=0; "out of range" exception otherwise + } + var (l, s) = log2_aux_f256(x); + s -= 248; // log_2(x) = s+l, l is fixed256, 0<=l<1 + // compute (s+l)*y = q+ll + var (q1, r1) = mulrshiftr248mod(s, y); // muldivmodr(s, y, 1 << 248) + var (q2, r2) = mulrshift256mod(l, y); + r2 >>= 247; + var (q3, r3) = rshiftr248mod(q2); // divmodr(q2, 1 << 248); + var (q, ll) = rshiftr248mod(r1 + r3); + ll = 512 * ll + r2; + q += q1 + q3; + // now log_2(x^y) = y*log_2(x) = q + ll, ss integer, ll fixed257, -1/2<=ll<1/2 + var sq: int = q + 248; + if (sq <= 0) { + return -(sq == 0); // underflow + } + y = expm1_f257(mulrshiftr256(ll, log2_const_f256())); + return (y ~>> (9 - q)) - (-1 << sq); +} + +/*-------------------- INVERSE TRIGONOMETRIC FUNCTIONS ------------------*/ + +/// number of terms n should be chosen as for atanh_f258() +/// fixed259 atan(fixed259 x); +@pure +@inline_ref +fun atan_f259(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed262 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + var t: int = One + muldivr(x2, 1 << 246, a); // t := 1 + x^2 / a + var n1: int = n - 1; + a = muldivr(t, n, n1) + One; + n = n1; + } + // x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - muldivr(x, x2, a + x2 ~/ 256) ~/ 256; +} + +/// number of terms n should be chosen as for atanh_f261() +/// fixed261 atan(fixed261 x); +@pure +@inline +fun atan_f261_inlined(x: int, n: int): int { + var x2: int = mulrshiftr256(x, x); // x^2 as fixed266 + var One: int = (1 << 254); + var a: int = One ~/ n + (1 << 255); // a := 2 + 1/n as fixed254 + repeat (n - 1) { + // a := 1 + (1 + x^2 / a)(1 + 1/n) as fixed254 + var t: int = One + muldivr(x2, 1 << 242, a); // t := 1 + x^2 / a + var n1: int = n - 1; + a = muldivr(t, n, n1) + One; + n = n1; + } + // x / (1 + x^2 / a) = x / (1 + d) = x - x * d / (1 + d) = x - x * x^2/(a+x^2) for d = x^2 / a + return x - muldivr(x, x2, a + x2 ~/ 4096) ~/ 4096; +} + +/// fixed261 atan(fixed261 x); +@pure +@inline_ref +fun atan_f261(x: int, n: int): int { + return atan_f261_inlined(x, n); +} + +/// computes (q,a,b) such that q is approximately atan(x)/atan(1/32) and a+b*I=(1+I/32)^q as fixed255 +/// then b/a=atan(q*atan(1/32)) exactly, and (a,b) is almost a unit vector pointing in the direction of (1,x) +/// must have |x|<1.1, x is fixed24 +/// (int, fixed255, fixed255) atan_aux_prereduce(fixed24 x); +@pure +@inline_ref +fun atan_aux_prereduce(x: int): (int, int, int) { + var xu: int = abs(x); + var tc: int = 7214596; // tan(13*theta) as fixed24 where theta=atan(1/32) + var t1: int = muldivr(xu - tc, 1 << 88, xu * tc + (1 << 48)); // tan(x') as fixed64 where x'=atan(x)-13*theta + // t1/(3+t1^2) * 3073/32 = x'/3 * 3072/32 = x' / (96/3072) = x' / theta + var q: int = muldivr(t1 * 3073, 1 << 59, t1 * t1 + (touch(3) << 128)) + 13; // approximately round(atan(x)/theta), 0<=q<=25 + var (pa, pb) = (33226912, 5232641); // (32+I)^5 + var (qh, ql) = divmod(q, 5); + var (a, b) = (1 << (5 * (51 - q)), 0); // (1/32^q, 0) as fixed255 + repeat (ql) { + // a+b*I *= 32+I + (a, b) = (sub_rev(touch(b), 32 * a), a + 32 * b); // same as (32 * a - b, 32 * b + a), but more efficient + } + repeat (qh) { + // a+b*I *= (32+I)^5 = pa + pb*I + (a, b) = (a * pa - b * pb, a * pb + b * pa); + } + var xs: int = sgn(x); + return (xs * q, a, xs * b); +} + +/// compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +/// this function is reasonably accurate (error < 7 ulp with ulp = 2^-261), but it consumes >7k gas +/// this is sufficient for most purposes +/// (int, fixed261) atan_aux(fixed256 x) +@pure +@inline_ref +fun atan_aux_f256(x: int): (int, int) { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); // convert x to fixed24 + // now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + // compute y = u/v = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + var (u, ul) = mulrshiftr256mod(a, x); + u = (ul ~>> 250) + ((u - b) << 6); // |u| < 1/32, convert fixed255 -> fixed261 + var v: int = a + mulrshiftr256(b, x); // v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + var y: int = muldivr(u, 1 << 255, v); // y = u/v as fixed261 + var z: int = atan_f261_inlined(y, 18); // z = atan(x)-q*atan(1/32) + return (q, z); +} + +/// compute (q, z) such that atan(x)=q*atan(1/32)+z for -1 <= x < 1 +/// this function is very accurate (error < 2 ulp), but it consumes >7k gas +/// in most cases, faster function atan_aux_f256() should be used +/// (int, fixed261) atan_auxx(fixed256 x) +@pure +@inline_ref +fun atan_auxx_f256(x: int): (int, int) { + var (q, a, b) = atan_aux_prereduce(x ~>> 232); // convert x to fixed24 + // now b/a = tan(q*atan(1/32)) exactly, where q is near atan(x)/atan(1/32); so b/a is near x + // compute y = (a*x-b)/(a+b*x) as fixed261 ; then |y|<0.0167 = 1.07/64 and atan(x)=atan(y)+q*atan(1/32) + // use sort of double precision arithmetic for this + var (u, ul) = mulrshiftr256mod(a, x); + ul /= 2; + u -= b; // |u| < 1/32 as fixed255 + var (v, vl) = mulrshiftr256mod(b, x); + vl /= 2; + v += a; // v is scalar product of (a,b) and (1,x), it is approximately in [1..sqrt(2)] as fixed255 + // y = (u + ul*eps) / (v + vl*eps) = u/v + (ul - vl * u/v)/v * eps where eps=1/2^255 + var (y, r) = lshift255divmodr(u, v); // y = u/v as fixed255 + var yl: int = muldivr(ul + r, 1 << 255, v) - muldivr(vl, y, v); // y/2^255 + yl/2^510 represent u/v + y = (yl ~>> 249) + (y << 6); // convert y to fixed261 + var z: int = atan_f261_inlined(y, 18); // z = atan(x)-q*atan(1/32) + return (q, z); +} + +/// consumes ~ 8k gas +/// fixed255 atan(fixed255 x); +@pure +@inline_ref +fun atan_f255(x: int): int { + var s: int = (x ~>> 256); + touch(x); + if (s) { + x = lshift256divr(-1 << 255, x); // x:=-1/x as fixed256 + } else { + x *= 2; // convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + // now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + var (Pi_h, Pi_l) = Pi_xconst_f254(); // Pi/2 as fixed255 + fixed383 + var (qh, ql) = mulrshiftr6mod(q, Atan1_32_f261()); + return qh + s * Pi_h + (z + ql + muldivr(s, Pi_l, 1 << 122)) ~/ 64; +} + +/// computes atan(x) for -1 <= x < 1 only +/// fixed256 atan_small(fixed256 x); +@pure +@inline_ref +fun atan_f256_small(x: int): int { + var (q, z) = atan_aux_f256(x); + // now atan(x) = z + q*atan(1/32), z is fixed261 + var (qh, ql) = mulrshiftr5mod(q, Atan1_32_f261()); + return qh + (z + ql) ~/ 32; +} + +/// fixed255 asin(fixed255 x); +@pure +@inline_ref +fun asin_f255(x: int): int { + var a: int = fixed255_One - fixed255_sqr(x); // a:=1-x^2 + if (!a) { + return sgn(x) * Pi_const_f254(); // Pi/2 or -Pi/2 + } + var y: int = fixed255_sqrt(a); // sqrt(1-x^2) + var t: int = -lshift256divr(x, (-1 << 255) - y); // t = x/(1+sqrt(1-x^2)) avoiding overflow + return atan_f256_small(t); // asin(x)=2*atan(t) +} + +/// fixed254 acos(fixed255 x); +@pure +@inline_ref +fun acos_f255(x: int): int { + var Pi: int = Pi_const_f254(); + if (x == (-1 << 255)) { + return Pi; // acos(-1) = Pi + } + Pi /= 2; + var y: int = fixed255_sqrt(fixed255_One - fixed255_sqr(x)); // sqrt(1-x^2) + var t: int = lshift256divr(x, (-1 << 255) - y); // t = -x/(1+sqrt(1-x^2)) avoiding overflow + return Pi + atan_f256_small(t) ~/ 2; // acos(x)=Pi/2 + 2*atan(t) +} + +/// consumes ~ 10k gas +/// fixed248 asin(fixed248 x) +@pure +@inline +fun fixed248_asin(x: int): int { + return asin_f255(x << 7) ~>> 7; +} + +/// consumes ~ 10k gas +/// fixed248 acos(fixed248 x) +@pure +@inline +fun fixed248_acos(x: int): int { + return acos_f255(x << 7) ~>> 6; +} + +/// consumes ~ 7500 gas +/// fixed248 atan(fixed248 x); +@pure +@inline_ref +fun fixed248_atan(x: int): int { + var s: int = (x ~>> 249); + touch(x); + if (s) { + s = sgn(s); + x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 + } else { + x <<= 8; // convert to fixed256 + } + var (q, z) = atan_aux_f256(x); + // now atan(x) = z + q*atan(1/32) + s*(Pi/2), z is fixed261 + return (z ~/ 64 + s * Pi_const_f254() + muldivr(q, Atan1_32_f261(), 64)) ~/ 128; // compute in fixed255, then convert +} + +/// fixed248 acot(fixed248 x); +@pure +@inline_ref +fun fixed248_acot(x: int): int { + var s: int = (x ~>> 249); + touch(x); + if (s) { + x = lshift256divr(-1 << 248, x); // x:=-1/x as fixed256 + s = 0; + } else { + x <<= 8; // convert to fixed256 + s = sgn(x); + } + var (q, z) = atan_aux_f256(x); + // now acot(x) = - z - q*atan(1/32) + s*(Pi/2), z is fixed261 + return (s * Pi_const_f254() - z ~/ 64 - muldivr(q, Atan1_32_f261(), 64)) ~/ 128; // compute in fixed255, then convert +} + +/*-------------------- PSEUDO-RANDOM NUMBERS ------------------*/ + +/// random number with standard normal distribution N(0,1) +/// generated by Kinderman--Monahan ratio method modified by J.Leva +/// spends ~ 2k..3k gas on average +/// fixed252 nrand(); +@inline_ref +fun nrand_f252(): int { + var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043); + // 4/sqrt(e*Pi) = 1.369 loop iterations on average + do { + var (u, v) = (random() / 16 + 1, muldivr(random() - (1 << 255), 7027, 1 << 16)); // fixed252; 7027=ceil(sqrt(8/e)*2^12) + var va: int = abs(v); + var (u1, v1) = (u - s, va - t); // (u - 29483/2^16, abs(v) + 3167/2^13) as fixed252 + // Q := u1^2 + v1 * (A*v1 - B*u1) as fixed252 where A=12845/2^16, B=16693/2^16 + var Q: int = muldivr(u1, u1, 1 << 252) + muldivr(v1, muldivr(v1, A, 1 << 16) - muldivr(u1, B, 1 << 16), 1 << 252); + // must have 9043 / 2^15 < Q < 9125 / 2^15, otherwise accept if smaller, reject if larger + var Qd: int = (Q >> 237) - r0; + if ((Qd < 9125 - 9043) & (va / u < 16)) { + x = muldivr(v, 1 << 252, u); // x:=v/u as fixed252; reject immediately if |v/u| >= 16 + if (Qd >= 0) { + // immediately accept if Qd < 0 + // rarely taken branch - 0.012 times per call on average + // check condition v^2 < -4*u^2*log(u), or equivalent condition u < exp(-x^2/4) for x=v/u + var xx: int = mulrshiftr256(x, x) ~/ 4; // x^2/4 as fixed248 + var ex: int = fixed248_exp(-xx) * 16; // exp(-x^2/4) as fixed252 + if (u > ex) { + x = nan(); // condition false, reject + } + } + } + } while (!(~ is_nan(x))); + return x; +} + +/// generates a random number approximately distributed according to the standard normal distribution +/// much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed +/// fixed252 nrand_fast(); +@inline_ref +fun nrand_fast_f252(): int { + var t: int = touch(-3) << 253; // -6. as fixed252 + repeat (12) { + t += random() / 16; // add together 12 uniformly random numbers + } + return t; +} + +/// random number uniformly distributed in [0..1) +/// fixed248 random(); +@inline +fun fixed248_random(): int { + return random() >> 8; +} + +/// random number with standard normal distribution +/// fixed248 nrand(); +@inline +fun fixed248_nrand(): int { + return nrand_f252() ~>> 4; +} + +/// generates a random number approximately distributed according to the standard normal distribution +/// fixed248 nrand_fast(); +@inline +fun fixed248_nrand_fast(): int { + return nrand_fast_f252() ~>> 4; } diff --git a/crypto/smartcont/stdlib.tolk b/crypto/smartcont/stdlib.tolk index b3dfbee0..10c3b36a 100644 --- a/crypto/smartcont/stdlib.tolk +++ b/crypto/smartcont/stdlib.tolk @@ -1,7 +1,7 @@ // Standard library for Tolk // (initially copied from stdlib.fc) // -#pragma version >=0.5; +tolk 0.6 /* This file is part of TON Tolk Standard Library. @@ -15,7 +15,6 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - */ /* @@ -30,139 +29,205 @@ # Lisp-style lists Lists can be represented as nested 2-elements tuples. - Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + Empty list is conventionally represented as TVM `null` value. For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. */ /// Adds an element to the beginning of lisp-style list. -forall X -> tuple cons(X head, tuple tail) pure asm "CONS"; +@pure +fun cons(head: X, tail: tuple): tuple + asm "CONS"; /// Extracts the head and the tail of lisp-style list. -forall X -> (X, tuple) uncons(tuple list) pure asm "UNCONS"; +@pure +fun uncons(list: tuple): (X, tuple) + asm "UNCONS"; /// Extracts the tail and the head of lisp-style list. -forall X -> (tuple, X) list_next(tuple list) pure asm( -> 1 0) "UNCONS"; +@pure +fun list_next(list: tuple): (tuple, X) + asm( -> 1 0) "UNCONS"; /// Returns the head of lisp-style list. -forall X -> X car(tuple list) pure asm "CAR"; +@pure +fun car(list: tuple): X + asm "CAR"; /// Returns the tail of lisp-style list. -tuple cdr(tuple list) pure asm "CDR"; +@pure +fun cdr(list: tuple): tuple + asm "CDR"; /// Creates tuple with zero elements. -tuple empty_tuple() pure asm "NIL"; +@pure +fun empty_tuple(): tuple + asm "NIL"; /// Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` /// is of length at most 255. Otherwise throws a type check exception. -forall X -> tuple tpush(tuple t, X value) pure asm "TPUSH"; -forall X -> (tuple, ()) ~tpush(tuple t, X value) pure asm "TPUSH"; +@pure +fun tpush(t: tuple, value: X): tuple + asm "TPUSH"; + +@pure +fun ~tpush(t: tuple, value: X): (tuple, ()) + asm "TPUSH"; /// Creates a tuple of length one with given argument as element. -forall X -> [X] single(X x) pure asm "SINGLE"; +@pure +fun single(x: X): [X] + asm "SINGLE"; /// Unpacks a tuple of length one -forall X -> X unsingle([X] t) pure asm "UNSINGLE"; +@pure +fun unsingle(t: [X]): X + asm "UNSINGLE"; /// Creates a tuple of length two with given arguments as elements. -forall X, Y -> [X, Y] pair(X x, Y y) pure asm "PAIR"; +@pure +fun pair(x: X, y: Y): [X, Y] + asm "PAIR"; /// Unpacks a tuple of length two -forall X, Y -> (X, Y) unpair([X, Y] t) pure asm "UNPAIR"; +@pure +fun unpair(t: [X, Y]): (X, Y) + asm "UNPAIR"; /// Creates a tuple of length three with given arguments as elements. -forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) pure asm "TRIPLE"; +@pure +fun triple(x: X, y: Y, z: Z): [X, Y, Z] + asm "TRIPLE"; /// Unpacks a tuple of length three -forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) pure asm "UNTRIPLE"; +@pure +fun untriple(t: [X, Y, Z]): (X, Y, Z) + asm "UNTRIPLE"; /// Creates a tuple of length four with given arguments as elements. -forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) pure asm "4 TUPLE"; +@pure +fun tuple4(x: X, y: Y, z: Z, w: W): [X, Y, Z, W] + asm "4 TUPLE"; /// Unpacks a tuple of length four -forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) pure asm "4 UNTUPLE"; +@pure +fun untuple4(t: [X, Y, Z, W]): (X, Y, Z, W) + asm "4 UNTUPLE"; /// Returns the first element of a tuple (with unknown element types). -forall X -> X first(tuple t) pure asm "FIRST"; +@pure +fun first(t: tuple): X + asm "FIRST"; /// Returns the second element of a tuple (with unknown element types). -forall X -> X second(tuple t) pure asm "SECOND"; +@pure +fun second(t: tuple): X + asm "SECOND"; /// Returns the third element of a tuple (with unknown element types). -forall X -> X third(tuple t) pure asm "THIRD"; +@pure +fun third(t: tuple): X + asm "THIRD"; /// Returns the fourth element of a tuple (with unknown element types). -forall X -> X fourth(tuple t) pure asm "3 INDEX"; +@pure +fun fourth(t: tuple): X + asm "3 INDEX"; /// Returns the [`index`]-th element of tuple [`t`]. -forall X -> X at(tuple t, int index) pure builtin; +@pure +fun at(t: tuple, index: int): X + builtin; /// Returns the first element of a pair tuple. -forall X, Y -> X pair_first([X, Y] p) pure asm "FIRST"; +@pure +fun pair_first(p: [X, Y]): X + asm "FIRST"; /// Returns the second element of a pair tuple. -forall X, Y -> Y pair_second([X, Y] p) pure asm "SECOND"; +@pure +fun pair_second(p: [X, Y]): Y + asm "SECOND"; /// Returns the first element of a triple tuple. -forall X, Y, Z -> X triple_first([X, Y, Z] p) pure asm "FIRST"; +@pure +fun triple_first(p: [X, Y, Z]): X + asm "FIRST"; /// Returns the second element of a triple tuple. -forall X, Y, Z -> Y triple_second([X, Y, Z] p) pure asm "SECOND"; +@pure +fun triple_second(p: [X, Y, Z]): Y + asm "SECOND"; /// Returns the third element of a triple tuple. -forall X, Y, Z -> Z triple_third([X, Y, Z] p) pure asm "THIRD"; +@pure +fun triple_third(p: [X, Y, Z]): Z + asm "THIRD"; -/// Push null element (casted to given type) -/// By the TVM type `Null` Tolk represents absence of a value of some atomic type. -/// So `null` can actually have any atomic type. -forall X -> X null() pure asm "PUSHNULL"; - -/// Checks whether the argument is null. -forall X -> int null?(X x) pure builtin; - /// Moves a variable [x] to the top of the stack. -forall X -> X touch(X x) pure builtin; +@pure +fun touch(x: X): X + builtin; /// Moves a variable [x] to the top of the stack. -forall X -> (X, ()) ~touch(X x) pure builtin; +@pure +fun ~touch(x: X): (X, ()) + builtin; /// Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. -forall X -> (X, ()) ~impure_touch(X x) asm "NOP"; +fun ~impure_touch(x: X): (X, ()) + asm "NOP"; /// Returns the current Unix time as an Integer -int now() pure asm "NOW"; +@pure +fun now(): int + asm "NOW"; /// Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. /// If necessary, it can be parsed further using primitives such as [parse_std_addr]. -slice my_address() pure asm "MYADDR"; +@pure +fun my_address(): slice + asm "MYADDR"; /// Returns the balance of the smart contract as a tuple consisting of an int /// (balance in nanotoncoins) and a `cell` /// (a dictionary with 32-bit keys representing the balance of "extra currencies") /// at the start of Computation Phase. /// Note that RAW primitives such as [send_raw_message] do not update this field. -[int, cell] get_balance() pure asm "BALANCE"; +@pure +fun get_balance(): [int, cell] + asm "BALANCE"; /// Returns the logical time of the current transaction. -int cur_lt() pure asm "LTIME"; +@pure +fun cur_lt(): int + asm "LTIME"; /// Returns the starting logical time of the current block. -int block_lt() pure asm "BLOCKLT"; +@pure +fun block_lt(): int + asm "BLOCKLT"; /// Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. /// Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. -int cell_hash(cell c) pure asm "HASHCU"; +@pure +fun cell_hash(c: cell): int + asm "HASHCU"; /// Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. /// The result is the same as if an ordinary cell containing only data and references from `s` had been created /// and its hash computed by [cell_hash]. -int slice_hash(slice s) pure asm "HASHSU"; +@pure +fun slice_hash(s: slice): int + asm "HASHSU"; /// Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, /// throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. -int string_hash(slice s) pure asm "SHA256U"; +@pure +fun string_hash(s: slice): int + asm "SHA256U"; /*** # Signature checks @@ -175,14 +240,18 @@ int string_hash(slice s) pure asm "SHA256U"; /// Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. /// That is, if [hash] is computed as the hash of some data, these data are hashed twice, /// the second hashing occurring inside `CHKSIGNS`. -int check_signature(int hash, slice signature, int public_key) pure asm "CHKSIGNU"; +@pure +fun check_signature(hash: int, signature: slice, public_key: int): int + asm "CHKSIGNU"; /// Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, /// similarly to [check_signature]. /// If the bit length of [data] is not divisible by eight, throws a cell underflow exception. /// The verification of Ed25519 signatures is the standard one, /// with sha256 used to reduce [data] to the 256-bit number that is actually signed. -int check_data_signature(slice data, slice signature, int public_key) pure asm "CHKSIGNS"; +@pure +fun check_data_signature(data: slice, signature: slice, public_key: int): int + asm "CHKSIGNS"; /*** # Computation of boc size @@ -190,10 +259,12 @@ int check_data_signature(slice data, slice signature, int public_key) pure asm " */ /// A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. -(int, int, int) compute_data_size(cell c, int max_cells) asm "CDATASIZE"; +fun compute_data_size(c: cell, max_cells: int): (int, int, int) + asm "CDATASIZE"; /// A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (`8`) on failure. -(int, int, int) slice_compute_data_size(slice s, int max_cells) asm "SDATASIZE"; +fun slice_compute_data_size(s: slice, max_cells: int): (int, int, int) + asm "SDATASIZE"; /// Returns `(x, y, z, -1)` or `(null, null, null, 0)`. /// Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` @@ -204,33 +275,16 @@ int check_data_signature(slice data, slice signature, int public_key) pure asm " /// The total count of visited cells `x` cannot exceed non-negative [max_cells]; /// otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and /// a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. -(int, int, int, int) compute_data_size?(cell c, int max_cells) pure asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +@pure +fun compute_data_size?(c: cell, max_cells: int): (int, int, int, int) + asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; /// Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. /// The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; /// however, the data bits and the cell references of [s] are accounted for in `y` and `z`. -(int, int, int, int) slice_compute_data_size?(slice s, int max_cells) pure asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; - -/// Throws exception [`excno`] with parameter zero. -/// In other words, it transfers control to the continuation in `c2`, -/// pushing `0` and [`excno`] into it's stack, and discarding the old stack altogether. -() throw(int excno) builtin; - -/// Throws exception [`excno`] with parameter zero only if [`cond`] != `0`. -() throw_if(int excno, int cond) builtin; - -/// Throws exception [`excno`] with parameter zero only if [`cond`] == `0`. -() throw_unless(int excno, int cond) builtin; - -/// Throws exception [`excno`] with parameter [`x`], -/// by copying [`x`] and [`excno`] into the stack of `c2` and transferring control to `c2`. -forall X -> () throw_arg(X x, int excno) builtin; - -/// Throws exception [`excno`] with parameter [`x`] only if [`cond`] != `0`. -forall X -> () throw_arg_if(X x, int excno, int cond) builtin; - -/// Throws exception [`excno`] with parameter [`x`] only if [`cond`] == `0`. -forall X -> () throw_arg_unless(X x, int excno, int cond) builtin; +@pure +fun slice_compute_data_size?(s: slice, max_cells: int): (int, int, int, int) + asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; /*** # Debug primitives @@ -238,39 +292,50 @@ forall X -> () throw_arg_unless(X x, int excno, int cond) builtin; */ /// Dump a variable [x] to the debug log. -forall X -> (X, ()) ~dump(X x) builtin; +fun ~dump(x: X): (X, ()) + builtin; /// Dump a string [x] to the debug log. -forall X -> (X, ()) ~strdump(X x) builtin; +fun ~strdump(x: X): (X, ()) + builtin; /// Dumps the stack (at most the top 255 values) and shows the total stack depth. -() dump_stack() asm "DUMPSTK"; +fun dump_stack(): void + asm "DUMPSTK"; /*** # Persistent storage save and load */ /// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. -cell get_data() pure asm "c4 PUSH"; +@pure +fun get_data(): cell + asm "c4 PUSH"; /// Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. -() set_data(cell c) asm "c4 POP"; +fun set_data(c: cell): void + asm "c4 POP"; /*** # Continuation primitives */ /// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. /// The primitive returns the current value of `c3`. -cont get_c3() pure asm "c3 PUSH"; +@pure +fun get_c3(): continuation + asm "c3 PUSH"; /// Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. /// Note that after execution of this primitive the current code /// (and the stack of recursive function calls) won't change, /// but any other function call will use a function from the new code. -() set_c3(cont c) asm "c3 POP"; +fun set_c3(c: continuation): void + asm "c3 POP"; /// Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. -cont bless(slice s) pure asm "BLESS"; +@pure +fun bless(s: slice): continuation + asm "BLESS"; /*** # Gas related primitives @@ -282,56 +347,77 @@ cont bless(slice s) pure asm "BLESS"; /// This action is required to process external messages, which bring no value (hence no gas) with themselves. /// /// For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). -() accept_message() asm "ACCEPT"; +fun accept_message(): void + asm "ACCEPT"; /// Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. /// If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, /// an (unhandled) out of gas exception is thrown before setting new gas limits. /// Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. -() set_gas_limit(int limit) asm "SETGASLIMIT"; +fun set_gas_limit(limit: int): void + asm "SETGASLIMIT"; /// Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) /// so that the current execution is considered “successful” with the saved values even if an exception /// in Computation Phase is thrown later. -() commit() asm "COMMIT"; - -/// Not implemented -//() buy_gas(int gram) asm "BUYGAS"; +fun commit(): void + asm "COMMIT"; /// Computes the amount of gas that can be bought for `amount` nanoTONs, /// and sets `gl` accordingly in the same way as [set_gas_limit]. -() buy_gas(int amount) asm "BUYGAS"; +fun buy_gas(amount: int): void + asm "BUYGAS"; /// Computes the minimum of two integers [x] and [y]. -int min(int x, int y) pure asm "MIN"; +@pure +fun min(x: int, y: int): int + asm "MIN"; /// Computes the maximum of two integers [x] and [y]. -int max(int x, int y) pure asm "MAX"; +@pure +fun max(x: int, y: int): int + asm "MAX"; /// Sorts two integers. -(int, int) minmax(int x, int y) pure asm "MINMAX"; +@pure +fun minmax(x: int, y: int): (int, int) + asm "MINMAX"; /// Computes the absolute value of an integer [x]. -int abs(int x) pure asm "ABS"; +@pure +fun abs(x: int): int + asm "ABS"; /// Computes the quotient and remainder of [x] / [y]. Example: divmod(112,3) = (37,1) -(int, int) divmod(int x, int y) pure builtin; +@pure +fun divmod(x: int, y: int): (int, int) + builtin; /// Computes the remainder and quotient of [x] / [y]. Example: moddiv(112,3) = (1,37) -(int, int) moddiv(int x, int y) pure builtin; +@pure +fun moddiv(x: int, y: int): (int, int) + builtin; /// Computes multiple-then-divide: floor([x] * [y] / [z]). /// The intermediate result is stored in a 513-bit integer to prevent precision loss. -int muldiv(int x, int y, int z) pure builtin; +@pure +fun muldiv(x: int, y: int, z: int): int + builtin; /// Similar to `muldiv`, but rounds the result: round([x] * [y] / [z]). -int muldivr(int x, int y, int z) pure builtin; +@pure +fun muldivr(x: int, y: int, z: int): int + builtin; /// Similar to `muldiv`, but ceils the result: ceil([x] * [y] / [z]). -int muldivc(int x, int y, int z) pure builtin; +@pure +fun muldivc(x: int, y: int, z: int): int + builtin; /// Computes the quotient and remainder of ([x] * [y] / [z]). Example: muldivmod(112,3,10) = (33,6) -(int, int) muldivmod(int x, int y, int z) pure builtin; +@pure +fun muldivmod(x: int, y: int, z: int): (int, int) + builtin; /*** # Slice primitives @@ -350,79 +436,131 @@ int muldivc(int x, int y, int z) pure builtin; /// Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, /// or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) /// which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. -slice begin_parse(cell c) pure asm "CTOS"; +@pure +fun begin_parse(c: cell): slice + asm "CTOS"; /// Checks if [s] is empty. If not, throws an exception. -() end_parse(slice s) asm "ENDS"; +fun end_parse(s: slice): void + asm "ENDS"; /// Loads the first reference from the slice. -(slice, cell) load_ref(slice s) pure asm( -> 1 0) "LDREF"; +@pure +fun load_ref(s: slice): (slice, cell) + asm( -> 1 0) "LDREF"; /// Preloads the first reference from the slice. -cell preload_ref(slice s) pure asm "PLDREF"; +@pure +fun preload_ref(s: slice): cell + asm "PLDREF"; /// Loads a signed [len]-bit integer from a slice [s]. -(slice, int) load_int(slice s, int len) pure builtin; +@pure +fun load_int(s: slice, len: int): (slice, int) + builtin; /// Loads an unsigned [len]-bit integer from a slice [s]. -(slice, int) load_uint(slice s, int len) pure builtin; +@pure +fun load_uint(s: slice, len: int): (slice, int) + builtin; /// Preloads a signed [len]-bit integer from a slice [s]. -int preload_int(slice s, int len) pure builtin; +@pure +fun preload_int(s: slice, len: int): int + builtin; /// Preloads an unsigned [len]-bit integer from a slice [s]. -int preload_uint(slice s, int len) pure builtin; +@pure +fun preload_uint(s: slice, len: int): int + builtin; /// Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. -(slice, slice) load_bits(slice s, int len) pure builtin; +@pure +fun load_bits(s: slice, len: int): (slice, slice) + builtin; /// Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. -slice preload_bits(slice s, int len) pure builtin; +@pure +fun preload_bits(s: slice, len: int): slice + builtin; /// Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). -(slice, int) load_grams(slice s) pure asm( -> 1 0) "LDGRAMS"; -(slice, int) load_coins(slice s) pure asm( -> 1 0) "LDGRAMS"; +@pure +fun load_grams(s: slice): (slice, int) + asm( -> 1 0) "LDGRAMS"; + +@pure +fun load_coins(s: slice): (slice, int) + asm( -> 1 0) "LDGRAMS"; /// Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice skip_bits(slice s, int len) pure asm "SDSKIPFIRST"; -(slice, ()) ~skip_bits(slice s, int len) pure asm "SDSKIPFIRST"; +@pure +fun skip_bits(s: slice, len: int): slice + asm "SDSKIPFIRST"; + +@pure +fun ~skip_bits(s: slice, len: int): (slice, ()) + asm "SDSKIPFIRST"; /// Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice first_bits(slice s, int len) pure asm "SDCUTFIRST"; +@pure +fun first_bits(s: slice, len: int): slice + asm "SDCUTFIRST"; /// Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice skip_last_bits(slice s, int len) pure asm "SDSKIPLAST"; -(slice, ()) ~skip_last_bits(slice s, int len) pure asm "SDSKIPLAST"; +@pure +fun skip_last_bits(s: slice, len: int): slice + asm "SDSKIPLAST"; +@pure +fun ~skip_last_bits(s: slice, len: int): (slice, ()) + asm "SDSKIPLAST"; /// Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice slice_last(slice s, int len) pure asm "SDCUTLAST"; +@pure +fun slice_last(s: slice, len: int): slice + asm "SDCUTLAST"; /// Loads a dictionary `D` (HashMapE) from `slice` [s]. /// (returns `null` if `nothing` constructor is used). -(slice, cell) load_dict(slice s) pure asm( -> 1 0) "LDDICT"; +@pure +fun load_dict(s: slice): (slice, cell) + asm( -> 1 0) "LDDICT"; /// Preloads a dictionary `D` from `slice` [s]. -cell preload_dict(slice s) pure asm "PLDDICT"; +@pure +fun preload_dict(s: slice): cell + asm "PLDDICT"; /// Loads a dictionary as [load_dict], but returns only the remainder of the slice. -slice skip_dict(slice s) pure asm "SKIPDICT"; -(slice, ()) ~skip_dict(slice s) pure asm "SKIPDICT"; +@pure +fun skip_dict(s: slice): slice + asm "SKIPDICT"; + +@pure +fun ~skip_dict(s: slice): (slice, ()) + asm "SKIPDICT"; /// Loads (Maybe ^Cell) from `slice` [s]. /// In other words loads 1 bit and if it is true /// loads first ref and return it with slice remainder /// otherwise returns `null` and slice remainder -(slice, cell) load_maybe_ref(slice s) pure asm( -> 1 0) "LDOPTREF"; +@pure +fun load_maybe_ref(s: slice): (slice, cell) + asm( -> 1 0) "LDOPTREF"; /// Preloads (Maybe ^Cell) from `slice` [s]. -cell preload_maybe_ref(slice s) pure asm "PLDOPTREF"; +@pure +fun preload_maybe_ref(s: slice): cell + asm "PLDOPTREF"; /// Returns the depth of `cell` [c]. /// If [c] has no references, then return `0`; /// otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. /// If [c] is a `null` instead of a cell, returns zero. -int cell_depth(cell c) pure asm "CDEPTH"; +@pure +fun cell_depth(c: cell): int + asm "CDEPTH"; /*** @@ -430,42 +568,62 @@ int cell_depth(cell c) pure asm "CDEPTH"; */ /// Returns the number of references in `slice` [s]. -int slice_refs(slice s) pure asm "SREFS"; +@pure +fun slice_refs(s: slice): int + asm "SREFS"; /// Returns the number of data bits in `slice` [s]. -int slice_bits(slice s) pure asm "SBITS"; +@pure +fun slice_bits(s: slice): int + asm "SBITS"; /// Returns both the number of data bits and the number of references in `slice` [s]. -(int, int) slice_bits_refs(slice s) pure asm "SBITREFS"; +@pure +fun slice_bits_refs(s: slice): (int, int) + asm "SBITREFS"; /// Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). -int slice_empty?(slice s) pure asm "SEMPTY"; +@pure +fun slice_empty?(s: slice): int + asm "SEMPTY"; /// Checks whether `slice` [s] has no bits of data. -int slice_data_empty?(slice s) pure asm "SDEMPTY"; +@pure +fun slice_data_empty?(s: slice): int + asm "SDEMPTY"; /// Checks whether `slice` [s] has no references. -int slice_refs_empty?(slice s) pure asm "SREMPTY"; +@pure +fun slice_refs_empty?(s: slice): int + asm "SREMPTY"; /// Returns the depth of `slice` [s]. /// If [s] has no references, then returns `0`; /// otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. -int slice_depth(slice s) pure asm "SDEPTH"; +@pure +fun slice_depth(s: slice): int + asm "SDEPTH"; /*** # Builder size primitives */ /// Returns the number of cell references already stored in `builder` [b] -int builder_refs(builder b) pure asm "BREFS"; +@pure +fun builder_refs(b: builder): int + asm "BREFS"; /// Returns the number of data bits already stored in `builder` [b]. -int builder_bits(builder b) pure asm "BBITS"; +@pure +fun builder_bits(b: builder): int + asm "BBITS"; /// Returns the depth of `builder` [b]. /// If no cell references are stored in [b], then returns 0; /// otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. -int builder_depth(builder b) pure asm "BDEPTH"; +@pure +fun builder_depth(b: builder): int + asm "BDEPTH"; /*** # Builder primitives @@ -478,22 +636,34 @@ int builder_depth(builder b) pure asm "BDEPTH"; */ /// Creates a new empty `builder`. -builder begin_cell() pure asm "NEWC"; +@pure +fun begin_cell(): builder + asm "NEWC"; /// Converts a `builder` into an ordinary `cell`. -cell end_cell(builder b) pure asm "ENDC"; +@pure +fun end_cell(b: builder): cell + asm "ENDC"; /// Stores a reference to `cell` [c] into `builder` [b]. -builder store_ref(builder b, cell c) pure asm(c b) "STREF"; +@pure +fun store_ref(b: builder, c: cell): builder + asm(c b) "STREF"; /// Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. -builder store_uint(builder b, int x, int len) pure builtin; +@pure +fun store_uint(b: builder, x: int, len: int): builder + builtin; /// Stores a signed [len]-bit integer `x` into `b` for `0 ≤ len ≤ 257`. -builder store_int(builder b, int x, int len) pure builtin; +@pure +fun store_int(b: builder, x: int, len: int): builder + builtin; /// Stores `slice` [s] into `builder` [b]. -builder store_slice(builder b, slice s) pure asm "STSLICER"; +@pure +fun store_slice(b: builder, s: slice): builder + asm "STSLICER"; /// Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. /// The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, @@ -502,17 +672,26 @@ builder store_slice(builder b, slice s) pure asm "STSLICER"; /// If [x] does not belong to the supported range, a range check exception is thrown. /// /// Store amounts of TonCoins to the builder as VarUInteger 16 -builder store_grams(builder b, int x) pure asm "STGRAMS"; -builder store_coins(builder b, int x) pure asm "STGRAMS"; +@pure +fun store_grams(b: builder, x: int): builder + asm "STGRAMS"; + +@pure +fun store_coins(b: builder, x: int): builder + asm "STGRAMS"; /// Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. /// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. -builder store_dict(builder b, cell c) pure asm(c b) "STDICT"; +@pure +fun store_dict(b: builder, c: cell): builder + asm(c b) "STDICT"; /// Stores (Maybe ^Cell) to builder: /// if cell is null store 1 zero bit /// otherwise store 1 true bit and ref to cell -builder store_maybe_ref(builder b, cell c) pure asm(c b) "STOPTREF"; +@pure +fun store_maybe_ref(b: builder, c: cell): builder + asm(c b) "STOPTREF"; /*** @@ -554,22 +733,30 @@ builder store_maybe_ref(builder b, cell c) pure asm(c b) "STOPTREF"; /// Loads from slice [s] the only prefix that is a valid `MsgAddress`, /// and returns both this prefix `s'` and the remainder `s''` of [s] as slices. -(slice, slice) load_msg_addr(slice s) pure asm( -> 1 0) "LDMSGADDR"; +@pure +fun load_msg_addr(s: slice): (slice, slice) + asm( -> 1 0) "LDMSGADDR"; /// Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. /// If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. -tuple parse_addr(slice s) pure asm "PARSEMSGADDR"; +@pure +fun parse_addr(s: slice): tuple + asm "PARSEMSGADDR"; /// Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), /// applies rewriting from the anycast (if present) to the same-length prefix of the address, /// and returns both the workchain and the 256-bit address as integers. /// If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, /// throws a cell deserialization exception. -(int, int) parse_std_addr(slice s) pure asm "REWRITESTDADDR"; +@pure +fun parse_std_addr(s: slice): (int, int) + asm "REWRITESTDADDR"; /// A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], /// even if it is not exactly 256 bit long (represented by a `msg_addr_var`). -(int, slice) parse_var_addr(slice s) pure asm "REWRITEVARADDR"; +@pure +fun parse_var_addr(s: slice): (int, slice) + asm "REWRITEVARADDR"; /*** # Dictionary primitives @@ -578,117 +765,344 @@ tuple parse_addr(slice s) pure asm "PARSEMSGADDR"; /// Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), /// and returns the resulting dictionary. -cell idict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTISETREF"; -(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTISETREF"; +@pure +fun idict_set_ref(dict: cell, key_len: int, index: int, value: cell): cell + asm(value index dict key_len) "DICTISETREF"; + +@pure +fun ~idict_set_ref(dict: cell, key_len: int, index: int, value: cell): (cell, ()) + asm(value index dict key_len) "DICTISETREF"; /// Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), /// and returns the resulting dictionary. -cell udict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTUSETREF"; -(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTUSETREF"; +@pure +fun udict_set_ref(dict: cell, key_len: int, index: int, value: cell): cell + asm(value index dict key_len) "DICTUSETREF"; -cell idict_get_ref(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIGETOPTREF"; -(cell, int) idict_get_ref?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; -(cell, int) udict_get_ref?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; -(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTISETGETOPTREF"; -(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTUSETGETOPTREF"; -(cell, int) idict_delete?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIDEL"; -(cell, int) udict_delete?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUDEL"; -(slice, int) idict_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; -(slice, int) udict_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; -(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; -(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; -(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; -(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; -cell udict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUSET"; -(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUSET"; -cell idict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTISET"; -(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTISET"; -cell dict_set(cell dict, int key_len, slice index, slice value) pure asm(value index dict key_len) "DICTSET"; -(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) pure asm(value index dict key_len) "DICTSET"; -(cell, int) udict_add?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUADD"; -(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUREPLACE"; -(cell, int) idict_add?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTIADD"; -(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTIREPLACE"; -cell udict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUSETB"; -(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUSETB"; -cell idict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTISETB"; -(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTISETB"; -cell dict_set_builder(cell dict, int key_len, slice index, builder value) pure asm(value index dict key_len) "DICTSETB"; -(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) pure asm(value index dict key_len) "DICTSETB"; -(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUADDB"; -(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUREPLACEB"; -(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTIADDB"; -(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTIREPLACEB"; -(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; -(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; -(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; -(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; -(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; -(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; -(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; -(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_min?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_max?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; -(int, cell, int) udict_get_min_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; -(int, cell, int) udict_get_max_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_min?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_max?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; -(int, cell, int) idict_get_min_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; -(int, cell, int) idict_get_max_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +@pure +fun ~udict_set_ref(dict: cell, key_len: int, index: int, value: cell): (cell, ()) + asm(value index dict key_len) "DICTUSETREF"; + +@pure +fun idict_get_ref(dict: cell, key_len: int, index: int): cell + asm(index dict key_len) "DICTIGETOPTREF"; + +@pure +fun idict_get_ref?(dict: cell, key_len: int, index: int): (cell, int) + asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; + +@pure +fun udict_get_ref?(dict: cell, key_len: int, index: int): (cell, int) + asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; + +@pure +fun idict_set_get_ref(dict: cell, key_len: int, index: int, value: cell): (cell, cell) + asm(value index dict key_len) "DICTISETGETOPTREF"; + +@pure +fun udict_set_get_ref(dict: cell, key_len: int, index: int, value: cell): (cell, cell) + asm(value index dict key_len) "DICTUSETGETOPTREF"; + +@pure +fun idict_delete?(dict: cell, key_len: int, index: int): (cell, int) + asm(index dict key_len) "DICTIDEL"; + +@pure +fun udict_delete?(dict: cell, key_len: int, index: int): (cell, int) + asm(index dict key_len) "DICTUDEL"; + +@pure +fun idict_get?(dict: cell, key_len: int, index: int): (slice, int) + asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; + +@pure +fun udict_get?(dict: cell, key_len: int, index: int): (slice, int) + asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; + +@pure +fun idict_delete_get?(dict: cell, key_len: int, index: int): (cell, slice, int) + asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; + +@pure +fun udict_delete_get?(dict: cell, key_len: int, index: int): (cell, slice, int) + asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; + +@pure +fun ~idict_delete_get?(dict: cell, key_len: int, index: int): (cell, (slice, int)) + asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; + +@pure +fun ~udict_delete_get?(dict: cell, key_len: int, index: int): (cell, (slice, int)) + asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; + +@pure +fun udict_set(dict: cell, key_len: int, index: int, value: slice): cell + asm(value index dict key_len) "DICTUSET"; + +@pure +fun ~udict_set(dict: cell, key_len: int, index: int, value: slice): (cell, ()) + asm(value index dict key_len) "DICTUSET"; + +@pure +fun idict_set(dict: cell, key_len: int, index: int, value: slice): cell + asm(value index dict key_len) "DICTISET"; + +@pure +fun ~idict_set(dict: cell, key_len: int, index: int, value: slice): (cell, ()) + asm(value index dict key_len) "DICTISET"; + +@pure +fun dict_set(dict: cell, key_len: int, index: slice, value: slice): cell + asm(value index dict key_len) "DICTSET"; + +@pure +fun ~dict_set(dict: cell, key_len: int, index: slice, value: slice): (cell, ()) + asm(value index dict key_len) "DICTSET"; + +@pure +fun udict_add?(dict: cell, key_len: int, index: int, value: slice): (cell, int) + asm(value index dict key_len) "DICTUADD"; + +@pure +fun udict_replace?(dict: cell, key_len: int, index: int, value: slice): (cell, int) + asm(value index dict key_len) "DICTUREPLACE"; + +@pure +fun idict_add?(dict: cell, key_len: int, index: int, value: slice): (cell, int) + asm(value index dict key_len) "DICTIADD"; + +@pure +fun idict_replace?(dict: cell, key_len: int, index: int, value: slice): (cell, int) + asm(value index dict key_len) "DICTIREPLACE"; + +@pure +fun udict_set_builder(dict: cell, key_len: int, index: int, value: builder): cell + asm(value index dict key_len) "DICTUSETB"; + +@pure +fun ~udict_set_builder(dict: cell, key_len: int, index: int, value: builder): (cell, ()) + asm(value index dict key_len) "DICTUSETB"; + +@pure +fun idict_set_builder(dict: cell, key_len: int, index: int, value: builder): cell + asm(value index dict key_len) "DICTISETB"; + +@pure +fun ~idict_set_builder(dict: cell, key_len: int, index: int, value: builder): (cell, ()) + asm(value index dict key_len) "DICTISETB"; + +@pure +fun dict_set_builder(dict: cell, key_len: int, index: slice, value: builder): cell + asm(value index dict key_len) "DICTSETB"; + +@pure +fun ~dict_set_builder(dict: cell, key_len: int, index: slice, value: builder): (cell, ()) + asm(value index dict key_len) "DICTSETB"; + +@pure +fun udict_add_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) + asm(value index dict key_len) "DICTUADDB"; + +@pure +fun udict_replace_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) + asm(value index dict key_len) "DICTUREPLACEB"; + +@pure +fun idict_add_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) + asm(value index dict key_len) "DICTIADDB"; + +@pure +fun idict_replace_builder?(dict: cell, key_len: int, index: int, value: builder): (cell, int) + asm(value index dict key_len) "DICTIREPLACEB"; + +@pure +fun udict_delete_get_min(dict: cell, key_len: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun ~udict_delete_get_min(dict: cell, key_len: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun idict_delete_get_min(dict: cell, key_len: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun ~idict_delete_get_min(dict: cell, key_len: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun dict_delete_get_min(dict: cell, key_len: int): (cell, slice, slice, int) + asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun ~dict_delete_get_min(dict: cell, key_len: int): (cell, (slice, slice, int)) + asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; + +@pure +fun udict_delete_get_max(dict: cell, key_len: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun ~udict_delete_get_max(dict: cell, key_len: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun idict_delete_get_max(dict: cell, key_len: int): (cell, int, slice, int) + asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun ~idict_delete_get_max(dict: cell, key_len: int): (cell, (int, slice, int)) + asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun dict_delete_get_max(dict: cell, key_len: int): (cell, slice, slice, int) + asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun ~dict_delete_get_max(dict: cell, key_len: int): (cell, (slice, slice, int)) + asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_min?(dict: cell, key_len: int): (int, slice, int) + asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_max?(dict: cell, key_len: int): (int, slice, int) + asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_min_ref?(dict: cell, key_len: int): (int, cell, int) + asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_max_ref?(dict: cell, key_len: int): (int, cell, int) + asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_min?(dict: cell, key_len: int): (int, slice, int) + asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_max?(dict: cell, key_len: int): (int, slice, int) + asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_min_ref?(dict: cell, key_len: int): (int, cell, int) + asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_max_ref?(dict: cell, key_len: int): (int, cell, int) + asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_next?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_nexteq?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_prev?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; + +@pure +fun udict_get_preveq?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_next?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_nexteq?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_prev?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; + +@pure +fun idict_get_preveq?(dict: cell, key_len: int, pivot: int): (int, slice, int) + asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; /// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL -cell new_dict() pure asm "NEWDICT"; -/// Checks whether a dictionary is empty. Equivalent to cell_null?. -int dict_empty?(cell c) pure asm "DICTEMPTY"; +@pure +fun new_dict(): cell + asm "NEWDICT"; + +/// Checks whether a dictionary is empty. +@pure +fun dict_empty?(c: cell): int + asm "DICTEMPTY"; /* Prefix dictionary primitives */ -(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) pure asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; -(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) pure asm(value key dict key_len) "PFXDICTSET"; -(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) pure asm(key dict key_len) "PFXDICTDEL"; +@pure +fun pfxdict_get?(dict: cell, key_len: int, key: slice): (slice, slice, slice, int) + asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; + +@pure +fun pfxdict_set?(dict: cell, key_len: int, key: slice, value: slice): (cell, int) + asm(value key dict key_len) "PFXDICTSET"; + +@pure +fun pfxdict_delete?(dict: cell, key_len: int, key: slice): (cell, int) + asm(key dict key_len) "PFXDICTDEL"; /// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. -cell config_param(int x) pure asm "CONFIGOPTPARAM"; -/// Checks whether c is a null. Note, that Tolk also has polymorphic null? built-in. -int cell_null?(cell c) pure asm "ISNULL"; -int builder_null?(builder b) asm "ISNULL"; +@pure +fun config_param(x: int): cell + asm "CONFIGOPTPARAM"; /// Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. -() raw_reserve(int amount, int mode) asm "RAWRESERVE"; +fun raw_reserve(amount: int, mode: int): void + asm "RAWRESERVE"; + /// Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. -() raw_reserve_extra(int amount, cell extra_amount, int mode) asm "RAWRESERVEX"; +fun raw_reserve_extra(amount: int, extra_amount: cell, mode: int): void + asm "RAWRESERVEX"; + /// Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. -() send_raw_message(cell msg, int mode) asm "SENDRAWMSG"; +fun send_raw_message(msg: cell, mode: int): void + asm "SENDRAWMSG"; + /// Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract -() set_code(cell new_code) asm "SETCODE"; +fun set_code(new_code: cell): void + asm "SETCODE"; /// Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. -int random() asm "RANDU256"; +fun random(): int + asm "RANDU256"; + /// Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. -int rand(int range) asm "RAND"; +fun rand(range: int): int + asm "RAND"; + /// Returns the current random seed as an unsigned 256-bit Integer. -int get_seed() pure asm "RANDSEED"; +@pure +fun get_seed(): int + asm "RANDSEED"; + /// Sets the random seed to unsigned 256-bit seed. -() set_seed(int) asm "SETRAND"; +fun set_seed(seed: int): void + asm "SETRAND"; + /// Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. -() randomize(int x) asm "ADDRAND"; +fun randomize(x: int): void + asm "ADDRAND"; + /// Equivalent to randomize(cur_lt());. -() randomize_lt() asm "LTIME" "ADDRAND"; +fun randomize_lt(): void + asm "LTIME" "ADDRAND"; /// Checks whether the data parts of two slices coinside -int equal_slice_bits (slice a, slice b) pure asm "SDEQ"; +@pure +fun equal_slice_bits(a: slice, b: slice): int + asm "SDEQ"; /// Concatenates two builders -builder store_builder(builder to, builder from) pure asm "STBR"; +@pure +fun store_builder(to: builder, from: builder): builder + asm "STBR"; diff --git a/tolk-tester/tests/a10.tolk b/tolk-tester/tests/a10.tolk new file mode 100644 index 00000000..8ea13774 --- /dev/null +++ b/tolk-tester/tests/a10.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/a6.tolk b/tolk-tester/tests/a6.tolk new file mode 100644 index 00000000..3bdcdbdf --- /dev/null +++ b/tolk-tester/tests/a6.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/a6_1.tolk b/tolk-tester/tests/a6_1.tolk new file mode 100644 index 00000000..ecbf56dd --- /dev/null +++ b/tolk-tester/tests/a6_1.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/a6_5.tolk b/tolk-tester/tests/a6_5.tolk new file mode 100644 index 00000000..8b300c0c --- /dev/null +++ b/tolk-tester/tests/a6_5.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/a7.tolk b/tolk-tester/tests/a7.tolk new file mode 100644 index 00000000..1c0ae2eb --- /dev/null +++ b/tolk-tester/tests/a7.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/allow_post_modification.tolk b/tolk-tester/tests/allow_post_modification.tolk new file mode 100644 index 00000000..07955886 --- /dev/null +++ b/tolk-tester/tests/allow_post_modification.tolk @@ -0,0 +1,108 @@ +fun unsafe_tuple(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 +*/ diff --git a/tolk-tester/tests/asm_arg_order.tolk b/tolk-tester/tests/asm_arg_order.tolk new file mode 100644 index 00000000..8bf46e3e --- /dev/null +++ b/tolk-tester/tests/asm_arg_order.tolk @@ -0,0 +1,145 @@ +@pure +fun empty_tuple2(): tuple +asm "NIL"; +@pure +fun tpush2(t: tuple, x: X): (tuple, ()) +asm "TPUSH"; +fun emptyTuple(): tuple { return empty_tuple2(); } +fun tuplePush(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 +*/ diff --git a/tolk-tester/tests/bit-operators.tolk b/tolk-tester/tests/bit-operators.tolk new file mode 100644 index 00000000..049406af --- /dev/null +++ b/tolk-tester/tests/bit-operators.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/c2.tolk b/tolk-tester/tests/c2.tolk new file mode 100644 index 00000000..ec8d32da --- /dev/null +++ b/tolk-tester/tests/c2.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/c2_1.tolk b/tolk-tester/tests/c2_1.tolk new file mode 100644 index 00000000..4e52b9ee --- /dev/null +++ b/tolk-tester/tests/c2_1.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/camel1.tolk b/tolk-tester/tests/camel1.tolk new file mode 100644 index 00000000..a9f1bf3e --- /dev/null +++ b/tolk-tester/tests/camel1.tolk @@ -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(t: tuple, value: X): tuple { return tpush(t, value); } +fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tpush(t, value); } +fun myTupleAt(t: tuple, index: int): X { return at(t, index); } +fun tripleSecond(p: [X1, Y2, Z3]): Y2 { return triple_second(p); } +@pure +fun nullValue(): X +asm "PUSHNULL"; + +fun initial1(x: tuple): tuple { return x; } +fun initial2(x: tuple): tuple { return initial1(x); } + +// int add(int x, int y) { return x + y; } // this is also a wrapper, as its body is _+_(x,y) + +fun fake1(a: int, b: int, c: int): void +asm(a b c) "DROP DROP DROP"; +fun fake2(a: int, b: int, c: int): void +asm(b c a) "DROP DROP DROP"; +fun fake3(a: int, b: int, c: int): () +asm(c a b) "DROP DROP DROP"; +fun fake4(a: int, b: int, c: int): () +asm(c b a) "DROP DROP DROP"; + +fun fake1Wrapper(a: int, b: int, c: int) { return fake1(a, b, c); } +fun fake2Wrapper(a: int, b: int, c: int) { return fake2(a, b, c); } +fun fake3Wrapper(a: int, b: int, c: int) { return fake3(a, b, c); } +fun fake4Wrapper(a: int, b: int, c: int) { return fake4(a, b, c); } + +@method_id(101) +fun test1(): [int, int, int] { + var x: int = 1; + var y: int = 1; + var to_be_ref: cell = myBeginCell().myEndCell(); + var in_c: builder = myBeginCell().myStoreUint(123, 8); + in_c = myStoreRef(in_c, to_be_ref); + var (a, b, c) = myComputeDataSize(in_c.myEndCell(), 10); + assert(!(b != 8)) throw 101; + assert(!(c != 1), 101); + return [a, b + x, c + y]; +} + +@method_id(102) +fun test2(): [[int, int, int], int, int, int] { + var dict: cell = dict__new(); + dict = dict__iset(dict, 32, 456, myBeginCell().myStoreUint(4560, 32).myEndCell().myBeginParse()); + dict.dict__iset(32, 789, myBeginCell().myStoreUint(7890, 32).myEndCell().myBeginParse()); + dict~dict__iset(32, 123, myBeginCell().myStoreUint(0, 64).myStoreUint(1230, 32).myStoreUint(1231, 32).myStoreUint(1232, 32).myEndCell().myBeginParse()); + + var (mink, minv, _) = dict__tryIGetMin(dict, 32); + // skip 64 bits + minv~mySkipBits(16); + minv = minv.mySkipBits(16); + minv.mySkipBits(11); // does nothing + (minv, _) = ~mySkipBits(minv, 16); + mySkipBits(minv, 11); // does nothing + minv~mySkipBits(16); + // load 3*32 + var minv1 = minv~myLoadUint(32); + var minv2 = minv~myLoadUint(32); + var minv3 = minv~myLoadUint(32); + + var (_, found123) = dict__tryIGet(dict, 32, 123); + var (_, found456) = dict__tryIGet(dict, 32, 456); + var (_, found789) = dict__tryIGet(dict, 32, 789); + return [[minv1, minv2, minv3], found123, found456, found789]; +} + +@method_id(103) +fun test3(): tuple { + var with34: tuple = initial2(emptyTuple1()); + with34~myTuplePush(34); + + var t: tuple = emptyTuple11(); + t = myTuplePush(t, 12); + myTuplePush(t, emptyTuple11()); // does nothing + t~myTuplePush(emptyTuple1()); + t~myTuplePush(with34.myTupleAt(0)); + t.myTuplePush("123"s); // does nothing + + var tri: [cell, int, cell] = [nullValue(), 90 + 1, null]; + var f: int = tripleSecond(tri); + (t, _) = ~myTuplePush(t, f); + + return t; +} + +@method_id(104) +fun test4(a: int, b: int, c: int): int { + fake1Wrapper(a, b, c); + fake2Wrapper(a, b, c); + fake3Wrapper(a, b, c); + fake4Wrapper(a, b, c); + return 10; +} + +fun main(): int { + var x: int = now(); + return 30; +} + +/** + method_id | in | out +@testcase | 101 | | [ 2 9 2 ] +@testcase | 102 | | [ [ 1230 1231 1232 ] -1 -1 0 ] +@testcase | 103 | | [ 12 [] 34 91 ] + +@fif_codegen +""" + main PROC:<{ + // + 30 PUSHINT + }> +""" + +@fif_codegen +""" + test1 PROC:<{ + // + NEWC // _5 + ENDC // to_be_ref + NEWC // to_be_ref _8 + 123 PUSHINT // to_be_ref _8 _9=123 + SWAP // to_be_ref _9=123 _8 + 8 STU // to_be_ref in_c + STREF // in_c + ENDC // _16 + 10 PUSHINT // _16 _17=10 + CDATASIZE // a b c + OVER // a b c b + 8 NEQINT // a b c _21 + 101 THROWIF + DUP // a b c c + 1 NEQINT // a b c _26 + 101 THROWIF + SWAP // a c b + INC // a c _30 + SWAP // a _30 c + INC // a _30 _31 + TRIPLE // _29 + }> +""" + +@fif_codegen +""" + test2 PROC:<{ + ... + 16 PUSHINT // dict minv _45=16 + SDSKIPFIRST // dict minv + 16 PUSHINT // dict minv _47=16 + SDSKIPFIRST // dict minv + 16 PUSHINT // dict minv _52=16 + SDSKIPFIRST // dict minv + 16 PUSHINT // dict minv _57=16 + SDSKIPFIRST // dict minv + ... + 32 PUSHINT // dict minv1 minv2 minv3 found123 found456 _83=32 + 789 PUSHINT // dict minv1 minv2 minv3 found123 found456 _83=32 _84=789 + s0 s7 s7 XCHG3 // found456 minv1 minv2 minv3 found123 _84=789 dict _83=32 + DICTIGET + NULLSWAPIFNOT // found456 minv1 minv2 minv3 found123 _101 _102 + NIP // found456 minv1 minv2 minv3 found123 found789 + ... + 4 TUPLE // _86 + }> +""" + +@fif_codegen +""" + test3 PROC:<{ + // + NIL // _1 + initial1 CALLDICT // with34 + ... + TRIPLE // t tri + SECOND // t f + TPUSH // t + }> +""" + +@fif_codegen +""" + test4 PROC:<{ + // a b c + s2 s1 s0 PUSH3 // a b c a b c + DROP DROP DROP + s1 s0 s2 PUSH3 // a b c b c a + DROP DROP DROP + s0 s2 s1 PUSH3 // a b c c a b + DROP DROP DROP + s0 s2 XCHG // c b a + DROP DROP DROP + 10 PUSHINT // _7=10 + }> +""" + +@fif_codegen_avoid DECLPROC myBeginCell +@fif_codegen_avoid DECLPROC myStoreUint +@fif_codegen_avoid DECLPROC myStoreRef +@fif_codegen_avoid DECLPROC myComputeDataSize +@fif_codegen_avoid DECLPROC tryIdictGet +@fif_codegen_avoid DECLPROC myEmptyTuple +@fif_codegen_avoid DECLPROC myStoreUint +@fif_codegen_avoid DECLPROC initial2 +@fif_codegen_avoid DECLPROC add +@fif_codegen_avoid DECLPROC increase +*/ diff --git a/tolk-tester/tests/camel2.tolk b/tolk-tester/tests/camel2.tolk new file mode 100644 index 00000000..121b7f78 --- /dev/null +++ b/tolk-tester/tests/camel2.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/camel3.tolk b/tolk-tester/tests/camel3.tolk new file mode 100644 index 00000000..e76c02b7 --- /dev/null +++ b/tolk-tester/tests/camel3.tolk @@ -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(t: tuple, value: X): tuple { return tpush(t, value); } +fun ~myTuplePush(t: tuple, value: X): (tuple, ()) { return ~tpush(t, value); } +fun tupleGetFirst(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 +*/ diff --git a/tolk-tester/tests/camel4.tolk b/tolk-tester/tests/camel4.tolk new file mode 100644 index 00000000..c6be6268 --- /dev/null +++ b/tolk-tester/tests/camel4.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/cells-slices.tolk b/tolk-tester/tests/cells-slices.tolk new file mode 100644 index 00000000..508cd31d --- /dev/null +++ b/tolk-tester/tests/cells-slices.tolk @@ -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 + */ diff --git a/tolk-tester/tests/co1.tolk b/tolk-tester/tests/co1.tolk new file mode 100644 index 00000000..bc56dfa8 --- /dev/null +++ b/tolk-tester/tests/co1.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/code_after_ifelse.tolk b/tolk-tester/tests/code_after_ifelse.tolk new file mode 100644 index 00000000..6a16262f --- /dev/null +++ b/tolk-tester/tests/code_after_ifelse.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/codegen_check_demo.tolk b/tolk-tester/tests/codegen_check_demo.tolk new file mode 100644 index 00000000..02379540 --- /dev/null +++ b/tolk-tester/tests/codegen_check_demo.tolk @@ -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 +""" +*/ diff --git a/tolk-tester/tests/comments.tolk b/tolk-tester/tests/comments.tolk new file mode 100644 index 00000000..cd287747 --- /dev/null +++ b/tolk-tester/tests/comments.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/if_stmt.tolk b/tolk-tester/tests/if_stmt.tolk new file mode 100644 index 00000000..2c51ac51 --- /dev/null +++ b/tolk-tester/tests/if_stmt.tolk @@ -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 +""" +*/ diff --git a/tolk-tester/tests/imports/invalid-no-import.tolk b/tolk-tester/tests/imports/invalid-no-import.tolk new file mode 100644 index 00000000..6c4ab6ce --- /dev/null +++ b/tolk-tester/tests/imports/invalid-no-import.tolk @@ -0,0 +1,4 @@ +fun demoOfInvalid(): (int) { + var f = someAdd; + return f(1, 2); +} diff --git a/tolk-tester/tests/imports/some-math.tolk b/tolk-tester/tests/imports/some-math.tolk new file mode 100644 index 00000000..dc0c9c9b --- /dev/null +++ b/tolk-tester/tests/imports/some-math.tolk @@ -0,0 +1,3 @@ +fun someAdd(a: int, b: int): int { + return a + b + 0; +} diff --git a/tolk-tester/tests/inline_big.tolk b/tolk-tester/tests/inline_big.tolk new file mode 100644 index 00000000..be014eb5 --- /dev/null +++ b/tolk-tester/tests/inline_big.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/inline_if.tolk b/tolk-tester/tests/inline_if.tolk new file mode 100644 index 00000000..9f1fa8c1 --- /dev/null +++ b/tolk-tester/tests/inline_if.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/inline_loops.tolk b/tolk-tester/tests/inline_loops.tolk new file mode 100644 index 00000000..eba595a5 --- /dev/null +++ b/tolk-tester/tests/inline_loops.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/invalid-bitwise-1.tolk b/tolk-tester/tests/invalid-bitwise-1.tolk new file mode 100644 index 00000000..f939d60d --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-1.tolk @@ -0,0 +1,9 @@ +fun main(flags: int): int { + return flags&0xFF!=0; +} + +/** +@compilation_should_fail +@stderr & has lower precedence than != +@stderr Use parenthesis +*/ diff --git a/tolk-tester/tests/invalid-bitwise-2.tolk b/tolk-tester/tests/invalid-bitwise-2.tolk new file mode 100644 index 00000000..e6fcd1e5 --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-2.tolk @@ -0,0 +1,8 @@ +fun justTrue(): int { return true; } + +const a = justTrue() | 1 < 9; + +/** +@compilation_should_fail +@stderr | has lower precedence than < +*/ diff --git a/tolk-tester/tests/invalid-bitwise-3.tolk b/tolk-tester/tests/invalid-bitwise-3.tolk new file mode 100644 index 00000000..ee43860b --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-3.tolk @@ -0,0 +1,8 @@ +fun justTrue(): int { return true; } + +const a = justTrue() | (1 < 9) | justTrue() != true; + +/** +@compilation_should_fail +@stderr | has lower precedence than != +*/ diff --git a/tolk-tester/tests/invalid-bitwise-4.tolk b/tolk-tester/tests/invalid-bitwise-4.tolk new file mode 100644 index 00000000..563ed535 --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-4.tolk @@ -0,0 +1,6 @@ +const a = (1) <=> (0) ^ 8; + +/** +@compilation_should_fail +@stderr ^ has lower precedence than <=> +*/ diff --git a/tolk-tester/tests/invalid-bitwise-5.tolk b/tolk-tester/tests/invalid-bitwise-5.tolk new file mode 100644 index 00000000..1030ed8d --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-5.tolk @@ -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 > +*/ diff --git a/tolk-tester/tests/invalid-bitwise-6.tolk b/tolk-tester/tests/invalid-bitwise-6.tolk new file mode 100644 index 00000000..9c4dc67e --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-6.tolk @@ -0,0 +1,9 @@ +fun main() { + if ((1==1)|(2==2)&(3==3)) { + } +} + +/** +@compilation_should_fail +@stderr mixing | with & without parenthesis +*/ diff --git a/tolk-tester/tests/invalid-bitwise-7.tolk b/tolk-tester/tests/invalid-bitwise-7.tolk new file mode 100644 index 00000000..39fba401 --- /dev/null +++ b/tolk-tester/tests/invalid-bitwise-7.tolk @@ -0,0 +1,8 @@ +fun main() { + var c = x && y || x && y; +} + +/** +@compilation_should_fail +@stderr mixing && with || without parenthesis +*/ diff --git a/tolk-tester/tests/invalid-builtin-1.tolk b/tolk-tester/tests/invalid-builtin-1.tolk new file mode 100644 index 00000000..6a7f1ca7 --- /dev/null +++ b/tolk-tester/tests/invalid-builtin-1.tolk @@ -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 +""" +*/ diff --git a/tolk-tester/tests/invalid-catch-1.tolk b/tolk-tester/tests/invalid-catch-1.tolk new file mode 100644 index 00000000..756722bb --- /dev/null +++ b/tolk-tester/tests/invalid-catch-1.tolk @@ -0,0 +1,12 @@ +fun main() { + try { + + } catch(int, arg) {} + return 0; +} + +/** +@compilation_should_fail +@stderr expected identifier, got `int` +@stderr catch(int + */ diff --git a/tolk-tester/tests/invalid-catch-2.tolk b/tolk-tester/tests/invalid-catch-2.tolk new file mode 100644 index 00000000..a0276146 --- /dev/null +++ b/tolk-tester/tests/invalid-catch-2.tolk @@ -0,0 +1,9 @@ +fun main() { + try {} + catch(err, arg, more) {} +} + +/** +@compilation_should_fail +@stderr expected `)`, got `,` + */ diff --git a/tolk-tester/tests/invalid-cmt-nested.tolk b/tolk-tester/tests/invalid-cmt-nested.tolk new file mode 100644 index 00000000..807e7be8 --- /dev/null +++ b/tolk-tester/tests/invalid-cmt-nested.tolk @@ -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 `*` +*/ diff --git a/tolk-tester/tests/invalid-cmt-old.tolk b/tolk-tester/tests/invalid-cmt-old.tolk new file mode 100644 index 00000000..eaf58db8 --- /dev/null +++ b/tolk-tester/tests/invalid-cmt-old.tolk @@ -0,0 +1,8 @@ +fun main(): int { + ;; this is not a comment +} + +/** +@compilation_should_fail +@stderr error: expected `;`, got `is` + */ diff --git a/tolk-tester/tests/invalid-cyclic-1.tolk b/tolk-tester/tests/invalid-cyclic-1.tolk new file mode 100644 index 00000000..c46b1640 --- /dev/null +++ b/tolk-tester/tests/invalid-cyclic-1.tolk @@ -0,0 +1,8 @@ +const ONE = TWO - 1; +const TWO = ONE + 1; + +/** +@compilation_should_fail +@stderr const ONE +@stderr undefined symbol `TWO` + */ diff --git a/tolk-tester/tests/invalid-declaration-1.tolk b/tolk-tester/tests/invalid-declaration-1.tolk new file mode 100644 index 00000000..ea27e723 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-1.tolk @@ -0,0 +1,6 @@ +const a = 10, b = 20; + +/** +@compilation_should_fail +@stderr multiple declarations are not allowed + */ diff --git a/tolk-tester/tests/invalid-declaration-10.tolk b/tolk-tester/tests/invalid-declaration-10.tolk new file mode 100644 index 00000000..7ccb182d --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-10.tolk @@ -0,0 +1,8 @@ +get fun onInternalMessage() { + return 0; +} + +/** +@compilation_should_fail +@stderr invalid declaration of a reserved function + */ diff --git a/tolk-tester/tests/invalid-declaration-2.tolk b/tolk-tester/tests/invalid-declaration-2.tolk new file mode 100644 index 00000000..70063251 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-2.tolk @@ -0,0 +1,8 @@ +fun main(int): int { + +} + +/** +@compilation_should_fail +@stderr expected parameter name, got `int` +*/ diff --git a/tolk-tester/tests/invalid-declaration-3.tolk b/tolk-tester/tests/invalid-declaration-3.tolk new file mode 100644 index 00000000..3edc09fd --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-3.tolk @@ -0,0 +1,8 @@ +int main() { + +} + +/** +@compilation_should_fail +@stderr expected fun or get, got `int` +*/ diff --git a/tolk-tester/tests/invalid-declaration-4.tolk b/tolk-tester/tests/invalid-declaration-4.tolk new file mode 100644 index 00000000..183dda96 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-4.tolk @@ -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 = ...` +*/ diff --git a/tolk-tester/tests/invalid-declaration-5.tolk b/tolk-tester/tests/invalid-declaration-5.tolk new file mode 100644 index 00000000..bf23d857 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-5.tolk @@ -0,0 +1,6 @@ +enum MyKind { } + +/** +@compilation_should_fail +@stderr `enum` is not supported yet +*/ diff --git a/tolk-tester/tests/invalid-declaration-6.tolk b/tolk-tester/tests/invalid-declaration-6.tolk new file mode 100644 index 00000000..731c299b --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-6.tolk @@ -0,0 +1,8 @@ +fun main() { + val imm = 10; +} + +/** +@compilation_should_fail +@stderr immutable variables are not supported yet +*/ diff --git a/tolk-tester/tests/invalid-declaration-7.tolk b/tolk-tester/tests/invalid-declaration-7.tolk new file mode 100644 index 00000000..8d188ea0 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-7.tolk @@ -0,0 +1,8 @@ +fun main() { + var a = 10, b = 20; +} + +/** +@compilation_should_fail +@stderr multiple declarations are not allowed + */ diff --git a/tolk-tester/tests/invalid-declaration-8.tolk b/tolk-tester/tests/invalid-declaration-8.tolk new file mode 100644 index 00000000..06cb9a98 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-8.tolk @@ -0,0 +1,8 @@ +fun someDemo() { + return 0; +} + +/** +@compilation_should_fail +@stderr the contract has no entrypoint + */ diff --git a/tolk-tester/tests/invalid-declaration-9.tolk b/tolk-tester/tests/invalid-declaration-9.tolk new file mode 100644 index 00000000..8cb71c73 --- /dev/null +++ b/tolk-tester/tests/invalid-declaration-9.tolk @@ -0,0 +1,9 @@ +fun recv_internal() { + return 0; +} + +/** +@compilation_should_fail +@stderr this is a reserved FunC/Fift identifier +@stderr you need `onInternalMessage` + */ diff --git a/tolk-tester/tests/invalid-get-method-1.tolk b/tolk-tester/tests/invalid-get-method-1.tolk new file mode 100644 index 00000000..263370d4 --- /dev/null +++ b/tolk-tester/tests/invalid-get-method-1.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/invalid-get-method-2.tolk b/tolk-tester/tests/invalid-get-method-2.tolk new file mode 100644 index 00000000..7c7a1413 --- /dev/null +++ b/tolk-tester/tests/invalid-get-method-2.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/invalid-import.tolk b/tolk-tester/tests/invalid-import.tolk new file mode 100644 index 00000000..b1c01518 --- /dev/null +++ b/tolk-tester/tests/invalid-import.tolk @@ -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"; + */ diff --git a/tolk-tester/tests/invalid-logical-1.tolk b/tolk-tester/tests/invalid-logical-1.tolk new file mode 100644 index 00000000..9aa210bb --- /dev/null +++ b/tolk-tester/tests/invalid-logical-1.tolk @@ -0,0 +1,8 @@ +fun main() { + return 1 && 2; +} + +/** +@compilation_should_fail +@stderr logical operators are not supported yet + */ diff --git a/tolk-tester/tests/invalid-no-import.tolk b/tolk-tester/tests/invalid-no-import.tolk new file mode 100644 index 00000000..89f879a3 --- /dev/null +++ b/tolk-tester/tests/invalid-no-import.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-nopar-1.tolk b/tolk-tester/tests/invalid-nopar-1.tolk new file mode 100644 index 00000000..a9a84865 --- /dev/null +++ b/tolk-tester/tests/invalid-nopar-1.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-nopar-2.tolk b/tolk-tester/tests/invalid-nopar-2.tolk new file mode 100644 index 00000000..c7c13650 --- /dev/null +++ b/tolk-tester/tests/invalid-nopar-2.tolk @@ -0,0 +1,12 @@ + +fun main(x: int): int { + if x { + return 10; + } + return 0; +} + +/** +@compilation_should_fail +@stderr expected `(`, got `x` + */ diff --git a/tolk-tester/tests/invalid-nopar-3.tolk b/tolk-tester/tests/invalid-nopar-3.tolk new file mode 100644 index 00000000..8249ca28 --- /dev/null +++ b/tolk-tester/tests/invalid-nopar-3.tolk @@ -0,0 +1,12 @@ + +fun main(x: int): int { + if (x, 1) { + return 10; + } + return 0; +} + +/** +@compilation_should_fail +@stderr expected `)`, got `,` + */ diff --git a/tolk-tester/tests/invalid-nopar-4.tolk b/tolk-tester/tests/invalid-nopar-4.tolk new file mode 100644 index 00000000..6e833f99 --- /dev/null +++ b/tolk-tester/tests/invalid-nopar-4.tolk @@ -0,0 +1,8 @@ +fun load_u32(cs: slice): (slice, int) { + return cs.load_uint 32; +} + +/** +@compilation_should_fail +@stderr expected `(`, got `32` + */ diff --git a/tolk-tester/tests/invalid-pure-1.tolk b/tolk-tester/tests/invalid-pure-1.tolk new file mode 100644 index 00000000..5baa3292 --- /dev/null +++ b/tolk-tester/tests/invalid-pure-1.tolk @@ -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(); +""" +*/ diff --git a/tolk-tester/tests/invalid-pure-2.tolk b/tolk-tester/tests/invalid-pure-2.tolk new file mode 100644 index 00000000..5f8f40ec --- /dev/null +++ b/tolk-tester/tests/invalid-pure-2.tolk @@ -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; +""" +*/ diff --git a/tolk-tester/tests/invalid-pure-3.tolk b/tolk-tester/tests/invalid-pure-3.tolk new file mode 100644 index 00000000..0e1b4104 --- /dev/null +++ b/tolk-tester/tests/invalid-pure-3.tolk @@ -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) +""" +*/ diff --git a/tolk-tester/tests/invalid-redefinition-1.tolk b/tolk-tester/tests/invalid-redefinition-1.tolk new file mode 100644 index 00000000..49771cea --- /dev/null +++ b/tolk-tester/tests/invalid-redefinition-1.tolk @@ -0,0 +1,7 @@ +global moddiv: int; + +/** +@compilation_should_fail +@stderr global moddiv: int; +@stderr redefinition of built-in symbol + */ diff --git a/tolk-tester/tests/invalid-redefinition-2.tolk b/tolk-tester/tests/invalid-redefinition-2.tolk new file mode 100644 index 00000000..3a300dc2 --- /dev/null +++ b/tolk-tester/tests/invalid-redefinition-2.tolk @@ -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 + */ diff --git a/tolk-tester/tests/invalid-redefinition-3.tolk b/tolk-tester/tests/invalid-redefinition-3.tolk new file mode 100644 index 00000000..04ed9383 --- /dev/null +++ b/tolk-tester/tests/invalid-redefinition-3.tolk @@ -0,0 +1,8 @@ +fun main(): int { + var demo_10: int = demo_10; +} + +/** +@compilation_should_fail +@stderr undefined symbol `demo_10` + */ diff --git a/tolk-tester/tests/invalid-redefinition-4.tolk b/tolk-tester/tests/invalid-redefinition-4.tolk new file mode 100644 index 00000000..993a869b --- /dev/null +++ b/tolk-tester/tests/invalid-redefinition-4.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-redefinition-5.tolk b/tolk-tester/tests/invalid-redefinition-5.tolk new file mode 100644 index 00000000..4a8f5ea1 --- /dev/null +++ b/tolk-tester/tests/invalid-redefinition-5.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-shift-1.tolk b/tolk-tester/tests/invalid-shift-1.tolk new file mode 100644 index 00000000..5127ce05 --- /dev/null +++ b/tolk-tester/tests/invalid-shift-1.tolk @@ -0,0 +1,8 @@ +fun main(flags: int) { + return flags << 1 + 32; +} + +/** +@compilation_should_fail +@stderr << has lower precedence than + +*/ diff --git a/tolk-tester/tests/invalid-symbol-1.tolk b/tolk-tester/tests/invalid-symbol-1.tolk new file mode 100644 index 00000000..5d392f52 --- /dev/null +++ b/tolk-tester/tests/invalid-symbol-1.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-symbol-2.tolk b/tolk-tester/tests/invalid-symbol-2.tolk new file mode 100644 index 00000000..f55e15ce --- /dev/null +++ b/tolk-tester/tests/invalid-symbol-2.tolk @@ -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` + */ diff --git a/tolk-tester/tests/invalid-syntax-1.tolk b/tolk-tester/tests/invalid-syntax-1.tolk new file mode 100644 index 00000000..4ccc8f22 --- /dev/null +++ b/tolk-tester/tests/invalid-syntax-1.tolk @@ -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 `{` + */ diff --git a/tolk-tester/tests/invalid-syntax-2.tolk b/tolk-tester/tests/invalid-syntax-2.tolk new file mode 100644 index 00000000..1180dbbf --- /dev/null +++ b/tolk-tester/tests/invalid-syntax-2.tolk @@ -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 + */ diff --git a/tolk-tester/tests/invalid-syntax-3.tolk b/tolk-tester/tests/invalid-syntax-3.tolk new file mode 100644 index 00000000..26ce82ac --- /dev/null +++ b/tolk-tester/tests/invalid-syntax-3.tolk @@ -0,0 +1,8 @@ +fun main(x: int) { + return null(); +} + +/** +@compilation_should_fail +@stderr null is not a function: use `null`, not `null()` + */ diff --git a/tolk-tester/tests/invalid-syntax-4.tolk b/tolk-tester/tests/invalid-syntax-4.tolk new file mode 100644 index 00000000..044dd329 --- /dev/null +++ b/tolk-tester/tests/invalid-syntax-4.tolk @@ -0,0 +1,8 @@ +fun main(x: int) { + assert(x > 0); +} + +/** +@compilation_should_fail +@stderr expected `throw excNo` after assert, got `;` + */ diff --git a/tolk-tester/tests/invalid-tolk-version.tolk b/tolk-tester/tests/invalid-tolk-version.tolk new file mode 100644 index 00000000..d66de9ff --- /dev/null +++ b/tolk-tester/tests/invalid-tolk-version.tolk @@ -0,0 +1,7 @@ +tolk asdf; + +/** +@compilation_should_fail +@stderr semver expected +@stderr tolk asdf; + */ diff --git a/tolk-tester/tests/invalid-typing-1.tolk b/tolk-tester/tests/invalid-typing-1.tolk new file mode 100644 index 00000000..a0fe296d --- /dev/null +++ b/tolk-tester/tests/invalid-typing-1.tolk @@ -0,0 +1,10 @@ +fun main() { + var tri: [int, scli] = [10, null()]; + return; +} + +/** +@compilation_should_fail +@stderr .tolk:2 +@stderr expected , got `scli` + */ diff --git a/tolk-tester/tests/invalid-typing-2.tolk b/tolk-tester/tests/invalid-typing-2.tolk new file mode 100644 index 00000000..d7c6745f --- /dev/null +++ b/tolk-tester/tests/invalid-typing-2.tolk @@ -0,0 +1,9 @@ +fun main() { + var tri: (int, bool) = (10, false); + return; +} + +/** +@compilation_should_fail +@stderr bool type is not supported yet + */ diff --git a/tolk-tester/tests/invalid.tolk b/tolk-tester/tests/invalid.tolk new file mode 100644 index 00000000..21774774 --- /dev/null +++ b/tolk-tester/tests/invalid.tolk @@ -0,0 +1,8 @@ +fun main(s: auto) { + var (z, t) = ; + +/** +@compilation_should_fail +@stderr expected , got `;` +@stderr var (z, t) = ; +*/ diff --git a/tolk-tester/tests/logical-operators.tolk b/tolk-tester/tests/logical-operators.tolk new file mode 100644 index 00000000..b73c2522 --- /dev/null +++ b/tolk-tester/tests/logical-operators.tolk @@ -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 + }> +""" + + */ diff --git a/tolk-tester/tests/method_id.tolk b/tolk-tester/tests/method_id.tolk new file mode 100644 index 00000000..c2d0b9aa --- /dev/null +++ b/tolk-tester/tests/method_id.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/no-spaces.tolk b/tolk-tester/tests/no-spaces.tolk new file mode 100644 index 00000000..018c99da --- /dev/null +++ b/tolk-tester/tests/no-spaces.tolk @@ -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 + }> +""" + + */ + diff --git a/tolk-tester/tests/null-keyword.tolk b/tolk-tester/tests/null-keyword.tolk new file mode 100644 index 00000000..0be4966f --- /dev/null +++ b/tolk-tester/tests/null-keyword.tolk @@ -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 + }> +""" +*/ diff --git a/tolk-tester/tests/op_priority.tolk b/tolk-tester/tests/op_priority.tolk new file mode 100644 index 00000000..e4f97b75 --- /dev/null +++ b/tolk-tester/tests/op_priority.tolk @@ -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 `_ 0, 3 & (3 > 0), 3 & (`_<_`(3, 0)), + 3 & `_ + 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 + }> +""" + + */ diff --git a/tolk-tester/tests/pure-functions.tolk b/tolk-tester/tests/pure-functions.tolk new file mode 100644 index 00000000..59b2f0da --- /dev/null +++ b/tolk-tester/tests/pure-functions.tolk @@ -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 + +*/ diff --git a/tolk-tester/tests/remove-unused-functions.tolk b/tolk-tester/tests/remove-unused-functions.tolk new file mode 100644 index 00000000..e5d8aabc --- /dev/null +++ b/tolk-tester/tests/remove-unused-functions.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/s1.tolk b/tolk-tester/tests/s1.tolk new file mode 100644 index 00000000..3f75f1a7 --- /dev/null +++ b/tolk-tester/tests/s1.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/special-fun-names.tolk b/tolk-tester/tests/special-fun-names.tolk new file mode 100644 index 00000000..8fae6d5d --- /dev/null +++ b/tolk-tester/tests/special-fun-names.tolk @@ -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 +""" + */ diff --git a/tolk-tester/tests/test-math.tolk b/tolk-tester/tests/test-math.tolk new file mode 100644 index 00000000..dde4c2b3 --- /dev/null +++ b/tolk-tester/tests/test-math.tolk @@ -0,0 +1,309 @@ +import "../../crypto/smartcont/mathlib.tolk"; + +@pure +fun ~tset(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 +*/ diff --git a/tolk-tester/tests/try-func.tolk b/tolk-tester/tests/try-func.tolk new file mode 100644 index 00000000..7963a850 --- /dev/null +++ b/tolk-tester/tests/try-func.tolk @@ -0,0 +1,151 @@ +fun unsafeGetInt(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 +*/ diff --git a/tolk-tester/tests/unbalanced_ret.tolk b/tolk-tester/tests/unbalanced_ret.tolk new file mode 100644 index 00000000..6cf42643 --- /dev/null +++ b/tolk-tester/tests/unbalanced_ret.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/unbalanced_ret_inline.tolk b/tolk-tester/tests/unbalanced_ret_inline.tolk new file mode 100644 index 00000000..4e24fbd8 --- /dev/null +++ b/tolk-tester/tests/unbalanced_ret_inline.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/unbalanced_ret_loops.tolk b/tolk-tester/tests/unbalanced_ret_loops.tolk new file mode 100644 index 00000000..9b59339d --- /dev/null +++ b/tolk-tester/tests/unbalanced_ret_loops.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/unbalanced_ret_nested.tolk b/tolk-tester/tests/unbalanced_ret_nested.tolk new file mode 100644 index 00000000..4d294ae9 --- /dev/null +++ b/tolk-tester/tests/unbalanced_ret_nested.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/use-before-declare.tolk b/tolk-tester/tests/use-before-declare.tolk new file mode 100644 index 00000000..84569c0c --- /dev/null +++ b/tolk-tester/tests/use-before-declare.tolk @@ -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 + }> +""" + */ diff --git a/tolk-tester/tests/w1.tolk b/tolk-tester/tests/w1.tolk new file mode 100644 index 00000000..eb06bec6 --- /dev/null +++ b/tolk-tester/tests/w1.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/w2.tolk b/tolk-tester/tests/w2.tolk new file mode 100644 index 00000000..b013ab06 --- /dev/null +++ b/tolk-tester/tests/w2.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/w6.tolk b/tolk-tester/tests/w6.tolk new file mode 100644 index 00000000..2f895644 --- /dev/null +++ b/tolk-tester/tests/w6.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/w7.tolk b/tolk-tester/tests/w7.tolk new file mode 100644 index 00000000..85081fbb --- /dev/null +++ b/tolk-tester/tests/w7.tolk @@ -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 +*/ diff --git a/tolk-tester/tests/w9.tolk b/tolk-tester/tests/w9.tolk new file mode 100644 index 00000000..b88dc736 --- /dev/null +++ b/tolk-tester/tests/w9.tolk @@ -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 +*/ diff --git a/tolk-tester/tolk-tester.js b/tolk-tester/tolk-tester.js new file mode 100644 index 00000000..3fb92ff0 --- /dev/null +++ b/tolk-tester/tolk-tester.js @@ -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 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 }) +} diff --git a/tolk-tester/tolk-tester.py b/tolk-tester/tolk-tester.py new file mode 100644 index 00000000..261ab496 --- /dev/null +++ b/tolk-tester/tolk-tester.py @@ -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 0) { val |= _NonZero | _Pos | _Finite; - } else if (!s) { - //if (*int_const == 1) { - // val |= _Bit; - //} - val |= _Zero | _Neg | _Pos | _Finite | _Bool | _Bit; + } else { + val |= _Zero | _Neg | _Pos | _Finite; } if (val & _Finite) { val |= int_const->get_bit(0) ? _Odd : _Even; diff --git a/tolk/analyzer.cpp b/tolk/analyzer.cpp index cefa83b9..b2ea55ec 100644 --- a/tolk/analyzer.cpp +++ b/tolk/analyzer.cpp @@ -883,7 +883,7 @@ void Op::set_impure(const CodeBlob &code) { // todo calling this function with `code` is a bad design (flags are assigned after Op is constructed) // later it's better to check this somewhere in code.emplace_back() if (code.flags & CodeBlob::_ForbidImpure) { - throw ParseError(where, "An impure operation in a pure function"); + throw ParseError(where, "an impure operation in a pure function"); } flags |= _Impure; } @@ -891,7 +891,7 @@ void Op::set_impure(const CodeBlob &code) { void Op::set_impure(const CodeBlob &code, bool flag) { if (flag) { if (code.flags & CodeBlob::_ForbidImpure) { - throw ParseError(where, "An impure operation in a pure function"); + throw ParseError(where, "an impure operation in a pure function"); } flags |= _Impure; } else { diff --git a/tolk/ast-from-tokens.cpp b/tolk/ast-from-tokens.cpp index 75cc0b4d..5116fcf5 100644 --- a/tolk/ast-from-tokens.cpp +++ b/tolk/ast-from-tokens.cpp @@ -18,6 +18,7 @@ #include "ast.h" #include "platform-utils.h" #include "type-expr.h" +#include "tolk-version.h" /* * Here we construct AST for a tolk file. @@ -35,11 +36,15 @@ static bool is_comparison_binary_op(TokenType tok) { } // same as above, but to detect bitwise operators: & | ^ -// (in Tolk, they are used as logical ones due to absence of a boolean type and && || operators) static bool is_bitwise_binary_op(TokenType tok) { return tok == tok_bitwise_and || tok == tok_bitwise_or || tok == tok_bitwise_xor; } +// same as above, but to detect logical operators: && || +static bool is_logical_binary_op(TokenType tok) { + return tok == tok_logical_and || tok == tok_logical_or; +} + // same as above, but to detect addition/subtraction static bool is_add_or_sub_binary_op(TokenType tok) { return tok == tok_plus || tok == tok_minus; @@ -58,11 +63,10 @@ static void fire_error_lower_precedence(SrcLocation loc, std::string_view op_low // fire an error for a case "arg1 & arg2 | arg3" GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD -static void fire_error_mix_bitwise_and_or(SrcLocation loc, std::string_view op1, std::string_view op2) { +static void fire_error_mix_and_or_no_parenthesis(SrcLocation loc, std::string_view op1, std::string_view op2) { std::string name1 = static_cast(op1); std::string name2 = static_cast(op2); - throw ParseError(loc, "mixing " + name1 + " with " + name2 + " without parenthesis" - ", probably this code won't work as you expected. " + throw ParseError(loc, "mixing " + name1 + " with " + name2 + " without parenthesis may lead to accidental errors. " "Use parenthesis to emphasize operator precedence."); } @@ -81,10 +85,22 @@ static void diagnose_bitwise_precedence(SrcLocation loc, std::string_view operat if (lhs->type == ast_binary_operator && is_comparison_binary_op(lhs->as()->tok)) { fire_error_lower_precedence(loc, operator_name, lhs->as()->operator_name); } +} - // handle "arg1 & arg2 | arg3" (lhs = "arg1 & arg2") - if (lhs->type == ast_binary_operator && is_bitwise_binary_op(lhs->as()->tok) && lhs->as()->operator_name != operator_name) { - fire_error_mix_bitwise_and_or(loc, lhs->as()->operator_name, operator_name); +// similar to above, but detect potentially invalid usage of && and || +// since anyway, using parenthesis when both && and || occur in the same expression, +// && and || have equal operator precedence in Tolk +static void diagnose_and_or_precedence(SrcLocation loc, AnyV lhs, TokenType rhs_tok, std::string_view rhs_operator_name) { + if (auto lhs_op = lhs->try_as()) { + // handle "arg1 & arg2 | arg3" (lhs = "arg1 & arg2") + if (is_bitwise_binary_op(lhs_op->tok) && is_bitwise_binary_op(rhs_tok) && lhs_op->tok != rhs_tok) { + fire_error_mix_and_or_no_parenthesis(loc, lhs_op->operator_name, rhs_operator_name); + } + + // handle "arg1 && arg2 || arg3" (lhs = "arg1 && arg2") + if (is_logical_binary_op(lhs_op->tok) && is_logical_binary_op(rhs_tok) && lhs_op->tok != rhs_tok) { + fire_error_mix_and_or_no_parenthesis(loc, lhs_op->operator_name, rhs_operator_name); + } } } @@ -95,6 +111,34 @@ static void diagnose_addition_in_bitshift(SrcLocation loc, std::string_view bits } } +// fire an error for FunC-style variable declaration, like "int i" +GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD +static void fire_error_FunC_style_var_declaration(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + std::string type_str = static_cast(lex.cur_str()); // int / slice / etc. + lex.next(); + std::string var_name = lex.tok() == tok_identifier ? static_cast(lex.cur_str()) : "name"; + throw ParseError(loc, "can't parse; probably, you use FunC-like declarations; valid syntax is `var " + var_name + ": " + type_str + " = ...`"); +} + +// replace (a == null) and similar to isNull(a) (call of a built-in function) +static AnyV maybe_replace_eq_null_with_isNull_call(V v) { + bool has_null = v->get_lhs()->type == ast_null_keyword || v->get_rhs()->type == ast_null_keyword; + bool replace = has_null && (v->tok == tok_eq || v->tok == tok_neq); + if (!replace) { + return v; + } + + auto v_ident = createV(v->loc, "__isNull"); // built-in function + AnyV v_null = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs(); + AnyV v_isNull = createV(v->loc, v_ident, createV(v->loc, {v_null})); + if (v->tok == tok_neq) { + v_isNull = createV(v->loc, "!", tok_logical_not, v_isNull); + } + return v_isNull; +} + + /* * * PARSE SOURCE @@ -103,9 +147,9 @@ static void diagnose_addition_in_bitshift(SrcLocation loc, std::string_view bits // TE ::= TA | TA -> TE // TA ::= int | ... | cont | var | _ | () | ( TE { , TE } ) | [ TE { , TE } ] -static TypeExpr* parse_type(Lexer& lex, V forall_list); +static TypeExpr* parse_type(Lexer& lex, V genericsT_list); -static TypeExpr* parse_type1(Lexer& lex, V forall_list) { +static TypeExpr* parse_type1(Lexer& lex, V genericsT_list) { switch (lex.tok()) { case tok_int: lex.next(); @@ -119,338 +163,307 @@ static TypeExpr* parse_type1(Lexer& lex, V forall_list) { case tok_builder: lex.next(); return TypeExpr::new_atomic(TypeExpr::_Builder); - case tok_cont: + case tok_continuation: lex.next(); return TypeExpr::new_atomic(TypeExpr::_Cont); case tok_tuple: lex.next(); return TypeExpr::new_atomic(TypeExpr::_Tuple); - case tok_var: - case tok_underscore: + case tok_auto: lex.next(); return TypeExpr::new_hole(); - case tok_identifier: { - if (int idx = forall_list ? forall_list->lookup_idx(lex.cur_str()) : -1; idx != -1) { + case tok_void: + lex.next(); + return TypeExpr::new_tensor({}); + case tok_bool: + lex.error("bool type is not supported yet"); + case tok_identifier: + if (int idx = genericsT_list ? genericsT_list->lookup_idx(lex.cur_str()) : -1; idx != -1) { lex.next(); - return forall_list->get_item(idx)->created_type; + return genericsT_list->get_item(idx)->created_type; } - lex.error("Is not a type identifier"); + break; + case tok_oppar: { + lex.next(); + if (lex.tok() == tok_clpar) { + lex.next(); + return TypeExpr::new_unit(); + } + std::vector sub{1, parse_type(lex, genericsT_list)}; + while (lex.tok() == tok_comma) { + lex.next(); + sub.push_back(parse_type(lex, genericsT_list)); + } + lex.expect(tok_clpar, "`)`"); + return TypeExpr::new_tensor(std::move(sub)); + } + case tok_opbracket: { + lex.next(); + if (lex.tok() == tok_clbracket) { + lex.next(); + return TypeExpr::new_tuple({}); + } + std::vector sub{1, parse_type(lex, genericsT_list)}; + while (lex.tok() == tok_comma) { + lex.next(); + sub.push_back(parse_type(lex, genericsT_list)); + } + lex.expect(tok_clbracket, "`]`"); + return TypeExpr::new_tuple(std::move(sub)); } default: break; } - TokenType c; - if (lex.tok() == tok_opbracket) { - lex.next(); - c = tok_clbracket; - } else { - lex.expect(tok_oppar, ""); - c = tok_clpar; - } - if (lex.tok() == c) { - lex.next(); - return c == tok_clpar ? TypeExpr::new_unit() : TypeExpr::new_tuple({}); - } - auto t1 = parse_type(lex, forall_list); - if (lex.tok() == tok_clpar) { - lex.expect(c, c == tok_clpar ? "')'" : "']'"); - return t1; - } - std::vector tlist{1, t1}; - while (lex.tok() == tok_comma) { - lex.next(); - tlist.push_back(parse_type(lex, forall_list)); - } - lex.expect(c, c == tok_clpar ? "')'" : "']'"); - return c == tok_clpar ? TypeExpr::new_tensor(std::move(tlist)) : TypeExpr::new_tuple(std::move(tlist)); + lex.unexpected(""); } -static TypeExpr* parse_type(Lexer& lex, V forall_list) { - TypeExpr* res = parse_type1(lex, forall_list); - if (lex.tok() == tok_mapsto) { +static TypeExpr* parse_type(Lexer& lex, V genericsT_list) { + TypeExpr* res = parse_type1(lex, genericsT_list); + if (lex.tok() == tok_arrow) { lex.next(); - TypeExpr* to = parse_type(lex, forall_list); + TypeExpr* to = parse_type(lex, genericsT_list); return TypeExpr::new_map(res, to); } return res; } -static AnyV parse_argument(Lexer& lex, V forall_list) { - TypeExpr* arg_type = nullptr; +AnyV parse_expr(Lexer& lex); + +static AnyV parse_parameter(Lexer& lex, V genericsT_list) { SrcLocation loc = lex.cur_location(); - if (lex.tok() == tok_underscore) { - lex.next(); - if (lex.tok() == tok_comma || lex.tok() == tok_clpar) { - auto v_empty = createV(lex.cur_location(), ""); - return createV(loc, v_empty, TypeExpr::new_hole()); - } - arg_type = TypeExpr::new_hole(); - loc = lex.cur_location(); - } else if (lex.tok() != tok_identifier) { // int, cell, [X], etc. - arg_type = parse_type(lex, forall_list); - } else if (lex.tok() == tok_identifier) { - if (forall_list && forall_list->lookup_idx(lex.cur_str()) != -1) { - arg_type = parse_type(lex, forall_list); - } else { - arg_type = TypeExpr::new_hole(); - } - } else { - lex.error("Is not a type identifier"); + + // argument name (or underscore for an unnamed parameter) + std::string_view param_name; + if (lex.tok() == tok_identifier) { + param_name = lex.cur_str(); + } else if (lex.tok() != tok_underscore) { + lex.unexpected("parameter name"); } - if (lex.tok() == tok_underscore || lex.tok() == tok_comma || lex.tok() == tok_clpar) { - if (lex.tok() == tok_underscore) { - loc = lex.cur_location(); - lex.next(); - } - auto v_empty = createV(lex.cur_location(), ""); - return createV(loc, v_empty, arg_type); - } - lex.check(tok_identifier, "parameter name"); - loc = lex.cur_location(); - auto v_ident = createV(lex.cur_location(), lex.cur_str()); + auto v_ident = createV(lex.cur_location(), param_name); lex.next(); - return createV(loc, v_ident, arg_type); + + // parameter type after colon, also mandatory (even explicit ":auto") + lex.expect(tok_colon, "`: `"); + TypeExpr* param_type = parse_type(lex, genericsT_list); + + return createV(loc, v_ident, param_type); } -static AnyV parse_global_var_declaration(Lexer& lex) { - TypeExpr* declared_type = nullptr; - SrcLocation loc = lex.cur_location(); - if (lex.tok() == tok_underscore) { - lex.next(); - declared_type = TypeExpr::new_hole(); - loc = lex.cur_location(); - } else if (lex.tok() != tok_identifier) { - declared_type = parse_type(lex, nullptr); +static AnyV parse_global_var_declaration(Lexer& lex, const std::vector>& annotations) { + if (!annotations.empty()) { + lex.error("@annotations are not applicable to global var declaration"); } + SrcLocation loc = lex.cur_location(); + lex.expect(tok_global, "`global`"); lex.check(tok_identifier, "global variable name"); auto v_ident = createV(lex.cur_location(), lex.cur_str()); lex.next(); + lex.expect(tok_colon, "`:`"); + TypeExpr* declared_type = parse_type(lex, nullptr); + if (lex.tok() == tok_comma) { + lex.error("multiple declarations are not allowed, split globals on separate lines"); + } + if (lex.tok() == tok_assign) { + lex.error("assigning to a global is not allowed at declaration"); + } + lex.expect(tok_semicolon, "`;`"); return createV(loc, v_ident, declared_type); } -AnyV parse_expr(Lexer& lex); - -static AnyV parse_constant_declaration(Lexer& lex) { - SrcLocation loc = lex.cur_location(); - TypeExpr *declared_type = nullptr; - if (lex.tok() == tok_int) { - declared_type = TypeExpr::new_atomic(TypeExpr::_Int); - lex.next(); - } else if (lex.tok() == tok_slice) { - declared_type = TypeExpr::new_atomic(TypeExpr::_Slice); - lex.next(); +static AnyV parse_constant_declaration(Lexer& lex, const std::vector>& annotations) { + if (!annotations.empty()) { + lex.error("@annotations are not applicable to global var declaration"); } + SrcLocation loc = lex.cur_location(); + lex.expect(tok_const, "`const`"); lex.check(tok_identifier, "constant name"); auto v_ident = createV(lex.cur_location(), lex.cur_str()); lex.next(); - lex.expect(tok_assign, "'='"); + TypeExpr *declared_type = nullptr; + if (lex.tok() == tok_colon) { + lex.next(); + if (lex.tok() == tok_int) { + declared_type = TypeExpr::new_atomic(TypeExpr::_Int); + lex.next(); + } else if (lex.tok() == tok_slice) { + declared_type = TypeExpr::new_atomic(TypeExpr::_Slice); + lex.next(); + } else { + lex.error("a constant can be int or slice only"); + } + } + lex.expect(tok_assign, "`=`"); AnyV init_value = parse_expr(lex); + if (lex.tok() == tok_comma) { + lex.error("multiple declarations are not allowed, split constants on separate lines"); + } + lex.expect(tok_semicolon, "`;`"); return createV(loc, v_ident, declared_type, init_value); } -static AnyV parse_argument_list(Lexer& lex, V forall_list) { +static AnyV parse_parameter_list(Lexer& lex, V genericsT_list) { SrcLocation loc = lex.cur_location(); - std::vector args; - lex.expect(tok_oppar, "argument list"); + std::vector params; + lex.expect(tok_oppar, "parameter list"); if (lex.tok() != tok_clpar) { - args.push_back(parse_argument(lex, forall_list)); + params.push_back(parse_parameter(lex, genericsT_list)); while (lex.tok() == tok_comma) { lex.next(); - args.push_back(parse_argument(lex, forall_list)); + params.push_back(parse_parameter(lex, genericsT_list)); } } - lex.expect(tok_clpar, "')'"); - return createV(loc, std::move(args)); + lex.expect(tok_clpar, "`)`"); + return createV(loc, std::move(params)); } -static AnyV parse_constant_declaration_list(Lexer& lex) { - std::vector consts; - SrcLocation loc = lex.cur_location(); - lex.expect(tok_const, "'const'"); - while (true) { - consts.push_back(parse_constant_declaration(lex)); - if (lex.tok() != tok_comma) { - break; - } - lex.expect(tok_comma, "','"); - } - lex.expect(tok_semicolon, "';'"); - return createV(loc, std::move(consts)); -} - -static AnyV parse_global_var_declaration_list(Lexer& lex) { - std::vector globals; - SrcLocation loc = lex.cur_location(); - lex.expect(tok_global, "'global'"); - while (true) { - globals.push_back(parse_global_var_declaration(lex)); - if (lex.tok() != tok_comma) { - break; - } - lex.expect(tok_comma, "','"); - } - lex.expect(tok_semicolon, "';'"); - return createV(loc, std::move(globals)); -} - -// parse ( E { , E } ) | () | [ E { , E } ] | [] | id | num | _ +// parse (expr) / [expr] / identifier / number static AnyV parse_expr100(Lexer& lex) { SrcLocation loc = lex.cur_location(); - if (lex.tok() == tok_oppar) { - lex.next(); - if (lex.tok() == tok_clpar) { + switch (lex.tok()) { + case tok_oppar: { lex.next(); - return createV(loc, {}); - } - AnyV res = parse_expr(lex); - if (lex.tok() == tok_clpar) { - lex.next(); - return createV(loc, res); - } - std::vector items; - bool is_type_expression = res->type == ast_type_expression; // to differ `(a,b)` and `(int,slice)` - items.emplace_back(res); - while (lex.tok() == tok_comma) { - lex.next(); - AnyV item = parse_expr(lex); - if (is_type_expression != (item->type == ast_type_expression)) { - lex.error("mixing type and non-type expressions inside the same tuple"); + if (lex.tok() == tok_clpar) { + lex.next(); + return createV(loc, {}); } - items.emplace_back(item); - } - lex.expect(tok_clpar, "')'"); - if (is_type_expression) { - std::vector types; - types.reserve(items.size()); - for (AnyV item : items) { - types.emplace_back(item->as()->declared_type); + AnyV first = parse_expr(lex); + if (lex.tok() == tok_clpar) { + lex.next(); + return createV(loc, first); } - return createV(loc, TypeExpr::new_tensor(std::move(types))); - } - return createV(loc, std::move(items)); - } - if (lex.tok() == tok_opbracket) { - lex.next(); - if (lex.tok() == tok_clbracket) { - lex.next(); - return createV(loc, {}); - } - AnyV res = parse_expr(lex); - std::vector items; - bool is_type_expression = res->type == ast_type_expression; // to differ `(a,b)` and `(int,slice)` - items.emplace_back(res); - while (lex.tok() == tok_comma) { - lex.next(); - AnyV item = parse_expr(lex); - if (is_type_expression != (item->type == ast_type_expression)) { - lex.error("mixing type and non-type expressions inside the same tuple"); + std::vector items(1, first); + while (lex.tok() == tok_comma) { + lex.next(); + items.emplace_back(parse_expr(lex)); } - items.emplace_back(item); + lex.expect(tok_clpar, "`)`"); + return createV(loc, std::move(items)); } - lex.expect(tok_clbracket, "']'"); - if (is_type_expression) { - std::vector types; - types.reserve(items.size()); - for (AnyV item : items) { - types.emplace_back(item->as()->declared_type); - } - return createV(loc, TypeExpr::new_tuple(TypeExpr::new_tensor(std::move(types)))); - } - return createV(loc, std::move(items)); - } - TokenType t = lex.tok(); - if (t == tok_int_const) { - std::string_view int_val = lex.cur_str(); - lex.next(); - return createV(loc, int_val); - } - if (t == tok_string_const) { - std::string_view str_val = lex.cur_str(); - lex.next(); - char modifier = 0; - if (lex.tok() == tok_string_modifier) { - modifier = lex.cur_str()[0]; + case tok_opbracket: { lex.next(); + if (lex.tok() == tok_clbracket) { + lex.next(); + return createV(loc, {}); + } + std::vector items(1, parse_expr(lex)); + while (lex.tok() == tok_comma) { + lex.next(); + items.emplace_back(parse_expr(lex)); + } + lex.expect(tok_clbracket, "`]`"); + return createV(loc, std::move(items)); + } + case tok_int_const: { + std::string_view int_val = lex.cur_str(); + lex.next(); + return createV(loc, int_val); + } + case tok_string_const: { + std::string_view str_val = lex.cur_str(); + lex.next(); + char modifier = 0; + if (lex.tok() == tok_string_modifier) { + modifier = lex.cur_str()[0]; + lex.next(); + } + return createV(loc, str_val, modifier); + } + case tok_underscore: { + lex.next(); + return createV(loc); + } + case tok_true: { + lex.next(); + return createV(loc, true); + } + case tok_false: { + lex.next(); + return createV(loc, false); + } + case tok_null: { + lex.next(); + return createV(loc); + } + case tok_identifier: { + std::string_view str_val = lex.cur_str(); + lex.next(); + return createV(loc, str_val); + } + default: { + // show a proper error for `int i` (FunC-style declarations) + TokenType t = lex.tok(); + if (t == tok_int || t == tok_cell || t == tok_slice || t == tok_builder || t == tok_tuple) { + fire_error_FunC_style_var_declaration(lex); + } + lex.unexpected(""); } - return createV(loc, str_val, modifier); } - if (t == tok_underscore) { - lex.next(); - return createV(loc); - } - if (t == tok_var) { - lex.next(); - return createV(loc, TypeExpr::new_hole()); - } - if (t == tok_int || t == tok_cell || t == tok_slice || t == tok_builder || t == tok_cont || t == tok_tuple) { - lex.next(); - return createV(loc, TypeExpr::new_atomic(t)); - } - if (t == tok_true || t == tok_false) { - lex.next(); - return createV(loc, t == tok_true); - } - if (t == tok_nil) { - lex.next(); - return createV(loc); - } - if (t == tok_identifier) { - std::string_view str_val = lex.cur_str(); - lex.next(); - return createV(loc, str_val); - } - lex.expect(tok_identifier, "identifier"); - return nullptr; } -// parse E { E } +// parse E(expr) static AnyV parse_expr90(Lexer& lex) { AnyV res = parse_expr100(lex); - while (lex.tok() == tok_oppar || lex.tok() == tok_opbracket || (lex.tok() == tok_identifier && lex.cur_str()[0] != '.' && lex.cur_str()[0] != '~')) { - if (const auto* v_type_expr = res->try_as()) { - AnyV dest = parse_expr100(lex); - return createV(v_type_expr->loc, v_type_expr->declared_type, dest); - } else { - AnyV arg = parse_expr100(lex); - return createV(res->loc, res, arg); + if (lex.tok() == tok_oppar) { + lex.next(); + + SrcLocation loc = lex.cur_location(); + std::vector args; + if (lex.tok() != tok_clpar) { + args.push_back(parse_expr(lex)); + while (lex.tok() == tok_comma) { + lex.next(); + args.push_back(parse_expr(lex)); + } } + lex.expect(tok_clpar, "`)`"); + + return createV(res->loc, res, createV(loc, std::move(args))); } return res; } -// parse E { .method E | ~method E } +// parse E .method ~method E (left-to-right) static AnyV parse_expr80(Lexer& lex) { AnyV lhs = parse_expr90(lex); while (lex.tok() == tok_identifier && (lex.cur_str()[0] == '.' || lex.cur_str()[0] == '~')) { std::string_view method_name = lex.cur_str(); - SrcLocation loc = lex.cur_location(); lex.next(); - const ASTNodeBase *arg = parse_expr100(lex); - lhs = createV(loc, method_name, lhs, arg); + + SrcLocation loc = lex.cur_location(); + std::vector args; + lex.expect(tok_oppar, "`(`"); + if (lex.tok() != tok_clpar) { + args.push_back(parse_expr(lex)); + while (lex.tok() == tok_comma) { + lex.next(); + args.push_back(parse_expr(lex)); + } + } + lex.expect(tok_clpar, "`)`"); + + lhs = createV(lhs->loc, method_name, lhs, createV(loc, std::move(args))); } return lhs; } -// parse [ ~ | - | + ] E +// parse ! ~ - + E (unary) static AnyV parse_expr75(Lexer& lex) { TokenType t = lex.tok(); - if (t == tok_bitwise_not || t == tok_minus || t == tok_plus) { + if (t == tok_logical_not || t == tok_bitwise_not || t == tok_minus || t == tok_plus) { SrcLocation loc = lex.cur_location(); std::string_view operator_name = lex.cur_str(); lex.next(); AnyV rhs = parse_expr75(lex); return createV(loc, operator_name, t, rhs); - } else { - return parse_expr80(lex); } + return parse_expr80(lex); } -// parse E { (* | / | % | /% | ^/ | ~/ | ^% | ~% ) E } +// parse E * / % ^/ ~/ E (left-to-right) static AnyV parse_expr30(Lexer& lex) { AnyV lhs = parse_expr75(lex); TokenType t = lex.tok(); - while (t == tok_mul || t == tok_div || t == tok_mod || t == tok_divmod || t == tok_divC || - t == tok_divR || t == tok_modC || t == tok_modR) { + while (t == tok_mul || t == tok_div || t == tok_mod || t == tok_divC || t == tok_divR) { SrcLocation loc = lex.cur_location(); std::string_view operator_name = lex.cur_str(); lex.next(); @@ -461,7 +474,7 @@ static AnyV parse_expr30(Lexer& lex) { return lhs; } -// parse E { (+ | -) E } +// parse E + - E (left-to-right) static AnyV parse_expr20(Lexer& lex) { AnyV lhs = parse_expr30(lex); TokenType t = lex.tok(); @@ -476,7 +489,7 @@ static AnyV parse_expr20(Lexer& lex) { return lhs; } -// parse E { ( << | >> | ~>> | ^>> ) E } +// parse E << >> ~>> ^>> E (left-to-right) static AnyV parse_expr17(Lexer& lex) { AnyV lhs = parse_expr20(lex); TokenType t = lex.tok(); @@ -492,7 +505,7 @@ static AnyV parse_expr17(Lexer& lex) { return lhs; } -// parse E [ (== | < | > | <= | >= | != | <=> ) E ] +// parse E == < > <= >= != <=> E (left-to-right) static AnyV parse_expr15(Lexer& lex) { AnyV lhs = parse_expr17(lex); TokenType t = lex.tok(); @@ -502,11 +515,14 @@ static AnyV parse_expr15(Lexer& lex) { lex.next(); AnyV rhs = parse_expr17(lex); lhs = createV(loc, operator_name, t, lhs, rhs); + if (t == tok_eq || t == tok_neq) { + lhs = maybe_replace_eq_null_with_isNull_call(lhs->as()); + } } return lhs; } -// parse E { ( & | `|` | ^ ) E } +// parse E & | ^ E (left-to-right) static AnyV parse_expr14(Lexer& lex) { AnyV lhs = parse_expr15(lex); TokenType t = lex.tok(); @@ -516,33 +532,36 @@ static AnyV parse_expr14(Lexer& lex) { lex.next(); AnyV rhs = parse_expr15(lex); diagnose_bitwise_precedence(loc, operator_name, lhs, rhs); + diagnose_and_or_precedence(loc, lhs, t, operator_name); lhs = createV(loc, operator_name, t, lhs, rhs); t = lex.tok(); } return lhs; } -// parse E [ ? E : E ] +// parse E && || E (left-to-right) static AnyV parse_expr13(Lexer& lex) { - AnyV res = parse_expr14(lex); - if (lex.tok() == tok_question) { + AnyV lhs = parse_expr14(lex); + TokenType t = lex.tok(); + while (t == tok_logical_and || t == tok_logical_or) { SrcLocation loc = lex.cur_location(); + std::string_view operator_name = lex.cur_str(); lex.next(); - AnyV when_true = parse_expr(lex); - lex.expect(tok_colon, "':'"); - AnyV when_false = parse_expr13(lex); - return createV(loc, res, when_true, when_false); + AnyV rhs = parse_expr14(lex); + diagnose_and_or_precedence(loc, lhs, t, operator_name); + lhs = createV(loc, operator_name, t, lhs, rhs); + t = lex.tok(); } - return res; + return lhs; } -// parse LE1 (= | += | -= | ... ) E2 +// parse E = += -= E and E ? E : E (right-to-left) static AnyV parse_expr10(Lexer& lex) { AnyV lhs = parse_expr13(lex); TokenType t = lex.tok(); - if (t == tok_set_plus || t == tok_set_minus || t == tok_set_mul || t == tok_set_div || t == tok_set_divR || t == tok_set_divC || - t == tok_set_mod || t == tok_set_modC || t == tok_set_modR || t == tok_set_lshift || t == tok_set_rshift || t == tok_set_rshiftC || - t == tok_set_rshiftR || t == tok_set_bitwise_and || t == tok_set_bitwise_or || t == tok_set_bitwise_xor || + if (t == tok_set_plus || t == tok_set_minus || t == tok_set_mul || t == tok_set_div || + t == tok_set_mod || t == tok_set_lshift || t == tok_set_rshift || + t == tok_set_bitwise_and || t == tok_set_bitwise_or || t == tok_set_bitwise_xor || t == tok_assign) { SrcLocation loc = lex.cur_location(); std::string_view operator_name = lex.cur_str(); @@ -550,6 +569,14 @@ static AnyV parse_expr10(Lexer& lex) { AnyV rhs = parse_expr10(lex); return createV(loc, operator_name, t, lhs, rhs); } + if (t == tok_question) { + SrcLocation loc = lex.cur_location(); + lex.next(); + AnyV when_true = parse_expr10(lex); + lex.expect(tok_colon, "`:`"); + AnyV when_false = parse_expr10(lex); + return createV(loc, lhs, when_true, when_false); + } return lhs; } @@ -557,100 +584,275 @@ AnyV parse_expr(Lexer& lex) { return parse_expr10(lex); } -static AnyV parse_return_stmt(Lexer& lex) { +AnyV parse_statement(Lexer& lex); + +static AnyV parse_var_declaration_lhs(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_return, "'return'"); - AnyV child = parse_expr(lex); - lex.expect(tok_semicolon, "';'"); - return createV(loc, child); + if (lex.tok() == tok_oppar) { + lex.next(); + AnyV first = parse_var_declaration_lhs(lex); + if (lex.tok() == tok_clpar) { + lex.next(); + return createV(loc, first); + } + std::vector args(1, first); + while (lex.tok() == tok_comma) { + lex.next(); + args.push_back(parse_var_declaration_lhs(lex)); + } + lex.expect(tok_clpar, "`)`"); + return createV(loc, std::move(args)); + } + if (lex.tok() == tok_opbracket) { + lex.next(); + std::vector args(1, parse_var_declaration_lhs(lex)); + while (lex.tok() == tok_comma) { + lex.next(); + args.push_back(parse_var_declaration_lhs(lex)); + } + lex.expect(tok_clbracket, "`]`"); + return createV(loc, std::move(args)); + } + if (lex.tok() == tok_identifier) { + auto v_ident = createV(loc, lex.cur_str()); + TypeExpr* declared_type = nullptr; + bool marked_as_redef = false; + lex.next(); + if (lex.tok() == tok_colon) { + lex.next(); + declared_type = parse_type(lex, nullptr); + } else if (lex.tok() == tok_redef) { + lex.next(); + marked_as_redef = true; + } + return createV(loc, v_ident, declared_type, marked_as_redef); + } + if (lex.tok() == tok_underscore) { + TypeExpr* declared_type = nullptr; + lex.next(); + if (lex.tok() == tok_colon) { + lex.next(); + declared_type = parse_type(lex, nullptr); + } + return createV(loc, createV(loc), declared_type, false); + } + lex.unexpected("variable name"); } -AnyV parse_statement(Lexer& lex); +static AnyV parse_local_vars_declaration(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + bool immutable = lex.tok() == tok_val; + lex.next(); + + if (immutable) { + lex.error("immutable variables are not supported yet"); + } + + AnyV lhs = parse_var_declaration_lhs(lex); + if (lex.tok() != tok_assign) { + lex.error("variables declaration must be followed by assignment: `var xxx = ...`"); + } + lex.next(); + AnyV assigned_val = parse_expr(lex); + + if (lex.tok() == tok_comma) { + lex.error("multiple declarations are not allowed, split variables on separate lines"); + } + lex.expect(tok_semicolon, "`;`"); + return createV(loc, lhs, assigned_val); +} static V parse_sequence(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_opbrace, "'{'"); + lex.expect(tok_opbrace, "`{`"); std::vector items; while (lex.tok() != tok_clbrace) { items.push_back(parse_statement(lex)); } SrcLocation loc_end = lex.cur_location(); - lex.expect(tok_clbrace, "'}'"); + lex.expect(tok_clbrace, "`}`"); return createV(loc, loc_end, items); } +static AnyV parse_return_statement(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + lex.expect(tok_return, "`return`"); + AnyV child = lex.tok() == tok_semicolon // `return;` actually means `return ();` (which is void) + ? createV(lex.cur_location(), {}) + : parse_expr(lex); + lex.expect(tok_semicolon, "`;`"); + return createV(loc, child); +} + +static AnyV parse_if_statement(Lexer& lex, bool is_ifnot) { + SrcLocation loc = lex.cur_location(); + lex.expect(tok_if, "`if`"); + + lex.expect(tok_oppar, "`(`"); + AnyV cond = parse_expr(lex); + lex.expect(tok_clpar, "`)`"); + // replace if(!expr) with ifnot(expr) (this should be done later, but for now, let this be right at parsing time) + if (auto v_not = cond->try_as(); v_not && v_not->tok == tok_logical_not) { + is_ifnot = !is_ifnot; + cond = v_not->get_rhs(); + } + + V if_body = parse_sequence(lex); + V else_body = nullptr; + if (lex.tok() == tok_else) { // else if(e) { } or else { } + lex.next(); + if (lex.tok() == tok_if) { + AnyV v_inner_if = parse_if_statement(lex, false); + else_body = createV(v_inner_if->loc, lex.cur_location(), {v_inner_if}); + } else { + else_body = parse_sequence(lex); + } + } else { // no 'else', create empty block + else_body = createV(lex.cur_location(), lex.cur_location(), {}); + } + return createV(loc, is_ifnot, cond, if_body, else_body); +} + static AnyV parse_repeat_statement(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_repeat, "'repeat'"); + lex.expect(tok_repeat, "`repeat`"); + lex.expect(tok_oppar, "`(`"); AnyV cond = parse_expr(lex); + lex.expect(tok_clpar, "`)`"); V body = parse_sequence(lex); return createV(loc, cond, body); } static AnyV parse_while_statement(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_while, "'while'"); + lex.expect(tok_while, "`while`"); + lex.expect(tok_oppar, "`(`"); AnyV cond = parse_expr(lex); + lex.expect(tok_clpar, "`)`"); V body = parse_sequence(lex); return createV(loc, cond, body); } -static AnyV parse_do_until_statement(Lexer& lex) { +static AnyV parse_do_while_statement(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_do, "'do'"); + lex.expect(tok_do, "`do`"); V body = parse_sequence(lex); - lex.expect(tok_until, "'until'"); + lex.expect(tok_while, "`while`"); + lex.expect(tok_oppar, "`(`"); AnyV cond = parse_expr(lex); - return createV(loc, body, cond); + lex.expect(tok_clpar, "`)`"); + lex.expect(tok_semicolon, "`;`"); + return createV(loc, body, cond); +} + +static AnyV parse_catch_variable(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + if (lex.tok() == tok_identifier) { + std::string_view var_name = lex.cur_str(); + lex.next(); + return createV(loc, var_name); + } + if (lex.tok() == tok_underscore) { + lex.next(); + return createV(loc); + } + lex.unexpected("identifier"); +} + +static AnyV parse_throw_statement(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + lex.expect(tok_throw, "`throw`"); + + AnyV thrown_code, thrown_arg; + if (lex.tok() == tok_oppar) { // throw (code) or throw (code, arg) + lex.next(); + thrown_code = parse_expr(lex); + if (lex.tok() == tok_comma) { + lex.next(); + thrown_arg = parse_expr(lex); + } else { + thrown_arg = createV(loc); + } + lex.expect(tok_clpar, "`)`"); + } else { // throw code + thrown_code = parse_expr(lex); + thrown_arg = createV(loc); + } + + lex.expect(tok_semicolon, "`;`"); + return createV(loc, thrown_code, thrown_arg); +} + +static AnyV parse_assert_statement(Lexer& lex) { + SrcLocation loc = lex.cur_location(); + lex.expect(tok_assert, "`assert`"); + + lex.expect(tok_oppar, "`(`"); + AnyV cond = parse_expr(lex); + AnyV thrown_code; + if (lex.tok() == tok_comma) { // assert(cond, code) + lex.next(); + thrown_code = parse_expr(lex); + lex.expect(tok_clpar, "`)`"); + } else { // assert(cond) throw code + lex.expect(tok_clpar, "`)`"); + lex.expect(tok_throw, "`throw excNo` after assert"); + thrown_code = parse_expr(lex); + } + + lex.expect(tok_semicolon, "`;`"); + return createV(loc, cond, thrown_code); } static AnyV parse_try_catch_statement(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_try, "'try'"); + lex.expect(tok_try, "`try`"); V try_body = parse_sequence(lex); - lex.expect(tok_catch, "'catch'"); - AnyV catch_expr = parse_expr(lex); + + std::vector catch_args; + lex.expect(tok_catch, "`catch`"); + SrcLocation catch_loc = lex.cur_location(); + if (lex.tok() == tok_oppar) { + lex.next(); + catch_args.push_back(parse_catch_variable(lex)); + if (lex.tok() == tok_comma) { // catch (excNo, arg) + lex.next(); + catch_args.push_back(parse_catch_variable(lex)); + } else { // catch (excNo) -> catch (excNo, _) + catch_args.push_back(createV(catch_loc)); + } + lex.expect(tok_clpar, "`)`"); + } else { // catch -> catch (_, _) + catch_args.push_back(createV(catch_loc)); + catch_args.push_back(createV(catch_loc)); + } + V catch_expr = createV(catch_loc, std::move(catch_args)); + V catch_body = parse_sequence(lex); return createV(loc, try_body, catch_expr, catch_body); } -static AnyV parse_if_statement(Lexer& lex, bool is_ifnot) { - SrcLocation loc = lex.cur_location(); - lex.next(); - AnyV cond = parse_expr(lex); - V if_body = parse_sequence(lex); - V else_body = nullptr; - if (lex.tok() == tok_else) { - lex.next(); - else_body = parse_sequence(lex); - } else if (lex.tok() == tok_elseif) { - AnyV v_inner_if = parse_if_statement(lex, false); - else_body = createV(v_inner_if->loc, lex.cur_location(), {v_inner_if}); - } else if (lex.tok() == tok_elseifnot) { - AnyV v_inner_if = parse_if_statement(lex, true); - else_body = createV(v_inner_if->loc, lex.cur_location(), {v_inner_if}); - } else { - else_body = createV(lex.cur_location(), lex.cur_location(), {}); - } - return createV(loc, is_ifnot, cond, if_body, else_body); -} - AnyV parse_statement(Lexer& lex) { switch (lex.tok()) { - case tok_return: - return parse_return_stmt(lex); + case tok_var: + case tok_val: + return parse_local_vars_declaration(lex); case tok_opbrace: return parse_sequence(lex); - case tok_repeat: - return parse_repeat_statement(lex); + case tok_return: + return parse_return_statement(lex); case tok_if: return parse_if_statement(lex, false); - case tok_ifnot: - return parse_if_statement(lex, true); + case tok_repeat: + return parse_repeat_statement(lex); case tok_do: - return parse_do_until_statement(lex); + return parse_do_while_statement(lex); case tok_while: return parse_while_statement(lex); + case tok_throw: + return parse_throw_statement(lex); + case tok_assert: + return parse_assert_statement(lex); case tok_try: return parse_try_catch_statement(lex); case tok_semicolon: { @@ -658,9 +860,12 @@ AnyV parse_statement(Lexer& lex) { lex.next(); return createV(loc); } + case tok_break: + case tok_continue: + lex.error("break/continue from loops are not supported yet"); default: { AnyV expr = parse_expr(lex); - lex.expect(tok_semicolon, "';'"); + lex.expect(tok_semicolon, "`;`"); return expr; } } @@ -670,25 +875,25 @@ static AnyV parse_func_body(Lexer& lex) { return parse_sequence(lex); } -static AnyV parse_asm_func_body(Lexer& lex, V arg_list) { +static AnyV parse_asm_func_body(Lexer& lex, V param_list) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_asm, "'asm'"); - size_t n_args = arg_list->size(); - if (n_args > 16) { + lex.expect(tok_asm, "`asm`"); + size_t n_params = param_list->size(); + if (n_params > 16) { throw ParseError{loc, "assembler built-in function can have at most 16 arguments"}; } std::vector arg_order, ret_order; if (lex.tok() == tok_oppar) { lex.next(); while (lex.tok() == tok_identifier || lex.tok() == tok_int_const) { - int arg_idx = arg_list->lookup_idx(lex.cur_str()); + int arg_idx = param_list->lookup_idx(lex.cur_str()); if (arg_idx == -1) { - lex.error("argument name expected"); + lex.unexpected("argument name"); } arg_order.push_back(arg_idx); lex.next(); } - if (lex.tok() == tok_mapsto) { + if (lex.tok() == tok_arrow) { lex.next(); while (lex.tok() == tok_int_const) { int ret_idx = std::atoi(static_cast(lex.cur_str()).c_str()); @@ -696,7 +901,7 @@ static AnyV parse_asm_func_body(Lexer& lex, V arg_list) { lex.next(); } } - lex.expect(tok_clpar, "')'"); + lex.expect(tok_clpar, "`)`"); } std::vector asm_commands; lex.check(tok_string_const, "\"ASM COMMAND\""); @@ -705,142 +910,184 @@ static AnyV parse_asm_func_body(Lexer& lex, V arg_list) { asm_commands.push_back(createV(lex.cur_location(), asm_command, 0)); lex.next(); } - lex.expect(tok_semicolon, "';'"); + lex.expect(tok_semicolon, "`;`"); return createV(loc, std::move(arg_order), std::move(ret_order), std::move(asm_commands)); } -static AnyV parse_forall(Lexer& lex) { +static AnyV parse_genericsT_list(Lexer& lex) { SrcLocation loc = lex.cur_location(); - std::vector forall_items; - lex.expect(tok_forall, "'forall'"); + std::vector genericsT_items; + lex.expect(tok_lt, "`<`"); int idx = 0; while (true) { - lex.check(tok_identifier, "T expected"); + lex.check(tok_identifier, "T"); std::string_view nameT = lex.cur_str(); TypeExpr* type = TypeExpr::new_var(idx++); - forall_items.emplace_back(createV(lex.cur_location(), type, static_cast(nameT))); + genericsT_items.emplace_back(createV(lex.cur_location(), type, nameT)); lex.next(); if (lex.tok() != tok_comma) { break; } lex.next(); } - lex.expect(tok_mapsto, "'->'"); - return createV{loc, std::move(forall_items)}; + lex.expect(tok_gt, "`>`"); + return createV{loc, std::move(genericsT_items)}; } -static AnyV parse_function_declaration(Lexer& lex) { +static V parse_annotation(Lexer& lex) { SrcLocation loc = lex.cur_location(); - V forall_list = nullptr; - bool is_get_method = false; - bool is_builtin = false; - bool marked_as_inline = false; - bool marked_as_inline_ref = false; - if (lex.tok() == tok_forall) { - forall_list = parse_forall(lex)->as(); - } else if (lex.tok() == tok_get) { - is_get_method = true; - lex.next(); - } - TypeExpr* ret_type = parse_type(lex, forall_list); - lex.check(tok_identifier, "function name identifier expected"); - auto v_ident = createV(lex.cur_location(), lex.cur_str()); + lex.check(tok_annotation_at, "`@`"); + std::string_view name = lex.cur_str(); + AnnotationKind kind = Vertex::parse_kind(name); lex.next(); - V arg_list = parse_argument_list(lex, forall_list)->as(); - bool marked_as_pure = false; - if (lex.tok() == tok_impure) { - static bool warning_shown = false; - if (!warning_shown) { - lex.cur_location().show_warning("`impure` specifier is deprecated. All functions are impure by default, use `pure` to mark a function as pure"); - warning_shown = true; - } + + V v_arg = nullptr; + if (lex.tok() == tok_oppar) { + SrcLocation loc_args = lex.cur_location(); lex.next(); - } else if (lex.tok() == tok_pure) { - marked_as_pure = true; - lex.next(); - } - if (lex.tok() == tok_inline) { - marked_as_inline = true; - lex.next(); - } else if (lex.tok() == tok_inlineref) { - marked_as_inline_ref = true; - lex.next(); - } - V method_id = nullptr; - if (lex.tok() == tok_method_id) { - if (is_get_method) { - lex.error("both `get` and `method_id` are not allowed"); - } - lex.next(); - if (lex.tok() == tok_oppar) { // method_id(N) + std::vector args; + args.push_back(parse_expr(lex)); + while (lex.tok() == tok_comma) { lex.next(); - lex.check(tok_int_const, "number"); - std::string_view int_val = lex.cur_str(); - method_id = createV(lex.cur_location(), int_val); - lex.next(); - lex.expect(tok_clpar, "')'"); - } else { - static bool warning_shown = false; - if (!warning_shown) { - lex.cur_location().show_warning("`method_id` specifier is deprecated, use `get` keyword.\nExample: `get int seqno() { ... }`"); - warning_shown = true; - } - is_get_method = true; + args.push_back(parse_expr(lex)); } + lex.expect(tok_clpar, "`)`"); + v_arg = createV(loc_args, std::move(args)); } - AnyV body = nullptr; + switch (kind) { + case AnnotationKind::unknown: + throw ParseError(loc, "unknown annotation " + static_cast(name)); + case AnnotationKind::inline_simple: + case AnnotationKind::inline_ref: + case AnnotationKind::pure: + case AnnotationKind::deprecated: + if (v_arg) { + throw ParseError(v_arg->loc, "arguments aren't allowed for " + static_cast(name)); + } + v_arg = createV(loc, {}); + break; + case AnnotationKind::method_id: + if (!v_arg || v_arg->size() != 1 || v_arg->get_item(0)->type != ast_int_const) { + throw ParseError(loc, "expecting `(number)` after " + static_cast(name)); + } + break; + } + + return createV(loc, kind, v_arg); +} + +static AnyV parse_function_declaration(Lexer& lex, const std::vector>& annotations) { + SrcLocation loc = lex.cur_location(); + bool is_get_method = lex.tok() == tok_get; + lex.next(); + if (is_get_method && lex.tok() == tok_fun) { + lex.next(); // 'get f()' and 'get fun f()' both correct + } + + lex.check(tok_identifier, "function name identifier"); + + std::string_view f_name = lex.cur_str(); + bool is_entrypoint = + f_name == "main" || f_name == "onInternalMessage" || f_name == "onExternalMessage" || + f_name == "onRunTickTock" || f_name == "onSplitPrepare" || f_name == "onSplitInstall"; + bool is_FunC_entrypoint = + f_name == "recv_internal" || f_name == "recv_external" || + f_name == "run_ticktock" || f_name == "split_prepare" || f_name == "split_install"; + if (is_FunC_entrypoint) { + lex.error("this is a reserved FunC/Fift identifier; you need `onInternalMessage`"); + } + + auto v_ident = createV(lex.cur_location(), f_name); + lex.next(); + + V genericsT_list = nullptr; + if (lex.tok() == tok_lt) { // 'fun f' + genericsT_list = parse_genericsT_list(lex)->as(); + } + + V param_list = parse_parameter_list(lex, genericsT_list)->as(); + + TypeExpr* ret_type = nullptr; + if (lex.tok() == tok_colon) { // : (if absent, it means "auto infer", not void) + lex.next(); + ret_type = parse_type(lex, genericsT_list); + } + + if (is_entrypoint && (is_get_method || genericsT_list || !annotations.empty())) { + throw ParseError(loc, "invalid declaration of a reserved function"); + } + + AnyV v_body = nullptr; if (lex.tok() == tok_builtin) { - is_builtin = true; - body = createV(lex.cur_location()); + v_body = createV(lex.cur_location()); lex.next(); - lex.expect(tok_semicolon, "';'"); + lex.expect(tok_semicolon, "`;`"); } else if (lex.tok() == tok_opbrace) { - body = parse_func_body(lex); + v_body = parse_func_body(lex); } else if (lex.tok() == tok_asm) { - body = parse_asm_func_body(lex, arg_list); + if (!ret_type) { + lex.error("asm function must specify return type"); + } + v_body = parse_asm_func_body(lex, param_list); } else { - lex.expect(tok_opbrace, "function body block"); + lex.unexpected("{ function body }"); } - auto f_declaration = createV(loc, v_ident, arg_list, body); - f_declaration->ret_type = ret_type; - f_declaration->forall_list = forall_list; - f_declaration->marked_as_pure = marked_as_pure; + auto f_declaration = createV(loc, v_ident, param_list, v_body); + f_declaration->ret_type = ret_type ? ret_type : TypeExpr::new_hole(); + f_declaration->is_entrypoint = is_entrypoint; + f_declaration->genericsT_list = genericsT_list; f_declaration->marked_as_get_method = is_get_method; - f_declaration->marked_as_builtin = is_builtin; - f_declaration->marked_as_inline = marked_as_inline; - f_declaration->marked_as_inline_ref = marked_as_inline_ref; - f_declaration->method_id = method_id; + f_declaration->marked_as_builtin = v_body->type == ast_empty; + + for (auto v_annotation : annotations) { + switch (v_annotation->kind) { + case AnnotationKind::inline_simple: + f_declaration->marked_as_inline = true; + break; + case AnnotationKind::inline_ref: + f_declaration->marked_as_inline_ref = true; + break; + case AnnotationKind::pure: + f_declaration->marked_as_pure = true; + break; + case AnnotationKind::method_id: + if (is_get_method || genericsT_list || is_entrypoint) { + v_annotation->error("@method_id can be specified only for regular functions"); + } + f_declaration->method_id = v_annotation->get_arg()->get_item(0)->as(); + break; + case AnnotationKind::deprecated: + // no special handling + break; + + default: + v_annotation->error("this annotation is not applicable to functions"); + } + } + return f_declaration; } -static AnyV parse_pragma(Lexer& lex) { +static AnyV parse_tolk_required_version(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.next_special(tok_pragma_name, "pragma name"); - std::string_view pragma_name = lex.cur_str(); - if (pragma_name == "version") { - lex.next(); - TokenType cmp_tok = lex.tok(); - bool valid = cmp_tok == tok_gt || cmp_tok == tok_geq || cmp_tok == tok_lt || cmp_tok == tok_leq || cmp_tok == tok_eq || cmp_tok == tok_bitwise_xor; - if (!valid) { - lex.error("invalid comparison operator"); - } - lex.next_special(tok_semver, "semver"); - std::string_view semver = lex.cur_str(); - lex.next(); - lex.expect(tok_semicolon, "';'"); - return createV(loc, cmp_tok, semver); - } + lex.next_special(tok_semver, "semver"); // syntax: "tolk 0.6" + std::string semver = static_cast(lex.cur_str()); lex.next(); - lex.expect(tok_semicolon, "';'"); - return createV(loc, pragma_name); + + // for simplicity, there is no syntax ">= version" and so on, just strict compare + if (TOLK_VERSION != semver && TOLK_VERSION != semver + ".0") { // 0.6 = 0.6.0 + loc.show_warning("the contract is written in Tolk v" + semver + ", but you use Tolk compiler v" + TOLK_VERSION + "; probably, it will lead to compilation errors or hash changes"); + } + + return createV(loc, tok_eq, semver); // semicolon is not necessary } -static AnyV parse_include_statement(Lexer& lex) { +static AnyV parse_import_statement(Lexer& lex) { SrcLocation loc = lex.cur_location(); - lex.expect(tok_include, "#include"); + lex.expect(tok_import, "`import`"); lex.check(tok_string_const, "source file name"); std::string_view rel_filename = lex.cur_str(); if (rel_filename.empty()) { @@ -848,29 +1095,65 @@ static AnyV parse_include_statement(Lexer& lex) { } auto v_str = createV(lex.cur_location(), rel_filename, 0); lex.next(); - lex.expect(tok_semicolon, "';'"); - return createV(loc, v_str); + return createV(loc, v_str); // semicolon is not necessary } // the main (exported) function AnyV parse_src_file_to_ast(const SrcFile* file) { std::vector toplevel_declarations; + std::vector> annotations; Lexer lex(file); + while (!lex.is_eof()) { - if (lex.tok() == tok_pragma) { - toplevel_declarations.push_back(parse_pragma(lex)); - } else if (lex.tok() == tok_include) { - toplevel_declarations.push_back(parse_include_statement(lex)); - } else if (lex.tok() == tok_global) { - toplevel_declarations.push_back(parse_global_var_declaration_list(lex)); - } else if (lex.tok() == tok_const) { - toplevel_declarations.push_back(parse_constant_declaration_list(lex)); - } else if (lex.tok() == tok_semicolon) { - lex.next(); // don't add op_empty, no need - } else { - toplevel_declarations.push_back(parse_function_declaration(lex)); + switch (lex.tok()) { + case tok_tolk: + if (!annotations.empty()) { + lex.unexpected("declaration after @annotations"); + } + toplevel_declarations.push_back(parse_tolk_required_version(lex)); + break; + case tok_import: + if (!annotations.empty()) { + lex.unexpected("declaration after @annotations"); + } + toplevel_declarations.push_back(parse_import_statement(lex)); + break; + case tok_semicolon: + if (!annotations.empty()) { + lex.unexpected("declaration after @annotations"); + } + lex.next(); // don't add ast_empty, no need + break; + + case tok_annotation_at: + annotations.push_back(parse_annotation(lex)); + break; + case tok_global: + toplevel_declarations.push_back(parse_global_var_declaration(lex, annotations)); + annotations.clear(); + break; + case tok_const: + toplevel_declarations.push_back(parse_constant_declaration(lex, annotations)); + annotations.clear(); + break; + case tok_fun: + case tok_get: + toplevel_declarations.push_back(parse_function_declaration(lex, annotations)); + annotations.clear(); + break; + + case tok_export: + case tok_struct: + case tok_enum: + case tok_operator: + case tok_infix: + lex.error("`" + static_cast(lex.cur_str()) +"` is not supported yet"); + + default: + lex.unexpected("fun or get"); } } + return createV(file, std::move(toplevel_declarations)); } diff --git a/tolk/ast-replacer.h b/tolk/ast-replacer.h index feae5616..16a9f64c 100644 --- a/tolk/ast-replacer.h +++ b/tolk/ast-replacer.h @@ -71,18 +71,16 @@ protected: using parent = ASTReplacerInFunctionBody; virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } @@ -91,26 +89,28 @@ protected: virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } - virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } + virtual AnyV replace(V v) { return replace_children(v); } virtual AnyV replace(V v) { return replace_children(v); } AnyV replace(AnyV v) final { switch (v->type) { case ast_empty: return replace(v->as()); + case ast_parenthesized_expr: return replace(v->as()); + case ast_tensor: return replace(v->as()); + case ast_tensor_square: return replace(v->as()); case ast_identifier: return replace(v->as()); case ast_int_const: return replace(v->as()); case ast_string_const: return replace(v->as()); case ast_bool_const: return replace(v->as()); - case ast_nil_tuple: return replace(v->as()); + case ast_null_keyword: return replace(v->as()); case ast_function_call: return replace(v->as()); - case ast_parenthesized_expr: return replace(v->as()); case ast_underscore: return replace(v->as()); - case ast_type_expression: return replace(v->as()); - case ast_variable_declaration: return replace(v->as()); - case ast_tensor: return replace(v->as()); - case ast_tensor_square: return replace(v->as()); case ast_dot_tilde_call: return replace(v->as()); case ast_unary_operator: return replace(v->as()); case ast_binary_operator: return replace(v->as()); @@ -119,9 +119,13 @@ protected: case ast_sequence: return replace(v->as()); case ast_repeat_statement: return replace(v->as()); case ast_while_statement: return replace(v->as()); - case ast_do_until_statement: return replace(v->as()); + case ast_do_while_statement: return replace(v->as()); + case ast_throw_statement: return replace(v->as()); + case ast_assert_statement: return replace(v->as()); case ast_try_catch_statement: return replace(v->as()); case ast_if_statement: return replace(v->as()); + case ast_local_var: return replace(v->as()); + case ast_local_vars_declaration: return replace(v->as()); case ast_asm_body: return replace(v->as()); default: throw UnexpectedASTNodeType(v, "ASTReplacerInFunctionBody::visit"); diff --git a/tolk/ast-stringifier.h b/tolk/ast-stringifier.h index fcd1f36c..cabda499 100644 --- a/tolk/ast-stringifier.h +++ b/tolk/ast-stringifier.h @@ -32,22 +32,18 @@ namespace tolk { class ASTStringifier final : public ASTVisitor { constexpr static std::pair name_pairs[] = { {ast_empty, "ast_empty"}, + {ast_parenthesized_expr, "ast_parenthesized_expr"}, + {ast_tensor, "ast_tensor"}, + {ast_tensor_square, "ast_tensor_square"}, {ast_identifier, "ast_identifier"}, {ast_int_const, "ast_int_const"}, {ast_string_const, "ast_string_const"}, {ast_bool_const, "ast_bool_const"}, - {ast_nil_tuple, "ast_nil_tuple"}, + {ast_null_keyword, "ast_null_keyword"}, {ast_function_call, "ast_function_call"}, - {ast_parenthesized_expr, "ast_parenthesized_expr"}, {ast_global_var_declaration, "ast_global_var_declaration"}, - {ast_global_var_declaration_list, "ast_global_var_declaration_list"}, {ast_constant_declaration, "ast_constant_declaration"}, - {ast_constant_declaration_list, "ast_constant_declaration_list"}, {ast_underscore, "ast_underscore"}, - {ast_type_expression, "ast_type_expression"}, - {ast_variable_declaration, "ast_variable_declaration"}, - {ast_tensor, "ast_tensor"}, - {ast_tensor_square, "ast_tensor_square"}, {ast_dot_tilde_call, "ast_dot_tilde_call"}, {ast_unary_operator, "ast_unary_operator"}, {ast_binary_operator, "ast_binary_operator"}, @@ -56,24 +52,39 @@ class ASTStringifier final : public ASTVisitor { {ast_sequence, "ast_sequence"}, {ast_repeat_statement, "ast_repeat_statement"}, {ast_while_statement, "ast_while_statement"}, - {ast_do_until_statement, "ast_do_until_statement"}, + {ast_do_while_statement, "ast_do_while_statement"}, + {ast_throw_statement, "ast_throw_statement"}, + {ast_assert_statement, "ast_assert_statement"}, {ast_try_catch_statement, "ast_try_catch_statement"}, {ast_if_statement, "ast_if_statement"}, - {ast_forall_item, "ast_forall_item"}, - {ast_forall_list, "ast_forall_list"}, - {ast_argument, "ast_argument"}, - {ast_argument_list, "ast_argument_list"}, + {ast_genericsT_item, "ast_genericsT_item"}, + {ast_genericsT_list, "ast_genericsT_list"}, + {ast_parameter, "ast_parameter"}, + {ast_parameter_list, "ast_parameter_list"}, {ast_asm_body, "ast_asm_body"}, + {ast_annotation, "ast_annotation"}, {ast_function_declaration, "ast_function_declaration"}, - {ast_pragma_no_arg, "ast_pragma_no_arg"}, - {ast_pragma_version, "ast_pragma_version"}, - {ast_include_statement, "ast_include_statement"}, + {ast_local_var, "ast_local_var"}, + {ast_local_vars_declaration, "ast_local_vars_declaration"}, + {ast_tolk_required_version, "ast_tolk_required_version"}, + {ast_import_statement, "ast_import_statement"}, {ast_tolk_file, "ast_tolk_file"}, }; + static_assert(std::size(name_pairs) == ast_tolk_file + 1, "name_pairs needs to be updated"); + + constexpr static std::pair annotation_kinds[] = { + {AnnotationKind::inline_simple, "@inline"}, + {AnnotationKind::inline_ref, "@inline_ref"}, + {AnnotationKind::method_id, "@method_id"}, + {AnnotationKind::pure, "@pure"}, + {AnnotationKind::deprecated, "@deprecated"}, + }; + + static_assert(std::size(annotation_kinds) == static_cast(AnnotationKind::unknown), "annotation_kinds needs to be updated"); + template constexpr static const char* ast_node_type_to_string() { - static_assert(std::size(name_pairs) == ast_tolk_file + 1, "name_pairs needs to be updated"); return name_pairs[node_type].second; } @@ -118,16 +129,6 @@ class ASTStringifier final : public ASTVisitor { return static_cast(v->as()->get_identifier()->name); case ast_constant_declaration: return static_cast(v->as()->get_identifier()->name); - case ast_type_expression: { - std::ostringstream os; - os << v->as()->declared_type; - return os.str(); - } - case ast_variable_declaration: { - std::ostringstream os; - os << v->as()->declared_type; - return os.str(); - } case ast_dot_tilde_call: return static_cast(v->as()->method_name); case ast_unary_operator: @@ -138,26 +139,34 @@ class ASTStringifier final : public ASTVisitor { return "↓" + std::to_string(v->as()->get_items().size()); case ast_if_statement: return v->as()->is_ifnot ? "ifnot" : ""; - case ast_argument: { + case ast_annotation: + return annotation_kinds[static_cast(v->as()->kind)].second; + case ast_parameter: { std::ostringstream os; - os << v->as()->arg_type; - return static_cast(v->as()->get_identifier()->name) + ": " + os.str(); + os << v->as()->param_type; + return static_cast(v->as()->get_identifier()->name) + ": " + os.str(); } case ast_function_declaration: { - std::string arg_names; - for (int i = 0; i < v->as()->get_num_args(); i++) { - if (!arg_names.empty()) - arg_names += ","; - arg_names += v->as()->get_arg(i)->get_identifier()->name; + std::string param_names; + for (int i = 0; i < v->as()->get_num_params(); i++) { + if (!param_names.empty()) + param_names += ","; + param_names += v->as()->get_param(i)->get_identifier()->name; } - return "fun " + static_cast(v->as()->get_identifier()->name) + "(" + arg_names + ")"; + return "fun " + static_cast(v->as()->get_identifier()->name) + "(" + param_names + ")"; } - case ast_pragma_no_arg: - return static_cast(v->as()->pragma_name); - case ast_pragma_version: - return static_cast(v->as()->semver); - case ast_include_statement: - return static_cast(v->as()->get_file_leaf()->str_val); + case ast_local_var: { + std::ostringstream os; + os << v->as()->declared_type; + if (auto v_ident = v->as()->get_identifier()->try_as()) { + return static_cast(v_ident->name) + ":" + os.str(); + } + return "_: " + os.str(); + } + case ast_tolk_required_version: + return static_cast(v->as()->semver); + case ast_import_statement: + return static_cast(v->as()->get_file_leaf()->str_val); case ast_tolk_file: return v->as()->file->rel_filename; default: @@ -191,22 +200,18 @@ public: void visit(AnyV v) override { switch (v->type) { case ast_empty: return handle_vertex(v->as()); + case ast_parenthesized_expr: return handle_vertex(v->as()); + case ast_tensor: return handle_vertex(v->as()); + case ast_tensor_square: return handle_vertex(v->as()); case ast_identifier: return handle_vertex(v->as()); case ast_int_const: return handle_vertex(v->as()); case ast_string_const: return handle_vertex(v->as()); case ast_bool_const: return handle_vertex(v->as()); - case ast_nil_tuple: return handle_vertex(v->as()); + case ast_null_keyword: return handle_vertex(v->as()); case ast_function_call: return handle_vertex(v->as()); - case ast_parenthesized_expr: return handle_vertex(v->as()); case ast_global_var_declaration: return handle_vertex(v->as()); - case ast_global_var_declaration_list: return handle_vertex(v->as()); case ast_constant_declaration: return handle_vertex(v->as()); - case ast_constant_declaration_list: return handle_vertex(v->as()); case ast_underscore: return handle_vertex(v->as()); - case ast_type_expression: return handle_vertex(v->as()); - case ast_variable_declaration: return handle_vertex(v->as()); - case ast_tensor: return handle_vertex(v->as()); - case ast_tensor_square: return handle_vertex(v->as()); case ast_dot_tilde_call: return handle_vertex(v->as()); case ast_unary_operator: return handle_vertex(v->as()); case ast_binary_operator: return handle_vertex(v->as()); @@ -215,18 +220,22 @@ public: case ast_sequence: return handle_vertex(v->as()); case ast_repeat_statement: return handle_vertex(v->as()); case ast_while_statement: return handle_vertex(v->as()); - case ast_do_until_statement: return handle_vertex(v->as()); + case ast_do_while_statement: return handle_vertex(v->as()); + case ast_throw_statement: return handle_vertex(v->as()); + case ast_assert_statement: return handle_vertex(v->as()); case ast_try_catch_statement: return handle_vertex(v->as()); case ast_if_statement: return handle_vertex(v->as()); - case ast_forall_item: return handle_vertex(v->as()); - case ast_forall_list: return handle_vertex(v->as()); - case ast_argument: return handle_vertex(v->as()); - case ast_argument_list: return handle_vertex(v->as()); + case ast_genericsT_item: return handle_vertex(v->as()); + case ast_genericsT_list: return handle_vertex(v->as()); + case ast_parameter: return handle_vertex(v->as()); + case ast_parameter_list: return handle_vertex(v->as()); case ast_asm_body: return handle_vertex(v->as()); + case ast_annotation: return handle_vertex(v->as()); case ast_function_declaration: return handle_vertex(v->as()); - case ast_pragma_no_arg: return handle_vertex(v->as()); - case ast_pragma_version: return handle_vertex(v->as()); - case ast_include_statement: return handle_vertex(v->as()); + case ast_local_var: return handle_vertex(v->as()); + case ast_local_vars_declaration: return handle_vertex(v->as()); + case ast_tolk_required_version: return handle_vertex(v->as()); + case ast_import_statement: return handle_vertex(v->as()); case ast_tolk_file: return handle_vertex(v->as()); default: throw UnexpectedASTNodeType(v, "ASTStringifier::visit"); diff --git a/tolk/ast-visitor.h b/tolk/ast-visitor.h index d1c38b9e..6fe9ed5d 100644 --- a/tolk/ast-visitor.h +++ b/tolk/ast-visitor.h @@ -67,18 +67,16 @@ protected: using parent = ASTVisitorFunctionBody; virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } @@ -87,26 +85,26 @@ protected: virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } - virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } + virtual void visit(V v) { return visit_children(v); } virtual void visit(V v) { return visit_children(v); } void visit(AnyV v) final { switch (v->type) { case ast_empty: return visit(v->as()); + case ast_parenthesized_expr: return visit(v->as()); + case ast_tensor: return visit(v->as()); + case ast_tensor_square: return visit(v->as()); case ast_identifier: return visit(v->as()); case ast_int_const: return visit(v->as()); case ast_string_const: return visit(v->as()); case ast_bool_const: return visit(v->as()); - case ast_nil_tuple: return visit(v->as()); + case ast_null_keyword: return visit(v->as()); case ast_function_call: return visit(v->as()); - case ast_parenthesized_expr: return visit(v->as()); case ast_underscore: return visit(v->as()); - case ast_type_expression: return visit(v->as()); - case ast_variable_declaration: return visit(v->as()); - case ast_tensor: return visit(v->as()); - case ast_tensor_square: return visit(v->as()); case ast_dot_tilde_call: return visit(v->as()); case ast_unary_operator: return visit(v->as()); case ast_binary_operator: return visit(v->as()); @@ -115,9 +113,13 @@ protected: case ast_sequence: return visit(v->as()); case ast_repeat_statement: return visit(v->as()); case ast_while_statement: return visit(v->as()); - case ast_do_until_statement: return visit(v->as()); + case ast_do_while_statement: return visit(v->as()); + case ast_throw_statement: return visit(v->as()); + case ast_assert_statement: return visit(v->as()); case ast_try_catch_statement: return visit(v->as()); case ast_if_statement: return visit(v->as()); + case ast_local_var: return visit(v->as()); + case ast_local_vars_declaration: return visit(v->as()); case ast_asm_body: return visit(v->as()); default: throw UnexpectedASTNodeType(v, "ASTVisitorFunctionBody::visit"); diff --git a/tolk/ast.cpp b/tolk/ast.cpp index 123dd896..f0506ef4 100644 --- a/tolk/ast.cpp +++ b/tolk/ast.cpp @@ -49,25 +49,44 @@ void ASTNodeBase::error(const std::string& err_msg) const { throw ParseError(loc, err_msg); } -int Vertex::lookup_idx(std::string_view nameT) const { +AnnotationKind Vertex::parse_kind(std::string_view name) { + if (name == "@pure") { + return AnnotationKind::pure; + } + if (name == "@inline") { + return AnnotationKind::inline_simple; + } + if (name == "@inline_ref") { + return AnnotationKind::inline_ref; + } + if (name == "@method_id") { + return AnnotationKind::method_id; + } + if (name == "@deprecated") { + return AnnotationKind::deprecated; + } + return AnnotationKind::unknown; +} + +int Vertex::lookup_idx(std::string_view nameT) const { for (size_t idx = 0; idx < children.size(); ++idx) { - if (children[idx] && children[idx]->as()->nameT == nameT) { + if (children[idx] && children[idx]->as()->nameT == nameT) { return static_cast(idx); } } return -1; } -int Vertex::lookup_idx(std::string_view arg_name) const { +int Vertex::lookup_idx(std::string_view param_name) const { for (size_t idx = 0; idx < children.size(); ++idx) { - if (children[idx] && children[idx]->as()->get_identifier()->name == arg_name) { + if (children[idx] && children[idx]->as()->get_identifier()->name == param_name) { return static_cast(idx); } } return -1; } -void Vertex::mutate_set_src_file(const SrcFile* file) const { +void Vertex::mutate_set_src_file(const SrcFile* file) const { const_cast(this)->file = file; } diff --git a/tolk/ast.h b/tolk/ast.h index 12b7da93..a233f09d 100644 --- a/tolk/ast.h +++ b/tolk/ast.h @@ -60,22 +60,18 @@ namespace tolk { enum ASTNodeType { ast_empty, + ast_parenthesized_expr, + ast_tensor, + ast_tensor_square, ast_identifier, ast_int_const, ast_string_const, ast_bool_const, - ast_nil_tuple, + ast_null_keyword, ast_function_call, - ast_parenthesized_expr, ast_global_var_declaration, - ast_global_var_declaration_list, ast_constant_declaration, - ast_constant_declaration_list, ast_underscore, - ast_type_expression, - ast_variable_declaration, - ast_tensor, - ast_tensor_square, ast_dot_tilde_call, ast_unary_operator, ast_binary_operator, @@ -84,21 +80,34 @@ enum ASTNodeType { ast_sequence, ast_repeat_statement, ast_while_statement, - ast_do_until_statement, + ast_do_while_statement, + ast_throw_statement, + ast_assert_statement, ast_try_catch_statement, ast_if_statement, - ast_forall_item, - ast_forall_list, - ast_argument, - ast_argument_list, + ast_genericsT_item, + ast_genericsT_list, + ast_parameter, + ast_parameter_list, ast_asm_body, + ast_annotation, ast_function_declaration, - ast_pragma_no_arg, - ast_pragma_version, - ast_include_statement, + ast_local_var, + ast_local_vars_declaration, + ast_tolk_required_version, + ast_import_statement, ast_tolk_file, }; +enum class AnnotationKind { + inline_simple, + inline_ref, + method_id, + pure, + deprecated, + unknown, +}; + struct ASTNodeBase; using AnyV = const ASTNodeBase*; @@ -210,6 +219,32 @@ struct Vertex final : ASTNodeLeaf { : ASTNodeLeaf(ast_empty, loc) {} }; +template<> +struct Vertex final : ASTNodeUnary { + AnyV get_expr() const { return child; } + + Vertex(SrcLocation loc, AnyV expr) + : ASTNodeUnary(ast_parenthesized_expr, loc, expr) {} +}; + +template<> +struct Vertex final : ASTNodeVararg { + const std::vector& get_items() const { return children; } + AnyV get_item(int i) const { return children.at(i); } + + Vertex(SrcLocation loc, std::vector items) + : ASTNodeVararg(ast_tensor, loc, std::move(items)) {} +}; + +template<> +struct Vertex final : ASTNodeVararg { + const std::vector& get_items() const { return children; } + AnyV get_item(int i) const { return children.at(i); } + + Vertex(SrcLocation loc, std::vector items) + : ASTNodeVararg(ast_tensor_square, loc, std::move(items)) {} +}; + template<> struct Vertex final : ASTNodeLeaf { std::string_view name; @@ -244,45 +279,29 @@ struct Vertex final : ASTNodeLeaf { }; template<> -struct Vertex final : ASTNodeLeaf { +struct Vertex final : ASTNodeLeaf { explicit Vertex(SrcLocation loc) - : ASTNodeLeaf(ast_nil_tuple, loc) {} + : ASTNodeLeaf(ast_null_keyword, loc) {} }; template<> struct Vertex final : ASTNodeBinary { // even for f(1,2,3), f (lhs) is called with a single arg (tensor "(1,2,3)") (rhs) AnyV get_called_f() const { return lhs; } - AnyV get_called_arg() const { return rhs; } + auto get_called_arg() const { return rhs->as(); } - Vertex(SrcLocation loc, AnyV lhs_f, AnyV arg) + Vertex(SrcLocation loc, AnyV lhs_f, V arg) : ASTNodeBinary(ast_function_call, loc, lhs_f, arg) {} }; -template<> -struct Vertex final : ASTNodeUnary { - AnyV get_expr() const { return child; } - - Vertex(SrcLocation loc, AnyV expr) - : ASTNodeUnary(ast_parenthesized_expr, loc, expr) {} -}; - template<> struct Vertex final : ASTNodeUnary { TypeExpr* declared_type; // may be nullptr auto get_identifier() const { return child->as(); } - Vertex(SrcLocation loc, V var_identifier, TypeExpr* declared_type) - : ASTNodeUnary(ast_global_var_declaration, loc, var_identifier), declared_type(declared_type) {} -}; - -template<> -struct Vertex final : ASTNodeVararg { - const std::vector& get_declarations() const { return children; } - - Vertex(SrcLocation loc, std::vector declarations) - : ASTNodeVararg(ast_global_var_declaration_list, loc, std::move(declarations)) {} + Vertex(SrcLocation loc, V name_identifier, TypeExpr* declared_type) + : ASTNodeUnary(ast_global_var_declaration, loc, name_identifier), declared_type(declared_type) {} }; template<> @@ -292,16 +311,8 @@ struct Vertex final : ASTNodeBinary { auto get_identifier() const { return lhs->as(); } AnyV get_init_value() const { return rhs; } - Vertex(SrcLocation loc, V const_identifier, TypeExpr* declared_type, AnyV init_value) - : ASTNodeBinary(ast_constant_declaration, loc, const_identifier, init_value), declared_type(declared_type) {} -}; - -template<> -struct Vertex final : ASTNodeVararg { - const std::vector& get_declarations() const { return children; } - - Vertex(SrcLocation loc, std::vector declarations) - : ASTNodeVararg(ast_constant_declaration_list, loc, std::move(declarations)) {} + Vertex(SrcLocation loc, V name_identifier, TypeExpr* declared_type, AnyV init_value) + : ASTNodeBinary(ast_constant_declaration, loc, name_identifier, init_value), declared_type(declared_type) {} }; template<> @@ -310,51 +321,15 @@ struct Vertex final : ASTNodeLeaf { : ASTNodeLeaf(ast_underscore, loc) {} }; -template<> -struct Vertex final : ASTNodeLeaf { - TypeExpr* declared_type; - - Vertex(SrcLocation loc, TypeExpr* declared_type) - : ASTNodeLeaf(ast_type_expression, loc), declared_type(declared_type) {} -}; - -template<> -struct Vertex final : ASTNodeUnary { - TypeExpr* declared_type; - - AnyV get_variable_or_list() const { return child; } // identifier, tuple, tensor - - Vertex(SrcLocation loc, TypeExpr* declared_type, AnyV dest) - : ASTNodeUnary(ast_variable_declaration, loc, dest), declared_type(declared_type) {} -}; - -template<> -struct Vertex final : ASTNodeVararg { - const std::vector& get_items() const { return children; } - AnyV get_item(int i) const { return children.at(i); } - - Vertex(SrcLocation loc, std::vector items) - : ASTNodeVararg(ast_tensor, loc, std::move(items)) {} -}; - -template<> -struct Vertex final : ASTNodeVararg { - const std::vector& get_items() const { return children; } - AnyV get_item(int i) const { return children.at(i); } - - Vertex(SrcLocation loc, std::vector items) - : ASTNodeVararg(ast_tensor_square, loc, std::move(items)) {} -}; - template<> struct Vertex final : ASTNodeBinary { std::string_view method_name; // starts with . or ~ AnyV get_lhs() const { return lhs; } - AnyV get_arg() const { return rhs; } + auto get_arg() const { return rhs->as(); } - Vertex(SrcLocation loc, std::string_view method_name, AnyV lhs, AnyV rhs) - : ASTNodeBinary(ast_dot_tilde_call, loc, lhs, rhs), method_name(method_name) {} + Vertex(SrcLocation loc, std::string_view method_name, AnyV lhs, V arg) + : ASTNodeBinary(ast_dot_tilde_call, loc, lhs, arg), method_name(method_name) {} }; template<> @@ -428,27 +403,46 @@ struct Vertex final : ASTNodeBinary { }; template<> -struct Vertex final : ASTNodeBinary { +struct Vertex final : ASTNodeBinary { auto get_body() const { return lhs->as(); } AnyV get_cond() const { return rhs; } Vertex(SrcLocation loc, V body, AnyV cond) - : ASTNodeBinary(ast_do_until_statement, loc, body, cond) {} + : ASTNodeBinary(ast_do_while_statement, loc, body, cond) {} +}; + +template<> +struct Vertex final : ASTNodeBinary { + AnyV get_thrown_code() const { return lhs; } + AnyV get_thrown_arg() const { return rhs; } // may be ast_empty + bool has_thrown_arg() const { return rhs->type != ast_empty; } + + Vertex(SrcLocation loc, AnyV thrown_code, AnyV thrown_arg) + : ASTNodeBinary(ast_throw_statement, loc, thrown_code, thrown_arg) {} +}; + +template<> +struct Vertex final : ASTNodeBinary { + AnyV get_cond() const { return lhs; } + AnyV get_thrown_code() const { return rhs; } + + Vertex(SrcLocation loc, AnyV cond, AnyV thrown_code) + : ASTNodeBinary(ast_assert_statement, loc, cond, thrown_code) {} }; template<> struct Vertex final : ASTNodeVararg { auto get_try_body() const { return children.at(0)->as(); } - AnyV get_catch_expr() const { return children.at(1); } // it's a tensor + auto get_catch_expr() const { return children.at(1)->as(); } // (excNo, arg), always len 2 auto get_catch_body() const { return children.at(2)->as(); } - Vertex(SrcLocation loc, V try_body, AnyV catch_expr, V catch_body) + Vertex(SrcLocation loc, V try_body, V catch_expr, V catch_body) : ASTNodeVararg(ast_try_catch_statement, loc, {try_body, catch_expr, catch_body}) {} }; template<> struct Vertex final : ASTNodeVararg { - bool is_ifnot; + bool is_ifnot; // if(!cond), to generate more optimal fift code AnyV get_cond() const { return children.at(0); } auto get_if_body() const { return children.at(1)->as(); } @@ -459,33 +453,44 @@ struct Vertex final : ASTNodeVararg { }; template<> -struct Vertex final : ASTNodeLeaf { +struct Vertex final : ASTNodeLeaf { TypeExpr* created_type; // used to keep same pointer, since TypeExpr::new_var(i) always allocates - std::string nameT; + std::string_view nameT; - Vertex(SrcLocation loc, TypeExpr* created_type, std::string nameT) - : ASTNodeLeaf(ast_forall_item, loc), created_type(created_type), nameT(std::move(nameT)) {} + Vertex(SrcLocation loc, TypeExpr* created_type, std::string_view nameT) + : ASTNodeLeaf(ast_genericsT_item, loc), created_type(created_type), nameT(nameT) {} }; template<> -struct Vertex final : ASTNodeVararg { +struct Vertex final : ASTNodeVararg { std::vector get_items() const { return children; } - auto get_item(int i) const { return children.at(i)->as(); } + auto get_item(int i) const { return children.at(i)->as(); } - Vertex(SrcLocation loc, std::vector forall_items) - : ASTNodeVararg(ast_forall_list, loc, std::move(forall_items)) {} + Vertex(SrcLocation loc, std::vector genericsT_items) + : ASTNodeVararg(ast_genericsT_list, loc, std::move(genericsT_items)) {} int lookup_idx(std::string_view nameT) const; }; template<> -struct Vertex final : ASTNodeUnary { - TypeExpr* arg_type; +struct Vertex final : ASTNodeUnary { + TypeExpr* param_type; - auto get_identifier() const { return child->as(); } + auto get_identifier() const { return child->as(); } // for underscore, its str_val is empty - Vertex(SrcLocation loc, V arg_identifier, TypeExpr* arg_type) - : ASTNodeUnary(ast_argument, loc, arg_identifier), arg_type(arg_type) {} + Vertex(SrcLocation loc, V name_identifier, TypeExpr* param_type) + : ASTNodeUnary(ast_parameter, loc, name_identifier), param_type(param_type) {} +}; + +template<> +struct Vertex final : ASTNodeVararg { + const std::vector& get_params() const { return children; } + auto get_param(int i) const { return children.at(i)->as(); } + + Vertex(SrcLocation loc, std::vector params) + : ASTNodeVararg(ast_parameter_list, loc, std::move(params)) {} + + int lookup_idx(std::string_view param_name) const; }; template<> @@ -500,26 +505,48 @@ struct Vertex final : ASTNodeVararg { }; template<> -struct Vertex final : ASTNodeVararg { - const std::vector& get_args() const { return children; } - auto get_arg(int i) const { return children.at(i)->as(); } +struct Vertex final : ASTNodeUnary { + AnnotationKind kind; - Vertex(SrcLocation loc, std::vector args) - : ASTNodeVararg(ast_argument_list, loc, std::move(args)) {} + auto get_arg() const { return child->as(); } - int lookup_idx(std::string_view arg_name) const; + static AnnotationKind parse_kind(std::string_view name); + + Vertex(SrcLocation loc, AnnotationKind kind, V arg_probably_empty) + : ASTNodeUnary(ast_annotation, loc, arg_probably_empty), kind(kind) {} +}; + +template<> +struct Vertex final : ASTNodeUnary { + TypeExpr* declared_type; + bool marked_as_redef; // var (existing_var redef, new_var: int) = ... + + AnyV get_identifier() const { return child; } // ast_identifier / ast_underscore + + Vertex(SrcLocation loc, AnyV name_identifier, TypeExpr* declared_type, bool marked_as_redef) + : ASTNodeUnary(ast_local_var, loc, name_identifier), declared_type(declared_type), marked_as_redef(marked_as_redef) {} +}; + +template<> +struct Vertex final : ASTNodeBinary { + AnyV get_lhs() const { return lhs; } // ast_local_var / ast_tensor / ast_tensor_square + AnyV get_assigned_val() const { return rhs; } + + Vertex(SrcLocation loc, AnyV lhs, AnyV assigned_val) + : ASTNodeBinary(ast_local_vars_declaration, loc, lhs, assigned_val) {} }; template<> struct Vertex final : ASTNodeVararg { auto get_identifier() const { return children.at(0)->as(); } - int get_num_args() const { return children.at(1)->as()->size(); } - auto get_arg_list() const { return children.at(1)->as(); } - auto get_arg(int i) const { return children.at(1)->as()->get_arg(i); } + int get_num_params() const { return children.at(1)->as()->size(); } + auto get_param_list() const { return children.at(1)->as(); } + auto get_param(int i) const { return children.at(1)->as()->get_param(i); } AnyV get_body() const { return children.at(2); } // ast_sequence / ast_asm_body TypeExpr* ret_type = nullptr; - V forall_list = nullptr; + V genericsT_list = nullptr; + bool is_entrypoint = false; bool marked_as_pure = false; bool marked_as_builtin = false; bool marked_as_get_method = false; @@ -529,29 +556,21 @@ struct Vertex final : ASTNodeVararg { bool is_asm_function() const { return children.at(2)->type == ast_asm_body; } - Vertex(SrcLocation loc, V name_identifier, V args, AnyV body) - : ASTNodeVararg(ast_function_declaration, loc, {name_identifier, args, body}) {} + Vertex(SrcLocation loc, V name_identifier, V parameters, AnyV body) + : ASTNodeVararg(ast_function_declaration, loc, {name_identifier, parameters, body}) {} }; template<> -struct Vertex final : ASTNodeLeaf { - std::string_view pragma_name; - - Vertex(SrcLocation loc, std::string_view pragma_name) - : ASTNodeLeaf(ast_pragma_no_arg, loc), pragma_name(pragma_name) {} -}; - -template<> -struct Vertex final : ASTNodeLeaf { +struct Vertex final : ASTNodeLeaf { TokenType cmp_tok; std::string_view semver; Vertex(SrcLocation loc, TokenType cmp_tok, std::string_view semver) - : ASTNodeLeaf(ast_pragma_version, loc), cmp_tok(cmp_tok), semver(semver) {} + : ASTNodeLeaf(ast_tolk_required_version, loc), cmp_tok(cmp_tok), semver(semver) {} }; template<> -struct Vertex final : ASTNodeUnary { +struct Vertex final : ASTNodeUnary { const SrcFile* file = nullptr; // assigned after includes have been resolved auto get_file_leaf() const { return child->as(); } @@ -561,7 +580,7 @@ struct Vertex final : ASTNodeUnary { void mutate_set_src_file(const SrcFile* file) const; Vertex(SrcLocation loc, V file_name) - : ASTNodeUnary(ast_include_statement, loc, file_name) {} + : ASTNodeUnary(ast_import_statement, loc, file_name) {} }; template<> diff --git a/tolk/builtins.cpp b/tolk/builtins.cpp index 4b31d177..52144d41 100644 --- a/tolk/builtins.cpp +++ b/tolk/builtins.cpp @@ -91,10 +91,6 @@ int emulate_negate(int a) { if ((a & f) && (~a & f)) { a ^= f; } - f = VarDescr::_Bit | VarDescr::_Bool; - if ((a & f) && (~a & f)) { - a ^= f; - } return a; } @@ -129,9 +125,9 @@ int emulate_sub(int a, int b) { } int emulate_mul(int a, int b) { - if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) { + if ((b & VarDescr::ConstOne) == VarDescr::ConstOne) { return a; - } else if ((a & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) { + } else if ((a & VarDescr::ConstOne) == VarDescr::ConstOne) { return b; } int u = a & b, v = a | b; @@ -151,11 +147,6 @@ int emulate_mul(int a, int b) { } else if (!(~v & (VarDescr::_Pos | VarDescr::_Neg))) { r |= VarDescr::_Neg; } - if (u & (VarDescr::_Bit | VarDescr::_Bool)) { - r |= VarDescr::_Bit; - } else if (!(~v & (VarDescr::_Bit | VarDescr::_Bool))) { - r |= VarDescr::_Bool; - } r |= v & VarDescr::_Even; r |= u & (VarDescr::_Odd | VarDescr::_NonZero); return r; @@ -172,7 +163,6 @@ int emulate_bitwise_and(int a, int b) { return VarDescr::ConstZero; } r |= both & (VarDescr::_Even | VarDescr::_Odd); - r |= both & (VarDescr::_Bit | VarDescr::_Bool); if (both & VarDescr::_Odd) { r |= VarDescr::_NonZero; } @@ -228,7 +218,7 @@ int emulate_bitwise_not(int a) { if ((a2 & f) && (~a2 & f)) { a2 ^= f; } - a2 &= ~(VarDescr::_Zero | VarDescr::_NonZero | VarDescr::_Bit | VarDescr::_Pos | VarDescr::_Neg); + a2 &= ~(VarDescr::_Zero | VarDescr::_NonZero | VarDescr::_Pos | VarDescr::_Neg); if ((a & VarDescr::_Neg) && (a & VarDescr::_NonZero)) { a2 |= VarDescr::_Pos; } @@ -251,9 +241,9 @@ int emulate_lshift(int a, int b) { } int emulate_div(int a, int b) { - if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) { + if ((b & VarDescr::ConstOne) == VarDescr::ConstOne) { return a; - } else if ((b & (VarDescr::_NonZero | VarDescr::_Bool)) == (VarDescr::_NonZero | VarDescr::_Bool)) { + } else if ((b & VarDescr::ConstOne) == VarDescr::ConstOne) { return emulate_negate(a); } if (b & VarDescr::_Zero) { @@ -276,11 +266,6 @@ int emulate_div(int a, int b) { } else if (!(~v & (VarDescr::_Pos | VarDescr::_Neg))) { r |= VarDescr::_Neg; } - if (u & (VarDescr::_Bit | VarDescr::_Bool)) { - r |= VarDescr::_Bit; - } else if (!(~v & (VarDescr::_Bit | VarDescr::_Bool))) { - r |= VarDescr::_Bool; - } return r; } @@ -297,9 +282,7 @@ int emulate_rshift(int a, int b) { } int emulate_mod(int a, int b, int round_mode = -1) { - if ((b & (VarDescr::_NonZero | VarDescr::_Bit)) == (VarDescr::_NonZero | VarDescr::_Bit)) { - return VarDescr::ConstZero; - } else if ((b & (VarDescr::_NonZero | VarDescr::_Bool)) == (VarDescr::_NonZero | VarDescr::_Bool)) { + if ((b & VarDescr::ConstOne) == VarDescr::ConstOne) { return VarDescr::ConstZero; } if (b & VarDescr::_Zero) { @@ -321,14 +304,6 @@ int emulate_mod(int a, int b, int round_mode = -1) { } else if (round_mode > 0) { r |= emulate_negate(b) & (VarDescr::_Pos | VarDescr::_Neg); } - if (a & (VarDescr::_Bit | VarDescr::_Bool)) { - if (r & VarDescr::_Pos) { - r |= VarDescr::_Bit; - } - if (r & VarDescr::_Neg) { - r |= VarDescr::_Bool; - } - } if (b & VarDescr::_Even) { r |= a & (VarDescr::_Even | VarDescr::_Odd); } @@ -513,6 +488,18 @@ AsmOp compile_unary_plus(std::vector& res, std::vector& args return AsmOp::Nop(); } +AsmOp compile_logical_not(std::vector& res, std::vector& args, SrcLocation where) { + tolk_assert(res.size() == 1 && args.size() == 1); + VarDescr &r = res[0], &x = args[0]; + if (x.is_int_const()) { + r.set_const(x.int_const == 0 ? -1 : 0); + x.unused(); + return push_const(r.int_const); + } + r.val = VarDescr::ValBool; + return exec_op("0 EQINT", 1); +} + AsmOp compile_bitwise_and(std::vector& res, std::vector& args, SrcLocation where) { tolk_assert(res.size() == 1 && args.size() == 2); VarDescr &r = res[0], &x = args[0], &y = args[1]; @@ -976,9 +963,14 @@ AsmOp compile_throw(std::vector& res, std::vector& args, Src } } -AsmOp compile_cond_throw(std::vector& res, std::vector& args, bool mode) { - tolk_assert(res.empty() && args.size() == 2); - VarDescr &x = args[0], &y = args[1]; +AsmOp compile_throw_if_unless(std::vector& res, std::vector& args) { + tolk_assert(res.empty() && args.size() == 3); + VarDescr &x = args[0], &y = args[1], &z = args[2]; + if (!z.always_true() && !z.always_false()) { + throw Fatal("invalid usage of built-in symbol"); + } + bool mode = z.always_true(); + z.unused(); std::string suff = (mode ? "IF" : "IFNOT"); bool skip_cond = false; if (y.always_true() || y.always_false()) { @@ -1008,27 +1000,6 @@ AsmOp compile_throw_arg(std::vector& res, std::vector& args, } } -AsmOp compile_cond_throw_arg(std::vector& res, std::vector& args, bool mode) { - tolk_assert(res.empty() && args.size() == 3); - VarDescr &x = args[1], &y = args[2]; - std::string suff = (mode ? "IF" : "IFNOT"); - bool skip_cond = false; - if (y.always_true() || y.always_false()) { - y.unused(); - skip_cond = true; - if (y.always_true() != mode) { - x.unused(); - return AsmOp::Nop(); - } - } - if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) { - x.unused(); - return skip_cond ? exec_arg_op("THROWARG", x.int_const, 1, 0) : exec_arg_op("THROWARG"s + suff, x.int_const, 2, 0); - } else { - return skip_cond ? exec_op("THROWARGANY", 2, 0) : exec_op("THROWARGANY"s + suff, 3, 0); - } -} - AsmOp compile_bool_const(std::vector& res, std::vector& args, bool val) { tolk_assert(res.size() == 1 && args.empty()); VarDescr& r = res[0]; @@ -1098,15 +1069,9 @@ AsmOp compile_tuple_at(std::vector& res, std::vector& args, return exec_op("INDEXVAR", 2, 1); } -// int null?(X arg) +// fun __isNull(X arg): int AsmOp compile_is_null(std::vector& res, std::vector& args, SrcLocation) { tolk_assert(args.size() == 1 && res.size() == 1); - auto &x = args[0], &r = res[0]; - if (x.always_null() || x.always_not_null()) { - x.unused(); - r.set_const(x.always_null() ? -1 : 0); - return push_const(r.int_const); - } res[0].val = VarDescr::ValBool; return exec_op("ISNULL", 1, 1); } @@ -1131,7 +1096,6 @@ void define_builtins() { auto XY = TypeExpr::new_tensor({X, Y}); auto arith_bin_op = TypeExpr::new_map(Int2, Int); auto arith_un_op = TypeExpr::new_map(Int, Int); - auto impure_bin_op = TypeExpr::new_map(Int2, Unit); auto impure_un_op = TypeExpr::new_map(Int, Unit); auto fetch_int_op = TypeExpr::new_map(SliceInt, SliceInt); auto prefetch_int_op = TypeExpr::new_map(SliceInt, Int); @@ -1142,7 +1106,6 @@ void define_builtins() { auto prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice); //auto arith_null_op = TypeExpr::new_map(TypeExpr::new_unit(), Int); auto throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit)); - auto cond_throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int, Int}), Unit)); // prevent unused vars warnings (there vars are created to acquire initial id of TypeExpr::value) static_cast(Z); @@ -1158,9 +1121,6 @@ void define_builtins() { define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); - define_builtin_func("_~%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0)); - define_builtin_func("_^%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1)); - define_builtin_func("_/%_", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("~divmod", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2)); define_builtin_func("moddiv", TypeExpr::new_map(Int2, Int2), AsmOp::Custom("DIVMOD", 2, 2), {}, {1, 0}); @@ -1169,23 +1129,18 @@ void define_builtins() { define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); + define_builtin_func("!_", arith_un_op, compile_logical_not); + define_builtin_func("~_", arith_un_op, compile_bitwise_not); define_builtin_func("_&_", arith_bin_op, compile_bitwise_and); define_builtin_func("_|_", arith_bin_op, compile_bitwise_or); define_builtin_func("_^_", arith_bin_op, compile_bitwise_xor); - define_builtin_func("~_", arith_un_op, compile_bitwise_not); define_builtin_func("^_+=_", arith_bin_op, compile_add); define_builtin_func("^_-=_", arith_bin_op, compile_sub); define_builtin_func("^_*=_", arith_bin_op, compile_mul); define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1)); - define_builtin_func("^_~/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0)); - define_builtin_func("^_^/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1)); define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1)); - define_builtin_func("^_~%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 0)); - define_builtin_func("^_^%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, 1)); define_builtin_func("^_<<=_", arith_bin_op, compile_lshift); define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1)); - define_builtin_func("^_~>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0)); - define_builtin_func("^_^>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1)); define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and); define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or); define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor); @@ -1200,17 +1155,13 @@ void define_builtins() { define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6)); define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3)); define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7)); - define_builtin_func("true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true)); - define_builtin_func("false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false)); - // define_builtin_func("null", Null, AsmOp::Const("PUSHNULL")); - define_builtin_func("nil", TypeExpr::new_map(TypeExpr::new_unit(), Tuple), AsmOp::Const("PUSHNULL")); - define_builtin_func("null?", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null); - define_builtin_func("throw", impure_un_op, compile_throw, true); - define_builtin_func("throw_if", impure_bin_op, std::bind(compile_cond_throw, _1, _2, true), true); - define_builtin_func("throw_unless", impure_bin_op, std::bind(compile_cond_throw, _1, _2, false), true); - define_builtin_func("throw_arg", throw_arg_op, compile_throw_arg, true); - define_builtin_func("throw_arg_if", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, true), true); - define_builtin_func("throw_arg_unless", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, false), true); + define_builtin_func("__true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true)); + define_builtin_func("__false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false)); + define_builtin_func("__null", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_unit(), X)), AsmOp::Const("PUSHNULL")); + define_builtin_func("__isNull", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null); + define_builtin_func("__throw", impure_un_op, compile_throw, true); + define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg, true); + define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), std::bind(compile_throw_if_unless, _1, _2), true); define_builtin_func("load_int", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0}); define_builtin_func("load_uint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0}); define_builtin_func("preload_int", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true)); diff --git a/tolk/codegen.cpp b/tolk/codegen.cpp index a5d432ee..9a90a3ed 100644 --- a/tolk/codegen.cpp +++ b/tolk/codegen.cpp @@ -497,11 +497,10 @@ bool Op::generate_code_step(Stack& stack) { asm_fv->compile(stack.o, res, args, where); // compile res := f (args) } else { auto fv = dynamic_cast(fun_ref->value); - // todo can be fv == nullptr? std::string name = G.symbols.get_name(fun_ref->sym_idx); - if (fv && (fv->is_inline() || fv->is_inline_ref())) { + if (fv->is_inline() || fv->is_inline_ref()) { stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size()); - } else if (fv && fv->code && fv->code->require_callxargs) { + } else if (fv->code && fv->code->require_callxargs) { stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2); exec_callxargs((int)right.size() + 1, (int)left.size()); } else { diff --git a/tolk/compiler-state.cpp b/tolk/compiler-state.cpp index a609d88d..fb70022f 100644 --- a/tolk/compiler-state.cpp +++ b/tolk/compiler-state.cpp @@ -15,33 +15,42 @@ along with TON Blockchain Library. If not, see . */ #include "compiler-state.h" +#include +#include namespace tolk { -std::string tolk_version{"0.5.0"}; - CompilerState G; // the only mutable global variable in tolk internals -void GlobalPragma::enable(SrcLocation loc) { - if (deprecated_from_v_) { - loc.show_warning(PSTRING() << "#pragma " << name_ << - " is deprecated since Tolk v" << deprecated_from_v_ << - ". Please, remove this line from your code."); - return; - } - if (!loc.get_src_file()->is_entrypoint_file()) { - // todo generally it's not true; rework pragmas completely - loc.show_warning(PSTRING() << "#pragma " << name_ << - " should be used in the main file only."); - } - - enabled_ = true; +void ExperimentalOption::mark_deprecated(const char* deprecated_from_v, const char* deprecated_reason) { + this->deprecated_from_v = deprecated_from_v; + this->deprecated_reason = deprecated_reason; } -void GlobalPragma::always_on_and_deprecated(const char *deprecated_from_v) { - deprecated_from_v_ = deprecated_from_v; - enabled_ = true; +void CompilerSettings::enable_experimental_option(std::string_view name) { + ExperimentalOption* to_enable = nullptr; + + if (name == remove_unused_functions.name) { + to_enable = &remove_unused_functions; + } + + if (to_enable == nullptr) { + std::cerr << "unknown experimental option: " << name << std::endl; + } else if (to_enable->deprecated_from_v) { + std::cerr << "experimental option " << name << " " + << "is deprecated since Tolk v" << to_enable->deprecated_from_v + << ": " << to_enable->deprecated_reason << std::endl; + } else { + to_enable->enabled = true; + } } +void CompilerSettings::parse_experimental_options_cmd_arg(const std::string& cmd_arg) { + std::istringstream stream(cmd_arg); + std::string token; + while (std::getline(stream, token, ',')) { + enable_experimental_option(token); + } +} } // namespace tolk diff --git a/tolk/compiler-state.h b/tolk/compiler-state.h index 324a21af..23df230b 100644 --- a/tolk/compiler-state.h +++ b/tolk/compiler-state.h @@ -24,21 +24,21 @@ namespace tolk { -extern std::string tolk_version; +// with cmd option -x, the user can pass experimental options to use +class ExperimentalOption { + friend struct CompilerSettings; -class GlobalPragma { - std::string name_; - bool enabled_ = false; - const char* deprecated_from_v_ = nullptr; + const std::string_view name; + bool enabled = false; + const char* deprecated_from_v = nullptr; // when an option becomes deprecated (after the next compiler release), + const char* deprecated_reason = nullptr; // but the user still passes it, we'll warn to stderr public: - explicit GlobalPragma(std::string name) : name_(std::move(name)) { } + explicit ExperimentalOption(std::string_view name) : name(name) {} - const std::string& name() const { return name_; } + void mark_deprecated(const char* deprecated_from_v, const char* deprecated_reason); - bool enabled() const { return enabled_; } - void enable(SrcLocation loc); - void always_on_and_deprecated(const char* deprecated_from_v); + explicit operator bool() const { return enabled; } }; // CompilerSettings contains settings that can be passed via cmd line or (partially) wasm envelope. @@ -58,6 +58,11 @@ struct CompilerSettings { std::string stdlib_filename; FsReadCallback read_callback; + + ExperimentalOption remove_unused_functions{"remove-unused-functions"}; + + void enable_experimental_option(std::string_view name); + void parse_experimental_options_cmd_arg(const std::string& cmd_arg); }; // CompilerState contains a mutable state that is changed while the compilation is going on. @@ -78,9 +83,6 @@ struct CompilerState { AllRegisteredSrcFiles all_src_files; std::string generated_from; - GlobalPragma pragma_allow_post_modification{"allow-post-modification"}; - GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"}; - GlobalPragma pragma_remove_unused_functions{"remove-unused-functions"}; bool is_verbosity(int gt_eq) const { return settings.verbosity >= gt_eq; } }; diff --git a/tolk/gen-abscode.cpp b/tolk/gen-abscode.cpp index 09a92686..97fb5d3f 100644 --- a/tolk/gen-abscode.cpp +++ b/tolk/gen-abscode.cpp @@ -148,8 +148,7 @@ bool Expr::deduce_type() { int Expr::define_new_vars(CodeBlob& code) { switch (cls) { case _Tensor: - case _MkTuple: - case _TypeApply: { + case _MkTuple: { int res = 0; for (const auto& x : args) { res += x->define_new_vars(code); @@ -174,8 +173,7 @@ int Expr::define_new_vars(CodeBlob& code) { int Expr::predefine_vars() { switch (cls) { case _Tensor: - case _MkTuple: - case _TypeApply: { + case _MkTuple: { int res = 0; for (const auto& x : args) { res += x->predefine_vars(); @@ -210,12 +208,6 @@ void add_set_globs(CodeBlob& code, std::vector>& g } std::vector pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, SrcLocation here) { - while (lhs->is_type_apply()) { - lhs = lhs->args.at(0); - } - while (rhs->is_type_apply()) { - rhs = rhs->args.at(0); - } if (lhs->is_mktuple()) { if (rhs->is_mktuple()) { return pre_compile_let(code, lhs->args.at(0), rhs->args.at(0), here); @@ -301,7 +293,7 @@ std::vector pre_compile_tensor(const std::vector& args, CodeB } std::vector Expr::pre_compile(CodeBlob& code, std::vector>* lval_globs) const { - if (lval_globs && !(cls == _Tensor || cls == _Var || cls == _Hole || cls == _TypeApply || cls == _GlobVar)) { + if (lval_globs && !(cls == _Tensor || cls == _Var || cls == _Hole || cls == _GlobVar)) { std::cerr << "lvalue expression constructor is " << cls << std::endl; throw Fatal{"cannot compile lvalue expression with unknown constructor"}; } @@ -344,8 +336,6 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vectorpre_compile(code, lval_globs); case _Var: case _Hole: if (val < 0) { diff --git a/tolk/lexer.cpp b/tolk/lexer.cpp index d2c05f34..0a2dd79c 100644 --- a/tolk/lexer.cpp +++ b/tolk/lexer.cpp @@ -158,8 +158,7 @@ struct ChunkInlineComment final : ChunkLexerBase { struct ChunkMultilineComment final : ChunkLexerBase { bool parse(Lexer* lex) const override { while (!lex->is_eof()) { - // todo drop -} later - if ((lex->char_at() == '-' && lex->char_at(1) == '}') || (lex->char_at() == '*' && lex->char_at(1) == '/')) { + if (lex->char_at() == '*' && lex->char_at(1) == '/') { lex->skip_chars(2); return true; } @@ -221,6 +220,22 @@ struct ChunkMultilineString final : ChunkLexerBase { } }; +// An annotation for a function (in the future, for vars also): +// @inline and others +struct ChunkAnnotation final : ChunkLexerBase { + bool parse(Lexer* lex) const override { + const char* str_begin = lex->c_str(); + lex->skip_chars(1); + while (std::isalnum(lex->char_at()) || lex->char_at() == '_') { + lex->skip_chars(1); + } + + std::string_view str_val(str_begin, lex->c_str() - str_begin); + lex->add_token(tok_annotation_at, str_val); + return true; + } +}; + // A number, may be a hex one. struct ChunkNumber final : ChunkLexerBase { bool parse(Lexer* lex) const override { @@ -255,32 +270,6 @@ struct ChunkNumber final : ChunkLexerBase { } }; -// Anything starting from # is a compiler directive. -// Technically, #include and #pragma can be mapped as separate chunks, -// but storing such long strings in a trie increases its memory usage. -struct ChunkCompilerDirective final : ChunkLexerBase { - bool parse(Lexer* lex) const override { - const char* str_begin = lex->c_str(); - - lex->skip_chars(1); - while (std::isalnum(lex->char_at())) { - lex->skip_chars(1); - } - - std::string_view str_val(str_begin, lex->c_str() - str_begin); - if (str_val == "#include") { - lex->add_token(tok_include, str_val); - return true; - } - if (str_val == "#pragma") { - lex->add_token(tok_pragma, str_val); - return true; - } - - lex->error("unknown compiler directive"); - } -}; - // Tokens like !=, &, etc. emit just a simple TokenType. // Since they are stored in trie, "parsing" them is just skipping len chars. struct ChunkSimpleToken final : ChunkLexerBase { @@ -307,23 +296,9 @@ struct ChunkSkipWhitespace final : ChunkLexerBase { }; // Here we handle corner cases of grammar that are requested on demand. -// E.g., for 'pragma version >0.5.0', '0.5.0' should be parsed specially to emit tok_semver. +// E.g., for 'tolk >0.5.0', '0.5.0' should be parsed specially to emit tok_semver. // See TolkLanguageGrammar::parse_next_chunk_special(). struct ChunkSpecialParsing { - static bool parse_pragma_name(Lexer* lex) { - const char* str_begin = lex->c_str(); - while (std::isalnum(lex->char_at()) || lex->char_at() == '-') { - lex->skip_chars(1); - } - - std::string_view str_val(str_begin, lex->c_str() - str_begin); - if (str_val.empty()) { - return false; - } - lex->add_token(tok_pragma_name, str_val); - return true; - } - static bool parse_semver(Lexer* lex) { const char* str_begin = lex->c_str(); while (std::isdigit(lex->char_at()) || lex->char_at() == '.') { @@ -358,53 +333,55 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { case 3: if (str == "int") return tok_int; if (str == "var") return tok_var; + if (str == "fun") return tok_fun; if (str == "asm") return tok_asm; if (str == "get") return tok_get; if (str == "try") return tok_try; - if (str == "nil") return tok_nil; + if (str == "val") return tok_val; break; case 4: if (str == "else") return tok_else; if (str == "true") return tok_true; - if (str == "pure") return tok_pure; - if (str == "then") return tok_then; if (str == "cell") return tok_cell; - if (str == "cont") return tok_cont; + if (str == "null") return tok_null; + if (str == "void") return tok_void; + if (str == "bool") return tok_bool; + if (str == "auto") return tok_auto; + if (str == "tolk") return tok_tolk; + if (str == "type") return tok_type; + if (str == "enum") return tok_enum; break; case 5: if (str == "slice") return tok_slice; if (str == "tuple") return tok_tuple; if (str == "const") return tok_const; if (str == "false") return tok_false; + if (str == "redef") return tok_redef; if (str == "while") return tok_while; - if (str == "until") return tok_until; + if (str == "break") return tok_break; + if (str == "throw") return tok_throw; if (str == "catch") return tok_catch; - if (str == "ifnot") return tok_ifnot; + if (str == "infix") return tok_infix; break; case 6: if (str == "return") return tok_return; - if (str == "repeat") return tok_repeat; - if (str == "elseif") return tok_elseif; - if (str == "forall") return tok_forall; - if (str == "extern") return tok_extern; + if (str == "assert") return tok_assert; + if (str == "import") return tok_import; if (str == "global") return tok_global; - if (str == "impure") return tok_impure; - if (str == "inline") return tok_inline; + if (str == "repeat") return tok_repeat; + if (str == "struct") return tok_struct; + if (str == "export") return tok_export; break; case 7: if (str == "builder") return tok_builder; if (str == "builtin") return tok_builtin; break; case 8: + if (str == "continue") return tok_continue; if (str == "operator") return tok_operator; break; - case 9: - if (str == "elseifnot") return tok_elseifnot; - if (str == "method_id") return tok_method_id; - break; - case 10: - if (str == "inline_ref") return tok_inlineref; - if (str == "auto_apply") return tok_autoapply; + case 12: + if (str == "continuation") return tok_continuation; break; default: break; @@ -418,7 +395,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase { while (!lex->is_eof()) { char c = lex->char_at(); // the pattern of valid identifier first symbol is provided in trie, here we test for identifier middle - bool allowed_in_identifier = std::isalnum(c) || c == '_' || c == '$' || c == ':' || c == '?' || c == '!' || c == '\''; + bool allowed_in_identifier = std::isalnum(c) || c == '_' || c == '$' || c == '?' || c == '!' || c == '\''; if (!allowed_in_identifier) { break; } @@ -445,12 +422,12 @@ struct ChunkIdentifierInBackticks final : ChunkLexerBase { lex->skip_chars(1); while (!lex->is_eof() && lex->char_at() != '`' && lex->char_at() != '\n') { if (std::isspace(lex->char_at())) { // probably, I'll remove this restriction after rewriting symtable and cur_sym_idx - lex->error("An identifier can't have a space in its name (even inside backticks)"); + lex->error("an identifier can't have a space in its name (even inside backticks)"); } lex->skip_chars(1); } if (lex->char_at() != '`') { - lex->error("Unclosed backtick `"); + lex->error("unclosed backtick `"); } std::string_view str_val(str_begin + 1, lex->c_str() - str_begin - 1); @@ -461,6 +438,28 @@ struct ChunkIdentifierInBackticks final : ChunkLexerBase { } }; +// Handle ~`some_method` and .`some_method` todo to be removed later +struct ChunkDotTildeAndBackticks final : ChunkLexerBase { + bool parse(Lexer* lex) const override { + const char* str_begin = lex->c_str(); + lex->skip_chars(2); + while (!lex->is_eof() && lex->char_at() != '`' && lex->char_at() != '\n') { + lex->skip_chars(1); + } + if (lex->char_at() != '`') { + lex->error("unclosed backtick `"); + } + + std::string_view in_backticks(str_begin + 2, lex->c_str() - str_begin - 2); + std::string full = std::string(1, *str_begin) + static_cast(in_backticks); + std::string* allocated = new std::string(full); + lex->skip_chars(1); + std::string_view str_val(allocated->c_str(), allocated->size()); + lex->add_token(tok_identifier, str_val); + return true; + } +}; + // // ---------------------------------------------------------------------- // Here we define a grammar of Tolk. @@ -477,8 +476,6 @@ struct TolkLanguageGrammar { static bool parse_next_chunk_special(Lexer* lex, TokenType parse_next_as) { switch (parse_next_as) { - case tok_pragma_name: - return ChunkSpecialParsing::parse_pragma_name(lex); case tok_semver: return ChunkSpecialParsing::parse_semver(lex); default: @@ -493,21 +490,21 @@ struct TolkLanguageGrammar { static void init() { trie.add_prefix("//", singleton()); - trie.add_prefix(";;", singleton()); trie.add_prefix("/*", singleton()); - trie.add_prefix("{-", singleton()); trie.add_prefix(R"(")", singleton()); trie.add_prefix(R"(""")", singleton()); + trie.add_prefix("@", singleton()); trie.add_prefix(" ", singleton()); trie.add_prefix("\t", singleton()); trie.add_prefix("\r", singleton()); trie.add_prefix("\n", singleton()); - trie.add_prefix("#", singleton()); trie.add_pattern("[0-9]", singleton()); // todo think of . ~ trie.add_pattern("[a-zA-Z_$.~]", singleton()); trie.add_prefix("`", singleton()); + // todo to be removed after ~ becomes invalid and . becomes a separate token + trie.add_pattern("[.~]`", singleton()); register_token("+", 1, tok_plus); register_token("-", 1, tok_minus); @@ -527,6 +524,7 @@ struct TolkLanguageGrammar { register_token("=", 1, tok_assign); register_token("<", 1, tok_lt); register_token(">", 1, tok_gt); + register_token("!", 1, tok_logical_not); register_token("&", 1, tok_bitwise_and); register_token("|", 1, tok_bitwise_or); register_token("^", 1, tok_bitwise_xor); @@ -536,11 +534,10 @@ struct TolkLanguageGrammar { register_token(">=", 2, tok_geq); register_token("<<", 2, tok_lshift); register_token(">>", 2, tok_rshift); + register_token("&&", 2, tok_logical_and); + register_token("||", 2, tok_logical_or); register_token("~/", 2, tok_divR); register_token("^/", 2, tok_divC); - register_token("~%", 2, tok_modR); - register_token("^%", 2, tok_modC); - register_token("/%", 2, tok_divmod); register_token("+=", 2, tok_set_plus); register_token("-=", 2, tok_set_minus); register_token("*=", 2, tok_set_mul); @@ -549,18 +546,12 @@ struct TolkLanguageGrammar { register_token("&=", 2, tok_set_bitwise_and); register_token("|=", 2, tok_set_bitwise_or); register_token("^=", 2, tok_set_bitwise_xor); - register_token("->", 2, tok_mapsto); + register_token("->", 2, tok_arrow); register_token("<=>", 3, tok_spaceship); register_token("~>>", 3, tok_rshiftR); register_token("^>>", 3, tok_rshiftC); - register_token("~/=", 3, tok_set_divR); - register_token("^/=", 3, tok_set_divC); - register_token("~%=", 3, tok_set_modR); - register_token("^%=", 3, tok_set_modC); register_token("<<=", 3, tok_set_lshift); register_token(">>=", 3, tok_set_rshift); - register_token("~>>=", 4, tok_set_rshiftR); - register_token("^>>=", 4, tok_set_rshiftC); } }; @@ -593,7 +584,7 @@ void Lexer::next() { while (cur_token_idx == last_token_idx && !is_eof()) { update_location(); if (!TolkLanguageGrammar::parse_next_chunk(this)) { - error("Failed to parse"); + error("failed to parse"); } } if (is_eof()) { @@ -616,8 +607,8 @@ void Lexer::error(const std::string& err_msg) const { throw ParseError(cur_location(), err_msg); } -void Lexer::on_expect_call_failed(const char* str_expected) const { - throw ParseError(cur_location(), std::string(str_expected) + " expected instead of `" + std::string(cur_str()) + "`"); +void Lexer::unexpected(const char* str_expected) const { + throw ParseError(cur_location(), "expected " + std::string(str_expected) + ", got `" + std::string(cur_str()) + "`"); } void lexer_init() { diff --git a/tolk/lexer.h b/tolk/lexer.h index 1c8188fc..8e04018c 100644 --- a/tolk/lexer.h +++ b/tolk/lexer.h @@ -25,15 +25,33 @@ namespace tolk { enum TokenType { tok_empty, + tok_fun, + tok_get, + tok_type, + tok_enum, + tok_struct, + tok_operator, + tok_infix, + + tok_global, + tok_const, + tok_var, + tok_val, + tok_redef, + + tok_annotation_at, + tok_colon, + tok_asm, + tok_builtin, + tok_int_const, tok_string_const, tok_string_modifier, - - tok_identifier, - tok_true, tok_false, - tok_nil, // todo "null" keyword is still absent, "nil" in FunC is an empty tuple + tok_null, + + tok_identifier, tok_plus, tok_minus, @@ -41,7 +59,6 @@ enum TokenType { tok_div, tok_mod, tok_question, - tok_colon, tok_comma, tok_semicolon, tok_oppar, @@ -54,11 +71,13 @@ enum TokenType { tok_underscore, tok_lt, tok_gt, + tok_logical_not, + tok_logical_and, + tok_logical_or, tok_bitwise_and, tok_bitwise_or, tok_bitwise_xor, tok_bitwise_not, - tok_dot, tok_eq, tok_neq, @@ -71,71 +90,45 @@ enum TokenType { tok_rshiftC, tok_divR, tok_divC, - tok_modR, - tok_modC, - tok_divmod, tok_set_plus, tok_set_minus, tok_set_mul, tok_set_div, - tok_set_divR, - tok_set_divC, tok_set_mod, - tok_set_modR, - tok_set_modC, tok_set_lshift, tok_set_rshift, - tok_set_rshiftR, - tok_set_rshiftC, tok_set_bitwise_and, tok_set_bitwise_or, tok_set_bitwise_xor, tok_return, - tok_var, tok_repeat, tok_do, tok_while, - tok_until, + tok_break, + tok_continue, tok_try, tok_catch, + tok_throw, + tok_assert, tok_if, - tok_ifnot, - tok_then, tok_else, - tok_elseif, - tok_elseifnot, tok_int, tok_cell, + tok_bool, tok_slice, tok_builder, - tok_cont, + tok_continuation, tok_tuple, - tok_mapsto, - tok_forall, + tok_auto, + tok_void, + tok_arrow, - tok_extern, - tok_global, - tok_asm, - tok_impure, - tok_pure, - tok_inline, - tok_inlineref, - tok_builtin, - tok_autoapply, - tok_method_id, - tok_get, - tok_operator, - tok_infix, - tok_infixl, - tok_infixr, - tok_const, - - tok_pragma, - tok_pragma_name, + tok_tolk, tok_semver, - tok_include, + tok_import, + tok_export, tok_eof }; @@ -167,9 +160,6 @@ class Lexer { location.char_offset = static_cast(p_next - p_start); } - GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD - void on_expect_call_failed(const char* str_expected) const; - public: explicit Lexer(const SrcFile* file); @@ -217,16 +207,18 @@ public: void check(TokenType next_tok, const char* str_expected) const { if (cur_token.type != next_tok) { - on_expect_call_failed(str_expected); // unlikely path, not inlined + unexpected(str_expected); // unlikely path, not inlined } } void expect(TokenType next_tok, const char* str_expected) { if (cur_token.type != next_tok) { - on_expect_call_failed(str_expected); + unexpected(str_expected); } next(); } + GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD + void unexpected(const char* str_expected) const; GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD void error(const std::string& err_msg) const; }; diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index 6db12374..539652c0 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -34,10 +34,10 @@ namespace tolk { static int calc_sym_idx(std::string_view sym_name) { - return G.symbols.lookup_add(sym_name); + return G.symbols.lookup(sym_name); } -Expr* process_expr(AnyV v, CodeBlob& code, bool nv = false); +Expr* process_expr(AnyV v, CodeBlob& code); static void check_global_func(SrcLocation loc, sym_idx_t func_name) { SymDef* sym_def = lookup_symbol(func_name); @@ -77,42 +77,63 @@ static void check_import_exists_when_using_sym(AnyV v_usage, const SymDef* used_ } } -static Expr* process_expr(V v, CodeBlob& code, bool nv) { +static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_name, TypeExpr* var_type) { + SymDef* sym = lookup_symbol(calc_sym_idx(var_name)); + if (sym) { // creating a new variable, but something found in symtable + if (sym->level != G.scope_level) { + sym = nullptr; // declaring a new variable with the same name, but in another scope + } else { + throw ParseError(loc, "redeclaration of local variable `" + static_cast(var_name) + "`"); + } + } + Expr* x = new Expr{Expr::_Var, loc}; + x->val = ~calc_sym_idx(var_name); + x->e_type = var_type; + x->flags = Expr::_IsLvalue; + return x; +} + +static Expr* create_new_underscore_variable(SrcLocation loc, TypeExpr* var_type) { + Expr* x = new Expr{Expr::_Hole, loc}; + x->val = -1; + x->flags = Expr::_IsLvalue; + x->e_type = var_type; + return x; +} + +static Expr* process_expr(V v, CodeBlob& code) { TokenType t = v->tok; std::string operator_name = static_cast(v->operator_name); - if (t == tok_set_plus || t == tok_set_minus || t == tok_set_mul || t == tok_set_div || t == tok_set_divR || t == tok_set_divC || - t == tok_set_mod || t == tok_set_modC || t == tok_set_modR || t == tok_set_lshift || t == tok_set_rshift || t == tok_set_rshiftC || - t == tok_set_rshiftR || t == tok_set_bitwise_and || t == tok_set_bitwise_or || t == tok_set_bitwise_xor) { - Expr* x = process_expr(v->get_lhs(), code, nv); + if (t == tok_set_plus || t == tok_set_minus || t == tok_set_mul || t == tok_set_div || + t == tok_set_mod || t == tok_set_lshift || t == tok_set_rshift || + t == tok_set_bitwise_and || t == tok_set_bitwise_or || t == tok_set_bitwise_xor) { + Expr* x = process_expr(v->get_lhs(), code); x->chk_lvalue(); x->chk_rvalue(); sym_idx_t name = G.symbols.lookup_add("^_" + operator_name + "_"); - Expr* y = process_expr(v->get_rhs(), code, false); + Expr* y = process_expr(v->get_rhs(), code); y->chk_rvalue(); Expr* z = new Expr{Expr::_Apply, name, {x, y}}; z->here = v->loc; - z->set_val(t); z->flags = Expr::_IsRvalue; z->deduce_type(); Expr* res = new Expr{Expr::_Letop, {x->copy(), z}}; res->here = v->loc; - res->flags = (x->flags & ~Expr::_IsType) | Expr::_IsRvalue; - res->set_val(t); + res->flags = x->flags | Expr::_IsRvalue; res->deduce_type(); return res; } if (t == tok_assign) { - Expr* x = process_expr(v->get_lhs(), code, nv); + Expr* x = process_expr(v->get_lhs(), code); x->chk_lvalue(); - Expr* y = process_expr(v->get_rhs(), code, false); + Expr* y = process_expr(v->get_rhs(), code); y->chk_rvalue(); x->predefine_vars(); x->define_new_vars(code); Expr* res = new Expr{Expr::_Letop, {x, y}}; res->here = v->loc; - res->flags = (x->flags & ~Expr::_IsType) | Expr::_IsRvalue; - res->set_val(t); + res->flags = x->flags | Expr::_IsRvalue; res->deduce_type(); return res; } @@ -120,20 +141,21 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { t == tok_bitwise_and || t == tok_bitwise_or || t == tok_bitwise_xor || t == tok_eq || t == tok_lt || t == tok_gt || t == tok_leq || t == tok_geq || t == tok_neq || t == tok_spaceship || t == tok_lshift || t == tok_rshift || t == tok_rshiftC || t == tok_rshiftR || - t == tok_mul || t == tok_div || t == tok_mod || t == tok_divmod || - t == tok_divC || t == tok_divR || t == tok_modC || t == tok_modR) { - Expr* res = process_expr(v->get_lhs(), code, nv); + t == tok_mul || t == tok_div || t == tok_mod || t == tok_divC || t == tok_divR) { + Expr* res = process_expr(v->get_lhs(), code); res->chk_rvalue(); sym_idx_t name = G.symbols.lookup_add("_" + operator_name + "_"); - Expr* x = process_expr(v->get_rhs(), code, false); + Expr* x = process_expr(v->get_rhs(), code); x->chk_rvalue(); res = new Expr{Expr::_Apply, name, {res, x}}; res->here = v->loc; - res->set_val(t); res->flags = Expr::_IsRvalue; res->deduce_type(); return res; } + if (t == tok_logical_and || t == tok_logical_or) { + v->error("logical operators are not supported yet"); + } v->error("unsupported binary operator"); } @@ -141,7 +163,7 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { static Expr* process_expr(V v, CodeBlob& code) { TokenType t = v->tok; sym_idx_t name = G.symbols.lookup_add(static_cast(v->operator_name) + "_"); - Expr* x = process_expr(v->get_rhs(), code, false); + Expr* x = process_expr(v->get_rhs(), code); x->chk_rvalue(); // here's an optimization to convert "-1" (tok_minus tok_int_const) to a const -1, not to Expr::Apply(-,1) @@ -151,28 +173,26 @@ static Expr* process_expr(V v, CodeBlob& code) { // `var snd = - 1;` // is Expr::Apply(-), a comment "snd=1" is lost in stack layout comments, and so on // hence, when after grammar modification tok_minus became a true unary operator (not a part of a number), // and thus to preserve existing behavior until compiler parts are completely rewritten, handle this case here - if (x->cls == Expr::_Const) { - if (t == tok_bitwise_not) { - x->intval = ~x->intval; - } else if (t == tok_minus) { - x->intval = -x->intval; - } + if (t == tok_minus && x->cls == Expr::_Const) { + x->intval = -x->intval; if (!x->intval->signed_fits_bits(257)) { v->error("integer overflow"); } return x; } + if (t == tok_plus && x->cls == Expr::_Const) { + return x; + } auto res = new Expr{Expr::_Apply, name, {x}}; res->here = v->loc; - res->set_val(t); res->flags = Expr::_IsRvalue; res->deduce_type(); return res; } -static Expr* process_expr(V v, CodeBlob& code, bool nv) { - Expr* res = process_expr(v->get_lhs(), code, nv); +static Expr* process_expr(V v, CodeBlob& code) { + Expr* res = process_expr(v->get_lhs(), code); bool modify = v->method_name[0] == '~'; Expr* obj = res; if (modify) { @@ -188,7 +208,6 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { const SymDef* sym1 = lookup_symbol(name1); if (sym1 && dynamic_cast(sym1->value)) { name_idx = name1; - sym = sym1; } } } @@ -198,7 +217,7 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { if (!val) { v->error("undefined method call"); } - Expr* x = process_expr(v->get_arg(), code, false); + Expr* x = process_expr(v->get_arg(), code); x->chk_rvalue(); if (x->cls == Expr::_Tensor) { res = new Expr{Expr::_Apply, name_idx, {obj}}; @@ -210,7 +229,7 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { res->flags = Expr::_IsRvalue | (val->is_marked_as_pure() ? 0 : Expr::_IsImpure); res->deduce_type(); if (modify) { - auto tmp = res; + Expr* tmp = res; res = new Expr{Expr::_LetFirst, {obj->copy(), tmp}}; res->here = v->loc; res->flags = tmp->flags; @@ -220,12 +239,12 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { return res; } -static Expr* process_expr(V v, CodeBlob& code, bool nv) { - Expr* cond = process_expr(v->get_cond(), code, nv); +static Expr* process_expr(V v, CodeBlob& code) { + Expr* cond = process_expr(v->get_cond(), code); cond->chk_rvalue(); - Expr* x = process_expr(v->get_when_true(), code, false); + Expr* x = process_expr(v->get_when_true(), code); x->chk_rvalue(); - Expr* y = process_expr(v->get_when_false(), code, false); + Expr* y = process_expr(v->get_when_false(), code); y->chk_rvalue(); Expr* res = new Expr{Expr::_CondExpr, {cond, x, y}}; res->here = v->loc; @@ -234,9 +253,14 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { return res; } -static Expr* process_expr(V v, CodeBlob& code, bool nv) { - Expr* res = process_expr(v->get_called_f(), code, nv); - Expr* x = process_expr(v->get_called_arg(), code, false); +static Expr* process_expr(V v, CodeBlob& code) { + // special error for "null()" which is a FunC syntax + if (v->get_called_f()->type == ast_null_keyword) { + v->error("null is not a function: use `null`, not `null()`"); + } + + Expr* res = process_expr(v->get_called_f(), code); + Expr* x = process_expr(v->get_called_arg(), code); x->chk_rvalue(); res = make_func_apply(res, x); res->here = v->loc; @@ -244,7 +268,7 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { return res; } -static Expr* process_expr(V v, CodeBlob& code, bool nv) { +static Expr* process_expr(V v, CodeBlob& code) { if (v->empty()) { Expr* res = new Expr{Expr::_Tensor, {}}; res->flags = Expr::_IsRvalue; @@ -253,13 +277,13 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { return res; } - Expr* res = process_expr(v->get_item(0), code, nv); + Expr* res = process_expr(v->get_item(0), code); std::vector type_list; type_list.push_back(res->e_type); int f = res->flags; res = new Expr{Expr::_Tensor, {res}}; for (int i = 1; i < v->size(); ++i) { - Expr* x = process_expr(v->get_item(i), code, nv); + Expr* x = process_expr(v->get_item(i), code); res->pb_arg(x); f &= x->flags; type_list.push_back(x->e_type); @@ -270,25 +294,7 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { return res; } -static Expr* process_expr(V v, CodeBlob& code) { - Expr* x = process_expr(v->get_variable_or_list(), code, true); - x->chk_lvalue(); // chk_lrvalue() ? - Expr* res = new Expr{Expr::_TypeApply, {x}}; - res->e_type = v->declared_type; - res->here = v->loc; - try { - unify(res->e_type, x->e_type); - } catch (UnifyError& ue) { - std::ostringstream os; - os << "cannot transform expression of type " << x->e_type << " to explicitly requested type " << res->e_type - << ": " << ue; - v->error(os.str()); - } - res->flags = x->flags; - return res; -} - -static Expr* process_expr(V v, CodeBlob& code, bool nv) { +static Expr* process_expr(V v, CodeBlob& code) { if (v->empty()) { Expr* res = new Expr{Expr::_Tensor, {}}; res->flags = Expr::_IsRvalue; @@ -301,13 +307,13 @@ static Expr* process_expr(V v, CodeBlob& code, bool nv) { return res; } - Expr* res = process_expr(v->get_item(0), code, nv); + Expr* res = process_expr(v->get_item(0), code); std::vector type_list; type_list.push_back(res->e_type); int f = res->flags; res = new Expr{Expr::_Tensor, {res}}; for (int i = 1; i < v->size(); ++i) { - Expr* x = process_expr(v->get_item(i), code, nv); + Expr* x = process_expr(v->get_item(i), code); res->pb_arg(x); f &= x->flags; type_list.push_back(x->e_type); @@ -364,7 +370,7 @@ static Expr* process_expr(V v) { unsigned char buff[128]; int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.data(), str.data() + str.size()); if (bits < 0) { - v->error("Invalid hex bitstring constant '" + str + "'"); + v->error("invalid hex bitstring constant '" + str + "'"); } break; } @@ -406,32 +412,23 @@ static Expr* process_expr(V v) { } static Expr* process_expr(V v) { - SymDef* sym = lookup_symbol(calc_sym_idx(v->bool_val ? "true" : "false")); - tolk_assert(sym); - Expr* res = new Expr{Expr::_Apply, sym, {}}; + SymDef* builtin_sym = lookup_symbol(calc_sym_idx(v->bool_val ? "__true" : "__false")); + Expr* res = new Expr{Expr::_Apply, builtin_sym, {}}; res->flags = Expr::_IsRvalue; res->deduce_type(); return res; } -static Expr* process_expr([[maybe_unused]] V v) { - SymDef* sym = lookup_symbol(calc_sym_idx("nil")); - tolk_assert(sym); - Expr* res = new Expr{Expr::_Apply, sym, {}}; +static Expr* process_expr([[maybe_unused]] V v) { + SymDef* builtin_sym = lookup_symbol(calc_sym_idx("__null")); + Expr* res = new Expr{Expr::_Apply, builtin_sym, {}}; res->flags = Expr::_IsRvalue; res->deduce_type(); return res; } -static Expr* process_expr(V v, bool nv) { +static Expr* process_identifier(V v) { SymDef* sym = lookup_symbol(calc_sym_idx(v->name)); - if (nv && sym) { - if (sym->level != G.scope_level) { - sym = nullptr; // declaring a new variable with the same name, but in another scope - } else { - v->error("redeclaration of local variable `" + static_cast(v->name) + "`"); - } - } if (sym && dynamic_cast(sym->value)) { check_import_exists_when_using_sym(v, sym); auto val = dynamic_cast(sym->value); @@ -455,7 +452,7 @@ static Expr* process_expr(V v, bool nv) { res->strval = val->get_str_value(); res->e_type = TypeExpr::new_atomic(tok_slice); } else { - v->error("Invalid symbolic constant type"); + v->error("invalid symbolic constant type"); } return res; } @@ -463,86 +460,65 @@ static Expr* process_expr(V v, bool nv) { check_import_exists_when_using_sym(v, sym); } Expr* res = new Expr{Expr::_Var, v->loc}; - if (nv) { - res->val = ~calc_sym_idx(v->name); - res->e_type = TypeExpr::new_hole(); - res->flags = Expr::_IsLvalue; - // std::cerr << "defined new variable " << lex.cur().str << " : " << res->e_type << std::endl; - } else { - if (!sym) { - check_global_func(v->loc, calc_sym_idx(v->name)); - sym = lookup_symbol(calc_sym_idx(v->name)); - } - res->sym = sym; - SymVal* val = nullptr; - bool impure = false; - if (sym) { - val = dynamic_cast(sym->value); - } - if (!val) { - v->error("undefined identifier '" + static_cast(v->name) + "'"); - } - if (val->kind == SymValKind::_Func) { - res->e_type = val->get_type(); - res->cls = Expr::_GlobFunc; - impure = !dynamic_cast(val)->is_marked_as_pure(); - } else { - tolk_assert(val->idx >= 0); - res->val = val->idx; - res->e_type = val->get_type(); - // std::cerr << "accessing variable " << lex.cur().str << " : " << res->e_type << std::endl; - } - // std::cerr << "accessing symbol " << lex.cur().str << " : " << res->e_type << (val->impure ? " (impure)" : " (pure)") << std::endl; - res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0); + if (!sym) { + check_global_func(v->loc, calc_sym_idx(v->name)); + sym = lookup_symbol(calc_sym_idx(v->name)); } + res->sym = sym; + SymVal* val = nullptr; + bool impure = false; + if (sym) { + val = dynamic_cast(sym->value); + } + if (!val) { + v->error("undefined identifier '" + static_cast(v->name) + "'"); + } + if (val->kind == SymValKind::_Func) { + res->e_type = val->get_type(); + res->cls = Expr::_GlobFunc; + impure = !dynamic_cast(val)->is_marked_as_pure(); + } else { + tolk_assert(val->idx >= 0); + res->val = val->idx; + res->e_type = val->get_type(); + // std::cerr << "accessing variable " << lex.cur().str << " : " << res->e_type << std::endl; + } + // std::cerr << "accessing symbol " << lex.cur().str << " : " << res->e_type << (val->impure ? " (impure)" : " (pure)") << std::endl; + res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0); res->deduce_type(); return res; } -Expr* process_expr(AnyV v, CodeBlob& code, bool nv) { +Expr* process_expr(AnyV v, CodeBlob& code) { switch (v->type) { case ast_binary_operator: - return process_expr(v->as(), code, nv); + return process_expr(v->as(), code); case ast_unary_operator: return process_expr(v->as(), code); case ast_dot_tilde_call: - return process_expr(v->as(), code, nv); + return process_expr(v->as(), code); case ast_ternary_operator: - return process_expr(v->as(), code, nv); + return process_expr(v->as(), code); case ast_function_call: - return process_expr(v->as(), code, nv); + return process_expr(v->as(), code); case ast_parenthesized_expr: - return process_expr(v->as()->get_expr(), code, nv); - case ast_variable_declaration: - return process_expr(v->as(), code); + return process_expr(v->as()->get_expr(), code); case ast_tensor: - return process_expr(v->as(), code, nv); + return process_expr(v->as(), code); case ast_tensor_square: - return process_expr(v->as(), code, nv); + return process_expr(v->as(), code); case ast_int_const: return process_expr(v->as()); case ast_string_const: return process_expr(v->as()); case ast_bool_const: return process_expr(v->as()); - case ast_nil_tuple: - return process_expr(v->as()); + case ast_null_keyword: + return process_expr(v->as()); case ast_identifier: - return process_expr(v->as(), nv); - - case ast_underscore: { - Expr* res = new Expr{Expr::_Hole, v->loc}; - res->val = -1; - res->flags = Expr::_IsLvalue; - res->e_type = TypeExpr::new_hole(); - return res; - } - case ast_type_expression: { - Expr* res = new Expr{Expr::_Type, v->loc}; - res->flags = Expr::_IsType; - res->e_type = v->as()->declared_type; - return res; - } + return process_identifier(v->as()); + case ast_underscore: + return create_new_underscore_variable(v->loc, TypeExpr::new_hole()); default: throw UnexpectedASTNodeType(v, "process_expr"); } @@ -562,6 +538,70 @@ void combine_parallel(val& x, const val y) { } } // namespace blk_fl +static Expr* process_local_vars_lhs(AnyV v, CodeBlob& code) { + switch (v->type) { + case ast_local_var: { + if (v->as()->marked_as_redef) { + return process_identifier(v->as()->get_identifier()->as()); + } + TypeExpr* declared_type = v->as()->declared_type; + if (auto v_ident = v->as()->get_identifier()->try_as()) { + return create_new_local_variable(v->loc, v_ident->name, declared_type ? declared_type : TypeExpr::new_hole()); + } else { + return create_new_underscore_variable(v->loc, declared_type ? declared_type : TypeExpr::new_hole()); + } + } + case ast_parenthesized_expr: + return process_local_vars_lhs(v->as()->get_expr(), code); + case ast_tensor: { + std::vector type_list; + Expr* res = new Expr{Expr::_Tensor, v->loc}; + for (AnyV item : v->as()->get_items()) { + Expr* x = process_local_vars_lhs(item, code); + res->pb_arg(x); + res->flags |= x->flags; + type_list.push_back(x->e_type); + } + res->e_type = TypeExpr::new_tensor(std::move(type_list)); + return res; + } + case ast_tensor_square: { + std::vector type_list; + Expr* res = new Expr{Expr::_Tensor, v->loc}; + for (AnyV item : v->as()->get_items()) { + Expr* x = process_local_vars_lhs(item, code); + res->pb_arg(x); + res->flags |= x->flags; + type_list.push_back(x->e_type); + } + res->e_type = TypeExpr::new_tensor(std::move(type_list)); + res = new Expr{Expr::_MkTuple, {res}}; + res->flags = res->args.at(0)->flags; + res->here = v->loc; + res->e_type = TypeExpr::new_tuple(res->args.at(0)->e_type); + return res; + } + default: + throw UnexpectedASTNodeType(v, "process_local_vars_lhs"); + } +} + +static blk_fl::val process_vertex(V v, CodeBlob& code) { + Expr* x = process_local_vars_lhs(v->get_lhs(), code); + x->chk_lvalue(); + Expr* y = process_expr(v->get_assigned_val(), code); + y->chk_rvalue(); + x->predefine_vars(); + x->define_new_vars(code); + Expr* res = new Expr{Expr::_Letop, {x, y}}; + res->here = v->loc; + res->flags = x->flags | Expr::_IsRvalue; + res->deduce_type(); + res->chk_rvalue(); + res->pre_compile(code); + return blk_fl::end; +} + static blk_fl::val process_vertex(V v, CodeBlob& code) { Expr* expr = process_expr(v->get_return_value(), code); expr->chk_rvalue(); @@ -593,7 +633,7 @@ static void append_implicit_ret_stmt(V v, CodeBlob& code) { code.emplace_back(v->loc_end, Op::_Return); } -blk_fl::val process_stmt(AnyV v, CodeBlob& code); +blk_fl::val process_statement(AnyV v, CodeBlob& code); static blk_fl::val process_vertex(V v, CodeBlob& code, bool no_new_scope = false) { if (!no_new_scope) { @@ -606,7 +646,7 @@ static blk_fl::val process_vertex(V v, CodeBlob& code, bool no_new item->loc.show_warning("unreachable code"); warned = true; } - blk_fl::combine(res, process_stmt(item, code)); + blk_fl::combine(res, process_statement(item, code)); } if (!no_new_scope) { close_scope(); @@ -660,12 +700,37 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { return res1 | blk_fl::end; } -static blk_fl::val process_vertex(V v, CodeBlob& code) { +static blk_fl::val process_vertex(V v, CodeBlob& code) { Op& until_op = code.emplace_back(v->loc, Op::_Until); code.push_set_cur(until_op.block0); open_scope(v->loc); blk_fl::val res = process_vertex(v->get_body(), code, true); - Expr* expr = process_expr(v->get_cond(), code); + + // in TVM, there is only "do until", but in Tolk, we want "do while" + // here we negate condition to pass it forward to legacy to Op::_Until + // also, handle common situations as a hardcoded "optimization": replace (a<0) with (a>=0) and so on + // todo these hardcoded conditions should be removed from this place in the future + AnyV cond = v->get_cond(); + AnyV until_cond; + if (auto v_not = cond->try_as(); v_not && v_not->tok == tok_logical_not) { + until_cond = v_not->get_rhs(); + } else if (auto v_eq = cond->try_as(); v_eq && v_eq->tok == tok_eq) { + until_cond = createV(cond->loc, "!=", tok_neq, v_eq->get_lhs(), v_eq->get_rhs()); + } else if (auto v_neq = cond->try_as(); v_neq && v_neq->tok == tok_neq) { + until_cond = createV(cond->loc, "==", tok_eq, v_neq->get_lhs(), v_neq->get_rhs()); + } else if (auto v_leq = cond->try_as(); v_leq && v_leq->tok == tok_leq) { + until_cond = createV(cond->loc, ">", tok_gt, v_leq->get_lhs(), v_leq->get_rhs()); + } else if (auto v_lt = cond->try_as(); v_lt && v_lt->tok == tok_lt) { + until_cond = createV(cond->loc, ">=", tok_geq, v_lt->get_lhs(), v_lt->get_rhs()); + } else if (auto v_geq = cond->try_as(); v_geq && v_geq->tok == tok_geq) { + until_cond = createV(cond->loc, "<", tok_lt, v_geq->get_lhs(), v_geq->get_rhs()); + } else if (auto v_gt = cond->try_as(); v_gt && v_gt->tok == tok_gt) { + until_cond = createV(cond->loc, "<=", tok_geq, v_gt->get_lhs(), v_gt->get_rhs()); + } else { + until_cond = createV(cond->loc, "!", tok_logical_not, cond); + } + + Expr* expr = process_expr(until_cond, code); expr->chk_rvalue(); close_scope(); auto cnt_type = TypeExpr::new_atomic(TypeExpr::_Int); @@ -673,17 +738,65 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { unify(expr->e_type, cnt_type); } catch (UnifyError& ue) { std::ostringstream os; - os << "`until` condition value of type " << expr->e_type << " is not an integer: " << ue; + os << "`while` condition value of type " << expr->e_type << " is not an integer: " << ue; v->get_cond()->error(os.str()); } until_op.left = expr->pre_compile(code); code.close_pop_cur(v->get_body()->loc_end); if (until_op.left.size() != 1) { - v->get_cond()->error("`until` condition value is not a singleton"); + v->get_cond()->error("`while` condition value is not a singleton"); } return res & ~blk_fl::empty; } +static blk_fl::val process_vertex(V v, CodeBlob& code) { + std::vector args; + SymDef* builtin_sym; + if (v->has_thrown_arg()) { + builtin_sym = lookup_symbol(calc_sym_idx("__throw_arg")); + args.push_back(process_expr(v->get_thrown_arg(), code)); + args.push_back(process_expr(v->get_thrown_code(), code)); + } else { + builtin_sym = lookup_symbol(calc_sym_idx("__throw")); + args.push_back(process_expr(v->get_thrown_code(), code)); + } + + Expr* expr = new Expr{Expr::_Apply, builtin_sym, std::move(args)}; + expr->here = v->loc; + expr->flags = Expr::_IsRvalue | Expr::_IsImpure; + expr->deduce_type(); + expr->pre_compile(code); + return blk_fl::end; +} + +static blk_fl::val process_vertex(V v, CodeBlob& code) { + std::vector args(3); + if (auto v_not = v->get_cond()->try_as(); v_not && v_not->tok == tok_logical_not) { + args[0] = process_expr(v->get_thrown_code(), code); + args[1] = process_expr(v->get_cond()->as()->get_rhs(), code); + args[2] = process_expr(createV(v->loc, true), code); + } else { + args[0] = process_expr(v->get_thrown_code(), code); + args[1] = process_expr(v->get_cond(), code); + args[2] = process_expr(createV(v->loc, false), code); + } + + SymDef* builtin_sym = lookup_symbol(calc_sym_idx("__throw_if_unless")); + Expr* expr = new Expr{Expr::_Apply, builtin_sym, std::move(args)}; + expr->here = v->loc; + expr->flags = Expr::_IsRvalue | Expr::_IsImpure; + expr->deduce_type(); + expr->pre_compile(code); + return blk_fl::end; +} + +static Expr* process_catch_variable(AnyV catch_var, TypeExpr* var_type) { + if (auto v_ident = catch_var->try_as()) { + return create_new_local_variable(catch_var->loc, v_ident->name, var_type); + } + return create_new_underscore_variable(catch_var->loc, var_type); +} + static blk_fl::val process_vertex(V v, CodeBlob& code) { code.require_callxargs = true; Op& try_catch_op = code.emplace_back(v->loc, Op::_TryCatch); @@ -692,20 +805,21 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) code.close_pop_cur(v->get_try_body()->loc_end); code.push_set_cur(try_catch_op.block1); open_scope(v->get_catch_expr()->loc); - Expr* expr = process_expr(v->get_catch_expr(), code, true); - expr->chk_lvalue(); + + // transform catch (excNo, arg) into TVM-catch (arg, excNo), where arg is untyped and thus almost useless now TypeExpr* tvm_error_type = TypeExpr::new_tensor(TypeExpr::new_var(), TypeExpr::new_atomic(TypeExpr::_Int)); - try { - unify(expr->e_type, tvm_error_type); - } catch (UnifyError& ue) { - std::ostringstream os; - os << "`catch` arguments have incorrect type " << expr->e_type << ": " << ue; - v->get_catch_expr()->error(os.str()); - } - expr->predefine_vars(); - expr->define_new_vars(code); - try_catch_op.left = expr->pre_compile(code); - tolk_assert(try_catch_op.left.size() == 2 || try_catch_op.left.size() == 1); + const std::vector& catch_items = v->get_catch_expr()->get_items(); + tolk_assert(catch_items.size() == 2); + Expr* e_catch = new Expr{Expr::_Tensor, v->get_catch_expr()->loc}; + e_catch->pb_arg(process_catch_variable(catch_items[1], tvm_error_type->args[0])); + e_catch->pb_arg(process_catch_variable(catch_items[0], tvm_error_type->args[1])); + e_catch->flags = Expr::_IsLvalue; + e_catch->e_type = tvm_error_type; + e_catch->predefine_vars(); + e_catch->define_new_vars(code); + try_catch_op.left = e_catch->pre_compile(code); + tolk_assert(try_catch_op.left.size() == 2); + blk_fl::val res1 = process_vertex(v->get_catch_body(), code); close_scope(); code.close_pop_cur(v->get_catch_body()->loc_end); @@ -716,7 +830,7 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) static blk_fl::val process_vertex(V v, CodeBlob& code) { Expr* expr = process_expr(v->get_cond(), code); expr->chk_rvalue(); - auto flag_type = TypeExpr::new_atomic(TypeExpr::_Int); + TypeExpr* flag_type = TypeExpr::new_atomic(TypeExpr::_Int); try { unify(expr->e_type, flag_type); } catch (UnifyError& ue) { @@ -743,8 +857,10 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { return res1; } -blk_fl::val process_stmt(AnyV v, CodeBlob& code) { +blk_fl::val process_statement(AnyV v, CodeBlob& code) { switch (v->type) { + case ast_local_vars_declaration: + return process_vertex(v->as(), code); case ast_return_statement: return process_vertex(v->as(), code); case ast_sequence: @@ -755,10 +871,14 @@ blk_fl::val process_stmt(AnyV v, CodeBlob& code) { return process_vertex(v->as(), code); case ast_if_statement: return process_vertex(v->as(), code); - case ast_do_until_statement: - return process_vertex(v->as(), code); + case ast_do_while_statement: + return process_vertex(v->as(), code); case ast_while_statement: return process_vertex(v->as(), code); + case ast_throw_statement: + return process_vertex(v->as(), code); + case ast_assert_statement: + return process_vertex(v->as(), code); case ast_try_catch_statement: return process_vertex(v->as(), code); default: { @@ -770,9 +890,9 @@ blk_fl::val process_stmt(AnyV v, CodeBlob& code) { } } -static FormalArg process_vertex(V v, int fa_idx) { +static FormalArg process_vertex(V v, int fa_idx) { if (v->get_identifier()->name.empty()) { - return std::make_tuple(v->arg_type, (SymDef*)nullptr, v->loc); + return std::make_tuple(v->param_type, (SymDef*)nullptr, v->loc); } SymDef* new_sym_def = define_symbol(calc_sym_idx(v->get_identifier()->name), true, v->loc); if (!new_sym_def) { @@ -781,8 +901,8 @@ static FormalArg process_vertex(V v, int fa_idx) { if (new_sym_def->value) { v->error("redefined argument"); } - new_sym_def->value = new SymVal{SymValKind::_Param, fa_idx, v->arg_type}; - return std::make_tuple(v->arg_type, new_sym_def, v->loc); + new_sym_def->value = new SymVal{SymValKind::_Param, fa_idx, v->param_type}; + return std::make_tuple(v->param_type, new_sym_def, v->loc); } static void convert_function_body_to_CodeBlob(V v, V v_body) { @@ -796,8 +916,8 @@ static void convert_function_body_to_CodeBlob(V v, Vflags |= CodeBlob::_ForbidImpure; } FormalArgList legacy_arg_list; - for (int i = 0; i < v->get_num_args(); ++i) { - legacy_arg_list.emplace_back(process_vertex(v->get_arg(i), i)); + for (int i = 0; i < v->get_num_params(); ++i) { + legacy_arg_list.emplace_back(process_vertex(v->get_param(i), i)); } blob->import_params(std::move(legacy_arg_list)); @@ -808,7 +928,7 @@ static void convert_function_body_to_CodeBlob(V v, Vloc.show_warning("unreachable code"); warned = true; } - blk_fl::combine(res, process_stmt(item, *blob)); + blk_fl::combine(res, process_statement(item, *blob)); } if (res & blk_fl::end) { append_implicit_ret_stmt(v_body, *blob); @@ -824,7 +944,7 @@ static void convert_asm_body_to_AsmOp(V v, V(sym_def->value); tolk_assert(sym_val != nullptr); - int cnt = v->get_num_args(); + int cnt = v->get_num_params(); int width = v->ret_type->get_width(); std::vector asm_ops; for (AnyV v_child : v_body->get_asm_commands()) { diff --git a/tolk/pipe-discover-parse-sources.cpp b/tolk/pipe-discover-parse-sources.cpp index f074e075..c57c9c1d 100644 --- a/tolk/pipe-discover-parse-sources.cpp +++ b/tolk/pipe-discover-parse-sources.cpp @@ -38,17 +38,18 @@ AllSrcFiles pipeline_discover_and_parse_sources(const std::string& stdlib_filena tolk_assert(!file->ast); file->ast = parse_src_file_to_ast(file); + // file->ast->debug_print(); for (AnyV v_toplevel : file->ast->as()->get_toplevel_declarations()) { - if (auto v_include = v_toplevel->try_as()) { + if (auto v_import = v_toplevel->try_as()) { size_t pos = file->rel_filename.rfind('/'); std::string rel_filename = pos == std::string::npos - ? v_include->get_file_name() - : file->rel_filename.substr(0, pos + 1) + v_include->get_file_name(); + ? v_import->get_file_name() + : file->rel_filename.substr(0, pos + 1) + v_import->get_file_name(); - SrcFile* imported = G.all_src_files.locate_and_register_source_file(rel_filename, v_include->loc); + SrcFile* imported = G.all_src_files.locate_and_register_source_file(rel_filename, v_import->loc); file->imports.push_back(SrcFile::ImportStatement{imported}); - v_include->mutate_set_src_file(imported); + v_import->mutate_set_src_file(imported); } } } diff --git a/tolk/pipe-find-unused-symbols.cpp b/tolk/pipe-find-unused-symbols.cpp index 0badd853..f83579f4 100644 --- a/tolk/pipe-find-unused-symbols.cpp +++ b/tolk/pipe-find-unused-symbols.cpp @@ -79,9 +79,7 @@ void pipeline_find_unused_symbols() { for (SymDef* func_sym : G.all_code_functions) { auto* func_val = dynamic_cast(func_sym->value); std::string name = G.symbols.get_name(func_sym->sym_idx); - if (func_val->method_id.not_null() || - name == "main" || name == "recv_internal" || name == "recv_external" || - name == "run_ticktock" || name == "split_prepare" || name == "split_install") { + if (func_val->method_id.not_null() || func_val->is_entrypoint()) { mark_function_used(func_val); } } diff --git a/tolk/pipe-generate-fif-output.cpp b/tolk/pipe-generate-fif-output.cpp index 538dc9ba..627b510f 100644 --- a/tolk/pipe-generate-fif-output.cpp +++ b/tolk/pipe-generate-fif-output.cpp @@ -32,7 +32,7 @@ namespace tolk { bool SymValCodeFunc::does_need_codegen() const { // when a function is declared, but not referenced from code in any way, don't generate its body - if (!is_really_used && G.pragma_remove_unused_functions.enabled()) { + if (!is_really_used && G.settings.remove_unused_functions) { return false; } // when a function is referenced like `var a = some_fn;` (or in some other non-call way), its continuation should exist @@ -137,6 +137,7 @@ void pipeline_generate_fif_output_to_std_cout() { std::cout << "// automatically generated from " << G.generated_from << std::endl; std::cout << "PROGRAM{\n"; + bool has_main_procedure = false; for (SymDef* func_sym : G.all_code_functions) { SymValCodeFunc* func_val = dynamic_cast(func_sym->value); tolk_assert(func_val); @@ -148,6 +149,10 @@ void pipeline_generate_fif_output_to_std_cout() { } std::string name = G.symbols.get_name(func_sym->sym_idx); + if (func_val->is_entrypoint() && (name == "main" || name == "onInternalMessage")) { + has_main_procedure = true; + } + std::cout << std::string(2, ' '); if (func_val->method_id.is_null()) { std::cout << "DECLPROC " << name << "\n"; @@ -156,10 +161,14 @@ void pipeline_generate_fif_output_to_std_cout() { } } + if (!has_main_procedure) { + throw Fatal("the contract has no entrypoint; forgot `fun onInternalMessage(...)`?"); + } + for (SymDef* gvar_sym : G.all_global_vars) { auto* glob_val = dynamic_cast(gvar_sym->value); tolk_assert(glob_val); - if (!glob_val->is_really_used && G.pragma_remove_unused_functions.enabled()) { + if (!glob_val->is_really_used && G.settings.remove_unused_functions) { if (G.is_verbosity(2)) { std::cerr << gvar_sym->name() << ": variable not generated, it's unused\n"; } diff --git a/tolk/pipe-handle-pragmas.cpp b/tolk/pipe-handle-pragmas.cpp deleted file mode 100644 index 1b0cd7d3..00000000 --- a/tolk/pipe-handle-pragmas.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - This file is part of TON Blockchain source code. - - TON Blockchain is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - TON Blockchain is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with TON Blockchain. If not, see . - - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement - from all source files in the program, then also delete it here. -*/ -#include "tolk.h" -#include "src-file.h" -#include "ast.h" -#include "compiler-state.h" -#include "td/utils/misc.h" - -namespace tolk { - -static void handle_pragma_no_arg(V v) { - std::string_view pragma_name = v->pragma_name; - if (pragma_name == G.pragma_allow_post_modification.name()) { - G.pragma_allow_post_modification.enable(v->loc); - } else if (pragma_name == G.pragma_compute_asm_ltr.name()) { - G.pragma_compute_asm_ltr.enable(v->loc); - } else if (pragma_name == G.pragma_remove_unused_functions.name()) { - G.pragma_remove_unused_functions.enable(v->loc); - } else { - v->error("unknown pragma name"); - } -} - -static void handle_pragma_version(V v) { - char op = '='; - bool eq = false; - TokenType cmp_tok = v->cmp_tok; - if (cmp_tok == tok_gt || cmp_tok == tok_geq) { - op = '>'; - eq = cmp_tok == tok_geq; - } else if (cmp_tok == tok_lt || cmp_tok == tok_leq) { - op = '<'; - eq = cmp_tok == tok_leq; - } else if (cmp_tok == tok_eq) { - op = '='; - } else if (cmp_tok == tok_bitwise_xor) { - op = '^'; - } else { - v->error("invalid comparison operator"); - } - std::string_view pragma_value = v->semver; - int sem_ver[3] = {0, 0, 0}; - char segs = 1; - auto stoi = [&](std::string_view s) { - auto R = td::to_integer_safe(static_cast(s)); - if (R.is_error()) { - v->error("invalid semver format"); - } - return R.move_as_ok(); - }; - std::istringstream iss_value(static_cast(pragma_value)); - for (int idx = 0; idx < 3; idx++) { - std::string s{"0"}; - std::getline(iss_value, s, '.'); - sem_ver[idx] = stoi(s); - } - // End reading semver from source code - int tolk_ver[3] = {0, 0, 0}; - std::istringstream iss(tolk_version); - for (int idx = 0; idx < 3; idx++) { - std::string s; - std::getline(iss, s, '.'); - tolk_ver[idx] = stoi(s); - } - // End parsing embedded semver - bool match = true; - switch (op) { - case '=': - if ((tolk_ver[0] != sem_ver[0]) || (tolk_ver[1] != sem_ver[1]) || (tolk_ver[2] != sem_ver[2])) { - match = false; - } - break; - case '>': - if (((tolk_ver[0] == sem_ver[0]) && (tolk_ver[1] == sem_ver[1]) && (tolk_ver[2] == sem_ver[2]) && !eq) || - ((tolk_ver[0] == sem_ver[0]) && (tolk_ver[1] == sem_ver[1]) && (tolk_ver[2] < sem_ver[2])) || - ((tolk_ver[0] == sem_ver[0]) && (tolk_ver[1] < sem_ver[1])) || ((tolk_ver[0] < sem_ver[0]))) { - match = false; - } - break; - case '<': - if (((tolk_ver[0] == sem_ver[0]) && (tolk_ver[1] == sem_ver[1]) && (tolk_ver[2] == sem_ver[2]) && !eq) || - ((tolk_ver[0] == sem_ver[0]) && (tolk_ver[1] == sem_ver[1]) && (tolk_ver[2] > sem_ver[2])) || - ((tolk_ver[0] == sem_ver[0]) && (tolk_ver[1] > sem_ver[1])) || ((tolk_ver[0] > sem_ver[0]))) { - match = false; - } - break; - case '^': - if (((segs == 3) && - ((tolk_ver[0] != sem_ver[0]) || (tolk_ver[1] != sem_ver[1]) || (tolk_ver[2] < sem_ver[2]))) || - ((segs == 2) && ((tolk_ver[0] != sem_ver[0]) || (tolk_ver[1] < sem_ver[1]))) || - ((segs == 1) && ((tolk_ver[0] < sem_ver[0])))) { - match = false; - } - break; - default: - tolk_assert(false); - } - if (!match) { - v->error("Tolk version " + tolk_version + " does not satisfy this condition"); - } -} - -void pipeline_handle_pragmas(const AllSrcFiles& all_src_files) { - for (const SrcFile* file : all_src_files) { - tolk_assert(file->ast); - - for (AnyV v : file->ast->as()->get_toplevel_declarations()) { - if (auto v_no_arg = v->try_as()) { - handle_pragma_no_arg(v_no_arg); - } else if (auto v_version = v->try_as()) { - handle_pragma_version(v_version); - } - } - } -} - -} // namespace tolk diff --git a/tolk/pipe-register-symbols.cpp b/tolk/pipe-register-symbols.cpp index 792037a7..c84474f8 100644 --- a/tolk/pipe-register-symbols.cpp +++ b/tolk/pipe-register-symbols.cpp @@ -33,7 +33,7 @@ namespace tolk { -Expr* process_expr(AnyV v, CodeBlob& code, bool nv = false); +Expr* process_expr(AnyV v, CodeBlob& code); GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD static void fire_error_redefinition_of_symbol(V v_ident, SymDef* existing) { @@ -50,13 +50,32 @@ static int calc_sym_idx(std::string_view sym_name) { return G.symbols.lookup_add(sym_name); } +static td::RefInt256 calculate_method_id_for_entrypoint(std::string_view func_name) { + if (func_name == "main" || func_name == "onInternalMessage") { + return td::make_refint(0); + } + if (func_name == "onExternalMessage") { + return td::make_refint(-1); + } + if (func_name == "onRunTickTock") { + return td::make_refint(-2); + } + if (func_name == "onSplitPrepare") { + return td::make_refint(-3); + } + if (func_name == "onSplitInstall") { + return td::make_refint(-4); + } + tolk_assert(false); +} + static td::RefInt256 calculate_method_id_by_func_name(std::string_view func_name) { unsigned int crc = td::crc16(static_cast(func_name)); return td::make_refint((crc & 0xffff) | 0x10000); } -static bool is_argument_of_function(AnyV v_variable, V v_func) { - return v_variable->type == ast_identifier && v_func->get_arg_list()->lookup_idx(v_variable->as()->name) != -1; +static bool is_parameter_of_function(AnyV v_variable, V v_func) { + return v_variable->type == ast_identifier && v_func->get_param_list()->lookup_idx(v_variable->as()->name) != -1; } // if a function looks like `T f(...args) { return anotherF(...args); }`, @@ -70,11 +89,11 @@ static bool is_argument_of_function(AnyV v_variable, V // in the future, when working on AST level, inlining should become much more powerful // (for instance, it should inline `return anotherF(constants)`, etc.) static bool detect_if_function_just_wraps_another(V v) { - if (v->method_id || v->marked_as_get_method || v->marked_as_inline_ref || v->ret_type->has_unknown_inside()) { + if (v->method_id || v->marked_as_get_method || v->marked_as_builtin || v->marked_as_inline_ref || v->is_entrypoint) { return false; } - for (int i = 0; i < v->get_num_args(); ++i) { - if (v->get_arg(i)->arg_type->get_width() != 1) { + for (int i = 0; i < v->get_num_params(); ++i) { + if (v->get_param(i)->param_type->get_width() != 1 || v->get_param(i)->param_type->has_unknown_inside()) { return false; // avoid situations like `f(int a, (int,int) b)`, inlining will be cumbersome } } @@ -82,7 +101,7 @@ static bool detect_if_function_just_wraps_another(V v) auto v_body = v->get_body()->try_as(); if (!v_body || v_body->size() != 1 || v_body->get_item(0)->type != ast_return_statement) { return false; - } + } auto v_return = v_body->get_item(0)->as(); auto v_anotherF = v_return->get_return_value()->try_as(); @@ -90,37 +109,23 @@ static bool detect_if_function_just_wraps_another(V v) return false; } - // todo simplify when removing ability of calling a function without parentheses - AnyV called_arg = v_anotherF->get_called_arg(); - bool ok_arg = called_arg->type == ast_tensor || called_arg->type == ast_parenthesized_expr; - if (!ok_arg || v_anotherF->get_called_f()->type != ast_identifier) { + V called_arg = v_anotherF->get_called_arg(); + if (v_anotherF->get_called_f()->type != ast_identifier) { return false; } std::string_view called_name = v_anotherF->get_called_f()->try_as()->name; std::string_view function_name = v->get_identifier()->name; - if (called_arg->type == ast_tensor) { - const std::vector& v_arg_items = called_arg->as()->get_items(); - std::set used_args; - for (AnyV v_arg : v_arg_items) { - if (!is_argument_of_function(v_arg, v)) { - return false; - } - used_args.emplace(v_arg->as()->name); - } - if (used_args.size() != v->get_num_args() || used_args.size() != v_arg_items.size()) { - return false; - } - } else if (called_arg->type == ast_parenthesized_expr) { - AnyV v_arg = called_arg->as()->get_expr(); - if (!is_argument_of_function(v_arg, v)) { + const std::vector& v_arg_items = called_arg->get_items(); + std::set used_args; + for (AnyV v_arg : v_arg_items) { + if (!is_parameter_of_function(v_arg, v)) { return false; } + used_args.emplace(v_arg->as()->name); } - - if (function_name == "main" || function_name == "recv_internal" || function_name == "recv_external" || - function_name == "run_ticktock" || function_name == "split_prepare" || function_name == "split_install") { + if (static_cast(used_args.size()) != v->get_num_params() || used_args.size() != v_arg_items.size()) { return false; } @@ -131,9 +136,9 @@ static bool detect_if_function_just_wraps_another(V v) return true; } -static void calc_arg_ret_order_of_asm_function(V v_body, V arg_list, TypeExpr* ret_type, +static void calc_arg_ret_order_of_asm_function(V v_body, V param_list, TypeExpr* ret_type, std::vector& arg_order, std::vector& ret_order) { - int cnt = arg_list->size(); + int cnt = param_list->size(); int width = ret_type->get_width(); if (width < 0 || width > 16) { v_body->error("return type of an assembler built-in function must have a well-defined fixed width"); @@ -145,16 +150,16 @@ static void calc_arg_ret_order_of_asm_function(V v_body, V arg = arg_list->get_arg(i); - int arg_width = arg->arg_type->get_width(); + V v_param = param_list->get_param(i); + int arg_width = v_param->param_type->get_width(); if (arg_width < 0 || arg_width > 16) { - arg->error("parameters of an assembler built-in function must have a well-defined fixed width"); + v_param->error("parameters of an assembler built-in function must have a well-defined fixed width"); } cum_arg_width.push_back(tot_width += arg_width); } if (!v_body->arg_order.empty()) { if (static_cast(v_body->arg_order.size()) != cnt) { - v_body->error("arg_order of asm function must specify all arguments"); + v_body->error("arg_order of asm function must specify all parameters"); } std::vector visited(cnt, false); for (int i = 0; i < cnt; ++i) { @@ -197,7 +202,7 @@ static void register_constant(V v) { // and waited to be a single expression // although it works, of course it should be later rewritten using AST calculations, as well as lots of other parts CodeBlob code("tmp", v->loc, nullptr); - Expr* x = process_expr(init_value, code, false); + Expr* x = process_expr(init_value, code); if (!x->is_rvalue()) { v->get_init_value()->error("expression is not strictly Rvalue"); } @@ -266,21 +271,21 @@ static void register_function(V v) { // calculate TypeExpr of a function: it's a map (args -> ret), probably surrounded by forall TypeExpr* func_type = nullptr; - if (int n_args = v->get_num_args()) { + if (int n_args = v->get_num_params()) { std::vector arg_types; arg_types.reserve(n_args); for (int idx = 0; idx < n_args; ++idx) { - arg_types.emplace_back(v->get_arg(idx)->arg_type); + arg_types.emplace_back(v->get_param(idx)->param_type); } func_type = TypeExpr::new_map(TypeExpr::new_tensor(std::move(arg_types)), v->ret_type); } else { func_type = TypeExpr::new_map(TypeExpr::new_unit(), v->ret_type); } - if (v->forall_list) { + if (v->genericsT_list) { std::vector type_vars; - type_vars.reserve(v->forall_list->size()); - for (int idx = 0; idx < v->forall_list->size(); ++idx) { - type_vars.emplace_back(v->forall_list->get_item(idx)->created_type); + type_vars.reserve(v->genericsT_list->size()); + for (int idx = 0; idx < v->genericsT_list->size(); ++idx) { + type_vars.emplace_back(v->genericsT_list->get_item(idx)->created_type); } func_type = TypeExpr::new_forall(std::move(type_vars), func_type); } @@ -315,7 +320,7 @@ static void register_function(V v) { sym_val = new SymValCodeFunc{static_cast(G.all_code_functions.size()), func_type, v->marked_as_pure}; } else if (const auto* v_asm = v->get_body()->try_as()) { std::vector arg_order, ret_order; - calc_arg_ret_order_of_asm_function(v_asm, v->get_arg_list(), v->ret_type, arg_order, ret_order); + calc_arg_ret_order_of_asm_function(v_asm, v->get_param_list(), v->ret_type, arg_order, ret_order); sym_val = new SymValAsmFunc{func_type, std::move(arg_order), std::move(ret_order), v->marked_as_pure}; } else { v->error("Unexpected function body statement"); @@ -333,6 +338,8 @@ static void register_function(V v) { v->error(PSTRING() << "GET methods hash collision: `" << other->name() << "` and `" << static_cast(func_name) << "` produce the same hash. Consider renaming one of these functions."); } } + } else if (v->is_entrypoint) { + sym_val->method_id = calculate_method_id_for_entrypoint(func_name); } if (v->marked_as_inline) { sym_val->flags |= SymValFunc::flagInline; @@ -343,6 +350,9 @@ static void register_function(V v) { if (v->marked_as_get_method) { sym_val->flags |= SymValFunc::flagGetMethod; } + if (v->is_entrypoint) { + sym_val->flags |= SymValFunc::flagIsEntrypoint; + } if (detect_if_function_just_wraps_another(v)) { sym_val->flags |= SymValFunc::flagWrapsAnotherF; } @@ -368,21 +378,17 @@ static void iterate_through_file_symbols(const SrcFile* file) { for (AnyV v : file->ast->as()->get_toplevel_declarations()) { switch (v->type) { - case ast_include_statement: + case ast_import_statement: // on `import "another-file.tolk"`, register symbols from that file at first // (for instance, it can calculate constants, which are used in init_val of constants in current file below import) - iterate_through_file_symbols(v->as()->file); + iterate_through_file_symbols(v->as()->file); break; - case ast_constant_declaration_list: - for (AnyV v_decl : v->as()->get_declarations()) { - register_constant(v_decl->as()); - } + case ast_constant_declaration: + register_constant(v->as()); break; - case ast_global_var_declaration_list: - for (AnyV v_decl : v->as()->get_declarations()) { - register_global_var(v_decl->as()); - } + case ast_global_var_declaration: + register_global_var(v->as()); break; case ast_function_declaration: register_function(v->as()); diff --git a/tolk/pipeline.h b/tolk/pipeline.h index b0081634..1330c97a 100644 --- a/tolk/pipeline.h +++ b/tolk/pipeline.h @@ -32,7 +32,6 @@ namespace tolk { AllSrcFiles pipeline_discover_and_parse_sources(const std::string& stdlib_filename, const std::string& entrypoint_filename); -void pipeline_handle_pragmas(const AllSrcFiles&); void pipeline_register_global_symbols(const AllSrcFiles&); void pipeline_convert_ast_to_legacy_Expr_Op(const AllSrcFiles&); diff --git a/tolk/src-file.h b/tolk/src-file.h index 9eaf3a67..28de7568 100644 --- a/tolk/src-file.h +++ b/tolk/src-file.h @@ -52,7 +52,6 @@ struct SrcFile { SrcFile &operator=(const SrcFile&) = delete; bool is_stdlib_file() const { return file_id == 0; /* stdlib always exists, has no imports and parsed the first */ } - bool is_entrypoint_file() const { return file_id == 1; /* after stdlib, the entrypoint file is parsed */ } bool is_offset_valid(int offset) const; SrcPosition convert_offset(int offset) const; diff --git a/tolk/tolk-main.cpp b/tolk/tolk-main.cpp index 38625534..2dc5a0df 100644 --- a/tolk/tolk-main.cpp +++ b/tolk/tolk-main.cpp @@ -24,12 +24,14 @@ from all source files in the program, then also delete it here. */ #include "tolk.h" +#include "tolk-version.h" #include "compiler-state.h" #include "td/utils/port/path.h" #include #include #include #include +#include #include "git.h" using namespace tolk; @@ -40,28 +42,54 @@ void usage(const char* progname) { "\tGenerates Fift TVM assembler code from a .tolk file\n" "-o\tWrites generated code into specified .fif file instead of stdout\n" "-b\tGenerate Fift instructions to save TVM bytecode into .boc file\n" - "-s\tSpecify stdlib location (same as env TOLK_STDLIB; if unset, auto-discover)\n" "-O\tSets optimization level (2 by default)\n" + "-x\tEnables experimental options, comma-separated\n" "-S\tDon't include stack layout comments into Fift output\n" "-e\tIncreases verbosity level (extra output into stderr)\n" "-v\tOutput version of Tolk and exit\n"; std::exit(2); } -static std::string auto_discover_stdlib_location() { +static bool stdlib_file_exists(std::filesystem::path& stdlib_tolk) { + struct stat f_stat; + stdlib_tolk = stdlib_tolk.lexically_normal(); + int res = stat(stdlib_tolk.c_str(), &f_stat); + return res == 0 && S_ISREG(f_stat.st_mode); +} + +static std::string auto_discover_stdlib_location(const char* argv0) { + // first, the user can specify env var that points directly to stdlib (useful for non-standard compiler locations) if (const char* env_var = getenv("TOLK_STDLIB")) { return env_var; } - // this define is automatically set if just building this repo locally with cmake -#ifdef STDLIB_TOLK_IF_BUILD_FROM_SOURCES - return STDLIB_TOLK_IF_BUILD_FROM_SOURCES; -#endif - // this define is automatically set when compiling a linux package for distribution - // (since binaries and smartcont/ folder are installed to a predefined path) - // todo provide in cmake -#ifdef STDLIB_TOLK_IF_BUILD_TO_PACKAGE - return STDLIB_TOLK_IF_BUILD_TO_PACKAGE; + + // if the user launches tolk compiler from a package installed (e.g. /usr/bin/tolk), + // locate stdlib in /usr/share/ton/smartcont (this folder exists on package installation) + // (note, that paths are not absolute, they are relative to the launched binary) + // consider https://github.com/ton-blockchain/packages for actual paths + std::filesystem::path executable_dir = std::filesystem::canonical(argv0).remove_filename(); + +#ifdef TD_DARWIN + auto def_location = executable_dir / "../share/ton/ton/smartcont/stdlib.tolk"; +#elif TD_WINDOWS + auto def_location = executable_dir / "smartcont/stdlib.tolk"; +#else // linux + auto def_location = executable_dir / "../share/ton/smartcont/stdlib.tolk"; #endif + + if (stdlib_file_exists(def_location)) { + return def_location; + } + + // so, the binary is not from a system package + // maybe it's just built from sources? e.g. ~/ton/cmake-build-debug/tolk/tolk + // then, check the ~/ton/crypto/smartcont folder + auto near_when_built_from_sources = executable_dir / "../../crypto/smartcont/stdlib.tolk"; + if (stdlib_file_exists(near_when_built_from_sources)) { + return near_when_built_from_sources; + } + + // no idea of where to find stdlib; let's show an error for the user, he should provide env var above return {}; } @@ -120,7 +148,7 @@ public: int main(int argc, char* const argv[]) { int i; - while ((i = getopt(argc, argv, "o:b:s:O:Sevh")) != -1) { + while ((i = getopt(argc, argv, "o:b:O:x:Sevh")) != -1) { switch (i) { case 'o': G.settings.output_filename = optarg; @@ -128,12 +156,12 @@ int main(int argc, char* const argv[]) { case 'b': G.settings.boc_output_filename = optarg; break; - case 's': - G.settings.stdlib_filename = optarg; - break; case 'O': G.settings.optimization_level = std::max(0, atoi(optarg)); break; + case 'x': + G.settings.parse_experimental_options_cmd_arg(optarg); + break; case 'S': G.settings.stack_layout_comments = false; break; @@ -141,9 +169,9 @@ int main(int argc, char* const argv[]) { G.settings.verbosity++; break; case 'v': - std::cout << "Tolk compiler v" << tolk_version << "\n"; - std::cout << "Build commit: " << GitMetadata::CommitSHA1() << "\n"; - std::cout << "Build date: " << GitMetadata::CommitDate() << "\n"; + std::cout << "Tolk compiler v" << TOLK_VERSION << std::endl; + std::cout << "Build commit: " << GitMetadata::CommitSHA1() << std::endl; + std::cout << "Build date: " << GitMetadata::CommitDate() << std::endl; std::exit(0); case 'h': default: @@ -153,14 +181,12 @@ int main(int argc, char* const argv[]) { StdCoutRedirectToFile redirect_cout(G.settings.output_filename); if (redirect_cout.is_failed()) { - std::cerr << "Failed to create output file " << G.settings.output_filename << '\n'; + std::cerr << "Failed to create output file " << G.settings.output_filename << std::endl; return 2; } - // if stdlib wasn't specify as an option — locate it based on env - if (G.settings.stdlib_filename.empty()) { - G.settings.stdlib_filename = auto_discover_stdlib_location(); - } + // locate stdlib.tolk based on env or default system paths + G.settings.stdlib_filename = auto_discover_stdlib_location(argv[0]); if (G.settings.stdlib_filename.empty()) { std::cerr << "Failed to discover stdlib.tolk.\n" "Probably, you have a non-standard Tolk installation.\n" @@ -168,11 +194,11 @@ int main(int argc, char* const argv[]) { return 2; } if (G.is_verbosity(2)) { - std::cerr << "stdlib located at " << G.settings.stdlib_filename << '\n'; + std::cerr << "stdlib located at " << G.settings.stdlib_filename << std::endl; } if (optind != argc - 1) { - std::cerr << "invalid usage: should specify exactly one input file.tolk"; + std::cerr << "invalid usage: should specify exactly one input file.tolk" << std::endl; return 2; } diff --git a/tolk/tolk-version.h b/tolk/tolk-version.h new file mode 100644 index 00000000..6e5b764c --- /dev/null +++ b/tolk/tolk-version.h @@ -0,0 +1,23 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once + +namespace tolk { + +constexpr const char* TOLK_VERSION = "0.6.0"; + +} // namespace tolk diff --git a/tolk/tolk-wasm.cpp b/tolk/tolk-wasm.cpp index 81953f79..a093a7f6 100644 --- a/tolk/tolk-wasm.cpp +++ b/tolk/tolk-wasm.cpp @@ -24,6 +24,7 @@ from all source files in the program, then also delete it here. */ #include "tolk.h" +#include "tolk-version.h" #include "compiler-state.h" #include "git.h" #include "td/utils/JsonBuilder.h" @@ -41,12 +42,16 @@ td::Result compile_internal(char *config_json) { TRY_RESULT(stdlib_tolk, td::get_json_object_string_field(config, "stdlibLocation", false)); TRY_RESULT(stack_comments, td::get_json_object_bool_field(config, "withStackComments", true, false)); TRY_RESULT(entrypoint_filename, td::get_json_object_string_field(config, "entrypointFileName", false)); + TRY_RESULT(experimental_options, td::get_json_object_string_field(config, "experimentalOptions", true)); G.settings.verbosity = 0; G.settings.optimization_level = std::max(0, opt_level); G.settings.stdlib_filename = stdlib_tolk; G.settings.stack_layout_comments = stack_comments; G.settings.entrypoint_filename = entrypoint_filename; + if (!experimental_options.empty()) { + G.settings.parse_experimental_options_cmd_arg(experimental_options.c_str()); + } std::ostringstream outs, errs; std::cout.rdbuf(outs.rdbuf()); @@ -100,7 +105,7 @@ extern "C" { const char* version() { auto version_json = td::JsonBuilder(); auto obj = version_json.enter_object(); - obj("tolkVersion", tolk_version); + obj("tolkVersion", TOLK_VERSION); obj("tolkFiftLibCommitHash", GitMetadata::CommitSHA1()); obj("tolkFiftLibCommitDate", GitMetadata::CommitDate()); obj.leave(); diff --git a/tolk/tolk.cpp b/tolk/tolk.cpp index 0a0cf144..46eb4dc9 100644 --- a/tolk/tolk.cpp +++ b/tolk/tolk.cpp @@ -32,11 +32,21 @@ namespace tolk { +void on_assertion_failed(const char *description, const char *file_name, int line_number) { + std::string message = static_cast("Assertion failed at ") + file_name + ":" + std::to_string(line_number) + ": " + description; +#ifdef TOLK_DEBUG +#ifdef __arm64__ + // when developing, it's handy when the debugger stops on assertion failure (stacktraces and watches are available) + std::cerr << message << std::endl; + __builtin_debugtrap(); +#endif +#endif + throw Fatal(std::move(message)); +} + int tolk_proceed(const std::string &entrypoint_filename) { define_builtins(); lexer_init(); - G.pragma_allow_post_modification.always_on_and_deprecated("0.5.0"); - G.pragma_compute_asm_ltr.always_on_and_deprecated("0.5.0"); try { if (G.settings.stdlib_filename.empty()) { @@ -48,7 +58,6 @@ int tolk_proceed(const std::string &entrypoint_filename) { AllSrcFiles all_files = pipeline_discover_and_parse_sources(G.settings.stdlib_filename, entrypoint_filename); - pipeline_handle_pragmas(all_files); pipeline_register_global_symbols(all_files); pipeline_convert_ast_to_legacy_Expr_Op(all_files); diff --git a/tolk/tolk.h b/tolk/tolk.h index a0106ffc..19919435 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -16,6 +16,7 @@ */ #pragma once +#include "platform-utils.h" #include "src-file.h" #include "type-expr.h" #include "symtable.h" @@ -26,12 +27,13 @@ #include #include -#define tolk_assert(expr) \ - (bool(expr) ? void(0) \ - : throw Fatal(PSTRING() << "Assertion failed at " << __FILE__ << ":" << __LINE__ << ": " << #expr)) +#define tolk_assert(expr) if(UNLIKELY(!(expr))) on_assertion_failed(#expr, __FILE__, __LINE__); namespace tolk { +GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN +void on_assertion_failed(const char *description, const char *file_name, int line_number); + /* * * TYPE EXPRESSIONS @@ -90,27 +92,23 @@ struct VarDescr { _NonZero = 128, _Pos = 256, _Neg = 512, - _Bool = 1024, - _Bit = 2048, _Finite = 4096, _Nan = 8192, _Even = 16384, _Odd = 32768, - _Null = (1 << 16), - _NotNull = (1 << 17) }; - static constexpr int ConstZero = _Int | _Zero | _Pos | _Neg | _Bool | _Bit | _Finite | _Even | _NotNull; - static constexpr int ConstOne = _Int | _NonZero | _Pos | _Bit | _Finite | _Odd | _NotNull; - static constexpr int ConstTrue = _Int | _NonZero | _Neg | _Bool | _Finite | _Odd | _NotNull; - static constexpr int ValBit = ConstZero & ConstOne; - static constexpr int ValBool = ConstZero & ConstTrue; - static constexpr int FiniteInt = _Int | _Finite | _NotNull; - static constexpr int FiniteUInt = FiniteInt | _Pos; + static constexpr int ConstZero = _Const | _Int | _Zero | _Pos | _Neg | _Finite | _Even; + static constexpr int ConstOne = _Const | _Int | _NonZero | _Pos | _Finite | _Odd; + static constexpr int ConstTrue = _Const | _Int | _NonZero | _Neg | _Finite | _Odd; + static constexpr int ValBit = _Int | _Pos | _Finite; + static constexpr int ValBool = _Int | _Neg | _Finite; + static constexpr int FiniteInt = _Int | _Finite; + static constexpr int FiniteUInt = _Int | _Finite | _Pos; int val; td::RefInt256 int_const; std::string str_const; - VarDescr(var_idx_t _idx = -1, int _flags = 0, int _val = 0) : idx(_idx), flags(_flags), val(_val) { + explicit VarDescr(var_idx_t _idx = -1, int _flags = 0, int _val = 0) : idx(_idx), flags(_flags), val(_val) { } bool operator<(var_idx_t other_idx) const { return idx < other_idx; @@ -139,15 +137,6 @@ struct VarDescr { bool always_odd() const { return val & _Odd; } - bool always_null() const { - return val & _Null; - } - bool always_not_null() const { - return val & _NotNull; - } - bool is_const() const { - return val & _Const; - } bool is_int_const() const { return (val & (_Int | _Const)) == (_Int | _Const) && int_const.not_null(); } @@ -260,7 +249,7 @@ class ListIterator { public: ListIterator() : ptr(nullptr) { } - ListIterator(T* _ptr) : ptr(_ptr) { + explicit ListIterator(T* _ptr) : ptr(_ptr) { } ListIterator& operator++() { ptr = ptr->next.get(); @@ -383,18 +372,6 @@ struct Op { const Op& last() const { return next ? next->last() : *this; } - ListIterator begin() { - return ListIterator{this}; - } - ListIterator end() const { - return ListIterator{}; - } - ListIterator cbegin() { - return ListIterator{this}; - } - ListIterator cend() const { - return ListIterator{}; - } }; inline ListIterator begin(const std::unique_ptr& op_list) { @@ -405,14 +382,6 @@ inline ListIterator end(const std::unique_ptr& op_list) { return ListIterator{}; } -inline ListIterator cbegin(const Op* op_list) { - return ListIterator{op_list}; -} - -inline ListIterator cend(const Op* op_list) { - return ListIterator{}; -} - inline ListIterator begin(const Op* op_list) { return ListIterator{op_list}; } @@ -421,78 +390,11 @@ inline ListIterator end(const Op* op_list) { return ListIterator{}; } -inline ListIterator begin(Op* op_list) { - return ListIterator{op_list}; -} - -inline ListIterator end(Op* op_list) { - return ListIterator{}; -} - typedef std::tuple FormalArg; typedef std::vector FormalArgList; struct AsmOpList; -struct CodeBlob { - enum { _ForbidImpure = 4 }; - int var_cnt, in_var_cnt, op_cnt; - TypeExpr* ret_type; - std::string name; - SrcLocation loc; - std::vector vars; - std::unique_ptr ops; - std::unique_ptr* cur_ops; - std::stack*> cur_ops_stack; - int flags = 0; - bool require_callxargs = false; - CodeBlob(std::string name, SrcLocation loc, TypeExpr* ret) - : var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), name(std::move(name)), loc(loc), cur_ops(&ops) { - } - template - Op& emplace_back(Args&&... args) { - Op& res = *(*cur_ops = std::make_unique(args...)); - cur_ops = &(res.next); - return res; - } - bool import_params(FormalArgList arg_list); - var_idx_t create_var(bool is_tmp_unnamed, TypeExpr* var_type, SymDef* sym, SrcLocation loc); - var_idx_t create_tmp_var(TypeExpr* var_type, SrcLocation loc) { - return create_var(true, var_type, nullptr, loc); - } - int split_vars(bool strict = false); - bool compute_used_code_vars(); - bool compute_used_code_vars(std::unique_ptr& ops, const VarDescrList& var_info, bool edit) const; - void print(std::ostream& os, int flags = 0) const; - void push_set_cur(std::unique_ptr& new_cur_ops) { - cur_ops_stack.push(cur_ops); - cur_ops = &new_cur_ops; - } - void close_blk(SrcLocation location) { - *cur_ops = std::make_unique(location, Op::_Nop); - } - void pop_cur() { - cur_ops = cur_ops_stack.top(); - cur_ops_stack.pop(); - } - void close_pop_cur(SrcLocation location) { - close_blk(location); - pop_cur(); - } - void simplify_var_types(); - void prune_unreachable_code(); - void fwd_analyze(); - void mark_noreturn(); - void generate_code(AsmOpList& out_list, int mode = 0); - void generate_code(std::ostream& os, int mode = 0, int indent = 0); - - void on_var_modification(var_idx_t idx, SrcLocation here) const { - for (auto& f : vars.at(idx).on_modification) { - f(here); - } - } -}; - /* * * SYMBOL VALUES @@ -512,13 +414,14 @@ struct SymVal : SymValBase { struct SymValFunc : SymVal { enum SymValFlag { - flagInline = 1, // function marked `inline` - flagInlineRef = 2, // function marked `inline_ref` - flagWrapsAnotherF = 4, // (T) thisF(...args) { return anotherF(...args); } (calls to thisF will be replaced) + flagInline = 1, // marked `@inline` + flagInlineRef = 2, // marked `@inline_ref` + flagWrapsAnotherF = 4, // `fun thisF(...args) { return anotherF(...args); }` (calls to thisF will be inlined) flagUsedAsNonCall = 8, // used not only as `f()`, but as a 1-st class function (assigned to var, pushed to tuple, etc.) flagMarkedAsPure = 16, // declared as `pure`, can't call impure and access globals, unused invocations are optimized out flagBuiltinFunction = 32, // was created via `define_builtin_func()`, not from source code - flagGetMethod = 64, // was declared via `get T func()`, method_id is auto-assigned + flagGetMethod = 64, // was declared via `get func(): T`, method_id is auto-assigned + flagIsEntrypoint = 128, // it's `main` / `onExternalMessage` / etc. }; td::RefInt256 method_id; // todo why int256? it's small @@ -559,6 +462,9 @@ struct SymValFunc : SymVal { bool is_get_method() const { return flags & flagGetMethod; } + bool is_entrypoint() const { + return flags & flagIsEntrypoint; + } }; struct SymValCodeFunc : SymValFunc { @@ -626,7 +532,6 @@ struct Expr { _None, _Apply, _VarApply, - _TypeApply, _MkTuple, _Tensor, _Const, @@ -636,13 +541,12 @@ struct Expr { _Letop, _LetFirst, _Hole, - _Type, _CondExpr, _SliceConst, }; ExprCls cls; int val{0}; - enum { _IsType = 1, _IsRvalue = 2, _IsLvalue = 4, _IsImpure = 32 }; + enum { _IsRvalue = 2, _IsLvalue = 4, _IsImpure = 32 }; int flags{0}; SrcLocation here; td::RefInt256 intval; @@ -681,12 +585,6 @@ struct Expr { bool is_lvalue() const { return flags & _IsLvalue; } - bool is_type() const { - return flags & _IsType; - } - bool is_type_apply() const { - return cls == _TypeApply; - } bool is_mktuple() const { return cls == _MkTuple; } @@ -1449,6 +1347,65 @@ struct SymValAsmFunc : SymValFunc { bool compile(AsmOpList& dest, std::vector& out, std::vector& in, SrcLocation where) const; }; +struct CodeBlob { + enum { _ForbidImpure = 4 }; + int var_cnt, in_var_cnt, op_cnt; + TypeExpr* ret_type; + std::string name; + SrcLocation loc; + std::vector vars; + std::unique_ptr ops; + std::unique_ptr* cur_ops; + std::stack*> cur_ops_stack; + int flags = 0; + bool require_callxargs = false; + CodeBlob(std::string name, SrcLocation loc, TypeExpr* ret) + : var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), name(std::move(name)), loc(loc), cur_ops(&ops) { + } + template + Op& emplace_back(Args&&... args) { + Op& res = *(*cur_ops = std::make_unique(args...)); + cur_ops = &(res.next); + return res; + } + bool import_params(FormalArgList arg_list); + var_idx_t create_var(bool is_tmp_unnamed, TypeExpr* var_type, SymDef* sym, SrcLocation loc); + var_idx_t create_tmp_var(TypeExpr* var_type, SrcLocation loc) { + return create_var(true, var_type, nullptr, loc); + } + int split_vars(bool strict = false); + bool compute_used_code_vars(); + bool compute_used_code_vars(std::unique_ptr& ops, const VarDescrList& var_info, bool edit) const; + void print(std::ostream& os, int flags = 0) const; + void push_set_cur(std::unique_ptr& new_cur_ops) { + cur_ops_stack.push(cur_ops); + cur_ops = &new_cur_ops; + } + void close_blk(SrcLocation location) { + *cur_ops = std::make_unique(location, Op::_Nop); + } + void pop_cur() { + cur_ops = cur_ops_stack.top(); + cur_ops_stack.pop(); + } + void close_pop_cur(SrcLocation location) { + close_blk(location); + pop_cur(); + } + void simplify_var_types(); + void prune_unreachable_code(); + void fwd_analyze(); + void mark_noreturn(); + void generate_code(AsmOpList& out_list, int mode = 0); + void generate_code(std::ostream& os, int mode = 0, int indent = 0); + + void on_var_modification(var_idx_t idx, SrcLocation here) const { + for (auto& f : vars.at(idx).on_modification) { + f(here); + } + } +}; + // defined in builtins.cpp AsmOp exec_arg_op(std::string op, long long arg); AsmOp exec_arg_op(std::string op, long long arg, int args, int retv = 1); diff --git a/tolk/type-expr.h b/tolk/type-expr.h index 4893df35..0e2a870f 100644 --- a/tolk/type-expr.h +++ b/tolk/type-expr.h @@ -14,7 +14,7 @@ struct TypeExpr { _Cell = tok_cell, _Slice = tok_slice, _Builder = tok_builder, - _Cont = tok_cont, + _Cont = tok_continuation, _Tuple = tok_tuple, }; Kind constr;