diff --git a/.eslintignore b/.eslintignore index 882f7100d..5a4a9e3e7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,5 +6,7 @@ src/test/**/output/ src/func/funcfiftlib.js src/grammar/grammar.ohm*.ts src/grammar/grammar.ohm*.js +src/func/grammar.ohm*.ts +src/func/grammar.ohm*.js jest.setup.js -jest.teardown.js \ No newline at end of file +jest.teardown.js diff --git a/.gitignore b/.gitignore index 52dfbea8b..c3391eefb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,7 @@ dist output/ src/grammar/grammar.ohm-bundle.js src/grammar/grammar.ohm-bundle.d.ts +src/func/grammar.ohm*.ts +src/func/grammar.ohm*.js src/func/funcfiftlib.wasm.js src/test/contracts/pretty-printer-output diff --git a/.prettierignore b/.prettierignore index 1af3219a2..37bb0107d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,5 +4,7 @@ /src/func/funcfiftlib.wasm.js /src/grammar/grammar.ohm-bundle.d.ts /src/grammar/grammar.ohm-bundle.js +/src/func/grammar.ohm*.ts +/src/func/grammar.ohm*.js /src/imports/stdlib.ts /grammar diff --git a/cspell.json b/cspell.json index e5b433ec2..3c2a2878c 100644 --- a/cspell.json +++ b/cspell.json @@ -64,6 +64,10 @@ "Korshakov", "Laika", "langle", + "lbrack", + "lparen", + "lquote", + "ltick", "LDIX", "LDREF", "LDSLICEX", @@ -77,6 +81,11 @@ "maxint", "minmax", "mintable", + "moddiv", + "muldiv", + "muldivc", + "muldivmod", + "muldivr", "mktemp", "multiformats", "MYADDR", @@ -97,6 +106,9 @@ "POSIX", "postpack", "prando", + "qmark", + "rangle", + "rbrack", "PUSHREF", "PUSHSLICE", "RANDU", @@ -107,6 +119,8 @@ "REWRITESTDADDR", "REWRITEVARADDR", "rparen", + "rquote", + "rtick", "rugpull", "rugpulled", "SBITS", @@ -126,6 +140,7 @@ "STBR", "STDICT", "stdlib", + "stdump", "STIX", "STOPTREF", "STREF", @@ -159,6 +174,14 @@ "*.spec.ts.snap", "node_modules", "dist", + "examples/__snapshots__/multisig-3.spec.ts.snap", + "/func", + "scripts/func", + "src/func/__snapshots__/*", + "src/func/grammar.ohm*", + "src/func/funcfiftlib*", + "src/func/grammar-test/*", + "src/func/grammar-test-failed/*", "func", "grammar/sample.json", "src/generator/writers/writeStdlib.ts", diff --git a/package.json b/package.json index 105fc75be..04a220417 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,15 @@ "author": "Steve Korshakov ", "license": "MIT", "scripts": { - "gen:grammar": "ohm generateBundles --withTypes src/grammar/*.ohm", + "gen:grammar": "ohm generateBundles --withTypes src/grammar/*.ohm src/func/*.ohm", "gen:pack": "ts-node ./scripts/pack.ts", "gen:compiler": "ts-node ./scripts/prepare.ts", "gen": "yarn gen:grammar && yarn gen:pack && yarn gen:compiler", "clean": "rm -fr dist", + "build": "tsc && cp ./src/grammar/grammar.ohm* ./dist/grammar/ && cp ./src/func/grammar.ohm* ./dist/func/ && cp ./src/func/funcfiftlib.* ./dist/func", "cleanall": "rm -fr dist node_modules", - "build": "tsc && cp ./src/grammar/grammar.ohm* ./dist/grammar/ && cp ./src/func/funcfiftlib.* ./dist/func", "test": "jest", + "test:func": "yarn gen:grammar && jest -t 'FunC grammar and parser'", "coverage": "cross-env COVERAGE=true jest", "release": "yarn clean && yarn build && yarn coverage && yarn release-it --npm.yarn1", "lint": "yarn eslint .", @@ -47,6 +48,7 @@ "@tact-lang/opcode": "^0.0.16", "@ton/core": "0.57.0", "@ton/crypto": "^3.2.0", + "@types/json-bigint": "^1.0.4", "blockstore-core": "1.0.5", "change-case": "^4.1.2", "crc-32": "1.2.2", diff --git a/src/func/__snapshots__/grammar.spec.ts.snap b/src/func/__snapshots__/grammar.spec.ts.snap new file mode 100644 index 000000000..8ca5d446b --- /dev/null +++ b/src/func/__snapshots__/grammar.spec.ts.snap @@ -0,0 +1,238 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FunC grammar and parser should NOT parse id-arith-operator 1`] = ` +"id-arith-operator.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () /(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-assign-operator 1`] = ` +"id-assign-operator.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () ^>>=(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-bitwise-operator 1`] = ` +"id-bitwise-operator.fc:1:5: Parse error: expected not (a whiteSpace or "(" or ")" or "[" or "]" or "," or "." or ";" or "~"), "~_", "_", or "\`" + +Line 1, col 5: +> 1 | () ~(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-comma 1`] = ` +"id-comma.fc:1:16: Parse error: expected "(" + +Line 1, col 16: +> 1 | () send_message,then_terminate(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-comparison-operator 1`] = ` +"id-comparison-operator.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () <=>(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-control-keyword 1`] = ` +"id-control-keyword.fc:1:1: Name of the function cannot be a control keyword +Line 1, col 1: +> 1 | () elseifnot(); + ^~~~~~~~~~~~~~ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-delimiter 1`] = ` +"id-delimiter.fc:1:4: Parse error: expected not (a whiteSpace or "(" or ")" or "[" or "]" or "," or "." or ";" or "~"), "~_", "_", "\`", or "->" + +Line 1, col 4: +> 1 | () [(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-directive 1`] = ` +"id-directive.fc:1:1: Name of the function cannot be a compiler directive +Line 1, col 1: +> 1 | () #include(); + ^~~~~~~~~~~~~ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-dot 1`] = ` +"id-dot.fc:1:7: Parse error: expected "(" + +Line 1, col 7: +> 1 | () msg.sender(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-keyword 1`] = ` +"id-keyword.fc:1:1: Name of the function cannot be a keyword +Line 1, col 1: +> 1 | () global(); + ^~~~~~~~~~~ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-multiline-comments 1`] = ` +"id-multiline-comments.fc:1:11: Parse error: expected not (a whiteSpace or "(" or ")" or "[" or "]" or "," or "." or ";" or "~"), "~_", "_", "\`", or "->" + +Line 1, col 11: +> 1 | () {-aaa-}(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-number 1`] = ` +"id-number.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () 123(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-number-decimal 1`] = ` +"id-number-decimal.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () 0(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-number-hexadecimal 1`] = ` +"id-number-hexadecimal.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () 0x0(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-number-hexadecimal-2 1`] = ` +"id-number-hexadecimal-2.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () 0xDEADBEEF(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-number-neg-decimal 1`] = ` +"id-number-neg-decimal.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () -1(); + ^ + 2 | native idTest(); +" +`; + +exports[`FunC grammar and parser should NOT parse id-number-neg-hexadecimal 1`] = ` +"id-number-neg-hexadecimal.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () -0x0(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-only-underscore 1`] = ` +"id-only-underscore.fc:1:4: Parse error: expected not ((a hole or an integerLiteral or a delimiter or an operator or a primitive) not a rawId) or "->" + +Line 1, col 4: +> 1 | () _(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-parens 1`] = ` +"id-parens.fc:1:15: Parse error: expected "{", ";", or "asm" + +Line 1, col 15: +> 1 | () take(first)Entry(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-semicolons 1`] = ` +"id-semicolons.fc:2:1: Parse error: expected "(" + +Line 2, col 1: + 1 | () pa;;in"\`aaa\`"(); +> 2 | + ^ +" +`; + +exports[`FunC grammar and parser should NOT parse id-space 1`] = ` +"id-space.fc:1:8: Parse error: expected "(" + +Line 1, col 8: +> 1 | () foo foo(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-string 1`] = ` +"id-string.fc:1:4: Parse error: expected not ("\\"" or "{-") or "->" + +Line 1, col 4: +> 1 | () "not_a_string(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-type-keyword 1`] = ` +"id-type-keyword.fc:1:6: Parse error: expected "
", "
", "\\r", "\\n", " ", or "\\t" + +Line 1, col 6: +> 1 | () ->(); + ^ + 2 | +" +`; + +exports[`FunC grammar and parser should NOT parse id-unclosed-parens 1`] = ` +"id-unclosed-parens.fc:1:9: Parse error: expected ")" + +Line 1, col 9: +> 1 | () aa(bb(); + ^ + 2 | +" +`; diff --git a/src/func/grammar-test-failed/id-arith-operator.fc b/src/func/grammar-test-failed/id-arith-operator.fc new file mode 100644 index 000000000..9ab3bcc39 --- /dev/null +++ b/src/func/grammar-test-failed/id-arith-operator.fc @@ -0,0 +1 @@ +() /(); diff --git a/src/func/grammar-test-failed/id-assign-operator.fc b/src/func/grammar-test-failed/id-assign-operator.fc new file mode 100644 index 000000000..e2ae7f1e6 --- /dev/null +++ b/src/func/grammar-test-failed/id-assign-operator.fc @@ -0,0 +1 @@ +() ^>>=(); diff --git a/src/func/grammar-test-failed/id-bitwise-operator.fc b/src/func/grammar-test-failed/id-bitwise-operator.fc new file mode 100644 index 000000000..ea7c369ea --- /dev/null +++ b/src/func/grammar-test-failed/id-bitwise-operator.fc @@ -0,0 +1 @@ +() ~(); diff --git a/src/func/grammar-test-failed/id-comma.fc b/src/func/grammar-test-failed/id-comma.fc new file mode 100644 index 000000000..c66116762 --- /dev/null +++ b/src/func/grammar-test-failed/id-comma.fc @@ -0,0 +1 @@ +() send_message,then_terminate(); diff --git a/src/func/grammar-test-failed/id-comparison-operator.fc b/src/func/grammar-test-failed/id-comparison-operator.fc new file mode 100644 index 000000000..8b1d65d51 --- /dev/null +++ b/src/func/grammar-test-failed/id-comparison-operator.fc @@ -0,0 +1 @@ +() <=>(); diff --git a/src/func/grammar-test-failed/id-control-keyword.fc b/src/func/grammar-test-failed/id-control-keyword.fc new file mode 100644 index 000000000..1f655d470 --- /dev/null +++ b/src/func/grammar-test-failed/id-control-keyword.fc @@ -0,0 +1 @@ +() elseifnot(); diff --git a/src/func/grammar-test-failed/id-delimiter.fc b/src/func/grammar-test-failed/id-delimiter.fc new file mode 100644 index 000000000..238252258 --- /dev/null +++ b/src/func/grammar-test-failed/id-delimiter.fc @@ -0,0 +1 @@ +() [(); diff --git a/src/func/grammar-test-failed/id-directive.fc b/src/func/grammar-test-failed/id-directive.fc new file mode 100644 index 000000000..36ab4d67a --- /dev/null +++ b/src/func/grammar-test-failed/id-directive.fc @@ -0,0 +1 @@ +() #include(); diff --git a/src/func/grammar-test-failed/id-dot.fc b/src/func/grammar-test-failed/id-dot.fc new file mode 100644 index 000000000..c9eeaa3f0 --- /dev/null +++ b/src/func/grammar-test-failed/id-dot.fc @@ -0,0 +1 @@ +() msg.sender(); diff --git a/src/func/grammar-test-failed/id-keyword.fc b/src/func/grammar-test-failed/id-keyword.fc new file mode 100644 index 000000000..29407c70e --- /dev/null +++ b/src/func/grammar-test-failed/id-keyword.fc @@ -0,0 +1 @@ +() global(); diff --git a/src/func/grammar-test-failed/id-multiline-comments.fc b/src/func/grammar-test-failed/id-multiline-comments.fc new file mode 100644 index 000000000..fd5a725ba --- /dev/null +++ b/src/func/grammar-test-failed/id-multiline-comments.fc @@ -0,0 +1 @@ +() {-aaa-}(); diff --git a/src/func/grammar-test-failed/id-number-decimal.fc b/src/func/grammar-test-failed/id-number-decimal.fc new file mode 100644 index 000000000..83d4de354 --- /dev/null +++ b/src/func/grammar-test-failed/id-number-decimal.fc @@ -0,0 +1 @@ +() 0(); diff --git a/src/func/grammar-test-failed/id-number-hexadecimal-2.fc b/src/func/grammar-test-failed/id-number-hexadecimal-2.fc new file mode 100644 index 000000000..84a5b35d3 --- /dev/null +++ b/src/func/grammar-test-failed/id-number-hexadecimal-2.fc @@ -0,0 +1 @@ +() 0xDEADBEEF(); diff --git a/src/func/grammar-test-failed/id-number-hexadecimal.fc b/src/func/grammar-test-failed/id-number-hexadecimal.fc new file mode 100644 index 000000000..c35553ca5 --- /dev/null +++ b/src/func/grammar-test-failed/id-number-hexadecimal.fc @@ -0,0 +1 @@ +() 0x0(); diff --git a/src/func/grammar-test-failed/id-number-neg-decimal.fc b/src/func/grammar-test-failed/id-number-neg-decimal.fc new file mode 100644 index 000000000..373dc4deb --- /dev/null +++ b/src/func/grammar-test-failed/id-number-neg-decimal.fc @@ -0,0 +1,2 @@ +() -1(); +native idTest(); diff --git a/src/func/grammar-test-failed/id-number-neg-hexadecimal.fc b/src/func/grammar-test-failed/id-number-neg-hexadecimal.fc new file mode 100644 index 000000000..e970ae28d --- /dev/null +++ b/src/func/grammar-test-failed/id-number-neg-hexadecimal.fc @@ -0,0 +1 @@ +() -0x0(); diff --git a/src/func/grammar-test-failed/id-number.fc b/src/func/grammar-test-failed/id-number.fc new file mode 100644 index 000000000..7e8936ab5 --- /dev/null +++ b/src/func/grammar-test-failed/id-number.fc @@ -0,0 +1 @@ +() 123(); diff --git a/src/func/grammar-test-failed/id-only-underscore.fc b/src/func/grammar-test-failed/id-only-underscore.fc new file mode 100644 index 000000000..cb6453698 --- /dev/null +++ b/src/func/grammar-test-failed/id-only-underscore.fc @@ -0,0 +1 @@ +() _(); diff --git a/src/func/grammar-test-failed/id-parens.fc b/src/func/grammar-test-failed/id-parens.fc new file mode 100644 index 000000000..7519de180 --- /dev/null +++ b/src/func/grammar-test-failed/id-parens.fc @@ -0,0 +1 @@ +() take(first)Entry(); diff --git a/src/func/grammar-test-failed/id-semicolons.fc b/src/func/grammar-test-failed/id-semicolons.fc new file mode 100644 index 000000000..d4efb416c --- /dev/null +++ b/src/func/grammar-test-failed/id-semicolons.fc @@ -0,0 +1 @@ +() pa;;in"`aaa`"(); diff --git a/src/func/grammar-test-failed/id-space.fc b/src/func/grammar-test-failed/id-space.fc new file mode 100644 index 000000000..500f4d62f --- /dev/null +++ b/src/func/grammar-test-failed/id-space.fc @@ -0,0 +1 @@ +() foo foo(); diff --git a/src/func/grammar-test-failed/id-string.fc b/src/func/grammar-test-failed/id-string.fc new file mode 100644 index 000000000..87323b295 --- /dev/null +++ b/src/func/grammar-test-failed/id-string.fc @@ -0,0 +1 @@ +() "not_a_string(); diff --git a/src/func/grammar-test-failed/id-type-keyword.fc b/src/func/grammar-test-failed/id-type-keyword.fc new file mode 100644 index 000000000..7b1471218 --- /dev/null +++ b/src/func/grammar-test-failed/id-type-keyword.fc @@ -0,0 +1 @@ +() ->(); diff --git a/src/func/grammar-test-failed/id-unclosed-parens.fc b/src/func/grammar-test-failed/id-unclosed-parens.fc new file mode 100644 index 000000000..b63e7a582 --- /dev/null +++ b/src/func/grammar-test-failed/id-unclosed-parens.fc @@ -0,0 +1 @@ +() aa(bb(); diff --git a/src/func/grammar-test/__tact_crc16.fc b/src/func/grammar-test/__tact_crc16.fc new file mode 100644 index 000000000..f52db071c --- /dev/null +++ b/src/func/grammar-test/__tact_crc16.fc @@ -0,0 +1,29 @@ +#include "include_stdlib.fc"; + +(slice) __tact_crc16(slice data) inline_ref { + slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) + .end_cell().begin_parse(); + int reg = 0; + while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } + } + (int q, int r) = divmod(reg, 256); + return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) + .end_cell().begin_parse(); +} diff --git a/src/func/grammar-test/__tact_debug.fc b/src/func/grammar-test/__tact_debug.fc new file mode 100644 index 000000000..3953df093 --- /dev/null +++ b/src/func/grammar-test/__tact_debug.fc @@ -0,0 +1,3 @@ +#include "include_stdlib.fc"; + +forall X -> () __tact_debug(X value, slice debug_print) impure asm "STRDUMP" "DROP" "s0 DUMP" "DROP"; diff --git a/src/func/grammar-test/__tact_load_address.fc b/src/func/grammar-test/__tact_load_address.fc new file mode 100644 index 000000000..caf5354d3 --- /dev/null +++ b/src/func/grammar-test/__tact_load_address.fc @@ -0,0 +1,7 @@ +#include "include_stdlib.fc"; +#include "__tact_verify_address.fc"; + +(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); +} diff --git a/src/func/grammar-test/__tact_load_address_opt.fc b/src/func/grammar-test/__tact_load_address_opt.fc new file mode 100644 index 000000000..6e8b09bb2 --- /dev/null +++ b/src/func/grammar-test/__tact_load_address_opt.fc @@ -0,0 +1,12 @@ +#include "include_stdlib.fc"; +#include "__tact_verify_address.fc"; + +(slice, slice) __tact_load_address_opt(slice cs) inline { + if (cs.preload_uint(2) != 0) { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); + } else { + cs~skip_bits(2); + return (cs, null()); + } +} diff --git a/src/func/grammar-test/__tact_load_bool.fc b/src/func/grammar-test/__tact_load_bool.fc new file mode 100644 index 000000000..5f5e969bf --- /dev/null +++ b/src/func/grammar-test/__tact_load_bool.fc @@ -0,0 +1,3 @@ +#include "include_stdlib.fc"; + +(slice, int) __tact_load_bool(slice s) asm( -> 1 0) "1 LDI"; diff --git a/src/func/grammar-test/__tact_store_address.fc b/src/func/grammar-test/__tact_store_address.fc new file mode 100644 index 000000000..a40511015 --- /dev/null +++ b/src/func/grammar-test/__tact_store_address.fc @@ -0,0 +1,6 @@ +#include "include_stdlib.fc"; +#include "__tact_verify_address.fc"; + +builder __tact_store_address(builder b, slice address) inline { + return b.store_slice(__tact_verify_address(address)); +} diff --git a/src/func/grammar-test/__tact_store_address_opt.fc b/src/func/grammar-test/__tact_store_address_opt.fc new file mode 100644 index 000000000..aa3314a9f --- /dev/null +++ b/src/func/grammar-test/__tact_store_address_opt.fc @@ -0,0 +1,11 @@ +#include "include_stdlib.fc"; +#include "__tact_store_address.fc"; + +builder __tact_store_address_opt(builder b, slice address) inline { + if (null?(address)) { + b = b.store_uint(0, 2); + return b; + } else { + return __tact_store_address(b, address); + } +} diff --git a/src/func/grammar-test/__tact_verify_address.fc b/src/func/grammar-test/__tact_verify_address.fc new file mode 100644 index 000000000..fb2d43c7b --- /dev/null +++ b/src/func/grammar-test/__tact_verify_address.fc @@ -0,0 +1,23 @@ +#include "include_stdlib.fc"; + +const int INVALID_ADDRESS = 136; +const int MC_NOT_ENABLED = 137; + +;; Without masterchain enabled +slice __tact_verify_address(slice address) inline { + throw_unless(INVALID_ADDRESS, address.slice_bits() == 267); + var h = address.preload_uint(11); + throw_if(MC_NOT_ENABLED, h == 1279); + throw_unless(INVALID_ADDRESS, h == 1024); + + return address; +} + +;; With masterchain enabled +slice __tact_verify_address_masterchain(slice address) inline { + throw_unless(INVALID_ADDRESS, address.slice_bits() == 267); + var h = address.preload_uint(11); + throw_unless(INVALID_ADDRESS, (h == 1024) | (h == 1279)); + + return address; +} diff --git a/src/func/grammar-test/asm-functions.fc b/src/func/grammar-test/asm-functions.fc new file mode 100644 index 000000000..49064db9a --- /dev/null +++ b/src/func/grammar-test/asm-functions.fc @@ -0,0 +1,43 @@ +;; Single assembly string +int inc_then_negate(int x) asm "INC NEGATE"; + +;; Multiple assembly strings +int inc_then_negate'(int x) asm "INC" "NEGATE"; + +;; Multi-line strings + +int inc_then_negate''(int x) asm """INC NEGATE"""; + +slice hello_world() asm """ + "Hello" + " " + "World" + $+ $+ $>s + PUSHSLICE +"""; + +;; Mix single-line and multi-line strings + +int inc_then_negate'''(int x) asm "INC" """ + NEGATE +"""; + +;; Arrangement of return values (and whitespaces check) + (builder, int) +store_uint_quiet (builder b, int x, int len) +asm (x b len) +"STUXQ"; + +;; Arrangement of arguments +(int, builder) store_uint_quiet'(int x, builder b, int len) + asm( -> 1 0) "STUXQ"; + +;; Arrangement of arguments mapped to return values +(int, builder) store_uint_quiet''(builder b, int x, int len) + asm(x b len -> 1 0) "STUXQ"; + +;; Parametric polymorphism with forall + +forall X, Y -> (X, Y) store_uint_quiet'''(builder b, int x, int len) asm(x b len -> 1 0) "STUXQ"; + +forall X -> X unsingle([X] t) asm "UNSINGLE"; diff --git a/src/func/grammar-test/constants.fc b/src/func/grammar-test/constants.fc new file mode 100644 index 000000000..a6fa81da2 --- /dev/null +++ b/src/func/grammar-test/constants.fc @@ -0,0 +1,8 @@ +;; Inferred type +const c1 = 42; + +;; Inferred type for multiple ones with using prior constants +const c2 = 27, c3 = c1; + +;; int, inferred int or slice from a string +const int c4 = 0, c5 = 0, slice c6 = "It's Zendaya"s; diff --git a/src/func/grammar-test/dns-auto-code.fc b/src/func/grammar-test/dns-auto-code.fc new file mode 100644 index 000000000..949f15716 --- /dev/null +++ b/src/func/grammar-test/dns-auto-code.fc @@ -0,0 +1,536 @@ +{- + Adapted from original version written by: + /------------------------------------------------------------------------\ + | Created for: Telegram (Open Network) Blockchain Contest | + | Task 2: DNS Resolver (Automatically registering) | + >------------------------------------------------------------------------< + | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) | + | October 2019 | + \------------------------------------------------------------------------/ + Updated to actual DNS standard version by starlightduck in 2022 +-} + +;;===========================================================================;; +;; Custom ASM instructions ;; +;;===========================================================================;; + +cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF"; + +;;===========================================================================;; +;; Utility functions ;; +;;===========================================================================;; + +{- + Data structure: + Root cell: [OptRef<1b+1r?>:HashmapUInt<32b>,CatTable>:domains] + [OptRef<1b+1r?>:Hashmap(Time|Hash128)->Slice(DomName)>:gc] + [UInt<32b>:stdperiod] [Gram:PPReg] [Gram:PPCell] [Gram:PPBit] + [UInt<32b>:lasthousekeeping] + := HashmapE 256 (~~16~~) ^DNSRecord + + STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value) + #Zeros allows to simultaneously store, for example, com\0 and com\0google\0 + That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons) + This will allow to resolve more specific requests to subdomains, and resort + to parent domain next resolver lookup if subdomain is not found + com\0goo\0 lookup will, for example look up \2com\0goo\0 and then + \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat +-} + +(cell, cell, cell, [int, int, int, int], int, int) load_data() inline_ref { + slice cs = get_data().begin_parse(); + return ( + cs~load_ref(), ;; control data + cs~load_dict(), ;; pfx tree: domains data and exp + cs~load_dict(), ;; gc auxillary with expiration and 128-bit hash slice + [ cs~load_uint(30), ;; length of this period of time in seconds + cs~load_grams(), ;; standard payment for registering a new subdomain + cs~load_grams(), ;; price paid for each cell (PPC) + cs~load_grams() ], ;; and bit (PPB) + cs~load_uint(32), ;; next housekeeping to be done at + cs~load_uint(32) ;; last housekeeping done at + ); +} + +(int, int, int, int) load_prices() inline_ref { + slice cs = get_data().begin_parse(); + (cs~load_ref(), cs~load_dict(), cs~load_dict()); + return (cs~load_uint(30), cs~load_grams(), cs~load_grams(), cs~load_grams()); +} + +() store_data(cell ctl, cell dd, cell gc, prices, int nhk, int lhk) impure { + var [sp, ppr, ppc, ppb] = prices; + set_data(begin_cell() + .store_ref(ctl) ;; control data + .store_dict(dd) ;; domains data and exp + .store_dict(gc) ;; keyed expiration time and 128-bit hash slice + .store_uint(sp, 30) ;; standard period + .store_grams(ppr) ;; price per registration + .store_grams(ppc) ;; price per cell + .store_grams(ppb) ;; price per bit + .store_uint(nhk, 32) ;; next housekeeping + .store_uint(lhk, 32) ;; last housekeeping + .end_cell()); +} + +global var query_info; + +() send_message(slice addr, int tag, int query_id, + int body, int grams, int mode) impure { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + ;; src:MsgAddress -> 011000 0x18 + var msg = begin_cell() + .store_uint (0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint (0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint (tag, 32) + .store_uint (query_id, 64); + if (body >= 0) { + msg~store_uint(body, 32); + } + send_raw_message(msg.end_cell(), mode); +} + +() send_error(int error_code) impure { + var (addr, query_id, op) = query_info; + return send_message(addr, error_code, query_id, op, 0, 64); +} + +() send_ok(int price) impure { + raw_reserve(price, 4); + var (addr, query_id, op) = query_info; + return send_message(addr, 0xef6b6179, query_id, op, 0, 128); +} + +() housekeeping(cell ctl, cell dd, cell gc, prices, int nhk, int lhk, int max_steps) impure { + int n = now(); + if (n < max(nhk, lhk + 60)) { ;; housekeeping cooldown: 1 minute + ;; if housekeeping was done recently, or if next housekeeping is in the future, just save + return store_data(ctl, dd, gc, prices, nhk, lhk); + } + ;; need to do some housekeeping - maybe remove entry with + ;; least expiration but only if it is already expired + ;; no iterating and deleting all to not put too much gas gc + ;; burden on any random specific user request + ;; over time it will do the garbage collection required + (int mkey, _, int found?) = gc.udict_get_min?(256); + while (found? & max_steps) { ;; no short circuit optimization, two nested ifs + nhk = (mkey >> (256 - 32)); + if (nhk < n) { + int key = mkey % (1 << (256 - 32)); + (slice val, found?) = dd.udict_get?(256 - 32, key); + if (found?) { + int exp = val.preload_uint(32); + if (exp <= n) { + dd~udict_delete?(256 - 32, key); + } + } + gc~udict_delete?(256, mkey); + (mkey, _, found?) = gc.udict_get_min?(256); + nhk = (found? ? mkey >> (256 - 32) : 0xffffffff); + max_steps -= 1; + } else { + found? = false; + } + } + store_data(ctl, dd, gc, prices, nhk, n); +} + +int calcprice_internal(slice domain, cell data, ppc, ppb) inline_ref { ;; only for internal calcs + var (_, bits, refs) = compute_data_size(data, 100); ;; 100 cells max + bits += slice_bits(domain) * 2 + (128 + 32 + 32); + return ppc * (refs + 2) + ppb * bits; +} + +int check_owner(cell cat_table, cell owner_info, int src_wc, int src_addr, int strict) inline_ref { + if (strict & cat_table.null?()) { ;; domain not found: return notf | 2^31 + return 0xee6f7466; + } + if (owner_info.null?()) { ;; no owner on this domain: no-2 (in strict mode), ok else + return strict & 0xee6f2d32; + } + var ERR_BAD2 = 0xe2616432; + slice sown = owner_info.begin_parse(); + if (sown.slice_bits() < 16 + 3 + 8 + 256) { ;; bad owner record: bad2 + return ERR_BAD2; + } + if (sown~load_uint(16 + 3) != 0x9fd3 * 8 + 4) { + return ERR_BAD2; + } + (int owner_wc, int owner_addr) = (sown~load_int(8), sown.preload_uint(256)); + if ((owner_wc != src_wc) | (owner_addr != src_addr)) { ;; not owner: nown + return 0xee6f776e; + } + return 0; ;; ok +} + +;;===========================================================================;; +;; Internal message handler (Code 0) ;; +;;===========================================================================;; + +{- + Internal message cell structure: + 8 4 2 1 + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddressInt dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 + Internal message data structure: + [UInt<32b>:op] [UInt<64b>:query_id] [Ref<1r>:domain] + (if not prolong: [Ref<1r>:value->CatTable]) + +-} + +;; Control operations: permitted only to the owner of this smartcontract +() perform_ctl_op(int op, int src_wc, int src_addr, slice in_msg) impure inline_ref { + var (ctl, domdata, gc, prices, nhk, lhk) = load_data(); + var cs = ctl.begin_parse(); + if ((cs~load_int(8) != src_wc) | (cs~load_uint(256) != src_addr)) { + return send_error(0xee6f776e); + } + if (op == 0x43685072) { ;; ChPr = Change Prices + var (stdper, ppr, ppc, ppb) = (in_msg~load_uint(32), in_msg~load_grams(), in_msg~load_grams(), in_msg~load_grams()); + in_msg.end_parse(); + ;; NB: stdper == 0 -> disable new actions + store_data(ctl, domdata, gc, [stdper, ppr, ppc, ppb], nhk, lhk); + return send_ok(0); + } + var (addr, query_id, op) = query_info; + if (op == 0x4344656c) { ;; CDel = destroy smart contract + ifnot (domdata.null?()) { + ;; domain dictionary not empty, force gc + housekeeping(ctl, domdata, gc, prices, nhk, 1, -1); + } + (ctl, domdata, gc, prices, nhk, lhk) = load_data(); + ifnot (domdata.null?()) { + ;; domain dictionary still not empty, error + return send_error(0xee74656d); + } + return send_message(addr, 0xef6b6179, query_id, op, 0, 128 + 32); + } + if (op == 0x54616b65) { ;; Take = take grams from the contract + var amount = in_msg~load_grams(); + return send_message(addr, 0xef6b6179, query_id, op, amount, 64); + } + return send_error(0xffffffff); +} + +;; Must send at least GR$1 more for possible gas fees! +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; this time very interested in internal messages + if (in_msg.slice_bits() < 32) { + return (); ;; simple transfer or short + } + slice cs = in_msg_cell.begin_parse(); + int flags = cs~load_uint(4); + if (flags & 1) { + return (); ;; bounced messages + } + slice s_addr = cs~load_msg_addr(); + (int src_wc, int src_addr) = s_addr.parse_std_addr(); + int op = in_msg~load_uint(32); + ifnot (op) { + return (); ;; simple transfer with comment + } + int query_id = 0; + if (in_msg.slice_bits() >= 64) { + query_id = in_msg~load_uint(64); + } + + query_info = (s_addr, query_id, op); + + if (op & (1 << 31)) { + return (); ;; an answer to our query + } + if ((op >> 24) == 0x43) { + ;; Control operations + return perform_ctl_op(op, src_wc, src_addr, in_msg); + } + + int qt = (op == 0x72656764) * 1 + (op == 0x70726f6c) * 2 + (op == 0x75706464) * 4 + (op == 0x676f6763) * 8; + ifnot (qt) { ;; unknown query, return error + return send_error(0xffffffff); + } + qt = - qt; + + (cell ctl, cell domdata, cell gc, [int, int, int, int] prices, int nhk, int lhk) = load_data(); + + if (qt == 8) { ;; 0x676f6763 -> GO, GC! go!!! + ;; Manual garbage collection iteration + int max_steps = in_msg~load_int(32); ;; -1 = infty + housekeeping(ctl, domdata, gc, prices, nhk, 1, max_steps); ;; forced + return send_error(0xef6b6179); + } + + slice domain = null(); + cell domain_cell = in_msg~load_maybe_ref(); + int fail = 0; + if (domain_cell.null?()) { + int bytes = in_msg~load_uint(6); + fail = (bytes == 0); + domain = in_msg~load_bits(bytes * 8); + } else { + domain = domain_cell.begin_parse(); + var (bits, refs) = slice_bits_refs(domain); + fail = (refs | ((bits - 8) & (7 - 128))); + } + + ifnot (fail) { + ;; domain must end with \0! no\0 error + fail = domain.slice_last(8).preload_uint(8); + } + if (fail) { + return send_error(0xee6f5c30); + } + + int n = now(); + cell cat_table = cell owner_info = null(); + int key = int exp = int zeros = 0; + slice tail = domain; + repeat (tail.slice_bits() ^>> 3) { + cat_table = null(); + int z = (tail~load_uint(8) == 0); + zeros -= z; + if (z) { + key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32); + var (val, found?) = domdata.udict_get?(256 - 32, key); + if (found?) { + exp = val~load_uint(32); + if (exp >= n) { ;; entry not expired + cell cat_table = val~load_ref(); + val.end_parse(); + ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2 + var (cown, ok) = cat_table.udict_get_ref?(256, 0); + if (ok) { + owner_info = cown; + } + } + } + } + } + + if (zeros > 4) { ;; too much zero chars (overflow): ov\0 + return send_error(0xef765c30); + } + + ;; ########################################################################## + + int err = check_owner(cat_table, owner_info, src_wc, src_addr, qt != 1); + if (err) { + return send_error(err); + } + + ;; ########################################################################## + + ;; load desired data (reuse old for a "prolong" operation) + cell data = null(); + + if (qt != 2) { ;; not a "prolong", load data dictionary + data = in_msg~load_ref(); + ;; basic integrity check of (client-provided) dictionary + ifnot (data.dict_empty?()) { ;; 1000 gas! + ;; update: category length now u256 instead of i16, owner index is now 0 instead of -2 + var (oinfo, ok) = data.udict_get_ref?(256, 0); + if (ok) { + var cs = oinfo.begin_parse(); + throw_unless(31, cs.slice_bits() >= 16 + 3 + 8 + 256); + throw_unless(31, cs.preload_uint(19) == 0x9fd3 * 8 + 4); + } + (_, _, int minok) = data.udict_get_min?(256); ;; update: category length now u256 instead of i16 + (_, _, int maxok) = data.udict_get_max?(256); ;; update: category length now u256 instead of i16 + throw_unless(31, minok & maxok); + } + } else { + data = cat_table; + } + + ;; load prices + var [stdper, ppr, ppc, ppb] = prices; + ifnot (stdper) { ;; smart contract disabled by owner, no new actions + return send_error(0xd34f4646); + } + + ;; compute action price + int price = calcprice_internal(domain, data, ppc, ppb) + (ppr & (qt != 4)); + if (msg_value - (1 << 30) < price) { ;; gr prol | prolong domain + if (exp > n + stdper) { ;; does not expire soon, cannot prolong + return send_error(0xf365726f); + } + domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp + stdper, 32).store_ref(data)); + + int gckeyO = (exp << (256 - 32)) + key; + int gckeyN = gckeyO + (stdper << (256 - 32)); + gc~udict_delete?(256, gckeyO); ;; delete old gc entry, add new + gc~udict_set_builder(256, gckeyN, begin_cell()); + + housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1); + return send_ok(price); + } + + ;; ########################################################################## + if (qt == 1) { ;; 0x72656764 -> regd | register domain + ifnot (cat_table.null?()) { ;; domain already exists: return alre | 2^31 + return send_error(0xe16c7265); + } + int expires_at = n + stdper; + domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(expires_at, 32).store_ref(data)); + + int gckey = (expires_at << (256 - 32)) | key; + gc~udict_set_builder(256, gckey, begin_cell()); + + housekeeping(ctl, domdata, gc, prices, min(nhk, expires_at), lhk, 1); + return send_ok(price); + } + + ;; ########################################################################## + if (qt == 4) { ;; 0x75706464 -> updd | update domain (data) + domdata~udict_set_builder(256 - 32, key, begin_cell().store_uint(exp, 32).store_ref(data)); + housekeeping(ctl, domdata, gc, prices, nhk, lhk, 1); + return send_ok(price); + } + ;; ########################################################################## + + return (); ;; should NEVER reach this part of code! +} + +;;===========================================================================;; +;; External message handler (Code -1) ;; +;;===========================================================================;; + +() recv_external(slice in_msg) impure { + ;; only for initialization + (cell ctl, cell dd, cell gc, var prices, int nhk, int lhk) = load_data(); + ifnot (lhk) { + accept_message(); + return store_data(ctl, dd, gc, prices, 0xffffffff, now()); + } +} + +;;===========================================================================;; +;; Getter methods ;; +;;===========================================================================;; + +(int, cell, int, slice) dnsdictlookup(slice domain, int nowtime) inline_ref { + (int bits, int refs) = domain.slice_bits_refs(); + throw_if(30, refs | (bits & 7)); ;; malformed input (~ 8n-bit) + ifnot (bits) { + ;; return (0, null(), 0, null()); ;; zero-length input + throw(30); ;; update: throw exception for empty input + } + + int domain_last_byte = domain.slice_last(8).preload_uint(8); + if (domain_last_byte) { + domain = begin_cell().store_slice(domain) ;; append zero byte + .store_uint(0, 8).end_cell().begin_parse(); + bits += 8; + } + if (bits == 8) { + return (0, null(), 8, null()); ;; zero-length input, but with zero byte + ;; update: return 8 as resolved, but with no data + } + int domain_first_byte = domain.preload_uint(8); + if (domain_first_byte == 0) { + ;; update: remove prefix \0 + domain~load_uint(8); + bits -= 8; + } + var ds = get_data().begin_parse(); + (_, cell root) = (ds~load_ref(), ds~load_dict()); + + slice val = null(); + int tail_bits = -1; + slice tail = domain; + + repeat (bits >> 3) { + if (tail~load_uint(8) == 0) { + var key = (string_hash(domain.skip_last_bits(tail.slice_bits())) >> 32); + var (v, found?) = root.udict_get?(256 - 32, key); + if (found?) { + if (v.preload_uint(32) >= nowtime) { ;; entry not expired + val = v; + tail_bits = tail.slice_bits(); + } + } + } + } + + if (val.null?()) { + return (0, null(), 0, null()); ;; failed to find entry in subdomain dictionary + } + + return (val~load_uint(32), val~load_ref(), tail_bits == 0, domain.skip_last_bits(tail_bits)); +} + +;;8m dns-record-value +(int, cell) dnsresolve(slice domain, int category) method_id { + (int exp, cell cat_table, int exact?, slice pfx) = dnsdictlookup(domain, now()); + ifnot (exp) { + return (exact?, null()); ;; update: reuse exact? to return 8 for \0 + } + ifnot (exact?) { ;; incomplete subdomain found, must return next resolver (-1) + category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff + ;; update: next resolver is now sha256("dns_next_resolver") instead of -1 + } + + int pfx_bits = pfx.slice_bits(); + + ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain + ;; COUNTING the zero byte (if structurally correct: no multiple-ZB keys) + ;; which corresponds to 8m, m=one plus the number of bytes in the subdomain found) + ifnot (category) { + return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0 + } else { + cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16 + return (pfx_bits, cat_found); + } +} + +;; getexpiration needs to know the current time to skip any possible expired +;; subdomains in the chain. it will return 0 if not found or expired. +int getexpirationx(slice domain, int nowtime) inline method_id { + (int exp, _, _, _) = dnsdictlookup(domain, nowtime); + return exp; +} + +int getexpiration(slice domain) method_id { + return getexpirationx(domain, now()); +} + +int getstdperiod() method_id { + (int stdper, _, _, _) = load_prices(); + return stdper; +} + +int getppr() method_id { + (_, int ppr, _, _) = load_prices(); + return ppr; +} + +int getppc() method_id { + (_, _, int ppc, _) = load_prices(); + return ppc; +} + +int getppb() method_id { + ( _, _, _, int ppb) = load_prices(); + return ppb; +} + +int calcprice(slice domain, cell val) method_id { ;; only for external gets (not efficient) + (_, _, int ppc, int ppb) = load_prices(); + return calcprice_internal(domain, val, ppc, ppb); +} + +int calcregprice(slice domain, cell val) method_id { ;; only for external gets (not efficient) + (_, int ppr, int ppc, int ppb) = load_prices(); + return ppr + calcprice_internal(domain, val, ppc, ppb); +} diff --git a/src/func/grammar-test/dns-manual-code.fc b/src/func/grammar-test/dns-manual-code.fc new file mode 100644 index 000000000..555e5952d --- /dev/null +++ b/src/func/grammar-test/dns-manual-code.fc @@ -0,0 +1,361 @@ +{- + Originally created by: + /------------------------------------------------------------------------\ + | Created for: Telegram (Open Network) Blockchain Contest | + | Task 3: DNS Resolver (Manually controlled) | + >------------------------------------------------------------------------< + | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) | + | October 2019 | + \------------------------------------------------------------------------/ + Updated to actual DNS standard version by starlightduck in 2022 +-} + +;;===========================================================================;; +;; Custom ASM instructions ;; +;;===========================================================================;; + +cell udict_get_ref_(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETOPTREF"; + +(cell, ()) pfxdict_set_ref(cell dict, int key_len, slice key, cell value) { + throw_unless(33, dict~pfxdict_set?(key_len, key, begin_cell().store_maybe_ref(value).end_cell().begin_parse())); + return (dict, ()); +} + +(slice, cell, slice, int) pfxdict_get_ref(cell dict, int key_len, slice key) inline_ref { + (slice pfx, slice val, slice tail, int succ) = dict.pfxdict_get?(key_len, key); + cell res = succ ? val~load_maybe_ref() : null(); + return (pfx, res, tail, succ); +} + +;;===========================================================================;; +;; Utility functions ;; +;;===========================================================================;; + +(int, int, int, cell, cell) load_data() inline_ref { + slice cs = get_data().begin_parse(); + var res = (cs~load_uint(32), cs~load_uint(64), cs~load_uint(256), cs~load_dict(), cs~load_dict()); + cs.end_parse(); + return res; +} + +() store_data(int contract_id, int last_cleaned, int public_key, cell root, old_queries) impure { + set_data(begin_cell() + .store_uint(contract_id, 32) + .store_uint(last_cleaned, 64) + .store_uint(public_key, 256) + .store_dict(root) + .store_dict(old_queries) + .end_cell()); +} + +;;===========================================================================;; +;; Internal message handler (Code 0) ;; +;;===========================================================================;; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; not interested at all +} + +;;===========================================================================;; +;; External message handler (Code -1) ;; +;;===========================================================================;; + +{- + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 11 VSet: set specified value to specified subdomain->category (x=2) + [UInt<256b>:category] [Name:subdomain] [Cell<1r>:value] + 12 VDel: delete specified subdomain->category (x=2) + [UInt<256b>:category] [Name:subdomain] + 21 DSet: replace entire category dictionary of domain with provided (x=0) + [Name:subdomain] [Cell<1r>:new_cat_table] + 22 DDel: delete entire category dictionary of specified domain (x=0) + [Name:subdomain] + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +-} + +() after_code_upgrade(cell root, slice ops, cont old_code) impure method_id(1666); + +(cell, slice) process_op(cell root, slice ops) inline_ref { + int op = ops~load_uint(6); + if (op < 10) { + ifnot (op) { + ;; 00 Noop: No operation + return (root, ops); + } + if (op == 1) { + ;; 01 SMsg: Send Message + var mode = ops~load_uint(8); + send_raw_message(ops~load_ref(), mode); + return (root, ops); + } + if (op == 9) { + ;; 09 CodeUpgrade + var new_code = ops~load_ref(); + set_code(new_code); + var old_code = get_c3(); + set_c3(new_code.begin_parse().bless()); + after_code_upgrade(root, ops, old_code); + throw(0); + return (root, ops); + } + throw(45); + return (root, ops); + } + int cat = 0; + if (op < 20) { + ;; for operations with codes 10..19 category is required + cat = ops~load_uint(256); ;; update: category length now u256 instead of i16 + } + slice name = null(); ;; any slice value + cell cat_table = null(); + if (op < 30) { + ;; for operations with codes 10..29 name is required + int is_name_ref = (ops~load_uint(1) == 1); + if (is_name_ref) { + ;; name is stored in separate referenced cell + name = ops~load_ref().begin_parse(); + } else { + ;; name is stored inline + int name_len = ops~load_uint(6) * 8; + name = ops~load_bits(name_len); + } + ;; at least one character not counting \0 + throw_unless(38, name.slice_bits() >= 16); + ;; name shall end with \0 + int name_last_byte = name.slice_last(8).preload_uint(8); + throw_if(40, name_last_byte); + ;; count zero separators + int zeros = 0; + slice cname = name; + repeat (cname.slice_bits() ^>> 3) { + int c = cname~load_uint(8); + zeros -= (c == 0); + } + ;; throw_unless(39, zeros == 1); + name = begin_cell().store_uint(zeros, 7).store_slice(name).end_cell().begin_parse(); + } + ;; operation with codes 10..19 manipulate category dict + ;; lets try to find it and store into a variable + ;; operations with codes 20..29 replace / delete dict, no need + if (op < 20) { + ;; lets resolve the name here so as not to duplicate the code + (slice pfx, cell val, slice tail, int succ) = + root.pfxdict_get_ref(1023, name); + if (succ) { + ;; must match EXACTLY to prevent accident changes + throw_unless(35, tail.slice_empty?()); + cat_table = val; + } + ;; otherwise cat_table is null which is reasonable for actions + } + ;; 11 VSet: set specified value to specified subdomain->category + if (op == 11) { + cell new_value = ops~load_maybe_ref(); + cat_table~udict_set_get_ref(256, cat, new_value); ;; update: category length now u256 instead of i16 + root~pfxdict_set_ref(1023, name, cat_table); + return (root, ops); + } + ;; 12 VDel: delete specified subdomain->category value + if (op == 12) { + if (cat_table~udict_delete?(256, cat)) { ;; update: category length now u256 instead of i16 + root~pfxdict_set_ref(1023, name, cat_table); + } + return (root, ops); + } + ;; 21 DSet: replace entire category dictionary of domain with provided + if (op == 21) { + cell new_cat_table = ops~load_maybe_ref(); + root~pfxdict_set_ref(1023, name, new_cat_table); + return (root, ops); + } + ;; 22 DDel: delete entire category dictionary of specified domain + if (op == 22) { + root~pfxdict_delete?(1023, name); + return (root, ops); + } + ;; 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell + if (op == 31) { + cell new_tree_root = ops~load_maybe_ref(); + ;; no sanity checks cause they would cost immense gas + return (new_tree_root, ops); + } + ;; 32 TDel: nullify ENTIRE DOMAIN TABLE + if (op == 32) { + return (null(), ops); + } + throw(44); ;; invalid operation + return (null(), ops); +} + +cell process_ops(cell root, slice ops) inline_ref { + var stop = false; + root~touch(); + ops~touch(); + do { + (root, ops) = process_op(root, ops); + if (ops.slice_data_empty?()) { + if (ops.slice_refs()) { + ops = ops~load_ref().begin_parse(); + } else { + stop = true; + } + } + } until (stop); + return root; +} + +() recv_external(slice in_msg) impure { + ;; Load data + (int contract_id, int last_cleaned, int public_key, cell root, cell old_queries) = load_data(); + + ;; validate signature and seqno + slice signature = in_msg~load_bits(512); + int shash = slice_hash(in_msg); + var (query_contract, query_id) = (in_msg~load_uint(32), in_msg~load_uint(64)); + var bound = (now() << 32); + throw_if(35, query_id < bound); + (_, var found?) = old_queries.udict_get?(64, query_id); + throw_if(32, found?); + throw_unless(34, contract_id == query_contract); + throw_unless(35, check_signature(shash, signature, public_key)); + accept_message(); ;; message is signed by owner, sanity not guaranteed yet + + int op = in_msg.preload_uint(6); + if (op == 51) { + in_msg~skip_bits(6); + public_key = in_msg~load_uint(256); + } else { + root = process_ops(root, in_msg); + } + + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + old_queries~udict_set_builder(64, query_id, begin_cell()); + var queries = old_queries; + do { + var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + old_queries = old_queries'; + last_cleaned = i; + } + } until (~ f); + + store_data(contract_id, last_cleaned, public_key, root, old_queries); +} + +() after_code_upgrade(cell root, slice ops, cont old_code) impure method_id(1666) { +} + +{- + Data structure: + Root cell: [UInt<32b>:seqno] [UInt<256b>:owner_public_key] + [OptRef<1b+1r?>:HashmapCatTable>:domains] + := HashmapE 256 (~~16~~) ^DNSRecord + + STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value) + #Zeros allows to simultaneously store, for example, com\0 and com\0google\0 + That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons) + This will allow to resolve more specific requests to subdomains, and resort + to parent domain next resolver lookup if subdomain is not found + com\0goo\0 lookup will, for example look up \2com\0goo\0 and then + \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat +-} + +;;===========================================================================;; +;; Getter methods ;; +;;===========================================================================;; + +;; Retrieve contract id (in case several contracts are managed with the same private key) +int get_contract_id() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32 + 64); + return cs.preload_uint(256); +} + +;;8m dns-record-value +(int, cell) dnsresolve(slice subdomain, int category) method_id { + int bits = subdomain.slice_bits(); + ifnot (bits) { + ;; return (0, null()); ;; zero-length input + throw(30); ;; update: throw exception for empty input + } + throw_if(30, bits & 7); ;; malformed input (~ 8n-bit) + + int name_last_byte = subdomain.slice_last(8).preload_uint(8); + if (name_last_byte) { + subdomain = begin_cell().store_slice(subdomain) ;; append zero byte + .store_uint(0, 8).end_cell().begin_parse(); + bits += 8; + } + if (bits == 8) { + return (8, null()); ;; zero-length input, but with zero byte + ;; update: return 8 as resolved, but with no data + } + int name_first_byte = subdomain.preload_uint(8); + if (name_first_byte == 0) { + ;; update: remove prefix \0 + subdomain~load_uint(8); + bits -= 8; + } + (_, _, _, cell root, _) = load_data(); + + slice cname = subdomain; + int zeros = 0; + repeat (bits >> 3) { + int c = cname~load_uint(8); + zeros -= (c == 0); + } + + ;; can't move these declarations lower, will cause errors! + slice pfx = cname; + cell val = null(); + slice tail = cname; + + do { + slice pfxname = begin_cell().store_uint(zeros, 7) + .store_slice(subdomain).end_cell().begin_parse(); + (pfx, val, tail, int succ) = root.pfxdict_get_ref(1023, pfxname); + zeros = succ ^ (zeros - 1); ;; break on success + } until (zeros <= 0); + + ifnot (zeros) { + return (0, null()); ;; failed to find entry in prefix dictionary + } + + zeros = - zeros; + + ifnot (tail.slice_empty?()) { ;; if we have tail then len(pfx) < len(subdomain) + ;; incomplete subdomain found, must return next resolver + category = "dns_next_resolver"H; ;; 0x19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff + ;; update: next resolver is now sha256("dns_next_resolver") instead of -1 + } + int pfx_bits = pfx.slice_bits() - 7; + cell cat_table = val; + ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain + ;; COUNTING the zero byte (if structurally correct: no multiple-ZB keys) + ;; which corresponds to 8m, m=one plus the number of bytes in the subdomain found) + if (category == 0) { + return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0 + } else { + cell cat_found = cat_table.udict_get_ref_(256, category); ;; update: category length now u256 instead of i16 + return (pfx_bits, cat_found); + } +} diff --git a/src/func/grammar-test/elector-code.fc b/src/func/grammar-test/elector-code.fc new file mode 100644 index 000000000..97185fc21 --- /dev/null +++ b/src/func/grammar-test/elector-code.fc @@ -0,0 +1,1187 @@ +;; Elector smartcontract + +;; cur_elect credits past_elections grams active_id active_hash +(cell, cell, cell, int, int, int) load_data() inline_ref { + var cs = get_data().begin_parse(); + var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256)); + cs.end_parse(); + return res; +} + +;; cur_elect credits past_elections grams active_id active_hash +() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref { + set_data(begin_cell() + .store_dict(elect) + .store_dict(credits) + .store_dict(past_elections) + .store_grams(grams) + .store_uint(active_id, 32) + .store_uint(active_hash, 256) + .end_cell()); +} + +;; elect -> elect_at elect_close min_stake total_stake members failed finished +_ unpack_elect(elect) inline_ref { + var es = elect.begin_parse(); + var res = (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1)); + es.end_parse(); + return res; +} + +cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref { + return begin_cell() + .store_uint(elect_at, 32) + .store_uint(elect_close, 32) + .store_grams(min_stake) + .store_grams(total_stake) + .store_dict(members) + .store_int(failed, 1) + .store_int(finished, 1) + .end_cell(); +} + +;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints +_ unpack_past_election(slice fs) inline_ref { + var res = (fs~load_uint(32), fs~load_uint(32), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict()); + fs.end_parse(); + return res; +} + +builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref { + return begin_cell() + .store_uint(unfreeze_at, 32) + .store_uint(stake_held, 32) + .store_uint(vset_hash, 256) + .store_dict(frozen_dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_dict(complaints); +} + +;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) +;; vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus; +_ unpack_complaint_status(slice cs) inline_ref { + throw_unless(9, cs~load_uint(8) == 0x2d); + var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64)); + cs.end_parse(); + return res; +} + +builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref { + return begin_cell() + .store_uint(0x2d, 8) + .store_ref(complaint) + .store_dict(voters) + .store_uint(vset_id, 256) + .store_int(weight_remaining, 64); +} + +;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr +;; created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams +;; suggested_fine_part:uint32 = ValidatorComplaint; +_ unpack_complaint(slice cs) inline_ref { + throw_unless(9, cs~load_int(8) == 0xbc - 0x100); + var res = (cs~load_uint(256), cs~load_ref(), cs~load_uint(32), cs~load_uint(8), cs~load_uint(256), cs~load_grams(), cs~load_grams(), cs~load_uint(32)); + cs.end_parse(); + return res; +} + +builder pack_complaint(int validator_pubkey, cell description, int created_at, int severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref { + return begin_cell() + .store_int(0xbc - 0x100, 8) + .store_uint(validator_pubkey, 256) + .store_ref(description) + .store_uint(created_at, 32) + .store_uint(severity, 8) + .store_uint(reward_addr, 256) + .store_grams(paid) + .store_grams(suggested_fine) + .store_uint(suggested_fine_part, 32); +} + +;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing; +(int, int, int) parse_complaint_prices(cell info) inline { + var cs = info.begin_parse(); + throw_unless(9, cs~load_uint(8) == 0x1a); + var res = (cs~load_grams(), cs~load_grams(), cs~load_grams()); + cs.end_parse(); + return res; +} + +;; deposit bit_price cell_price +(int, int, int) get_complaint_prices() inline_ref { + var info = config_param(13); + return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices(); +} + +;; elected_for elections_begin_before elections_end_before stake_held_for +(int, int, int, int) get_validator_conf() { + var cs = config_param(15).begin_parse(); + return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32)); +} + +;; next three functions return information about current validator set (config param #34) +;; they are borrowed from config-code.fc +(cell, int, cell) get_current_vset() inline_ref { + var vset = config_param(34); + var cs = begin_parse(vset); + ;; validators_ext#12 utime_since:uint32 utime_until:uint32 + ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } + ;; total_weight:uint64 + throw_unless(40, cs~load_uint(8) == 0x12); + cs~skip_bits(32 + 32 + 16 + 16); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); +} + +(slice, int) get_validator_descr(int idx) inline_ref { + var (vset, total_weight, dict) = get_current_vset(); + var (value, _) = dict.udict_get?(16, idx); + return (value, total_weight); +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_grams(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64); + if (body >= 0) { + msg~store_uint(body, 32); + } + send_raw_message(msg.end_cell(), mode); +} + +() return_stake(addr, query_id, reason) impure inline_ref { + return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64); +} + +() send_confirmation(addr, query_id, comment) impure inline_ref { + return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2); +} + +() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref { + var msg = begin_cell() + .store_uint(0xc4ff, 17) ;; 0 11000100 0xff + .store_uint(config_addr, 256) + .store_grams(1 << 30) ;; ~1 gram of value to process and obtain answer + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x4e565354, 32) + .store_uint(query_id, 64) + .store_ref(vset); + send_raw_message(msg.end_cell(), 1); +} + +;; credits 'amount' to 'addr' inside credit dictionary 'credits' +_ ~credit_to(credits, addr, amount) inline_ref { + var (val, f) = credits.udict_get?(256, addr); + if (f) { + amount += val~load_grams(); + } + credits~udict_set_builder(256, addr, begin_cell().store_grams(amount)); + return (credits, ()); +} + +() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref { + var (src_wc, src_addr) = parse_std_addr(s_addr); + var ds = get_data().begin_parse(); + var elect = ds~load_dict(); + if (elect.null?() | (src_wc + 1)) { + ;; no elections active, or source is not in masterchain + ;; bounce message + return return_stake(s_addr, query_id, 0); + } + ;; parse the remainder of new stake message + var validator_pubkey = cs~load_uint(256); + var stake_at = cs~load_uint(32); + var max_factor = cs~load_uint(32); + var adnl_addr = cs~load_uint(256); + var signature = cs~load_ref().begin_parse().preload_bits(512); + cs.end_parse(); + ifnot (check_data_signature(begin_cell() + .store_uint(0x654c5074, 32) + .store_uint(stake_at, 32) + .store_uint(max_factor, 32) + .store_uint(src_addr, 256) + .store_uint(adnl_addr, 256) + .end_cell().begin_parse(), signature, validator_pubkey)) { + ;; incorrect signature, return stake + return return_stake(s_addr, query_id, 1); + } + if (max_factor < 0x10000) { + ;; factor must be >= 1. = 65536/65536 + return return_stake(s_addr, query_id, 6); + } + ;; parse current election data + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + ;; elect_at~dump(); + msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation + if ((msg_value << 12) < total_stake) { + ;; stake smaller than 1/4096 of the total accumulated stakes, return + return return_stake(s_addr, query_id, 2); + } + total_stake += msg_value; ;; (provisionally) increase total stake + if (stake_at != elect_at) { + ;; stake for some other elections, return + return return_stake(s_addr, query_id, 3); + } + if (finished) { + ;; elections already finished, return stake + return return_stake(s_addr, query_id, 0); + } + var (mem, found) = members.udict_get?(256, validator_pubkey); + if (found) { + ;; entry found, merge stakes + msg_value += mem~load_grams(); + mem~load_uint(64); ;; skip timestamp and max_factor + found = (src_addr != mem~load_uint(256)); + } + if (found) { + ;; can make stakes for a public key from one address only + return return_stake(s_addr, query_id, 4); + } + if (msg_value < min_stake) { + ;; stake too small, return it + return return_stake(s_addr, query_id, 5); + } + throw_unless(44, msg_value); + accept_message(); + ;; store stake in the dictionary + members~udict_set_builder(256, validator_pubkey, begin_cell() + .store_grams(msg_value) + .store_uint(now(), 32) + .store_uint(max_factor, 32) + .store_uint(src_addr, 256) + .store_uint(adnl_addr, 256)); + ;; gather and save election data + elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, false, false); + set_data(begin_cell().store_dict(elect).store_slice(ds).end_cell()); + ;; return confirmation message + if (query_id) { + return send_confirmation(s_addr, query_id, 0); + } + return (); +} + +(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref { + var total = var recovered = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey); + if (f) { + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + if (banned) { + recovered += stake; + } else { + credits~credit_to(addr, stake); + } + total += stake; + } + } until (~ f); + throw_unless(59, total == tot_stakes); + return (credits, recovered); +} + +(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref { + var total = var recovered = var returned_bonuses = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey); + if (f) { + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + if (banned) { + recovered += stake; + } else { + var bonus = muldiv(tot_bonuses, stake, tot_stakes); + returned_bonuses += bonus; + credits~credit_to(addr, stake + bonus); + } + total += stake; + } + } until (~ f); + throw_unless(59, (total == tot_stakes) & (returned_bonuses <= tot_bonuses)); + return (credits, recovered + tot_bonuses - returned_bonuses); +} + +int stakes_sum(frozen_dict) inline_ref { + var total = 0; + var pubkey = -1; + do { + (pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey); + if (f) { + cs~skip_bits(256 + 64); + total += cs~load_grams(); + } + } until (~ f); + return total; +} + +_ unfreeze_all(credits, past_elections, elect_id) inline_ref { + var (fs, f) = past_elections~udict_delete_get?(32, elect_id); + ifnot (f) { + ;; no elections with this id + return (credits, past_elections, 0); + } + var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election(); + ;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX + var unused_prizes = (bonuses > 0) ? + credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) : + credits~unfreeze_without_bonuses(fdict, tot_stakes); + return (credits, past_elections, unused_prizes); +} + +() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref { + var (src_wc, src_addr) = parse_std_addr(s_addr); + var config_addr = config_param(0).begin_parse().preload_uint(256); + var ds = get_data().begin_parse(); + var elect = ds~load_dict(); + if ((src_wc + 1) | (src_addr != config_addr) | elect.null?()) { + ;; not from config smc, somebody's joke? + ;; or no elections active (or just completed) + return (); + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + if ((elect_at != query_id) | ~ finished) { + ;; not these elections, or elections not finished yet + return (); + } + accept_message(); + ifnot (ok) { + ;; cancel elections, return stakes + var (credits, past_elections, grams) = (ds~load_dict(), ds~load_dict(), ds~load_grams()); + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, elect_at); + set_data(begin_cell() + .store_int(false, 1) + .store_dict(credits) + .store_dict(past_elections) + .store_grams(grams + unused_prizes) + .store_slice(ds) + .end_cell()); + } + ;; ... do not remove elect until we see this set as the next elected validator set +} + +() process_simple_transfer(s_addr, msg_value) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + (int src_wc, int src_addr) = parse_std_addr(s_addr); + if (src_addr | (src_wc + 1) | (active_id == 0)) { + ;; simple transfer to us (credit "nobody's" account) + ;; (or no known active validator set) + grams += msg_value; + return store_data(elect, credits, past_elections, grams, active_id, active_hash); + } + ;; zero source address -1:00..00 (collecting validator fees) + var (fs, f) = past_elections.udict_get?(32, active_id); + ifnot (f) { + ;; active validator set not found (?) + grams += msg_value; + } else { + ;; credit active validator set bonuses + var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election(); + bonuses += msg_value; + past_elections~udict_set_builder(32, active_id, + pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints)); + } + return store_data(elect, credits, past_elections, grams, active_id, active_hash); +} + +() recover_stake(op, s_addr, cs, query_id) impure inline_ref { + (int src_wc, int src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { + ;; not from masterchain, return error + return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64); + } + var ds = get_data().begin_parse(); + var (elect, credits) = (ds~load_dict(), ds~load_dict()); + var (cs, f) = credits~udict_delete_get?(256, src_addr); + ifnot (f) { + ;; no credit for sender, return error + return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64); + } + var amount = cs~load_grams(); + cs.end_parse(); + ;; save data + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + ;; send amount to sender in a new message + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0xf96f7324, 32) + .store_uint(query_id, 64) + .end_cell(), 64); +} + +() after_code_upgrade(slice s_addr, slice cs, int query_id) impure method_id(1666) { + var op = 0x4e436f64; + return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64); +} + +int upgrade_code(s_addr, cs, query_id) inline_ref { + var c_addr = config_param(0); + if (c_addr.null?()) { + ;; no configuration smart contract known + return false; + } + var config_addr = c_addr.begin_parse().preload_uint(256); + var (src_wc, src_addr) = parse_std_addr(s_addr); + if ((src_wc + 1) | (src_addr != config_addr)) { + ;; not from configuration smart contract, return error + return false; + } + accept_message(); + var code = cs~load_ref(); + set_code(code); + ifnot(cs.slice_empty?()) { + set_c3(code.begin_parse().bless()); + after_code_upgrade(s_addr, cs, query_id); + throw(0); + } + return true; +} + +int register_complaint(s_addr, complaint, msg_value) { + var (src_wc, src_addr) = parse_std_addr(s_addr); + if (src_wc + 1) { ;; not from masterchain, return error + return -1; + } + if (complaint.slice_depth() >= 128) { + return -3; ;; invalid complaint + } + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var election_id = complaint~load_uint(32); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var expire_in = fs.preload_uint(32) - now(); + if (expire_in <= 0) { ;; already expired + return -4; + } + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint); + reward_addr = src_addr; + created_at = now(); + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var (_, bits, refs) = slice_compute_data_size(complaint, 4096); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + paid = pps * expire_in + deposit; + if (msg_value < paid + (1 << 30)) { ;; not enough money + return -5; + } + ;; re-pack modified complaint + cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell(); + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey); + ifnot (f) { ;; no such validator, cannot complain + return -6; + } + fs~skip_bits(256 + 64); ;; addr weight + var validator_stake = fs~load_grams(); + int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32); + if (fine > validator_stake) { ;; validator's stake is less than suggested fine + return -7; + } + if (fine <= paid) { ;; fine is less than the money paid for creating complaint + return -8; + } + ;; create complaint status + var cstatus = pack_complaint_status(complaint, null(), 0, 0); + ;; save complaint status into complaints + var cpl_id = complaint.cell_hash(); + ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) { + return -9; ;; complaint already exists + } + ;; pack past election info + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + ;; pack persistent data + ;; next line can be commented, but it saves a lot of stack manipulations + var (elect, credits, _, grams, active_id, active_hash) = load_data(); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return paid; +} + +(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref { + var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint(); + var (cs, f) = frozen.udict_get?(256, validator_pubkey); + ifnot (f) { + ;; no validator to punish + return (credits, frozen, 0, 0); + } + var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1)); + cs.end_parse(); + int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32)); + stake -= fine; + frozen~udict_set_builder(256, validator_pubkey, begin_cell() + .store_uint(addr, 256) + .store_uint(weight, 64) + .store_grams(stake) + .store_int(banned, 1)); + int reward = min(fine >> 3, paid * 8); + credits~credit_to(reward_addr, reward); + return (credits, frozen, fine - reward, fine); +} + +(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref { + var (cstatus, found?) = complaints.udict_get?(256, chash); + ifnot (found?) { + ;; complaint not found + return (complaints, null(), -1); + } + var (cur_vset, total_weight, _) = get_current_vset(); + int cur_vset_id = cur_vset.cell_hash(); + var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus); + int vset_old? = (vset_id != cur_vset_id); + if ((weight_remaining < 0) & vset_old?) { + ;; previous validator set already collected 2/3 votes, skip new votes + return (complaints, null(), -3); + } + if (vset_old?) { + ;; complaint votes belong to a previous validator set, reset voting + vset_id = cur_vset_id; + voters = null(); + weight_remaining = muldiv(total_weight, 2, 3); + } + var (_, found?) = voters.udict_get?(16, idx); + if (found?) { + ;; already voted for this proposal, ignore vote + return (complaints, null(), 0); + } + ;; register vote + voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); + int old_wr = weight_remaining; + weight_remaining -= weight; + old_wr ^= weight_remaining; + ;; save voters and weight_remaining + complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining)); + if (old_wr >= 0) { + ;; not enough votes or already accepted + return (complaints, null(), 1); + } + ;; complaint wins, prepare punishment + return (complaints, complaint, 2); +} + +int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, f) = past_elections.udict_get?(32, election_id); + ifnot (f) { ;; election not found + return -2; + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + (complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight); + if (status <= 0) { + return status; + } + ifnot (accepted_complaint.null?()) { + (credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint); + grams += fine_unalloc; + total_stake -= fine_collected; + } + past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints)); + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return status; +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; do nothing for internal messages + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + var s_addr = cs~load_msg_addr(); + if (in_msg.slice_empty?()) { + ;; inbound message has empty body + return process_simple_transfer(s_addr, msg_value); + } + int op = in_msg~load_uint(32); + if (op == 0) { + ;; simple transfer with comment, return + return process_simple_transfer(s_addr, msg_value); + } + int query_id = in_msg~load_uint(64); + if (op == 0x4e73744b) { + ;; new stake message + return process_new_stake(s_addr, msg_value, in_msg, query_id); + } + if (op == 0x47657424) { + ;; recover stake request + return recover_stake(op, s_addr, in_msg, query_id); + } + if (op == 0x4e436f64) { + ;; upgrade code (accepted only from configuration smart contract) + var ok = upgrade_code(s_addr, in_msg, query_id); + return send_message_back(s_addr, ok ? 0xce436f64 : 0xffffffff, query_id, op, 0, 64); + } + var cfg_ok = (op == 0xee764f4b); + if (cfg_ok | (op == 0xee764f6f)) { + ;; confirmation from configuration smart contract + return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok); + } + if (op == 0x52674370) { + ;; new complaint + var price = register_complaint(s_addr, in_msg, msg_value); + int mode = 64; + int ans_tag = - price; + if (price >= 0) { + ;; ok, debit price + raw_reserve(price, 4); + ans_tag = 0; + mode = 128; + } + return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode); + } + if (op == 0x56744370) { + ;; vote for a complaint + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, elect_id, chash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(32), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x56744350); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(elect_id, chash, idx, weight); + return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64); + } + + ifnot (op & (1 << 31)) { + ;; unknown query, return error + return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64); + } + ;; unknown answer, ignore + return (); +} + +int postpone_elections() impure { + return false; +} + +;; computes the total stake out of the first n entries of list l +_ compute_total_stake(l, n, m_stake) inline_ref { + int tot_stake = 0; + repeat (n) { + (var h, l) = uncons(l); + var stake = h.at(0); + var max_f = h.at(1); + stake = min(stake, (max_f * m_stake) >> 16); + tot_stake += stake; + } + return tot_stake; +} + +(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) { + var cs = 16.config_param().begin_parse(); + var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16)); + cs.end_parse(); + min_validators = max(min_validators, 1); + int n = 0; + var sdict = new_dict(); + var pubkey = -1; + do { + (pubkey, var cs, var f) = members.udict_get_next?(256, pubkey); + if (f) { + var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); + cs.end_parse(); + var key = begin_cell() + .store_uint(stake, 128) + .store_int(- time, 32) + .store_uint(pubkey, 256) + .end_cell().begin_parse(); + sdict~dict_set_builder(128 + 32 + 256, key, begin_cell() + .store_uint(min(max_factor, max_stake_factor), 32) + .store_uint(addr, 256) + .store_uint(adnl_addr, 256)); + n += 1; + } + } until (~ f); + n = min(n, max_validators); + if (n < min_validators) { + return (credits, new_dict(), 0, new_dict(), 0, 0); + } + var l = nil; + do { + var (key, cs, f) = sdict~dict::delete_get_min(128 + 32 + 256); + if (f) { + var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256)); + var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256)); + l = cons([stake, max_f, pubkey, adnl_addr], l); + } + } until (~ f); + ;; l is the list of all stakes in decreasing order + int i = min_validators - 1; + var l1 = l; + repeat (i) { + l1 = cdr(l1); + } + var (best_stake, m) = (0, 0); + do { + var stake = l1~list_next().at(0); + i += 1; + if (stake >= min_stake) { + var tot_stake = compute_total_stake(l, i, stake); + if (tot_stake > best_stake) { + (best_stake, m) = (tot_stake, i); + } + } + } until (i >= n); + if ((m == 0) | (best_stake < min_total_stake)) { + return (credits, new_dict(), 0, new_dict(), 0, 0); + } + ;; we have to select first m validators from list l + l1 = touch(l); + ;; l1~dump(); ;; DEBUG + repeat (m - 1) { + l1 = cdr(l1); + } + var m_stake = car(l1).at(0); ;; minimal stake + ;; create both the new validator set and the refund set + int i = 0; + var tot_stake = 0; + var tot_weight = 0; + var vset = new_dict(); + var frozen = new_dict(); + do { + var [stake, max_f, pubkey, adnl_addr] = l~list_next(); + ;; lookup source address first + var (val, f) = members.udict_get?(256, pubkey); + throw_unless(61, f); + (_, _, var src_addr) = (val~load_grams(), val~load_uint(64), val.preload_uint(256)); + if (i < m) { + ;; one of the first m members, include into validator set + var true_stake = min(stake, (max_f * m_stake) >> 16); + stake -= true_stake; + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; // 288 bits + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + var weight = (true_stake << 60) / best_stake; + tot_stake += true_stake; + tot_weight += weight; + var vinfo = begin_cell() + .store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53 + .store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a + .store_uint(pubkey, 256) ;; pubkey:bits256 + .store_uint(weight, 64); ;; weight:uint64 + if (adnl_addr) { + vinfo~store_uint(adnl_addr, 256); ;; adnl_addr:bits256 + } + vset~udict_set_builder(16, i, vinfo); + frozen~udict_set_builder(256, pubkey, begin_cell() + .store_uint(src_addr, 256) + .store_uint(weight, 64) + .store_grams(true_stake) + .store_int(false, 1)); + } + if (stake) { + ;; non-zero unused part of the stake, credit to the source address + credits~credit_to(src_addr, stake); + } + i += 1; + } until (l.null?()); + throw_unless(49, tot_stake == best_stake); + return (credits, vset, tot_weight, frozen, tot_stake, m); +} + +int conduct_elections(ds, elect, credits) impure { + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + if (now() < elect_close) { + ;; elections not finished yet + return false; + } + if (config_param(0).null?()) { + ;; no configuration smart contract to send result to + return postpone_elections(); + } + var cs = config_param(17).begin_parse(); + min_stake = cs~load_grams(); + var max_stake = cs~load_grams(); + var min_total_stake = cs~load_grams(); + var max_stake_factor = cs~load_uint(32); + cs.end_parse(); + if (total_stake < min_total_stake) { + ;; insufficient total stake, postpone elections + return postpone_elections(); + } + if (failed) { + ;; do not retry failed elections until new stakes arrive + return postpone_elections(); + } + if (finished) { + ;; elections finished + return false; + } + (credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor); + ;; pack elections; if cnt==0, set failed=true, finished=false. + failed = (cnt == 0); + finished = ~ failed; + elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished); + ifnot (cnt) { + ;; elections failed, set elect_failed to true + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + return postpone_elections(); + } + ;; serialize a query to the configuration smart contract + ;; to install the computed validator set as the next validator set + var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf(); + var start = max(now() + elect_end_before - 60, elect_at); + var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16); + var vset = begin_cell() + .store_uint(0x12, 8) ;; validators_ext#12 + .store_uint(start, 32) ;; utime_since:uint32 + .store_uint(start + elect_for, 32) ;; utime_until:uint32 + .store_uint(cnt, 16) ;; total:(## 16) + .store_uint(min(cnt, main_validators), 16) ;; main:(## 16) + .store_uint(total_weight, 64) ;; total_weight:uint64 + .store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr) + .end_cell(); + var config_addr = config_param(0).begin_parse().preload_uint(256); + send_validator_set_to_config(config_addr, vset, elect_at); + ;; add frozen to the dictionary of past elections + var past_elections = ds~load_dict(); + past_elections~udict_set_builder(32, elect_at, pack_past_election( + start + elect_for + stake_held, stake_held, vset.cell_hash(), + frozen, total_stakes, 0, null())); + ;; store credits and frozen until end + set_data(begin_cell() + .store_dict(elect) + .store_dict(credits) + .store_dict(past_elections) + .store_slice(ds) + .end_cell()); + return true; +} + +int update_active_vset_id() impure { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var cur_hash = config_param(34).cell_hash(); + if (cur_hash == active_hash) { + ;; validator set unchanged + return false; + } + if (active_id) { + ;; active_id becomes inactive + var (fs, f) = past_elections.udict_get?(32, active_id); + if (f) { + ;; adjust unfreeze time of this validator set + var unfreeze_time = fs~load_uint(32); + var fs0 = fs; + var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256)); + throw_unless(57, hash == active_hash); + unfreeze_time = now() + stake_held; + past_elections~udict_set_builder(32, active_id, begin_cell() + .store_uint(unfreeze_time, 32) + .store_slice(fs0)); + } + } + ;; look up new active_id by hash + var id = -1; + do { + (id, var fs, var f) = past_elections.udict_get_next?(32, id); + if (f) { + var (tm, hash) = (fs~load_uint(64), fs~load_uint(256)); + if (hash == cur_hash) { + ;; parse more of this record + var (dict, total_stake, bonuses) = (fs~load_dict(), fs~load_grams(), fs~load_grams()); + ;; transfer 1/8 of accumulated everybody's grams to this validator set as bonuses + var amount = (grams >> 3); + grams -= amount; + bonuses += amount; + ;; serialize back + past_elections~udict_set_builder(32, id, begin_cell() + .store_uint(tm, 64) + .store_uint(hash, 256) + .store_dict(dict) + .store_grams(total_stake) + .store_grams(bonuses) + .store_slice(fs)); + ;; found + f = false; + } + } + } until (~ f); + active_id = (id.null?() ? 0 : id); + active_hash = cur_hash; + store_data(elect, credits, past_elections, grams, active_id, active_hash); + return true; +} + +int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref { + return vset.null?() ? false : cell_hash(vset) == expected_vset_hash; +} + +int validator_set_installed(ds, elect, credits) impure { + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + ifnot (finished) { + ;; elections not finished yet + return false; + } + var past_elections = ds~load_dict(); + var (fs, f) = past_elections.udict_get?(32, elect_at); + ifnot (f) { + ;; no election data in dictionary + return false; + } + ;; recover validator set hash + var vset_hash = fs.skip_bits(64).preload_uint(256); + if (config_param(34).cell_hash_eq?(vset_hash) | config_param(36).cell_hash_eq?(vset_hash)) { + ;; this validator set has been installed, forget elections + set_data(begin_cell() + .store_int(false, 1) ;; forget current elections + .store_dict(credits) + .store_dict(past_elections) + .store_slice(ds) + .end_cell()); + update_active_vset_id(); + return true; + } + return false; +} + +int check_unfreeze() impure { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + int id = -1; + do { + (id, var fs, var f) = past_elections.udict_get_next?(32, id); + if (f) { + var unfreeze_at = fs~load_uint(32); + if ((unfreeze_at <= now()) & (id != active_id)) { + ;; unfreeze! + (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id); + grams += unused_prizes; + ;; unfreeze only one at time, exit loop + store_data(elect, credits, past_elections, grams, active_id, active_hash); + ;; exit loop + f = false; + } + } + } until (~ f); + return ~ id.null?(); +} + +int announce_new_elections(ds, elect, credits) { + var next_vset = config_param(36); ;; next validator set + ifnot (next_vset.null?()) { + ;; next validator set exists, no elections needed + return false; + } + var elector_addr = config_param(1).begin_parse().preload_uint(256); + var (my_wc, my_addr) = my_address().parse_std_addr(); + if ((my_wc + 1) | (my_addr != elector_addr)) { + ;; this smart contract is not the elections smart contract anymore, no new elections + return false; + } + var cur_vset = config_param(34); ;; current validator set + if (cur_vset.null?()) { + return false; + } + var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf(); + var cur_valid_until = cur_vset.begin_parse().skip_bits(8 + 32).preload_uint(32); + var t = now(); + var t0 = cur_valid_until - elect_begin_before; + if (t < t0) { + ;; too early for the next elections + return false; + } + ;; less than elect_before_begin seconds left, create new elections + if (t - t0 < 60) { + ;; pretend that the elections started at t0 + t = t0; + } + ;; get stake parameters + (_, var min_stake) = config_param(17).begin_parse().load_grams(); + ;; announce new elections + var elect_at = t + elect_begin_before; + ;; elect_at~dump(); + var elect_close = elect_at - elect_end_before; + elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false); + set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell()); + return true; +} + +() run_ticktock(int is_tock) impure { + ;; check whether an election is being conducted + var ds = get_data().begin_parse(); + var (elect, credits) = (ds~load_dict(), ds~load_dict()); + ifnot (elect.null?()) { + ;; have an active election + throw_if(0, conduct_elections(ds, elect, credits)); ;; elections conducted, exit + throw_if(0, validator_set_installed(ds, elect, credits)); ;; validator set installed, current elections removed + } else { + throw_if(0, announce_new_elections(ds, elect, credits)); ;; new elections announced, exit + } + throw_if(0, update_active_vset_id()); ;; active validator set id updated, exit + check_unfreeze(); +} + +;; Get methods + +;; returns active election id or 0 +int active_election_id() method_id { + var elect = get_data().begin_parse().preload_dict(); + return elect.null?() ? 0 : elect.begin_parse().preload_uint(32); +} + +;; checks whether a public key participates in current elections +int participates_in(int validator_pubkey) method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return 0; + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var (mem, found) = members.udict_get?(256, validator_pubkey); + return found ? mem~load_grams() : 0; +} + +;; returns the list of all participants of current elections with their stakes +_ participant_list() method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return nil; + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var l = nil; + var id = (1 << 255) + ((1 << 255) - 1); + do { + (id, var fs, var f) = members.udict_get_prev?(256, id); + if (f) { + l = cons([id, fs~load_grams()], l); + } + } until (~ f); + return l; +} + +;; returns the list of all participants of current elections with their data +_ participant_list_extended() method_id { + var elect = get_data().begin_parse().preload_dict(); + if (elect.null?()) { + return (0, 0, 0, 0, nil, 0, 0); + } + var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect(); + var l = nil; + var id = (1 << 255) + ((1 << 255) - 1); + do { + (id, var cs, var f) = members.udict_get_prev?(256, id); + if (f) { + var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); + cs.end_parse(); + l = cons([id, [stake, max_factor, addr, adnl_addr]], l); + } + } until (~ f); + return (elect_at, elect_close, min_stake, total_stake, l, failed, finished); +} + +;; computes the return stake +int compute_returned_stake(int wallet_addr) method_id { + var cs = get_data().begin_parse(); + (_, var credits) = (cs~load_dict(), cs~load_dict()); + var (val, f) = credits.udict_get?(256, wallet_addr); + return f ? val~load_grams() : 0; +} + +;; returns the list of past election ids +tuple past_election_ids() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var f) = past_elections.udict_get_prev?(32, id); + if (f) { + list = cons(id, list); + } + } until (~ f); + return list; +} + +tuple past_elections() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + list = cons([id, unpack_past_election(fs)], list); + } + } until (~ found); + return list; +} + +tuple past_elections_list() method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var id = (1 << 32); + var list = null(); + do { + (id, var fs, var found) = past_elections.udict_get_prev?(32, id); + if (found) { + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + list = cons([id, unfreeze_at, vset_hash, stake_held], list); + } + } until (~ found); + return list; +} + +_ complete_unpack_complaint(slice cs) inline_ref { + var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status(); + var voters_list = null(); + var voter_id = (1 << 32); + do { + (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id); + if (f) { + voters_list = cons(voter_id, voters_list); + } + } until (~ f); + return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining]; +} + +cell get_past_complaints(int election_id) inline_ref method_id { + var (elect, credits, past_elections, grams, active_id, active_hash) = load_data(); + var (fs, found?) = past_elections.udict_get?(32, election_id); + ifnot (found?) { + return null(); + } + var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs); + return complaints; +} + +_ show_complaint(int election_id, int chash) method_id { + var complaints = get_past_complaints(election_id); + var (cs, found) = complaints.udict_get?(256, chash); + return found ? complete_unpack_complaint(cs) : null(); +} + +tuple list_complaints(int election_id) method_id { + var complaints = get_past_complaints(election_id); + int id = (1 << 255) + ((1 << 255) - 1); + var list = null(); + do { + (id, var cs, var found?) = complaints.udict_get_prev?(256, id); + if (found?) { + list = cons(pair(id, complete_unpack_complaint(cs)), list); + } + } until (~ found?); + return list; +} + +int complaint_storage_price(int bits, int refs, int expire_in) method_id { + ;; compute complaint storage/creation price + var (deposit, bit_price, cell_price) = get_complaint_prices(); + var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price; + var paid = pps * expire_in + deposit; + return paid + (1 << 30); +} diff --git a/src/func/grammar-test/expressions.fc b/src/func/grammar-test/expressions.fc new file mode 100644 index 000000000..aa1737692 --- /dev/null +++ b/src/func/grammar-test/expressions.fc @@ -0,0 +1 @@ +;; TODO: ... diff --git a/src/func/grammar-test/functions.fc b/src/func/grammar-test/functions.fc new file mode 100644 index 000000000..4da7e341f --- /dev/null +++ b/src/func/grammar-test/functions.fc @@ -0,0 +1,20 @@ +;; Void +() void(); + +;; Regular parameters +() params(int p1, cell p2, () p3, (slice) p4, [builder] p5); + +;; Parametric polymorphism with forall +forall X -> () poly(X p1); +forall X, Y -> (Y, X) poly'(X p1, Y p2); + +;; Attributes (called specifiers in TON Docs) +() attr() impure inline_ref inline method_id(0x2A); +() attr'() method_id(42); +() attr''() method_id; + +;; Empty body (note, that {} is an identifier, so there must be whitespace in-between) +() body() { } + +;; Everything +forall XXX -> XXX everything(XXX x) impure inline_ref inline method_id(0x2A) method_id { } diff --git a/src/func/grammar-test/globals.fc b/src/func/grammar-test/globals.fc new file mode 100644 index 000000000..4eb7884da --- /dev/null +++ b/src/func/grammar-test/globals.fc @@ -0,0 +1,14 @@ +;; No type +global glob1; + +;; Primitive type +global int glob2; + +;; Mapped type +global int -> int glob3; + +;; Convoluted example +global ((int, (_, cont, cell, (), [int])) -> int) glob4; + +;; Multiple at once +global int glob5, (builder, tuple) glob6, glob7; diff --git a/src/func/grammar-test/highload-wallet-code.fc b/src/func/grammar-test/highload-wallet-code.fc new file mode 100644 index 000000000..8420c1d82 --- /dev/null +++ b/src/func/grammar-test/highload-wallet-code.fc @@ -0,0 +1,47 @@ +;; Heavy-duty wallet for mass transfers (e.g., for cryptocurrency exchanges) +;; accepts orders for up to 254 internal messages (transfers) in one external message + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + var dict = cs~load_dict(); + cs.end_parse(); + accept_message(); + int i = -1; + do { + (i, var cs, var f) = dict.idict_get_next?(16, i); + if (f) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + } until (~ f); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} diff --git a/src/func/grammar-test/highload-wallet-v2-code.fc b/src/func/grammar-test/highload-wallet-v2-code.fc new file mode 100644 index 000000000..b7626bbe5 --- /dev/null +++ b/src/func/grammar-test/highload-wallet-v2-code.fc @@ -0,0 +1,87 @@ +;; Heavy-duty wallet for mass transfers (e.g., for cryptocurrency exchanges) +;; accepts orders for up to 254 internal messages (transfers) in one external message +;; this version does not use seqno for replay protection; instead, it remembers all recent query_ids +;; in this way several external messages with different query_id can be sent in parallel + + +;; Note, when dealing with highload-wallet the following limits need to be checked and taken into account: +;; 1) Storage size limit. Currently, size of contract storage should be less than 65535 cells. If size of +;; old_queries will grow above this limit, exception in ActionPhase will be thrown and transaction will fail. +;; Failed transaction may be replayed. +;; 2) Gas limit. Currently, gas limit is 1'000'000 gas units, that means that there is a limit of how much +;; old queries may be cleaned in one tx. If number of expired queries will be higher, contract will stuck. + +;; That means that it is not recommended to set too high expiration date: +;; number of queries during expiration timespan should not exceed 1000. +;; Also, number of expired queries cleaned in one transaction should be below 100. + +;; Such precautions are not easy to follow, so it is recommended to use highload contract +;; only when strictly necessary and the developer understands the above details. + + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, query_id) = (cs~load_uint(32), cs~load_uint(64)); + var bound = (now() << 32); + throw_if(35, query_id < bound); + var ds = get_data().begin_parse(); + var (stored_subwallet, last_cleaned, public_key, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict()); + ds.end_parse(); + (_, var found?) = old_queries.udict_get?(64, query_id); + throw_if(32, found?); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + var dict = cs~load_dict(); + cs.end_parse(); + accept_message(); + int i = -1; + do { + (i, var cs, var f) = dict.idict_get_next?(16, i); + if (f) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + } until (~ f); + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + old_queries~udict_set_builder(64, query_id, begin_cell()); + var queries = old_queries; + do { + var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + old_queries = old_queries'; + last_cleaned = i; + } + } until (~ f); + set_data(begin_cell() + .store_uint(stored_subwallet, 32) + .store_uint(last_cleaned, 64) + .store_uint(public_key, 256) + .store_dict(old_queries) + .end_cell()); +} + +;; Get methods + +;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) +int processed?(int query_id) method_id { + var ds = get_data().begin_parse(); + var (_, last_cleaned, _, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict()); + ds.end_parse(); + (_, var found) = old_queries.udict_get?(64, query_id); + return found ? true : - (query_id <= last_cleaned); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32 + 64); + return cs.preload_uint(256); +} diff --git a/src/func/grammar-test/identifiers.fc b/src/func/grammar-test/identifiers.fc new file mode 100644 index 000000000..5fa29ad3b --- /dev/null +++ b/src/func/grammar-test/identifiers.fc @@ -0,0 +1,44 @@ +;; Identifiers +global query'; +global query''; +global CHECK; +global _internal_val; +global message_found?; +global get_pubkeys&signatures; +global dict::udict_set_builder; +global _+_; +global __; +global fatal!; +global 123validname; +global 2+2=2*2; +global -alsovalidname; +global 0xefefefhahaha; +global {hehehe}; +global pa{--}in"`aaa`"; +global `I'm a function too`; +global `any symbols ; ~ () are allowed here...`; +global C4; +global C4g; +global 4C; +global _0x0; +global _0; +global 0x_; +global 0x0_; +global 0_; +global hash#256; +global 💀💀💀0xDEADBEEF💀💀💀; +global __tact_verify_address; +global __tact_pow2; +global randomize_lt; +global fixed248::asin; +global fixed248::nrand_fast; +global atan_f261_inlined; +global f̷̨͈͚́͌̀i̵̩͔̭̐͐̊n̸̟̝̻̩̎̓͋̕e̸̝̙̒̿͒̾̕; +global ❤️❤️❤️thanks❤️❤️❤️; +global intslice; +global int2; + +;; Function identifiers, which can start with . or ~ +() ~impure_touch(); +() ~udict::delete_get_min(); +() .something(); diff --git a/src/func/grammar-test/include_stdlib.fc b/src/func/grammar-test/include_stdlib.fc new file mode 100644 index 000000000..d0d52fd2a --- /dev/null +++ b/src/func/grammar-test/include_stdlib.fc @@ -0,0 +1 @@ +#include "../../../stdlib/stdlib.fc"; diff --git a/src/func/grammar-test/literals-and-comments.fc b/src/func/grammar-test/literals-and-comments.fc new file mode 100644 index 000000000..de116c921 --- /dev/null +++ b/src/func/grammar-test/literals-and-comments.fc @@ -0,0 +1,61 @@ +;; Integer literals +() integers() { + 42; + 0x2A; + 0x2a; + + -42; + -0x2A; + -0x2a; +} + +;; String literals +() strings() { + "slice"; + "2A"s; + "EQAFmjUoZUqKFEBGYFEMbv-m61sFStgAfUR8J6hJDwUU09iT"a; + "int hex"u; + "int 32 bits of sha256"h; + "int 256 bits of sha256"H; + "int crc32"c; + + """ + multi + line + """; + + """2A"""s; + """EQAFmjUoZUqKFEBGYFEMbv-m61sFStgAfUR8J6hJDwUU09iT"""a; + """ + ... + """u; + """ + ... + """h; + """ + ... + """H; + """ + ... + """c; +} + +;; Single-line comment +{- + Multi-line comment + {- + Nested! "Not a string, part of the comment!" + ;; Also a part of the nested multi-line comment, not a single-line one + -} +-} + +;; Comment madness +forall ;; ... +X, Y ;; ... +-> ;; ... +() {- + ... +-} ;; ... +comments! ;; ... +() ;; ... +{ {- ... -} return (); {- ... -} } diff --git a/src/func/grammar-test/mathlib.fc b/src/func/grammar-test/mathlib.fc new file mode 100644 index 000000000..5935bf1fd --- /dev/null +++ b/src/func/grammar-test/mathlib.fc @@ -0,0 +1,939 @@ +{- + - + - FunC fixed-point mathematical library + - + -} + +{- + This file is part of TON FunC Standard Library. + + FunC Standard 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. + + FunC Standard 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. + +-} + +#include "stdlib.fc"; +#pragma version >=0.4.2; + +{---------------- 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`. +-} + +{- function declarations have been commented out, otherwise they are not inlined by the current FunC compiler + +;; 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; + +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() impure inline; +;; random number with standard normal distribution (2100 gas on average) +;; fixed248 nrand(); +int fixed248::nrand() impure 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() impure 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 FunC 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 FunC 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 (FunC 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 xx = 2 * x; + int y = lshift256divr(xx, (x >> 1) - t); + ;; y = xx - (mulrshiftr256(xx, 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 FunC 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 + } + int 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() impure 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 + } + } + } + } until (~ 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(); +int nrand_fast_f252() impure inline_ref { + int t = 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(); +int fixed248::random() impure inline { + return random() >> 8; +} + +;; random number with standard normal distribution +;; fixed248 nrand(); +int fixed248::nrand() impure inline { + return nrand_f252() ~>> 4; +} + +;; generates a random number approximately distributed according to the standard normal distribution +;; fixed248 nrand_fast(); +int fixed248::nrand_fast() impure inline { + return nrand_fast_f252() ~>> 4; +} diff --git a/src/func/grammar-test/payment-channel-code.fc b/src/func/grammar-test/payment-channel-code.fc new file mode 100644 index 000000000..b4b14d5cd --- /dev/null +++ b/src/func/grammar-test/payment-channel-code.fc @@ -0,0 +1,357 @@ +;; WARINIG: NOT READY FOR A PRODUCTION! + +int err:wrong_a_signature() asm "31 PUSHINT"; +int err:wrong_b_signature() asm "32 PUSHINT"; +int err:msg_value_too_small() asm "33 PUSHINT"; +int err:replay_protection() asm "34 PUSHINT"; +int err:no_timeout() asm "35 PUSHINT"; +int err:expected_init() asm "36 PUSHINT"; +int err:expected_close() asm "37 PUSHINT"; +int err:expected_payout() asm "37 PUSHINT"; +int err:no_promise_signature() asm "38 PUSHINT"; +int err:wrong_channel_id() asm "39 PUSHINT"; +int err:unknown_op() asm "40 PUSHINT"; +int err:not_enough_fee() asm "41 PUSHINT"; + +int op:pchan_cmd() asm "0x912838d1 PUSHINT"; + +int msg:init() asm "0x27317822 PUSHINT"; +int msg:close() asm "0xf28ae183 PUSHINT"; +int msg:timeout() asm "0x43278a28 PUSHINT"; +int msg:payout() asm "0x37fe7810 PUSHINT"; + +int state:init() asm "0 PUSHINT"; +int state:close() asm "1 PUSHINT"; +int state:payout() asm "2 PUSHINT"; + +int min_fee() asm "1000000000 PUSHINT"; + + +;; A - initial balance of Alice, +;; B - initial balance of B +;; +;; To determine balance we track nondecreasing list of promises +;; promise_A ;; promised by Alice to Bob +;; promise_B ;; promised by Bob to Alice +;; +;; diff - balance between Alice and Bob. 0 in the beginning +;; diff = promise_B - promise_A; +;; diff = clamp(diff, -A, +B); +;; +;; final_A = A + diff; +;; final_B = B + diff; + +;; Data pack/unpack +;; +_ unpack_data() inline_ref { + var cs = get_data().begin_parse(); + var res = (cs~load_ref(), cs~load_ref()); + cs.end_parse(); + return res; +} + +_ pack_data(cell config, cell state) impure inline_ref { + set_data(begin_cell().store_ref(config).store_ref(state).end_cell()); +} + +;; Config pack/unpack +;; +;; config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:uint64 = Config; +;; +_ unpack_config(cell config) { + var cs = config.begin_parse(); + var res = ( + cs~load_uint(32), + cs~load_uint(32), + cs~load_uint(256), + cs~load_uint(256), + cs~load_ref().begin_parse(), + cs~load_ref().begin_parse(), + cs~load_uint(64), + cs~load_grams()); + cs.end_parse(); + return res; +} + +;; takes +;; signedMesage$_ a_sig:Maybe b_sig:Maybe msg:Message = SignedMessage; +;; checks signatures and unwap message. +(slice, (int, int)) unwrap_signatures(slice cs, int a_key, int b_key) { + int a? = cs~load_int(1); + slice a_sig = cs; + if (a?) { + a_sig = cs~load_ref().begin_parse().preload_bits(512); + } + var b? = cs~load_int(1); + slice b_sig = cs; + if (b?) { + b_sig = cs~load_ref().begin_parse().preload_bits(512); + } + int hash = cs.slice_hash(); + if (a?) { + throw_unless(err:wrong_a_signature(), check_signature(hash, a_sig, a_key)); + } + if (b?) { + throw_unless(err:wrong_b_signature(), check_signature(hash, b_sig, b_key)); + } + return (cs, (a?, b?)); +} + +;; process message, give state is stateInit +;; +;; stateInit signed_A?:Bool signed_B?:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = State; +_ unpack_state_init(slice state) { + return ( + state~load_int(1), + state~load_int(1), + state~load_grams(), + state~load_grams(), + state~load_uint(32), + state~load_grams(), + state~load_grams()); + +} +_ pack_state_init(int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) { + return begin_cell() + .store_int(state:init(), 3) + .store_int(signed_A?, 1) + .store_int(signed_B?, 1) + .store_grams(min_A) + .store_grams(min_B) + .store_uint(expire_at, 32) + .store_grams(A) + .store_grams(B).end_cell(); +} + +;; stateClosing$10 signed_A?:bool signed_B?:Bool promise_A:Grams promise_B:Grams exipire_at:uint32 A:Grams B:Grams = State; +_ unpack_state_close(slice state) { + return ( + state~load_int(1), + state~load_int(1), + state~load_grams(), + state~load_grams(), + state~load_uint(32), + state~load_grams(), + state~load_grams()); +} + +_ pack_state_close(int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) { + return begin_cell() + .store_int(state:close(), 3) + .store_int(signed_A?, 1) + .store_int(signed_B?, 1) + .store_grams(promise_A) + .store_grams(promise_B) + .store_uint(expire_at, 32) + .store_grams(A) + .store_grams(B).end_cell(); +} + +_ send_payout(slice s_addr, int amount, int channel_id, int flags) impure { + send_raw_message(begin_cell() + .store_uint(0x10, 6) + .store_slice(s_addr) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(msg:payout(), 32) + .store_uint(channel_id, 64) + .end_cell(), flags); +} + + +cell do_payout(int promise_A, int promise_B, int A, int B, slice a_addr, slice b_addr, int channel_id) impure { + accept_message(); + + int diff = promise_B - promise_A; + if (diff < - A) { + diff = - A; + } + if (diff > B) { + diff = B; + } + A += diff; + B -= diff; + + send_payout(b_addr, B, channel_id, 3); + send_payout(a_addr, A, channel_id, 3 + 128); + + return begin_cell() + .store_int(state:payout(), 3) + .store_grams(A) + .store_grams(B) + .end_cell(); +} + + +;; +;; init$000 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams = Message; +;; +cell with_init(slice state, int msg_value, slice msg, int msg_signed_A?, int msg_signed_B?, + slice a_addr, slice b_addr, int init_timeout, int channel_id, int min_A_extra) { + ;; parse state + (int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) = unpack_state_init(state); + + if (expire_at == 0) { + expire_at = now() + init_timeout; + } + + int op = msg~load_uint(32); + if (op == msg:timeout()) { + throw_unless(err:no_timeout(), expire_at < now()); + return do_payout(0, 0, A, B, a_addr, b_addr, channel_id); + } + throw_unless(err:expected_init(), op == msg:init()); + + ;; unpack init message + (int inc_A, int inc_B, int upd_min_A, int upd_min_B, int got_channel_id) = + (msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_uint(64)); + throw_unless(err:wrong_channel_id(), got_channel_id == channel_id); + + ;; TODO: we should reserve some part of the value for comission + throw_if(err:msg_value_too_small(), msg_value < inc_A + inc_B); + throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?)); + + A += inc_A; + B += inc_B; + + signed_A? |= msg_signed_A?; + if (min_A < upd_min_A) { + min_A = upd_min_A; + } + + signed_B? |= msg_signed_B?; + if (min_B < upd_min_B) { + min_B = upd_min_B; + } + + if (signed_A? & signed_B?) { + A -= min_A_extra; + if ((min_A > A) | (min_B > B)) { + return do_payout(0, 0, A, B, a_addr, b_addr, channel_id); + } + + return pack_state_close(0, 0, 0, 0, 0, A, B); + } + + return pack_state_init(signed_A?, signed_B?, min_A, min_B, expire_at, A, B); +} + +;; close$001 extra_A:Grams extra_B:Grams sig:Maybe promise_A:Grams promise_B:Grams = Message; + +cell with_close(slice cs, slice msg, int msg_signed_A?, int msg_signed_B?, int a_key, int b_key, + slice a_addr, slice b_addr, int expire_timeout, int channel_id) { + ;; parse state + (int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) = unpack_state_close(cs); + + if (expire_at == 0) { + expire_at = now() + expire_timeout; + } + + int op = msg~load_uint(32); + if (op == msg:timeout()) { + throw_unless(err:no_timeout(), expire_at < now()); + return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id); + } + throw_unless(err:expected_close(), op == msg:close()); + + ;; also ensures that (msg_signed_A? | msg_signed_B?) is true + throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?)); + signed_A? |= msg_signed_A?; + signed_B? |= msg_signed_B?; + + ;; unpack close message + (int extra_A, int extra_B) = (msg~load_grams(), msg~load_grams()); + int has_sig = msg~load_int(1); + if (has_sig) { + slice sig = msg~load_ref().begin_parse().preload_bits(512); + int hash = msg.slice_hash(); + ifnot (msg_signed_A?) { + throw_unless(err:wrong_a_signature(), check_signature(hash, sig, a_key)); + extra_A = 0; + } + ifnot (msg_signed_B?) { + throw_unless(err:wrong_b_signature(), check_signature(hash, sig, b_key)); + extra_B = 0; + } + } else { + throw_unless(err:no_promise_signature(), msg_signed_A? & msg_signed_B?); + extra_A = 0; + extra_B = 0; + } + (int got_channel_id, int update_promise_A, int update_promise_B) = (msg~load_uint(64), msg~load_grams(), msg~load_grams()); + throw_unless(err:wrong_channel_id(), got_channel_id == channel_id); + + + accept_message(); + update_promise_A += extra_A; + if (promise_A < update_promise_A) { + promise_A = update_promise_A; + } + update_promise_B += extra_B; + if (promise_B < update_promise_B) { + promise_B = update_promise_B; + } + + if (signed_A? & signed_B?) { + return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id); + } + return pack_state_close(signed_A?, signed_B?, promise_A, promise_B, expire_at, A, B); +} + +() with_payout(slice cs, slice msg, slice a_addr, slice b_addr, int channel_id) impure { + int op = msg~load_uint(32); + throw_unless(err:expected_payout(), op == msg:payout()); + (int A, int B) = (cs~load_grams(), cs~load_grams()); + throw_unless(err:not_enough_fee(), A + B + 1000000000 < get_balance().pair_first()); + accept_message(); + send_payout(b_addr, B, channel_id, 3); + send_payout(a_addr, A, channel_id, 3 + 128); +} + +() recv_any(int msg_value, slice msg) impure { + if (msg.slice_empty?()) { + return(); + } + ;; op is not signed, but we don't need it to be signed. + int op = msg~load_uint(32); + if (op <= 1) { + ;; simple transfer with comment, return + ;; external message will be aborted + ;; internal message will be accepted + return (); + } + throw_unless(err:unknown_op(), op == op:pchan_cmd()); + + (cell config, cell state) = unpack_data(); + (int init_timeout, int close_timeout, int a_key, int b_key, + slice a_addr, slice b_addr, int channel_id, int min_A_extra) = config.unpack_config(); + (int msg_signed_A?, int msg_signed_B?) = msg~unwrap_signatures(a_key, b_key); + + slice cs = state.begin_parse(); + int state_type = cs~load_uint(3); + + if (state_type == state:init()) { ;; init + state = with_init(cs, msg_value, msg, msg_signed_A?, msg_signed_B?, a_addr, b_addr, init_timeout, channel_id, min_A_extra); + } if (state_type == state:close()) { + state = with_close(cs, msg, msg_signed_A?, msg_signed_B?, a_key, b_key, a_addr, b_addr, close_timeout, channel_id); + } if (state_type == state:payout()) { + with_payout(cs, msg, a_addr, b_addr, channel_id); + } + + pack_data(config, state); +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; TODO: uncomment when supported in tests + ;; var cs = in_msg_cell.begin_parse(); + ;; var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + ;; if (flags & 1) { + ;; ;; ignore all bounced messages + ;; return (); + ;; } + recv_any(msg_value, in_msg); +} + +() recv_external(slice in_msg) impure { + recv_any(0, in_msg); +} diff --git a/src/func/grammar-test/pow-testgiver-code.fc b/src/func/grammar-test/pow-testgiver-code.fc new file mode 100644 index 000000000..6361a4613 --- /dev/null +++ b/src/func/grammar-test/pow-testgiver-code.fc @@ -0,0 +1,158 @@ +;; Advanced TestGiver smart contract with Proof-of-Work verification + +int ufits(int x, int bits) impure asm "UFITSX"; + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() check_proof_of_work(slice cs) impure inline_ref { + var hash = slice_hash(cs); + var ds = get_data().begin_parse(); + var (stored_seqno_sw, public_key, seed, pow_complexity) = (ds~load_uint(64), ds~load_uint(256), ds~load_uint(128), ds~load_uint(256)); + throw_unless(24, hash < pow_complexity); ;; hash problem NOT solved + var (op, flags, expire, whom, rdata1, rseed, rdata2) = (cs~load_uint(32), cs~load_int(8), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256), cs~load_uint(128), cs~load_uint(256)); + cs.end_parse(); + ufits(expire - now(), 10); + throw_unless(25, (rseed == seed) & (rdata1 == rdata2)); + ;; Proof of Work correct + accept_message(); + randomize_lt(); + randomize(rdata1); + var (last_success, xdata) = (ds~load_uint(32), ds~load_ref()); + ds.end_parse(); + ds = xdata.begin_parse(); + var (amount, target_delta, min_cpl, max_cpl) = (ds~load_grams(), ds~load_uint(32), ds~load_uint(8), ds~load_uint(8)); + ds.end_parse(); + ;; recompute complexity + int delta = now() - last_success; + if (delta > 0) { + int factor = muldivr(delta, 1 << 128, target_delta); + factor = min(max(factor, 7 << 125), 9 << 125); ;; factor must be in range 7/8 .. 9/8 + pow_complexity = muldivr(pow_complexity, factor, 1 << 128); ;; rescale complexity + pow_complexity = min(max(pow_complexity, 1 << min_cpl), 1 << max_cpl); + } + ;; update data + set_data(begin_cell() + .store_uint(stored_seqno_sw, 64) + .store_uint(public_key, 256) + .store_uint(random() >> 128, 128) ;; new seed + .store_uint(pow_complexity, 256) + .store_uint(now(), 32) ;; new last_success + .store_ref(xdata) + .end_cell()); + commit(); + ;; create outbound message + send_raw_message(begin_cell() + .store_uint(((flags & 1) << 6) | 0x84, 9) + .store_int(flags >> 2, 8) + .store_uint(whom, 256) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .end_cell(), 3); +} + +() rescale_complexity(slice cs) impure inline_ref { + var (op, expire) = (cs~load_uint(32), cs~load_uint(32)); + cs.end_parse(); + int time = now(); + throw_unless(28, time > expire); + var ds = get_data().begin_parse(); + var (skipped_data, pow_complexity, last_success, xdata) = (ds~load_bits(64 + 256 + 128), ds~load_uint(256), ds~load_uint(32), ds~load_ref()); + ds.end_parse(); + throw_unless(29, expire > last_success); + ds = xdata.begin_parse(); + var (amount, target_delta) = (ds~load_grams(), ds~load_uint(32)); + int delta = time - last_success; + throw_unless(30, delta >= target_delta * 16); + accept_message(); + var (min_cpl, max_cpl) = (ds~load_uint(8), ds~load_uint(8)); + ds.end_parse(); + int factor = muldivr(delta, 1 << 128, target_delta); + int max_complexity = (1 << max_cpl); + int max_factor = muldiv(max_complexity, 1 << 128, pow_complexity); + pow_complexity = (max_factor < factor ? max_complexity : muldivr(pow_complexity, factor, 1 << 128)); + last_success = time - target_delta; + set_data(begin_cell() + .store_slice(skipped_data) + .store_uint(pow_complexity, 256) + .store_uint(last_success, 32) ;; new last_success + .store_ref(xdata) + .end_cell()); +} + +(slice, ()) ~update_params(slice ds, cell pref) inline_ref { + var cs = pref.begin_parse(); + var reset_cpl = cs~load_uint(8); + var (seed, pow_complexity, last_success) = (ds~load_uint(128), ds~load_uint(256), ds~load_uint(32)); + if (reset_cpl) { + randomize(seed); + pow_complexity = (1 << reset_cpl); + seed = (random() >> 128); + } + var c = begin_cell() + .store_uint(seed, 128) + .store_uint(pow_complexity, 256) + .store_uint(now(), 32) + .store_ref(begin_cell().store_slice(cs).end_cell()) + .end_cell(); + return (begin_parse(c), ()); +} + +() recv_external(slice in_msg) impure { + var op = in_msg.preload_uint(32); + if (op == 0x4d696e65) { + ;; Mine = Obtain test grams by presenting valid proof of work + return check_proof_of_work(in_msg); + } + if (op == 0x5253636c) { + ;; RScl = Rescale complexity if no success for long time + return rescale_complexity(in_msg); + } + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, (subwallet_id == stored_subwallet) | (subwallet_id == 0)); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var ref = cs~load_ref(); + var mode = cs~load_uint(8); + if (mode < 0xff) { + send_raw_message(ref, mode); + } else { + ds~update_params(ref); + } + } + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_slice(ds) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +;; gets (seed, pow_complexity, amount, interval) +(int, int, int, int) get_pow_params() method_id { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var (seed, pow_complexity, xdata) = (ds~load_uint(128), ds~load_uint(256), ds.preload_ref()); + ds = xdata.begin_parse(); + return (seed, pow_complexity, ds~load_grams(), ds.preload_uint(32)); +} + +int get_public_key() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32 + 32); + return ds.preload_uint(256); +} diff --git a/src/func/grammar-test/pragmas.fc b/src/func/grammar-test/pragmas.fc new file mode 100644 index 000000000..72415dddf --- /dev/null +++ b/src/func/grammar-test/pragmas.fc @@ -0,0 +1,30 @@ +;; Literals +#pragma allow-post-modification; +#pragma compute-asm-ltr; + +;; Version ranges +#pragma version 0; +#pragma version 0.0; +#pragma version 0.0.0; +#pragma version =0; +#pragma version =0.0; +#pragma version =0.0.0; +#pragma version ^0; +#pragma version <0; +#pragma version >0; +#pragma version <=0; +#pragma version >=0; +#pragma not-version 0; +#pragma not-version 0.0; +#pragma not-version 0.0.0; +#pragma not-version =0; +#pragma not-version =0.0; +#pragma not-version =0.0.0; +#pragma not-version ^0; +#pragma not-version <0; +#pragma not-version >0; +#pragma not-version <=0; +#pragma not-version >=0; + +;; Used in internal tests of FunC in ton-blockchain/ton monorepo +#pragma test-version-set "0.4.4"; diff --git a/src/func/grammar-test/restricted-wallet-code.fc b/src/func/grammar-test/restricted-wallet-code.fc new file mode 100644 index 000000000..a31683406 --- /dev/null +++ b/src/func/grammar-test/restricted-wallet-code.fc @@ -0,0 +1,73 @@ +;; Restricted wallet (a variant of wallet-code.fc) +;; until configuration parameter -13 is set, accepts messages only to elector smc + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +_ restricted?() inline { + var p = config_param(-13); + return null?(p) ? true : begin_parse(p).preload_uint(32) > now(); +} + +_ check_destination(msg, dest) inline_ref { + var cs = msg.begin_parse(); + var flags = cs~load_uint(4); + if (flags & 8) { + ;; external messages are always valid + return true; + } + var (s_addr, d_addr) = (cs~load_msg_addr(), cs~load_msg_addr()); + var (dest_wc, dest_addr) = parse_std_addr(d_addr); + return (dest_wc == -1) & (dest_addr == dest); +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, public_key) = (ds~load_uint(32), ds~load_uint(256)); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + ifnot (msg_seqno) { + accept_message(); + set_data(begin_cell().store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell()); + return (); + } + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + var restrict = restricted?(); + var elector = config_param(1).begin_parse().preload_uint(256); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + var msg = cs~load_ref(); + var ok = true; + if (restrict) { + ok = check_destination(msg, elector); + } + if (ok) { + send_raw_message(msg, mode); + } + } + cs.end_parse(); + set_data(begin_cell().store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32); + return cs.preload_uint(256); +} + +int balance() method_id { + return restricted?() ? 0 : get_balance().pair_first(); +} diff --git a/src/func/grammar-test/restricted-wallet2-code.fc b/src/func/grammar-test/restricted-wallet2-code.fc new file mode 100644 index 000000000..fbad6c092 --- /dev/null +++ b/src/func/grammar-test/restricted-wallet2-code.fc @@ -0,0 +1,88 @@ +;; Restricted wallet (a variant of wallet-code.fc) +;; restricts access to parts of balance until certain dates + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +_ seconds_passed(int start_at, int utime) inline_ref { + ifnot (start_at) { + var p = config_param(-13); + start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32); + } + return start_at ? utime - start_at : -1; +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, public_key, start_at, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_dict()); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + ifnot (msg_seqno) { + accept_message(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(public_key, 256) + .store_uint(start_at, 32) + .store_dict(rdict) + .end_cell()); + return (); + } + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + var ts = seconds_passed(start_at, now()); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); + if (found) { + raw_reserve(value~load_grams(), 2); + } + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + var msg = cs~load_ref(); + send_raw_message(msg, mode); + } + cs.end_parse(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(public_key, 256) + .store_uint(start_at, 32) + .store_dict(rdict) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32); + return cs.preload_uint(256); +} + +int compute_balance_at(int utime) inline_ref { + var ds = get_data().begin_parse().skip_bits(32 + 256); + var (start_at, rdict) = (ds~load_uint(32), ds~load_dict()); + ds.end_parse(); + var ts = seconds_passed(start_at, utime); + var balance = get_balance().pair_first(); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); + if (found) { + balance = max(balance - value~load_grams(), 0); + } + return balance; +} + +int balance_at(int utime) method_id { + return compute_balance_at(utime); +} + +int balance() method_id { + return compute_balance_at(now()); +} diff --git a/src/func/grammar-test/restricted-wallet3-code.fc b/src/func/grammar-test/restricted-wallet3-code.fc new file mode 100644 index 000000000..aab860654 --- /dev/null +++ b/src/func/grammar-test/restricted-wallet3-code.fc @@ -0,0 +1,103 @@ +;; Restricted wallet initialized by a third party (a variant of restricted-wallet2-code.fc) +;; restricts access to parts of balance until certain dates + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +_ seconds_passed(int start_at, int utime) inline_ref { + ifnot (start_at) { + var p = config_param(-13); + start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32); + } + return start_at ? utime - start_at : -1; +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(36, check_signature(slice_hash(in_msg), signature, public_key)); + ifnot (msg_seqno) { + public_key = ds~load_uint(256); ;; load "final" public key + ds.end_parse(); + cs~touch(); + var (start_at, rdict) = (cs~load_uint(32), cs~load_dict()); + cs.end_parse(); + accept_message(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_uint(start_at, 32) + .store_dict(rdict) + .end_cell()); + return (); + } + var (start_at, rdict) = (ds~load_uint(32), ds~load_dict()); + ds.end_parse(); + accept_message(); + var ts = seconds_passed(start_at, now()); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); + if (found) { + raw_reserve(value~load_grams(), 2); + } + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + var msg = cs~load_ref(); + send_raw_message(msg, mode); + } + cs.end_parse(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_uint(start_at, 32) + .store_dict(rdict) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int wallet_id() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32); + return ds.preload_uint(32); +} + +int get_public_key() method_id { + var ds = get_data().begin_parse(); + ds~load_uint(32 + 32); + return ds.preload_uint(256); +} + +int compute_balance_at(int utime) inline_ref { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var (start_at, rdict) = (ds~load_uint(32), ds~load_dict()); + ds.end_parse(); + var ts = seconds_passed(start_at, utime); + var balance = get_balance().pair_first(); + var (_, value, found) = rdict.idict_get_preveq?(32, ts); + if (found) { + balance = max(balance - value~load_grams(), 0); + } + return balance; +} + +int balance_at(int utime) method_id { + return compute_balance_at(utime); +} + +int balance() method_id { + return compute_balance_at(now()); +} diff --git a/src/func/grammar-test/simple-wallet-code.fc b/src/func/grammar-test/simple-wallet-code.fc new file mode 100644 index 000000000..a43b8b929 --- /dev/null +++ b/src/func/grammar-test/simple-wallet-code.fc @@ -0,0 +1,25 @@ +;; Simple wallet smart contract + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + int msg_seqno = cs~load_uint(32); + var cs2 = begin_parse(get_data()); + var stored_seqno = cs2~load_uint(32); + var public_key = cs2~load_uint(256); + cs2.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + if (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + cs.end_parse(); + set_data(begin_cell().store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell()); +} diff --git a/src/func/grammar-test/simple-wallet-ext-code.fc b/src/func/grammar-test/simple-wallet-ext-code.fc new file mode 100644 index 000000000..fb43e8332 --- /dev/null +++ b/src/func/grammar-test/simple-wallet-ext-code.fc @@ -0,0 +1,68 @@ +;; Simple wallet smart contract + +cell create_state(int seqno, int public_key) { + return begin_cell().store_uint(seqno, 32).store_uint(public_key, 256).end_cell(); +} + +(int, int) load_state() { + var cs2 = begin_parse(get_data()); + return (cs2~load_uint(32), cs2~load_uint(256)); +} + +() save_state(int seqno, int public_key) impure { + set_data(create_state(seqno, public_key)); +} + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +slice do_verify_message(slice in_msg, int seqno, int public_key) { + var signature = in_msg~load_bits(512); + var cs = in_msg; + int msg_seqno = cs~load_uint(32); + throw_unless(33, msg_seqno == seqno); + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + return cs; +} + +() recv_external(slice in_msg) impure { + (int stored_seqno, int public_key) = load_state(); + var cs = do_verify_message(in_msg, stored_seqno, public_key); + accept_message(); + cs~touch(); + if (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + cs.end_parse(); + save_state(stored_seqno + 1, public_key); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var (seqno, public_key) = load_state(); + return public_key; +} + +cell create_init_state(int public_key) method_id { + return create_state(0, public_key); +} + +cell prepare_send_message_with_seqno(int mode, cell msg, int seqno) method_id { + return begin_cell().store_uint(seqno, 32).store_uint(mode, 8).store_ref(msg).end_cell(); +} + +cell prepare_send_message(int mode, cell msg) method_id { + return prepare_send_message_with_seqno(mode, msg, seqno()); +} + +slice verify_message(slice msg) method_id { + var (stored_seqno, public_key) = load_state(); + return do_verify_message(msg, stored_seqno, public_key); +} diff --git a/src/func/grammar-test/statements.fc b/src/func/grammar-test/statements.fc new file mode 100644 index 000000000..9cbe3a5b9 --- /dev/null +++ b/src/func/grammar-test/statements.fc @@ -0,0 +1,88 @@ +;; return +() return_stmt() { return (); } +int return_stmt'() { return 42; } + +;; Block +() block_stmt() { + { + { return (); } + } +} + +;; Empty +() empty_stmt() { ; + ; ; ; ; +} + +;; Condition +() cond_stmt() { + if 1 { } + ifnot 0 { } + + if 1 { ; ; } + ifnot 0 { ; ; } + + if 0 { ; ; } else { ; ; } + ifnot 1 { ; ; } else { ; ; } + + if 0 { } elseif 1 { } + if 0 { } elseifnot 0 { } + ifnot 1 { } elseif 1 { } + ifnot 1 { } elseifnot 0 { } + + if 0 { ; ; } elseif 1 { ; ; } + if 0 { ; ; } elseifnot 0 { ; ; } + ifnot 1 { ; ; } elseif 1 { ; ; } + ifnot 1 { ; ; } elseifnot 0 { ; ; } + + if 0 { ; ; } elseif 0 { ; ; } else { ; ; } + ifnot 1 { ; ; } elseifnot 1 { ; ; } else { ; ; } +} + +;; repeat +() repeat_stmt() { + repeat (1) { } + + repeat 1 { + ; ; ; + } +} + +;; do...until +() until_stmt() { + do { } until 0; + + do { + ; ; ; + { repeat 1 { } } + } until 0; +} + +;; while +() while_stmt() { + while 0 { } + + while 0 { + ; ; ; + { repeat 1 { } } + } +} + +;; try...catch +() try_catch_stmt() { + try { + ; ; ; + { } + } catch (_, _) { + ; ; ; + { } + } + + try { } catch (_) { } + try { } catch (exception, exit_code) { } +} + +;; Expression (most of the related tests are in another file) +() expression_stmt() { + 42; +} diff --git a/src/func/grammar-test/stdlib.fc b/src/func/grammar-test/stdlib.fc new file mode 100644 index 000000000..978b94738 --- /dev/null +++ b/src/func/grammar-test/stdlib.fc @@ -0,0 +1,639 @@ +;; Standard library for funC +;; + +{- + This file is part of TON FunC Standard Library. + + FunC Standard 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. + + FunC Standard 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. + +-} + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # 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()]). + 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) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_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) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) 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) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) 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) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() 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() 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() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() 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) 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) 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) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; 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) 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) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; 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) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure 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() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure 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() impure 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) impure 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) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, 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() impure 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) impure 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() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; 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) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; 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) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) 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) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) 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) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) 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) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) 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) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) 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) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) 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) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) 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`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) 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) 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) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; 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) 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) 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) 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) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; 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) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) 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) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) 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() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) 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) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; 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) impure 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) impure 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) impure 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) impure 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() impure 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) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure 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) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/src/func/grammar-test/wallet-code.fc b/src/func/grammar-test/wallet-code.fc new file mode 100644 index 000000000..9d4cddb71 --- /dev/null +++ b/src/func/grammar-test/wallet-code.fc @@ -0,0 +1,37 @@ +;; Simple wallet smart contract + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, public_key) = (ds~load_uint(32), ds~load_uint(256)); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + cs.end_parse(); + set_data(begin_cell().store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(32); + return cs.preload_uint(256); +} diff --git a/src/func/grammar-test/wallet3-code.fc b/src/func/grammar-test/wallet3-code.fc new file mode 100644 index 000000000..964b35cda --- /dev/null +++ b/src/func/grammar-test/wallet3-code.fc @@ -0,0 +1,41 @@ +;; Simple wallet smart contract + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(35, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + cs~touch(); + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse(); + cs~load_uint(64); + return cs.preload_uint(256); +} diff --git a/src/func/grammar.ohm b/src/func/grammar.ohm new file mode 100644 index 000000000..9586f188b --- /dev/null +++ b/src/func/grammar.ohm @@ -0,0 +1,346 @@ +FunC { + Module = ModuleItem* + + // + // Top-level, module items (with compiler directives) + // + + ModuleItem = Pragma + | Include + | GlobalVariablesDeclaration + | ConstantsDefinition + | AsmFunctionDefinition + | FunctionDeclaration + | FunctionDefinition + + // + // Compiler pragmas and includes + // + + Pragma = "#pragma" ("allow-post-modification" | "compute-asm-ltr") ";" --literal + | "#pragma" ("version" | "not-version") versionRange ";" --versionRange + | "#pragma" "test-version-set" stringLiteral ";" --versionString + + Include = "#include" stringLiteral ";" + + // + // Declarations of global variables + // + + GlobalVariablesDeclaration = "global" NonemptyListOf ";" + GlobalVariableDeclaration = TypeGlob? id + + TypeGlob = TypeBuiltinGlob ("->" TypeGlob)? + + TypeBuiltinGlob = #("int" | "cell" | "slice" | "builder" | "cont" | "tuple" | hole) ~#rawId --simple + | unit | tupleEmpty | TensorGlob | TupleGlob + + TensorGlob = "(" NonemptyListOf ")" + TupleGlob = "[" NonemptyListOf "]" + + // + // Definitions of constants + // + + ConstantsDefinition = "const" NonemptyListOf ";" + ConstantDefinition = ("slice" | "int")? id "=" Expression + + // + // Definitions of asm functions + // + + AsmFunctionDefinition = FunctionCommonPrefix "asm" AsmArrangement? stringLiteral+ ";" + + AsmArrangement = "(" "->" &#whiteSpace integerLiteralDec+ ")" --returns + | "(" id+ ~"->" ")" --arguments + | "(" id+ &#whiteSpace "->" &#whiteSpace integerLiteralDec+ ")" --argumentsToReturns + + // + // Function declarations, definitions and common prefix of their syntax + // + + FunctionDeclaration = FunctionCommonPrefix ";" + FunctionDefinition = FunctionCommonPrefix "{" Statement* "}" + + // (not a separate thing, added purely for convenience) + FunctionCommonPrefix = Forall? TypeReturn functionId Parameters FunctionAttribute* + + // forall type1, ..., typeN -> + Forall = "forall" &#whiteSpace NonemptyListOf &#whiteSpace "->" &#whiteSpace + TypeVar = ("type" &#whiteSpace)? id + + // Function return types + TypeReturn = TypeBuiltinReturn (&#whiteSpace "->" &#whiteSpace TypeReturn)? + + TypeBuiltinReturn = id + | "int" | "cell" | "slice" | "builder" | "cont" | "tuple" + | "_" | unit | tupleEmpty | TensorReturn | TupleReturn + + TensorReturn = "(" NonemptyListOf ")" + TupleReturn = "[" NonemptyListOf "]" + + // Function parameters (upper is defined in Ohm's built-in rules) + Parameters = "(" ListOf ")" + Parameter = TypeParameter (unusedId | functionId)? --regular + | functionId --inferredType + | "_" --hole + + // Function parameter types + TypeParameter = TypeBuiltinParameter (&#whiteSpace "->" &#whiteSpace TypeParameter)? + + TypeBuiltinParameter = upperId + | "int" | "cell" | "slice" | "builder" | "cont" | "tuple" + | hole | unit | tupleEmpty | TensorParameter | TupleParameter + + TensorParameter = "(" NonemptyListOf ")" + TupleParameter = "[" NonemptyListOf "]" + + // Called "specifiers" in https://docs.ton.org/develop/func/functions#specifiers + FunctionAttribute = "impure" | "inline_ref" | "inline" | MethodIdValue | "method_id" + MethodIdValue = "method_id" "(" integerLiteral ")" --int + | "method_id" "(" stringLiteral ")" --string + + // + // Statements (with mandatory whitespace padding in most places) + // + + Statement = StatementReturn + | StatementBlock + | StatementEmpty + | StatementCondition + | StatementRepeat + | StatementUntil + | StatementWhile + | StatementTryCatch + | StatementExpression + + StatementReturn = "return" Expression ";" + + StatementBlock = "{" Statement* "}" + + StatementEmpty = ";" + + StatementCondition = ("ifnot" | "if") Expression "{" Statement* "}" + ("elseifnot" | "elseif") Expression "{" Statement* "}" + ElseBlock? --elseif + | ("ifnot" | "if") Expression "{" Statement* "}" + ElseBlock? --if + + // (not a separate statement, added purely for convenience) + ElseBlock = "else" "{" Statement* "}" + + StatementRepeat = "repeat" Expression "{" Statement* "}" + + StatementUntil = "do" "{" Statement* "}" "until" Expression ";" + + StatementWhile = "while" Expression "{" Statement* "}" + + StatementTryCatch = "try" + "{" Statement* "}" + "catch" "(" CatchClauseContents ")" + "{" Statement* "}" + + // (not a separate thing, added purely for convenience) + CatchClauseContents = (unusedId | id) "," (unusedId | id) --both + | unusedId --unused + + StatementExpression = Expression ";" + + // + // Expressions, ordered by precedence (from lowest to highest), + // with comments referencing exact function names in C++ code of FunC's parser: + // https://github.com/ton-blockchain/ton/blob/master/crypto/func/parse-func.cpp + // + + // parse_expr + Expression = ExpressionAssign + + // parse_expr10 + ExpressionAssign = ExpressionConditional + &#whiteSpace ("=" ~#"=" | "+=" | "-=" | "*=" | "/=" | "%=" + | "~/=" | "~%=" | "^/=" | "^%=" | "&=" + | "|=" | "^=" | "<<=" | ">>=" | "~>>=" | "^>>=") &#whiteSpace + ExpressionAssign --op + | ExpressionConditional + + // parse_expr13 + ExpressionConditional = ExpressionCompare + &#whiteSpace "?" &#whiteSpace Expression + &#whiteSpace ":" &#whiteSpace ExpressionConditional --ternary + | ExpressionCompare + + // parse_expr15 + ExpressionCompare = ExpressionBitwiseShift + &#whiteSpace ("==" | "<=>" | "<=" | "<" | ">=" | ">" | "!=") &#whiteSpace + ExpressionBitwiseShift --op + | ExpressionBitwiseShift + + // parse_expr17 + ExpressionBitwiseShift = ExpressionAddBitwise + (&#whiteSpace ("<<" | ">>" | "~>>" | "^>>") &#whiteSpace + ExpressionAddBitwise)+ --ops + | ExpressionAddBitwise + + // parse_expr20 + ExpressionAddBitwise = ("-" &#whiteSpace)? + ExpressionMulBitwise + (&#whiteSpace ("+" | "-" | "|" | "^") &#whiteSpace + ExpressionMulBitwise)+ --ops + | "-" &#whiteSpace ExpressionMulBitwise --negate + | ExpressionMulBitwise + + // parse_expr30 + ExpressionMulBitwise = ExpressionUnary + (&#whiteSpace ("*" | "/%" | "/" | "%" + | "~/" | "~%" | "^/" | "^%" | "&") &#whiteSpace + ExpressionUnary)+ --ops + | ExpressionUnary + + // parse_expr75 + ExpressionUnary = "~" &#whiteSpace ExpressionMethod --bitwiseNot + | ExpressionMethod + + // parse_expr80 + ExpressionMethod = ExpressionVarFun (methodId ExpressionTensor)+ --calls + | ExpressionVarFun + + // parse_expr90 + ExpressionVarFun = | ExpressionVarDecl | ExpressionFunCall | ExpressionPrimary + + // Variable declarations + ExpressionVarDecl = TypeVarDecl ExpressionVarDeclPart + + // (not a separate expression, added purely for convenience) + ExpressionVarDeclPart = id + | unusedId + | ExpressionTensorVarDecl + | ExpressionTupleVarDecl + + ExpressionTensorVarDecl = "(" NonemptyListOf ")" + ExpressionTupleVarDecl = "[" NonemptyListOf "]" + // (not a separate expression, added purely for convenience) + IdOrUnusedId = id | unusedId + + // Function calls + ExpressionFunCall = &("(" | functionId) ExpressionPrimary ExpressionTensor + + // parse_expr100 + ExpressionPrimary = unit + | ExpressionTensor + | tupleEmpty + | ExpressionTuple + | integerLiteral + | stringLiteral + | functionId + | unusedId + + ExpressionTensor = "(" ListOf ")" + ExpressionTuple = "[" ListOf "]" + + // Variable declaration types + TypeVarDecl = TypeBuiltinVarDecl (&#whiteSpace "->" &#whiteSpace TypeVarDecl)? + + TypeBuiltinVarDecl = #("int" | "cell" | "slice" | "builder" | "cont" | "tuple" | hole) ~#rawId --simple + | unit | tupleEmpty | TensorVarDecl | TupleVarDecl + // TODO: return `| id`, differentiate between function calls and variable declarations during syntax analysis and AST construction (through careful use of declared type variables in `forall`) + // NOTE: this can be easily added using the same heuristic as in parameters -- upperId, because type variables are advised to be named starting with an uppercase letter. + + TensorVarDecl = "(" NonemptyListOf ")" + TupleVarDecl = "[" NonemptyListOf "]" + + // + // Lexical rules, see: https://ohmjs.org/docs/syntax-reference#syntactic-lexical + // + + // Special types or values + hole = ("_" | "var") ~rawId + unit = "(" whiteSpace* ")" + // (not a separate type, added purely for convenience) + tupleEmpty = "[" whiteSpace* "]" + primitive = "int" | "cell" | "slice" | "builder" | "cont" | "tuple" + + // Operators and delimiters, order matters + operator = "!=" | "?" | ":" + | "%=" | "%" + | "&=" | "&" + | "*=" | "*" + | "+=" | "+" + | "-=" | "-" + | "/%" | "/=" | "/" + | "<=>" + | "<<=" | "<<" | "<=" | "<" + | "==" | "=" + | ">>=" | ">>" | ">=" | ">" + | "^>>=" | "^>>" | "^=" | "^/=" | "^/" | "^%=" | "^%" | "^" + | "|=" | "|" + | "~>>=" | "~>>" | "~/=" | "~/" | "~%" | "~" + delimiter = "->" | "{" | "}" + + // Function identifiers + functionId = ~("\"" | "{-") ("." | "~")? ~((hole | integerLiteral | delimiter | operator | primitive) ~rawId) rawId + + // Method identifiers (not a separate type, added purely for convenience) + methodId = ~("\"" | "{-") ("." | "~") ~((hole | integerLiteral | delimiter | operator | primitive) ~rawId) rawId + + // Uppercase plain identifiers (common for type variables) + upperId = &upper plainId + + // Identifiers, with invalidation (keywords, numbers, etc.) during syntax analysis + // — this makes this parser closer to the C++ one, and also improves error messages + id = ~("\"" | "{-" | "." | "~") ~((hole | integerLiteral | delimiter | operator | primitive) ~rawId) rawId + + // Contents of any identifiers + rawId = quotedId | operatorId | plainId + + // Kinds + quotedId = "`" (~("`" | "\n") any)+ "`" + operatorId = "^"? "_" operator "_" --common + | "~_" --not + plainId = (~(whiteSpace | "(" | ")" | "[" | "]" | "," | "." | ";" | "~") any)+ + + // Unused identifiers + unusedId = "_" + + /* + FunC can parse much more than Fift can handle. For example, _0x0 and _0 are valid identifiers in FunC, and using either of them compiles and is then interpreted fine by Fift. But if you use both, FunC still compiles, but Fift crashes. + + Same goes for plain identifiers using hashes # or emojis — you can have one FunC function with any of those combinations of characters, but you (generally) cannot have two or more of such functions. + */ + + // Version ranges + versionRange = ("=" | "^" | "<=" | ">=" | "<" | ">")? integerLiteralDec ("." integerLiteralDec)? ("." integerLiteralDec)? + + // Integers + integerLiteral = "-"? integerLiteralNonNegative + integerLiteralNonNegative = integerLiteralHex | integerLiteralDec + + // hexDigit is defined in Ohm's built-in rules as: hexDigit = "0".."9" | "a".."f" | "A".."F" + integerLiteralHex = "0x" hexDigit+ + + // digit is defined in Ohm's built-in rules as: digit = "0".."9" + integerLiteralDec = digit+ + + // Strings + stringLiteral = "\"\"\"" (~"\"\"\"" any)* "\"\"\"" stringType? --multiLine + | "\"" (~"\"" any)* "\"" stringType? --singleLine + + stringType = "s" --sliceHex + | "a" --sliceInternalAddress + | "u" --intHex + | "h" --int32Sha256 + | "H" --int256Sha256 + | "c" --intCrc32 + + // Whitespace + space += comment | lineTerminator + whiteSpace = "\t" | " " | lineTerminator + + // Comments + comment = ";;" (~lineTerminator any)* --singleLine + | multiLineComment --multiLine + + // Nested multi-line comments + multiLineComment = "{-" (~("-}" | "{-") any)* multiLineComment? (~"-}" any)* "-}" + + lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" +} diff --git a/src/func/grammar.spec.ts b/src/func/grammar.spec.ts new file mode 100644 index 000000000..3629a3ae4 --- /dev/null +++ b/src/func/grammar.spec.ts @@ -0,0 +1,48 @@ +import { FuncParseError, FuncSyntaxError, match, parseFile } from "./grammar"; +import { loadCases } from "../utils/loadCases"; + +describe("FunC grammar and parser", () => { + beforeEach(() => {}); + const ext = "fc"; + let matchedAll = true; + + // Checking that valid FunC files match the grammar + for (const r of loadCases(__dirname + "/grammar-test/", ext)) { + it(r.name + " should match the grammar", () => { + const res = match(r.code); + + if (res.ok === false) { + matchedAll = false; + console.log(res.message, res.interval.getLineAndColumn()); + } + + expect(res.ok).toStrictEqual(true); + }); + } + + // If didn't match the grammar, don't throw any more errors from full parse + if (!matchedAll) { + return; + } + + // Checking that valid FunC files parse + for (const r of loadCases(__dirname + "/grammar-test/", ext)) { + it("should parse " + r.name, () => { + let parsed: Object | undefined; + try { + parsed = parseFile(r.code, r.name + `.${ext}`); + } finally { + expect(parsed).not.toBe(undefined); + } + }); + } + + // Checking that invalid FunC files does NOT parse + for (const r of loadCases(__dirname + "/grammar-test-failed/", ext)) { + it("should NOT parse " + r.name, () => { + expect(() => + parseFile(r.code, r.name + `.${ext}`), + ).toThrowErrorMatchingSnapshot(); + }); + } +}); diff --git a/src/func/grammar.ts b/src/func/grammar.ts new file mode 100644 index 000000000..c665657a4 --- /dev/null +++ b/src/func/grammar.ts @@ -0,0 +1,2821 @@ +import { + Interval as RawInterval, + IterationNode, + MatchResult, + grammar, + Grammar, + Node, +} from "ohm-js"; +import path from "path"; +import { cwd } from "process"; +import FuncGrammar from "./grammar.ohm-bundle"; + +// +// Utility declarations and definitions +// + +/** Currently processed file */ +let currentFile: string | undefined; + +/** + * Information about source code location (file and interval within it) + * and the source code contents. + */ +export class FuncSrcInfo { + readonly #interval: RawInterval; + readonly #file: string | undefined; + + constructor(interval: RawInterval, file: string | undefined) { + this.#interval = interval; + this.#file = file; + } + + get file() { + return this.#file; + } + + get contents() { + return this.#interval.contents; + } + + get interval() { + return this.#interval; + } +} + +// Dummy definitions needed to generate AST programmatically. +const DummyGrammar: Grammar = grammar("Dummy { DummyRule = any }"); +const DUMMY_INTERVAL = DummyGrammar.match("").getInterval(); +export const dummySrcInfo: FuncSrcInfo = new FuncSrcInfo( + DUMMY_INTERVAL, + undefined, +); + +/** + * Generic FunC error in FunC parser + */ +export class FuncError extends Error { + readonly loc: FuncSrcInfo; + + constructor(message: string, loc: FuncSrcInfo) { + super(message); + this.loc = loc; + } +} + +/** + * FunC parse error, which generally occurs when either the sources didn't match the grammar, or the AST couldn't be constructed + */ +export class FuncParseError extends FuncError { + constructor(message: string, loc: FuncSrcInfo) { + super(message, loc); + } +} + +/** + * FunC syntax error, which occurs when the AST couldn't be constructed based on the obtained parse results + */ +export class FuncSyntaxError extends FuncError { + constructor(message: string, loc: FuncSrcInfo) { + super(message, loc); + } +} + +/** + * Constructs a location string based on the `sourceInfo` + */ +function locationStr(sourceInfo: FuncSrcInfo): string { + if (sourceInfo.file === undefined) { + return ""; + } + + const loc = sourceInfo.interval.getLineAndColumn() as { + lineNum: number; + colNum: number; + }; + const file = path.relative(cwd(), sourceInfo.file); + return `${file}:${loc.lineNum}:${loc.colNum}: `; +} + +/** + * Throws a FunC parse error occurred in the given `path` file + */ +export function throwFuncParseError( + matchResult: MatchResult, + path: string | undefined, +): never { + const interval = matchResult.getInterval(); + const source = new FuncSrcInfo(interval, path); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = `Parse error: expected ${(matchResult as any).getExpectedText()}\n`; + throw new FuncParseError( + `${locationStr(source)}${message}\n${interval.getLineAndColumnMessage()}`, + source, + ); +} + +/** + * Throws a FunC syntax error occurred with the given `source` + */ +// TODO(jubnzv): Move to `errors.ts` for consistency +export function throwFuncSyntaxError( + message: string, + source: FuncSrcInfo, +): never { + throw new FuncSyntaxError( + `${locationStr(source)}${message}\n${source.interval.getLineAndColumnMessage()}`, + source, + ); +} + +/** + * Temporarily sets `currentFile` to `path`, calls a callback function, then resets `currentValue` + * Returns a value produced by a callback function call + */ +export function inFile(path: string, callback: () => T) { + currentFile = path; + const r = callback(); + currentFile = undefined; + return r; +} + +/** + * Creates a `FuncSrcInfo` reference to the Node `s` and `currentFile` + */ +export function createSrcInfo(s: Node) { + return new FuncSrcInfo(s.source, currentFile); +} + +/** + * Unwraps optional grammar elements (marked with "?"), + * since ohm-js represents those as lists (IterationNodes) + */ +function unwrapOptNode( + optional: IterationNode, + f: (n: Node) => T, +): T | undefined { + const optNode = optional.children[0] as Node | undefined; + return optNode !== undefined ? f(optNode) : undefined; +} + +const funcBuiltinOperatorFunctions = [ + "_+_", + "_-_", + "-_", + "_*_", + "_/_", + "_~/_", + "_^/_", + "_%_", + "_~%_", + "_^%_", + "_/%_", + "_<<_", + "_>>_", + "_~>>_", + "_^>>_", + "_&_", + "_|_", + "_^_", + "~_", + "^_+=_", + "^_-=_", + "^_*=_", + "^_/=_", + "^_~/=_", + "^_^/=_", + "^_%=_", + "^_~%=_", + "^_^%=_", + "^_<<=_", + "^_>>=_", + "^_~>>=_", + "^_^>>=_", + "^_&=_", + "^_|=_", + "^_^=_", + "_==_", + "_!=_", + "_<_", + "_>_", + "_<=_", + "_>=_", + "_<=>_", +]; + +export const funcBuiltinFunctions = [ + "divmod", + "moddiv", + "muldiv", + "muldivr", + "muldivc", + "muldivmod", + "null?", + "throw", + "throw_if", + "throw_unless", + "throw_arg", + "throw_arg_if", + "throw_arg_unless", + "load_int", + "load_uint", + "preload_int", + "preload_uint", + "store_int", + "store_uint", + "load_bits", + "preload_bits", + "int_at", + "cell_at", + "slice_at", + "tuple_at", + "at", + "touch", + "touch2", + "run_method0", + "run_method1", + "run_method2", + "run_method3", +]; + +const funcBuiltinMethods = [ + "~divmod", + "~moddiv", + "~store_int", + "~store_uint", + "~touch", + "~touch2", + "~dump", + "~stdump", +]; + +const funcBuiltinConstants = ["true", "false", "nil", "Nil"]; + +const funcKeywords = [ + "extern", + "global", + "asm", + "impure", + "inline_ref", + "inline", + "auto_apply", + "method_id", + "operator", + "infixl", + "infixr", + "infix", + "const", +]; + +const funcControlKeywords = [ + "return", + "var", + "repeat", + "do", + "while", + "until", + "try", + "catch", + "ifnot", + "if", + "then", + "elseifnot", + "elseif", + "else", +]; + +const funcTypeKeywords = [ + "int", + "cell", + "slice", + "builder", + "cont", + "tuple", + "type", + "forall", +]; + +const funcDirectives = ["#include", "#pragma"]; + +const funcDelimiters = ["->", "{", "}", ",", ".", ";"]; + +const funcOperators = [ + "!=", + "?", + ":", + "%=", + "%", + "&=", + "&", + "*=", + "*", + "+=", + "+", + "-=", + "-", + "/%", + "/=", + "/", + "<=>", + "<<=", + "<<", + "<=", + "<", + "==", + "=", + ">>=", + ">>", + ">=", + ">", + "^>>=", + "^>>", + "^=", + "^/=", + "^/", + "^%=", + "^%", + "^", + "|=", + "|", + "~>>=", + "~>>", + "~/=", + "~/", + "~%", + "~", +]; + +const funcDecIntRegex = /^\-?[0-9]+$/; + +const funcHexIntRegex = /^\-?0x[0-9a-fA-F]+$/; + +/** + * Checks that the given identifier (including the prefix in case of methodId) + * can be used in declarations/definitions, i.e. it's: + * - NOT a builtin operator function + * - NOT a builtin function + * - NOT a builtin method + * - NOT a builtin constant + * - NOT an underscore (unless it's a parameter or variable declaration) + */ +function checkDeclaredId( + ident: string, + loc: FuncSrcInfo, + altPrefix?: string, + allowUnused?: boolean, +): void | never { + // not an operatorId + if (funcBuiltinOperatorFunctions.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Declared identifier"} cannot shadow or override a builtin operator function`, + loc, + ); + } + + if (funcBuiltinFunctions.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Declared identifier"} cannot shadow or override a builtin function`, + loc, + ); + } + + if (funcBuiltinMethods.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Declared identifier"} cannot shadow or override a builtin method`, + loc, + ); + } + + if (funcBuiltinConstants.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Declared identifier"} cannot shadow or override a builtin constant`, + loc, + ); + } + + // not an unusedId + if (allowUnused !== true && ident === "_") { + throwFuncSyntaxError( + `${altPrefix ?? "Declared identifier"} cannot be an underscore`, + loc, + ); + } +} + +/** + * Checks that the given identifier is a valid operatorId, i.e. that it actually exists on the list of builtin operator functions + * Unlike other checking functions it doesn't throw, but returns `true`, if identifier is a valid operatorId, and `false` otherwise + */ +function checkOperatorId(ident: string): boolean { + if (funcBuiltinOperatorFunctions.includes(ident)) { + return true; + } + return false; +} + +/** + * Checks that the given identifier is a valid plainId, i.e. it's: + * - NOT a keyword + * - NOT a control keyword + * - NOT a type keyword + * - NOT a directive + * - NOT a delimiter + * - NOT an operator (without underscores, like operatorId) + * - NOT a number + */ +function checkPlainId( + ident: string, + loc: FuncSrcInfo, + altPrefix?: string, +): void | never { + if (funcKeywords.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be a keyword`, + loc, + ); + } + + if (funcControlKeywords.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be a control keyword`, + loc, + ); + } + + if (funcTypeKeywords.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be a type keyword`, + loc, + ); + } + + if (funcDirectives.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be a compiler directive`, + loc, + ); + } + + if (funcDelimiters.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be a delimiter`, + loc, + ); + } + + if (funcOperators.includes(ident)) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be an operator`, + loc, + ); + } + + if ( + ident.match(funcDecIntRegex) !== null || + ident.match(funcHexIntRegex) !== null + ) { + throwFuncSyntaxError( + `${altPrefix ?? "Identifier"} cannot be an integer literal`, + loc, + ); + } +} + +/** + * Checks that the given identifier is a valid methodId, i.e. it starts with either . or ~ and has some characters after + */ +function checkMethodId(ident: string, loc: FuncSrcInfo): void | never { + if (!(ident.startsWith(".") || ident.startsWith("~"))) { + throwFuncSyntaxError("Identifier doesn't start with ~ or .", loc); + } + + if (ident.length === 1) { + throwFuncSyntaxError("Method identifier cannot be just ~ or .", loc); + } +} + +// +// Types used to construct the AST of FunC +// Those mostly match the grammar.ohm, albeit with some minor optimizations for clarity and ease of use +// + +export type FuncAstNode = + | FuncAstModule + | FuncAstModuleItem + | FuncAstStatement + | FuncAstExpression + | FuncAstId + | FuncAstComment; + +export type FuncAstModule = { + kind: "module"; + items: FuncAstModuleItem[]; + loc: FuncSrcInfo; +}; + +// +// Compiler pragmas and includes +// + +/** + * #pragma ...; + */ +export type FuncAstPragma = + | FuncAstPragmaLiteral + | FuncAstPragmaVersionRange + | FuncAstPragmaVersionString; + +/** + * #pragma something-something-something; + */ +export type FuncAstPragmaLiteral = { + kind: "pragma_literal"; + literal: FuncPragmaLiteralValue; + loc: FuncSrcInfo; +}; + +export type FuncPragmaLiteralValue = + | "allow-post-modification" + | "compute-asm-ltr"; + +/** + * `allow` — if set to `true` corresponds to version enforcement + * `allow` — if set to `false` corresponds to version prohibiting (or not-version enforcement) + * + * #pragma (version | not-version) semverRange; + */ +export type FuncAstPragmaVersionRange = { + kind: "pragma_version_range"; + allow: boolean; + range: FuncAstVersionRange; + loc: FuncSrcInfo; +}; + +/** + * #pragma test-version-set "exact.version.semver"; + */ +export type FuncAstPragmaVersionString = { + kind: "pragma_version_string"; + version: FuncAstStringLiteral; + loc: FuncSrcInfo; +}; + +/** + * #include "path/to/file"; + */ +export type FuncAstInclude = { + kind: "include"; + path: FuncAstStringLiteral; + loc: FuncSrcInfo; +}; + +// +// Top-level, module items +// + +export type FuncAstModuleItem = + | FuncAstComment + | FuncAstPragma + | FuncAstInclude + | FuncAstGlobalVariablesDeclaration + | FuncAstConstantsDefinition + | FuncAstAsmFunctionDefinition + | FuncAstFunctionDeclaration + | FuncAstFunctionDefinition; + +/** + * global ..., ...; + */ +export type FuncAstGlobalVariablesDeclaration = { + kind: "global_variables_declaration"; + globals: FuncAstGlobalVariable[]; + loc: FuncSrcInfo; +}; + +/** + * Note, that the type here cannot be polymorphic, i.e. a type variable + * + * nonVarType? (quotedId | plainId) + */ +export type FuncAstGlobalVariable = { + kind: "global_variable"; + ty: FuncAstType | undefined; + name: FuncAstQuotedId | FuncAstPlainId; + loc: FuncSrcInfo; +}; + +/** + * const ..., ...; + */ +export type FuncAstConstantsDefinition = { + kind: "constants_definition"; + constants: FuncAstConstant[]; + loc: FuncSrcInfo; +}; + +/** + * (slice | int)? (quotedId | plainId) = Expression + */ +export type FuncAstConstant = { + kind: "constant"; + ty: FuncConstantType | undefined; + name: FuncAstQuotedId | FuncAstPlainId; + value: FuncAstExpression; + loc: FuncSrcInfo; +}; + +export type FuncConstantType = "slice" | "int"; + +/** + * Note, that name cannot be an unusedId + * + * Forall? TypeReturn functionId Parameters FunctionAttribute* "asm" AsmArrangement? stringLiteral+; + */ +export type FuncAstAsmFunctionDefinition = { + kind: "asm_function_definition"; + forall: FuncAstForall | undefined; + returnTy: FuncAstType; + name: FuncAstMethodId | FuncAstQuotedId | FuncAstPlainId; + parameters: FuncAstParameter[]; + attributes: FuncAstFunctionAttribute[]; + arrangement: FuncAstAsmArrangement | undefined; + asmStrings: FuncAstStringLiteral[]; + loc: FuncSrcInfo; +}; + +/** + * Notice, that integers must be unsigned and decimal + * Notice, that either arguments, returns or both must be defined, i.e. () is prohibited + * + * (id+) + * or + * (-> integerLiteralDec+) + * or + * (id+ -> integerLiteralDec+) + */ +export type FuncAstAsmArrangement = { + kind: "asm_arrangement"; + arguments: FuncAstId[] | undefined; + returns: FuncAstIntegerLiteral[] | undefined; + loc: FuncSrcInfo; +}; + +/** + * Note, that name cannot be an unusedId + * + * Forall? TypeReturn functionId Parameters FunctionAttribute*; + */ +export type FuncAstFunctionDeclaration = { + kind: "function_declaration"; + forall: FuncAstForall | undefined; + returnTy: FuncAstType; + name: FuncAstMethodId | FuncAstQuotedId | FuncAstPlainId; + parameters: FuncAstParameter[]; + attributes: FuncAstFunctionAttribute[]; + loc: FuncSrcInfo; +}; + +/** + * Note, that name cannot be an unusedId + * + * Forall? TypeReturn functionId Parameters FunctionAttribute* { ... } + */ +export type FuncAstFunctionDefinition = { + kind: "function_definition"; + forall: FuncAstForall | undefined; + returnTy: FuncAstType; + name: FuncAstMethodId | FuncAstQuotedId | FuncAstPlainId; + parameters: FuncAstParameter[]; + attributes: FuncAstFunctionAttribute[]; + statements: FuncAstStatement[]; + loc: FuncSrcInfo; +}; + +/** + * forall (type? typeName1, type? typeName2, ...) -> + */ +export type FuncAstForall = { + kind: "forall"; + tyVars: FuncAstTypeVar[]; + loc: FuncSrcInfo; +}; + +/** + * Note, that the "type" keyword prior to identifier can only occur in `forall` declarations + * + * "type"? id + */ +export type FuncAstTypeVar = { + kind: "type_var"; + keyword: boolean; + name: FuncAstId; + loc: FuncSrcInfo; +}; + +/** + * Note, that id can be an underscore only if type is defined + * + * Type id? + */ +export type FuncAstParameter = { + kind: "parameter"; + ty: FuncAstType | undefined; + name: + | FuncAstMethodId + | FuncAstQuotedId + | FuncAstPlainId + | FuncAstUnusedId + | undefined; + loc: FuncSrcInfo; +}; + +/** + * Called "specifiers" in https://docs.ton.org/develop/func/functions#specifiers + * + * impure | inline_ref | inline | method_id ("(" Integer | String ")")? + */ +export type FuncAstFunctionAttribute = + | { kind: "impure"; loc: FuncSrcInfo } + | { kind: "inline_ref"; loc: FuncSrcInfo } + | { kind: "inline"; loc: FuncSrcInfo } + | { + kind: "method_id"; + value: FuncAstIntegerLiteral | FuncAstStringLiteral | undefined; + loc: FuncSrcInfo; + }; + +// +// Statements +// + +export type FuncAstStatement = + | FuncAstStatementReturn + | FuncAstStatementBlock + | FuncAstStatementEmpty + | FuncAstStatementCondition + | FuncAstStatementRepeat + | FuncAstStatementUntil + | FuncAstStatementWhile + | FuncAstStatementTryCatch + | FuncAstStatementExpression + | FuncAstCR + | FuncAstComment; + +/** + * return Expression; + */ +export type FuncAstStatementReturn = { + kind: "statement_return"; + expression: FuncAstExpression | undefined; + loc: FuncSrcInfo; +}; + +/** + * { ... } + */ +export type FuncAstStatementBlock = { + kind: "statement_block"; + statements: FuncAstStatement[]; + loc: FuncSrcInfo; +}; + +/** + * ; + */ +export type FuncAstStatementEmpty = { + kind: "statement_empty"; + loc: FuncSrcInfo; +}; + +/** + * (if | ifnot) Expression { ... } + * (else { ... })? + * + * or + * + * (if | ifnot) Expression { ... } + * (elseif | elseifnot) Expression { ... } + * (else { ... })? + */ +export type FuncAstStatementCondition = + | FuncAstStatementConditionIf + | FuncAstStatementConditionElseIf; + +/** + * (if | ifnot) Expression { ... } (else { ... })? + * + * @field positive If true, then it represents `if`. If false, it's an `ifnot`. + * @field condition Expression + * @field consequences Left branch (after `if`), truthy case (or falsy in case of `ifnot`) + * @field alternatives Optional right branch (after `else`), falsy case (or truthy in case of `ifnot`) + */ +export type FuncAstStatementConditionIf = { + kind: "statement_condition_if"; + // if | ifnot + positive: boolean; + // expression + condition: FuncAstExpression; + // left branch { ... } + consequences: FuncAstStatement[]; + // optional right branch { ... } + alternatives: FuncAstStatement[] | undefined; + loc: FuncSrcInfo; +}; + +/** + * (if | ifnot) Expression { ... } (elseif | elseifnot) Expression { ... } (else { ... })? + * + * @field positiveIf If true, then it represents `if`. If false, it's an `ifnot`. + * @field conditionIf Expression + * @field consequencesIf Branch after `if`, truthy case (or falsy in case of `ifnot`) + * @field positiveElseif If true, then it represents `elseif`. If false, it's an `elseifnot`. + * @field conditionElseif Expression + * @field consequencesElseif Branch after `elseif`, truthy case (or falsy in case of `elseifnot`) + * @field alternativesElseif Optional third branch (after `else`), falsy case (or truthy in case of `elseifnot`) + */ +export type FuncAstStatementConditionElseIf = { + kind: "statement_condition_elseif"; + // if | ifnot + positiveIf: boolean; + // expression after if | ifnot + conditionIf: FuncAstExpression; + // branch after if | ifnot { ... } + consequencesIf: FuncAstStatement[]; + // elseif | elseifnot + positiveElseif: boolean; + // expression after elseif | elseifnot + conditionElseif: FuncAstExpression; + // branch after elseif | elseifnot { ... } + consequencesElseif: FuncAstStatement[]; + // optional third branch after else { ... } + alternativesElseif: FuncAstStatement[] | undefined; + loc: FuncSrcInfo; +}; + +/** + * repeat Expression { ... } + */ +export type FuncAstStatementRepeat = { + kind: "statement_repeat"; + iterations: FuncAstExpression; + statements: FuncAstStatement[]; + loc: FuncSrcInfo; +}; + +/** + * do { ... } until Expression; + */ +export type FuncAstStatementUntil = { + kind: "statement_until"; + statements: FuncAstStatement[]; + condition: FuncAstExpression; + loc: FuncSrcInfo; +}; + +/** + * while Expression { ... } + */ +export type FuncAstStatementWhile = { + kind: "statement_while"; + condition: FuncAstExpression; + statements: FuncAstStatement[]; + loc: FuncSrcInfo; +}; + +/** (id, id) */ +export type FuncCatchDefinitions = { + exceptionName: FuncAstId; + exitCodeName: FuncAstId; +}; + +/** + * try { ... } catch (id, id) { ... } + * try { ... } catch (_) { ... } + */ +export type FuncAstStatementTryCatch = { + kind: "statement_try_catch"; + statementsTry: FuncAstStatement[]; + catchDefinitions: FuncCatchDefinitions | "_"; + statementsCatch: FuncAstStatement[]; + loc: FuncSrcInfo; +}; + +/** + * Expression; + */ +export type FuncAstStatementExpression = { + kind: "statement_expression"; + expression: FuncAstExpression; + loc: FuncSrcInfo; +}; + +// +// Expressions, ordered by precedence (from lowest to highest), +// with comments referencing exact function names in C++ code of FunC's parser: +// https://github.com/ton-blockchain/ton/blob/master/crypto/func/parse-func.cpp +// + +/** + * parse_expr + */ +export type FuncAstExpression = + | FuncAstExpressionAssign + | FuncAstExpressionConditional + | FuncAstExpressionCompare + | FuncAstExpressionBitwiseShift + | FuncAstExpressionAddBitwise + | FuncAstExpressionMulBitwise + | FuncAstExpressionUnary + | FuncAstExpressionMethod + | FuncAstExpressionVarFun + | FuncAstExpressionPrimary; + +/** + * parse_expr10 + */ +export type FuncAstExpressionAssign = { + kind: "expression_assign"; + left: FuncAstExpression; + op: FuncOpAssign; + right: FuncAstExpression; + loc: FuncSrcInfo; +}; + +export type FuncOpAssign = + | "=" + | "+=" + | "-=" + | "*=" + | "/=" + | "%=" + | "~/=" + | "~%=" + | "^/=" + | "^%=" + | "&=" + | "|=" + | "^=" + | "<<=" + | ">>=" + | "~>>=" + | "^>>="; + +/** + * parse_expr13 + */ +export type FuncAstExpressionConditional = { + kind: "expression_conditional"; + condition: FuncAstExpression; + consequence: FuncAstExpression; + alternative: FuncAstExpression; + loc: FuncSrcInfo; +}; + +/** + * parse_expr15 + */ +export type FuncAstExpressionCompare = { + kind: "expression_compare"; + left: FuncAstExpression; + op: FuncOpCompare; + right: FuncAstExpression; + loc: FuncSrcInfo; +}; + +export const funcOpCompare = ["==", "<=>", "<=", "<", ">=", ">", "!="] as const; +export type FuncOpCompare = (typeof funcOpCompare)[number]; + +/** + * parse_expr17 + */ +export type FuncAstExpressionBitwiseShift = { + kind: "expression_bitwise_shift"; + left: FuncAstExpression; + ops: FuncExpressionBitwiseShiftPart[]; + loc: FuncSrcInfo; +}; + +export type FuncExpressionBitwiseShiftPart = { + op: FuncOpBitwiseShift; + expr: FuncAstExpression; +}; + +export const funcOpBitwiseShift = ["<<", ">>", "~>>", "^>>"] as const; +export type FuncOpBitwiseShift = (typeof funcOpBitwiseShift)[number]; + +/** + * Note, that sometimes `ops` can be an empty array, due to the unary minus (`negateLeft`) + * + * parse_expr20 + */ +export type FuncAstExpressionAddBitwise = { + kind: "expression_add_bitwise"; + negateLeft: boolean; + left: FuncAstExpression; + ops: FuncExpressionAddBitwisePart[]; + loc: FuncSrcInfo; +}; + +export type FuncExpressionAddBitwisePart = { + op: FuncOpAddBitwise; + expr: FuncAstExpression; +}; + +export const funcOpAddBitwise = ["+", "-", "|", "^"] as const; +export type FuncOpAddBitwise = (typeof funcOpAddBitwise)[number]; + +/** + * parse_expr30 + */ +export type FuncAstExpressionMulBitwise = { + kind: "expression_mul_bitwise"; + left: FuncAstExpression; + ops: FuncBitwiseExpressionPart[]; + loc: FuncSrcInfo; +}; + +export type FuncBitwiseExpressionPart = { + op: FuncOpMulBitwise; + expr: FuncAstExpression; +}; + +export const funcOpMulBitwise = [ + "*", + "/%", + "/", + "%", + "~/", + "~%", + "^/", + "^%", + "&", +] as const; +export type FuncOpMulBitwise = (typeof funcOpMulBitwise)[number]; + +/** + * parse_expr75 + */ +export type FuncAstExpressionUnary = { + kind: "expression_unary"; + op: FuncOpUnary; + operand: FuncAstExpression; + loc: FuncSrcInfo; +}; + +export type FuncOpUnary = "~" | "-" | "+"; + +/** + * parse_expr80 + */ +export type FuncAstExpressionMethod = { + kind: "expression_method"; + object: FuncAstExpressionVarFun; + calls: FuncExpressionMethodCall[]; + loc: FuncSrcInfo; +}; + +export type FuncExpressionMethodCall = { + name: FuncAstMethodId; + argument: FuncAstExpressionTensor; +}; + +/** + * parse_expr90 + */ +export type FuncAstExpressionVarFun = + | FuncAstExpressionVarDecl + | FuncAstExpressionFunCall; + +/** + * Variable declaration + * + * Type SingleOrMultipleIds + */ +export type FuncAstExpressionVarDecl = { + kind: "expression_var_decl"; + ty: FuncAstType; + names: FuncVarDeclPart; + loc: FuncSrcInfo; +}; + +/** + * Note, that methodId and operatorId should be prohibited + */ +export type FuncVarDeclPart = + | FuncAstId + | FuncAstExpressionTensorVarDecl + | FuncAstExpressionTupleVarDecl; + +/** + * ( Id, Id, ... ) + */ +export type FuncAstExpressionTensorVarDecl = { + kind: "expression_tensor_var_decl"; + names: FuncAstId[]; + loc: FuncSrcInfo; +}; + +/** + * [ Id, Id, ... ] + */ +export type FuncAstExpressionTupleVarDecl = { + kind: "expression_tuple_var_decl"; + names: FuncAstId[]; + loc: FuncSrcInfo; +}; + +/** + * Function call + * + * (functionId | functionCallReturningFunction) ExpressionTensor + */ +export type FuncAstExpressionFunCall = { + kind: "expression_fun_call"; + object: FuncAstExpressionPrimary; + argument: FuncAstExpressionTensor; + loc: FuncSrcInfo; +}; + +/** + * parse_expr100 + */ +export type FuncAstExpressionPrimary = + | FuncAstUnit + | FuncAstExpressionTensor + | FuncAstExpressionTuple + | FuncAstIntegerLiteral + | FuncAstStringLiteral + | FuncAstId; + +/** + * ( Expression, Expression, ... ) + */ +export type FuncAstExpressionTensor = { + kind: "expression_tensor"; + expressions: FuncAstExpression[]; + loc: FuncSrcInfo; +}; + +/** + * [ Expression, Expression, ... ] + */ +export type FuncAstExpressionTuple = { + kind: "expression_tuple"; + expressions: FuncAstExpression[]; + loc: FuncSrcInfo; +}; + +// +// Ternary, binary, unary expression utility sub-types +// + +/** + * Expression ? Expression : Expression + */ +export type FuncAstTernaryExpression = FuncAstExpressionConditional; + +/** + * Expression op Expression + */ +export type FuncAstBinaryExpression = + | FuncAstExpressionCompare + | FuncAstExpressionBitwiseShift + | FuncAstExpressionAddBitwise + | FuncAstExpressionMulBitwise; + +export type FuncBinaryOp = + | FuncOpCompare + | FuncOpBitwiseShift + | FuncOpAddBitwise + | FuncOpMulBitwise; + +/** + * op Expression + * + * Note, that there are no unary plus, and unary minus is handled elsewhere! + */ +export type FuncAstUnaryExpression = FuncAstExpressionUnary; + +// +// Types +// + +/** + * Mapped or unmapped types + */ +export type FuncAstType = + | FuncAstTypePrimitive + | FuncAstTypeComposite + | FuncAstTypeVar + | FuncAstTypeMapped; + +/** + * TypeUnmapped -> Type + */ +export type FuncAstTypeMapped = { + kind: "type_mapped"; + value: FuncAstTypePrimitive | FuncAstTypeComposite | FuncAstTypeVar; + mapsTo: FuncAstType; + loc: FuncSrcInfo; +}; + +/** + * "int" | "cell" | "slice" | "builder" | "cont" | "tuple" + */ +export type FuncAstTypePrimitive = { + kind: "type_primitive"; + value: FuncTypePrimitive; + loc: FuncSrcInfo; +}; + +export type FuncTypePrimitive = + | "int" + | "cell" + | "slice" + | "builder" + | "cont" + | "tuple"; + +/** + * (..., ...) or [..., ...] or (_ | var) or () + */ +export type FuncAstTypeComposite = + | FuncAstTypeTensor + | FuncAstTypeTuple + | FuncAstHole + | FuncAstUnit; + +/** + * (..., ...) + */ +export type FuncAstTypeTensor = { + kind: "type_tensor"; + types: FuncAstType[]; + loc: FuncSrcInfo; +}; + +/** + * [..., ...] + */ +export type FuncAstTypeTuple = { + kind: "type_tuple"; + types: FuncAstType[]; + loc: FuncSrcInfo; +}; + +// +// Lexical rules, see: https://ohmjs.org/docs/syntax-reference#syntactic-lexical +// + +/** + * _ | var + */ +export type FuncAstHole = { + kind: "hole"; + value: "_" | "var"; + loc: FuncSrcInfo; +}; + +/** + * () + */ +export type FuncAstUnit = { + kind: "unit"; + value: "()"; + loc: FuncSrcInfo; +}; + +/** + * Identifier variations + */ +export type FuncAstId = + | FuncAstMethodId + | FuncAstQuotedId + | FuncAstOperatorId + | FuncAstPlainId + | FuncAstUnusedId; + +/** + * Like quotedId, plainId or operatorId, but starts with . or ~ + */ +export type FuncAstMethodId = { + kind: "method_id"; + prefix: "." | "~"; + value: string; + loc: FuncSrcInfo; +}; + +/** + * \`anything, except \` or new line\` + */ +export type FuncAstQuotedId = { + kind: "quoted_id"; + value: string; + loc: FuncSrcInfo; +}; + +/** + * _+_, etc. + */ +export type FuncAstOperatorId = { + kind: "operator_id"; + value: string; + loc: FuncSrcInfo; +}; + +/** + * *magic* + */ +export type FuncAstPlainId = { + kind: "plain_id"; + value: string; + loc: FuncSrcInfo; +}; + +/** + * _ + */ +export type FuncAstUnusedId = { + kind: "unused_id"; + value: "_"; + loc: FuncSrcInfo; +}; + +/** + * op? decNum (. decNum)? (. decNum)? + */ +export type FuncAstVersionRange = { + kind: "version_range"; + op: "=" | "^" | "<=" | ">=" | "<" | ">" | undefined; + major: bigint; + minor: bigint | undefined; + patch: bigint | undefined; + loc: FuncSrcInfo; +}; + +/** + * -? decNum + * or + * -? hexNum + */ +export type FuncAstIntegerLiteral = { + kind: "integer_literal"; + value: bigint; + isHex: boolean; + loc: FuncSrcInfo; +}; + +/** + * "..."ty? + * or + * """ ... """ty? + */ +export type FuncAstStringLiteral = + | FuncAstStringLiteralSingleLine + | FuncAstStringLiteralMultiLine; + +/** + * "..."ty? + */ +export type FuncAstStringLiteralSingleLine = { + kind: "string_singleline"; + value: string; + ty: FuncStringType | undefined; + loc: FuncSrcInfo; +}; + +/** + * """ ... """ty? + */ +export type FuncAstStringLiteralMultiLine = { + kind: "string_multiline"; + value: string; + ty: FuncStringType | undefined; + // Perhaps: alignIndent: boolean; + // Perhaps: trim: boolean; + loc: FuncSrcInfo; +}; + +/** + * An additional modifier. See: https://docs.ton.org/develop/func/literals_identifiers#string-literals + */ +export type FuncStringType = "s" | "a" | "u" | "h" | "H" | "c"; + +export type FuncWhiteSpace = { + kind: "whitespace"; + value: `\t` | ` ` | `\n` | `\r` | `\u2028` | `\u2029`; +}; + +/** + * ;; ... + * or + * {- ... -} + */ +export type FuncAstComment = FuncAstCommentSingleLine | FuncAstCommentMultiLine; + +/** + * ;; ... + * + * Doesn't include the leading ;; characters + */ +export type FuncAstCommentSingleLine = { + kind: "comment_singleline"; + line: string; + loc: FuncSrcInfo; +}; + +/** + * {- ...can be nested... -} + * or + * ;; line1 + * ;; line2 + * + * Doesn't include the leftmost {- and rightmost -} or leading ;; + * + * @field skipCR If set to true, skips CR before the next line + */ +export type FuncAstCommentMultiLine = { + kind: "comment_multiline"; + lines: string[]; + skipCR: boolean; + style: "{-" | ";;"; + loc: FuncSrcInfo; +}; + +export type FuncAstCR = { + kind: "cr"; + lines: number; +}; + +// +// AST generation through syntax analysis +// + +const semantics = FuncGrammar.createSemantics(); + +semantics.addOperation("astOfModule", { + Module(items) { + return { + kind: "module", + items: items.children.map((x) => x.astOfModuleItem()), + loc: createSrcInfo(this), + }; + }, + comment(cmt) { + return cmt.astOfModule(); + }, + comment_singleLine(_commentStart, lineContents) { + return { + kind: "comment_singleline", + line: lineContents.sourceString, + loc: createSrcInfo(this), + }; + }, + comment_multiLine(cmt) { + return cmt.astOfModule(); + }, + multiLineComment( + _commentStart, + preInnerComment, + innerComment, + postInnerComment, + _commentEnd, + ) { + return { + kind: "comment_multiline", + lines: [ + preInnerComment.sourceString, + innerComment.children.map((x) => x.astOfModule()).join("") ?? + "", + postInnerComment.sourceString, + ], + style: "{-", + skipCR: false, + loc: createSrcInfo(this), + }; + }, +}); + +semantics.addOperation("astOfModuleItem", { + ModuleItem(item) { + return item.astOfModuleItem(); + }, + Pragma(pragma) { + return pragma.astOfModuleItem(); + }, + Pragma_literal(_pragmaKwd, literal, _semicolon) { + return { + kind: "pragma_literal", + literal: literal.sourceString as FuncPragmaLiteralValue, + loc: createSrcInfo(this), + }; + }, + Pragma_versionRange(_pragmaKwd, literal, range, _semicolon) { + return { + kind: "pragma_version_range", + allow: literal.sourceString === "version" ? true : false, + range: range.astOfVersionRange(), + loc: createSrcInfo(this), + }; + }, + Pragma_versionString(_pragmaKwd, _literal, value, _semicolon) { + const versionString = value.astOfExpression() as FuncAstStringLiteral; + + if (versionString.ty !== undefined) { + throwFuncSyntaxError( + "Version string cannot have a string type specified", + createSrcInfo(this), + ); + } + + if ( + versionString.value.match( + /^\"{0,3}[0-9]+(?:\.[0-9]+)?(?:\.[0-9]+)?\"{0,3}$/, + ) === null + ) { + throwFuncSyntaxError("Invalid version string", createSrcInfo(this)); + } + + return { + kind: "pragma_version_string", + version: versionString, + loc: createSrcInfo(this), + }; + }, + Include(_includeKwd, path, _semicolon) { + return { + kind: "include", + path: path.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + GlobalVariablesDeclaration(_globalKwd, globals, _semicolon) { + return { + kind: "global_variables_declaration", + globals: globals + .asIteration() + .children.map((x) => x.astOfGlobalVariable()), + loc: createSrcInfo(this), + }; + }, + ConstantsDefinition(_constKwd, constants, _semicolon) { + return { + kind: "constants_definition", + constants: constants + .asIteration() + .children.map((x) => x.astOfConstant()), + loc: createSrcInfo(this), + }; + }, + AsmFunctionDefinition( + fnCommonPrefix, + _asmKwd, + optArrangement, + asmStrings, + _semicolon, + ) { + const prefix = fnCommonPrefix.astOfFunctionCommonPrefix(); + return { + kind: "asm_function_definition", + forall: prefix.forall, + returnTy: prefix.returnTy, + name: prefix.name, + parameters: prefix.parameters, + attributes: prefix.attributes, + arrangement: unwrapOptNode(optArrangement, (t) => + t.astOfAsmArrangement(), + ), + asmStrings: asmStrings.children.map((x) => x.astOfExpression()), + loc: createSrcInfo(this), + }; + }, + FunctionDeclaration(fnCommonPrefix, _semicolon) { + const prefix = fnCommonPrefix.astOfFunctionCommonPrefix(); + return { + kind: "function_declaration", + forall: prefix.forall, + returnTy: prefix.returnTy, + name: prefix.name, + parameters: prefix.parameters, + attributes: prefix.attributes, + loc: createSrcInfo(this), + }; + }, + FunctionDefinition(fnCommonPrefix, _lbrace, stmts, _rbrace) { + const prefix = fnCommonPrefix.astOfFunctionCommonPrefix(); + return { + kind: "function_definition", + forall: prefix.forall, + returnTy: prefix.returnTy, + name: prefix.name, + parameters: prefix.parameters, + attributes: prefix.attributes, + statements: stmts.children.map((x) => x.astOfStatement()), + loc: createSrcInfo(this), + }; + }, +}); + +// Statements +semantics.addOperation("astOfStatement", { + Statement(stmt) { + return stmt.astOfStatement(); + }, + StatementReturn(_returnKwd, expr, _semicolon) { + return { + kind: "statement_return", + expression: expr.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + StatementBlock(_lbrace, statements, _rbrace) { + return { + kind: "statement_block", + statements: statements.children.map((x) => x.astOfStatement()), + loc: createSrcInfo(this), + }; + }, + StatementEmpty(_semicolon) { + return { + kind: "statement_empty", + loc: createSrcInfo(this), + }; + }, + StatementCondition(cond) { + return cond.astOfStatement(); + }, + StatementCondition_if(ifOr, cond, _lbrace, stmts, _rbrace, optElse) { + return { + kind: "statement_condition_if", + positive: ifOr.sourceString === "if" ? true : false, + condition: cond.astOfExpression(), + consequences: stmts.children.map((x) => x.astOfStatement()), + alternatives: unwrapOptNode(optElse, (t) => t.astOfElseBlock()), + loc: createSrcInfo(this), + }; + }, + StatementCondition_elseif( + ifOr, + condIf, + _lbrace, + stmtsIf, + _rbrace, + elseifOr, + condElseif, + _lbrace2, + stmtsElseif, + _rbrace2, + optElse, + ) { + return { + kind: "statement_condition_elseif", + positiveIf: ifOr.sourceString === "if" ? true : false, + conditionIf: condIf.astOfExpression(), + consequencesIf: stmtsIf.children.map((x) => x.astOfStatement()), + positiveElseif: elseifOr.sourceString === "elseif" ? true : false, + conditionElseif: condElseif.astOfExpression(), + consequencesElseif: stmtsElseif.children.map((x) => + x.astOfStatement(), + ), + alternativesElseif: unwrapOptNode(optElse, (t) => + t.astOfElseBlock(), + ), + loc: createSrcInfo(this), + }; + }, + StatementRepeat(_repeatKwd, expr, _lbrace, stmts, _rbrace) { + return { + kind: "statement_repeat", + iterations: expr.astOfExpression(), + statements: stmts.children.map((x) => x.astOfStatement()), + loc: createSrcInfo(this), + }; + }, + StatementUntil( + _doKwd, + _lbrace, + stmts, + _rbrace, + _untilKwd, + cond, + _semicolon, + ) { + return { + kind: "statement_until", + statements: stmts.children.map((x) => x.astOfStatement()), + condition: cond.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + StatementWhile(_whileKwd, cond, _lbrace, stmts, _rbrace) { + return { + kind: "statement_while", + condition: cond.astOfExpression(), + statements: stmts.children.map((x) => x.astOfStatement()), + loc: createSrcInfo(this), + }; + }, + StatementTryCatch( + _tryKwd, + _lbrace, + stmtsTry, + _rbrace, + _catchKwd, + _lparen, + catchClauseContents, + _rparen, + _lbrace2, + stmtsCatch, + _rbrace2, + ) { + return { + kind: "statement_try_catch", + statementsTry: stmtsTry.children.map((x) => x.astOfStatement()), + catchDefinitions: catchClauseContents.astOfCatchClauseContents(), + statementsCatch: stmtsCatch.children.map((x) => x.astOfStatement()), + loc: createSrcInfo(this), + }; + }, + StatementExpression(expr, _semicolon) { + return { + kind: "statement_expression", + expression: expr.astOfExpression(), + loc: createSrcInfo(this), + }; + }, +}); + +// Expressions +semantics.addOperation("astOfExpression", { + // parse_expr + Expression(expr) { + return expr.astOfExpression(); + }, + + // parse_expr10 + ExpressionAssign(expr) { + return expr.astOfExpression(); + }, + ExpressionAssign_op(exprLeft, _space1, op, _space2, exprRight) { + return { + kind: "expression_assign", + left: exprLeft.astOfExpression(), + op: op.sourceString as FuncOpAssign, + right: exprRight.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + + // parse_expr13 + ExpressionConditional(expr) { + return expr.astOfExpression(); + }, + ExpressionConditional_ternary( + exprLeft, + _space1, + _qmark, + _space2, + exprMiddle, + _space3, + _colon, + _space4, + exprRight, + ) { + return { + kind: "expression_conditional", + condition: exprLeft.astOfExpression(), + consequence: exprMiddle.astOfExpression(), + alternative: exprRight.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + + // parse_expr15 + ExpressionCompare(expr) { + return expr.astOfExpression(); + }, + ExpressionCompare_op(exprLeft, _space1, op, _space2, exprRight) { + return { + kind: "expression_compare", + left: exprLeft.astOfExpression(), + op: op.sourceString as FuncOpCompare, + right: exprRight.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + + // parse_expr17 + ExpressionBitwiseShift(expr) { + return expr.astOfExpression(); + }, + ExpressionBitwiseShift_ops(exprLeft, _space, ops, _spaces, exprs) { + const resolvedOps = ops.children.map( + (x) => x.sourceString as FuncOpBitwiseShift, + ); + const resolvedExprs = exprs.children.map( + (x) => x.astOfExpression() as FuncAstExpressionAddBitwise, + ); + const zipped = resolvedOps.map((resOp, i) => { + return { op: resOp, expr: resolvedExprs[i]! }; + }); + return { + kind: "expression_bitwise_shift", + left: exprLeft.astOfExpression(), + ops: zipped, + loc: createSrcInfo(this), + }; + }, + + // parse_expr20 + ExpressionAddBitwise(expr) { + return expr.astOfExpression(); + }, + ExpressionAddBitwise_ops( + optNegate, + _optSpace, + exprLeft, + _space, + ops, + _spaces, + exprs, + ) { + const negate = unwrapOptNode(optNegate, (t) => t.sourceString); + const resolvedOps = ops.children.map( + (x) => x.sourceString as FuncOpAddBitwise, + ); + const resolvedExprs = exprs.children.map( + (x) => x.astOfExpression() as FuncAstExpressionMulBitwise, + ); + const zipped = resolvedOps.map((resOp, i) => { + return { op: resOp, expr: resolvedExprs[i]! }; + }); + return { + kind: "expression_add_bitwise", + negateLeft: negate === undefined ? false : true, + left: exprLeft.astOfExpression(), + ops: zipped, + loc: createSrcInfo(this), + }; + }, + ExpressionAddBitwise_negate(_negateOp, _space, expr) { + return { + kind: "expression_add_bitwise", + negateLeft: true, + left: expr.astOfExpression(), + ops: [], + loc: createSrcInfo(this), + }; + }, + + // parse_expr30 + ExpressionMulBitwise(expr) { + return expr.astOfExpression(); + }, + ExpressionMulBitwise_ops(exprLeft, _space, ops, _spaces, exprs) { + const resolvedOps = ops.children.map( + (x) => x.sourceString as FuncOpMulBitwise, + ); + const resolvedExprs = exprs.children.map( + (x) => x.astOfExpression() as FuncAstExpressionUnary, + ); + const zipped = resolvedOps.map((resOp, i) => { + return { op: resOp, expr: resolvedExprs[i]! }; + }); + return { + kind: "expression_mul_bitwise", + left: exprLeft.astOfExpression(), + ops: zipped, + loc: createSrcInfo(this), + }; + }, + + // parse_expr75 + ExpressionUnary(expr) { + return expr.astOfExpression(); + }, + ExpressionUnary_bitwiseNot(notOp, _space, operand) { + return { + kind: "expression_unary", + op: notOp.sourceString as "~", + operand: operand.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + + // parse_expr80 + ExpressionMethod(expr) { + return expr.astOfExpression(); + }, + ExpressionMethod_calls(exprLeft, methodIds, exprs) { + const resolvedIds = methodIds.children.map( + (x) => x.astOfExpression() as FuncAstMethodId, + ); + const resolvedExprs = exprs.children.map( + (x) => x.astOfExpression() as FuncAstExpressionTensor, + ); + const zipped = resolvedIds.map((resId, i) => { + return { name: resId, argument: resolvedExprs[i]! }; + }); + return { + kind: "expression_method", + object: exprLeft.astOfExpression(), + calls: zipped, + loc: createSrcInfo(this), + } as FuncAstExpression; + }, + + // parse_expr90, and some inner things + ExpressionVarFun(expr) { + return expr.astOfExpression(); + }, + ExpressionVarDecl(varDeclTy, varNames) { + const names = varNames.astOfVarDeclPart() as FuncVarDeclPart; + const checkVarDeclName = (node: FuncAstId) => { + if (node.kind === "unused_id" || node.kind === "quoted_id") { + return; + } + if (node.kind === "method_id") { + throwFuncSyntaxError( + "Name of the variable cannot start with . or ~", + createSrcInfo(this), + ); + } + if (node.kind === "plain_id") { + checkPlainId( + node.value, + createSrcInfo(this), + "Name of the variable", + ); + } + checkDeclaredId( + node.value, + createSrcInfo(this), + "Name of the variable", + ); + }; + + if ( + names.kind === "expression_tensor_var_decl" || + names.kind === "expression_tuple_var_decl" + ) { + for (let i = 0; i < names.names.length; i += 1) { + checkVarDeclName(names.names[i]!); + } + } else { + checkVarDeclName(names); + } + + return { + kind: "expression_var_decl", + ty: varDeclTy.astOfType(), + names: names, + loc: createSrcInfo(this), + }; + }, + ExpressionVarDeclPart(expr) { + return expr.astOfExpression(); + }, + ExpressionFunCall(_lookahead, expr, arg) { + return { + kind: "expression_fun_call", + object: expr.astOfExpression(), + argument: arg.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + + // parse_expr100, and some inner things + ExpressionPrimary(expr) { + return expr.astOfExpression(); + }, + unit(_lparen, _space, _rparen) { + return { + kind: "unit", + value: "()", + loc: createSrcInfo(this), + }; + }, + ExpressionTensor(_lparen, exprs, _rparen) { + return { + kind: "expression_tensor", + expressions: exprs + .asIteration() + .children.map((x) => x.astOfExpression()), + loc: createSrcInfo(this), + }; + }, + tupleEmpty(_lparen, _spaces, _rparen) { + return { + kind: "expression_tuple", + expressions: [], + loc: createSrcInfo(this), + }; + }, + ExpressionTuple(_lparen, exprs, _rparen) { + return { + kind: "expression_tuple", + expressions: exprs + .asIteration() + .children.map((x) => x.astOfExpression()), + loc: createSrcInfo(this), + }; + }, + integerLiteral(optNegate, numLit) { + const negate = unwrapOptNode(optNegate, (t) => t.sourceString); + const value = + (negate === undefined ? 1n : -1n) * BigInt(numLit.sourceString); + + return { + kind: "integer_literal", + value: value, + isHex: false, + loc: createSrcInfo(this), + }; + }, + integerLiteralNonNegative(nonNegNumLit) { + return { + kind: "integer_literal", + value: BigInt(nonNegNumLit.sourceString), + isHex: false, + loc: createSrcInfo(this), + }; + }, + integerLiteralDec(nonNegNumLit) { + return { + kind: "integer_literal", + value: BigInt(nonNegNumLit.sourceString), + isHex: false, + loc: createSrcInfo(this), + }; + }, + integerLiteralHex(hexPrefix, nonNegNumLit) { + return { + kind: "integer_literal", + value: BigInt(hexPrefix.sourceString + nonNegNumLit.sourceString), + isHex: true, + loc: createSrcInfo(this), + }; + }, + stringLiteral(strLit) { + return strLit.astOfExpression(); + }, + stringLiteral_singleLine(_lquote, contents, _rquote, optTy) { + return { + kind: "string_singleline", + value: contents.sourceString, + ty: unwrapOptNode(optTy, (t) => t.sourceString) as + | FuncStringType + | undefined, + loc: createSrcInfo(this), + }; + }, + stringLiteral_multiLine(_lquote, contents, _rquote, optTy) { + return { + kind: "string_multiline", + value: contents.sourceString, + ty: unwrapOptNode(optTy, (t) => t.sourceString) as + | FuncStringType + | undefined, + loc: createSrcInfo(this), + }; + }, + functionId(optPrefix, rawId) { + const prefix = unwrapOptNode(optPrefix, (t) => t.sourceString); + + if (prefix === undefined) { + return rawId.astOfExpression(); + } + + return { + kind: "method_id", + prefix: prefix as "." | "~", + value: (rawId.astOfExpression() as FuncAstId).value, + loc: createSrcInfo(this), + }; + }, + methodId(prefix, rawId) { + return { + kind: "method_id", + prefix: prefix.sourceString as "." | "~", + value: (rawId.astOfExpression() as FuncAstId).value, + loc: createSrcInfo(this), + }; + }, + id(rawId) { + return rawId.astOfExpression(); + }, + rawId(someId) { + return someId.astOfExpression(); + }, + quotedId(ltick, idContents, rtick) { + return { + kind: "quoted_id", + value: [ltick, idContents.sourceString, rtick].join(""), + loc: createSrcInfo(this), + }; + }, + operatorId(opId) { + return opId.astOfExpression(); + }, + operatorId_common(optCaret, underscore1, op, underscore2) { + const value = [ + unwrapOptNode(optCaret, (t) => t.sourceString) ?? "", + underscore1, + op, + underscore2, + ].join(""); + + return { + kind: checkOperatorId(value) ? "operator_id" : "plain_id", + value: value, + loc: createSrcInfo(this), + }; + }, + operatorId_not(notOp) { + return { + kind: "operator_id", + value: notOp.sourceString, + loc: createSrcInfo(this), + }; + }, + plainId(idContents) { + const value = idContents.sourceString; + if (value === "_") { + return { + kind: "unused_id", + value: "_", + loc: createSrcInfo(this), + }; + } + return { + kind: "plain_id", + value: value, + loc: createSrcInfo(this), + }; + }, + unusedId(underscore) { + return { + kind: "unused_id", + value: underscore.sourceString as "_", + loc: createSrcInfo(this), + }; + }, +}); + +// Some parts of expressions which do not produce FuncAstExpression node +semantics.addOperation("astOfVarDeclPart", { + id(node) { + return node.astOfExpression(); + }, + unusedId(node) { + return node.astOfExpression(); + }, + ExpressionTensorVarDecl(_lparen, ids, _rparen) { + return { + kind: "expression_tensor_var_decl", + names: ids.asIteration().children.map((x) => x.astOfIdOrUnusedId()), + loc: createSrcInfo(this), + }; + }, + ExpressionTupleVarDecl(_lbrack, ids, _rbrack) { + return { + kind: "expression_tuple_var_decl", + names: ids.asIteration().children.map((x) => x.astOfIdOrUnusedId()), + loc: createSrcInfo(this), + }; + }, +}); +semantics.addOperation("astOfIdOrUnusedId", { + IdOrUnusedId(node) { + return node.astOfExpression(); + }, +}); + +// Miscellaneous utility nodes, gathered together for convenience +// +// A couple of them don't even have their own dedicated TypeScript types, +// and most were introduced mostly for parsing convenience + +// op? decNum (. decNum)? (. decNum)? +semantics.addOperation("astOfVersionRange", { + versionRange( + optOp, + majorVers, + _optDot, + optMinorVers, + _optDot2, + optPatchVers, + ) { + const op = unwrapOptNode(optOp, (t) => t.sourceString) as + | "=" + | "^" + | "<=" + | ">=" + | "<" + | ">" + | undefined; + const major = majorVers.astOfExpression() as FuncAstIntegerLiteral; + const minor = unwrapOptNode(optMinorVers, (t) => + t.astOfExpression(), + ) as FuncAstIntegerLiteral | undefined; + const patch = unwrapOptNode(optPatchVers, (t) => + t.astOfExpression(), + ) as FuncAstIntegerLiteral | undefined; + return { + kind: "version_range", + op: op, + major: major.value, + minor: minor?.value, + patch: patch?.value, + loc: createSrcInfo(this), + }; + }, +}); + +// nonVarType? (quotedId | plainId) +semantics.addOperation("astOfGlobalVariable", { + GlobalVariableDeclaration(optGlobTy, globName) { + const name = globName.astOfExpression() as FuncAstId; + // if a plainId, then check for validity + if (name.kind === "plain_id") { + checkPlainId( + name.value, + createSrcInfo(this), + "Name of the global variable", + ); + } + // check that it can be declared (also excludes operatorId and unusedId) + checkDeclaredId( + name.value, + createSrcInfo(this), + "Name of the global variable", + ); + // and that it's not a methodId + if (name.kind === "method_id") { + throwFuncSyntaxError( + "Name of the global variable cannot start with ~ or .", + createSrcInfo(this), + ); + } + // leaving only quotedId or plainId + return { + kind: "global_variable", + ty: unwrapOptNode(optGlobTy, (t) => t.astOfType()), + name: name as FuncAstQuotedId | FuncAstPlainId, + loc: createSrcInfo(this), + }; + }, +}); + +// (slice | int)? id = Expression +semantics.addOperation("astOfConstant", { + ConstantDefinition(optConstTy, constName, _eqSign, expr) { + const ty = unwrapOptNode(optConstTy, (t) => t.sourceString); + const name = constName.astOfExpression() as FuncAstId; + // if a plainId, then check for validity + if (name.kind === "plain_id") { + checkPlainId( + name.value, + createSrcInfo(this), + "Name of the constant", + ); + } + // check that it can be declared (also excludes operatorId and unusedId) + checkDeclaredId( + name.value, + createSrcInfo(this), + "Name of the constant", + ); + // and that it's not a methodId + if (name.kind === "method_id") { + throwFuncSyntaxError( + "Name of the constant cannot start with ~ or .", + createSrcInfo(this), + ); + } + return { + kind: "constant", + ty: ty !== undefined ? (ty as "slice" | "int") : undefined, + name: name as FuncAstQuotedId | FuncAstPlainId, + value: expr.astOfExpression(), + loc: createSrcInfo(this), + }; + }, +}); + +/** Not for export, purely for internal convenience reasons */ +type FuncFunctionCommonPrefix = { + forall: FuncAstForall | undefined; + returnTy: FuncAstType; + name: FuncAstMethodId | FuncAstQuotedId | FuncAstPlainId; + parameters: FuncAstParameter[]; + attributes: FuncAstFunctionAttribute[]; +}; + +// Common prefix of all function declarations/definitions +semantics.addOperation("astOfFunctionCommonPrefix", { + FunctionCommonPrefix(optForall, retTy, fnName, fnParams, fnAttributes) { + const name = fnName.astOfExpression() as FuncAstId; + // if a plainId, then check for validity + if (name.kind === "plain_id") { + checkPlainId( + name.value, + createSrcInfo(this), + "Name of the function", + ); + } + // check that it can be declared (also excludes operatorId and unusedId) + checkDeclaredId( + name.value, + createSrcInfo(this), + "Name of the function", + ); + return { + forall: unwrapOptNode(optForall, (t) => t.astOfForall()), + returnTy: retTy.astOfType(), + name: name as FuncAstMethodId | FuncAstQuotedId | FuncAstPlainId, + parameters: fnParams.astOfParameters(), + attributes: fnAttributes.children.map((x) => + x.astOfFunctionAttribute(), + ), + }; + }, +}); + +// forall (type? typeName1, type? typeName2, ...) -> +semantics.addOperation("astOfForall", { + Forall(_forallKwd, _space1, typeVars, _space2, _mapsToKwd, _space3) { + return { + kind: "forall", + tyVars: typeVars.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, +}); + +// (Type? Id, ...) +semantics.addOperation("astOfParameters", { + Parameters(_lparen, params, _rparen) { + return params.asIteration().children.map((x) => x.astOfParameter()); + }, +}); + +// Type? Id +semantics.addOperation("astOfParameter", { + Parameter(param) { + return param.astOfParameter(); + }, + Parameter_regular(paramTy, optId) { + const name = unwrapOptNode( + optId, + (t) => t.astOfExpression() as FuncAstId, + ); + if (name === undefined) { + return { + kind: "parameter", + ty: paramTy.astOfType(), + name: undefined, + loc: createSrcInfo(this), + }; + } + // if a plainId, then check for validity + if (name.kind === "plain_id") { + checkPlainId( + name.value, + createSrcInfo(this), + "Name of the parameter", + ); + } + // check that it can be declared (also excludes operatorId, but not unusedId) + checkDeclaredId( + name.value, + createSrcInfo(this), + "Name of the parameter", + true, + ); + return { + kind: "parameter", + ty: paramTy.astOfType(), + name: name as + | FuncAstMethodId + | FuncAstQuotedId + | FuncAstPlainId + | FuncAstUnusedId + | undefined, + loc: createSrcInfo(this), + }; + }, + Parameter_inferredType(funId) { + const name = funId.astOfExpression() as FuncAstId; + // if a plainId, then check for validity + if (name.kind === "plain_id") { + checkPlainId( + name.value, + createSrcInfo(this), + "Name of the parameter", + ); + } + // check that it can be declared (also excludes operatorId and unusedId) + checkDeclaredId( + name.value, + createSrcInfo(this), + "Name of the parameter", + ); + return { + kind: "parameter", + ty: undefined, + name: name as FuncAstMethodId | FuncAstQuotedId | FuncAstPlainId, + loc: createSrcInfo(this), + }; + }, + Parameter_hole(_node) { + return { + kind: "parameter", + ty: { + kind: "hole", + value: "_", + loc: createSrcInfo(this), + }, + name: undefined, + loc: createSrcInfo(this), + }; + }, +}); + +// (id+) or (-> integerLiteralDec+) or (id+ -> integerLiteralDec+) +semantics.addOperation("astOfAsmArrangement", { + AsmArrangement(asmArrangement) { + return asmArrangement.astOfAsmArrangement(); + }, + AsmArrangement_arguments(_lparen, args, _rparen) { + return { + kind: "asm_arrangement", + arguments: args.children.map((x) => x.astOfExpression()), + returns: undefined, + loc: createSrcInfo(this), + }; + }, + AsmArrangement_returns(_lparen, _mapsTo, _space, rets, _rparen) { + return { + kind: "asm_arrangement", + arguments: undefined, + returns: rets.children.map((x) => x.astOfExpression()), + loc: createSrcInfo(this), + }; + }, + AsmArrangement_argumentsToReturns( + _lparen, + args, + _space, + _mapsTo, + _space1, + rets, + _rparen, + ) { + return { + kind: "asm_arrangement", + arguments: args.children.map((x) => x.astOfExpression()), + returns: rets.children.map((x) => x.astOfExpression()), + loc: createSrcInfo(this), + }; + }, +}); + +// impure | inline_ref | inline | method_id ("(" Integer | String ")")? +semantics.addOperation("astOfFunctionAttribute", { + FunctionAttribute(attr) { + if (attr.isTerminal()) { + if (attr.sourceString === "method_id") { + return { + kind: "method_id", + value: undefined, + loc: createSrcInfo(this), + }; + } + return { + kind: attr.sourceString as "impure" | "inline_ref" | "inline", + loc: createSrcInfo(this), + }; + } + + return { + kind: "method_id", + value: attr.astOfMethodIdValue(), + loc: createSrcInfo(this), + }; + }, +}); + +// method_id "(" Integer | String ")" +semantics.addOperation( + "astOfMethodIdValue", + { + MethodIdValue(mtd) { + return mtd.astOfMethodIdValue(); + }, + MethodIdValue_int(_methodIdKwd, _lparen, intLit, _rparen) { + return intLit.astOfExpression() as FuncAstIntegerLiteral; + }, + MethodIdValue_string(_methodIdKwd, _lparen, strLit, _rparen) { + return strLit.astOfExpression() as FuncAstStringLiteral; + }, + }, +); + +// All the types, united under FuncAstType +semantics.addOperation("astOfType", { + id(rawId) { + return { + kind: "type_var", + keyword: false, + name: rawId.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + upperId(_lookahead, upperPlainId) { + return { + kind: "type_var", + keyword: false, + name: upperPlainId.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + hole(node) { + return { + kind: "hole", + value: node.sourceString as "var" | "_", + loc: createSrcInfo(this), + }; + }, + unit(_lparen, _space, _rparen) { + return { + kind: "unit", + value: "()", + loc: createSrcInfo(this), + }; + }, + tupleEmpty(_lparen, _spaces, _rparen) { + return { + kind: "type_tuple", + types: [], + loc: createSrcInfo(this), + }; + }, + TypeGlob(globBiTy, _optMapsTo, optGlobTy) { + const mapsTo = unwrapOptNode(optGlobTy, (t) => t.astOfType()); + if (mapsTo !== undefined) { + return { + kind: "type_mapped", + value: globBiTy.astOfType(), + mapsTo: mapsTo, + loc: createSrcInfo(this), + }; + } + return globBiTy.astOfType(); + }, + TypeBuiltinGlob(globBiTy) { + return globBiTy.astOfType(); + }, + TypeBuiltinGlob_simple(globBiTy) { + if (!globBiTy.isTerminal()) { + return globBiTy.astOfType(); + } + return { + kind: "type_primitive", + value: globBiTy.sourceString as FuncTypePrimitive, + loc: createSrcInfo(this), + }; + }, + TensorGlob(_lparen, globTys, _rparen) { + return { + kind: "type_tensor", + types: globTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TupleGlob(_lbrack, globTys, _rbrack) { + return { + kind: "type_tuple", + types: globTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TypeVar(optTypeKwd, _space, typeVar) { + const typeKwd = unwrapOptNode(optTypeKwd, (t) => t.sourceString); + return { + kind: "type_var", + keyword: typeKwd !== undefined ? true : false, + name: typeVar.astOfExpression(), + loc: createSrcInfo(this), + }; + }, + TypeReturn(retBiTy, _space1, _optMapsTo, _space2, optRetTy) { + const mapsTo = unwrapOptNode(optRetTy, (t) => t.astOfType()); + if (mapsTo !== undefined) { + return { + kind: "type_mapped", + value: retBiTy.astOfType(), + mapsTo: mapsTo, + loc: createSrcInfo(this), + }; + } + return retBiTy.astOfType(); + }, + TypeBuiltinReturn(retBiTy) { + if (retBiTy.isTerminal()) { + const ty = retBiTy.sourceString; + if (ty === "_") { + return { + kind: "hole", + value: "_", + loc: createSrcInfo(this), + }; + } + return { + kind: "type_primitive", + value: ty as FuncTypePrimitive, + loc: createSrcInfo(this), + }; + } + return retBiTy.astOfType(); + }, + TensorReturn(_lparen, retTys, _rparen) { + return { + kind: "type_tensor", + types: retTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TupleReturn(_lparen, retTys, _rparen) { + return { + kind: "type_tuple", + types: retTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TypeParameter(paramBiTy, _space1, _optMapsTo, _space2, optRetTy) { + const mapsTo = unwrapOptNode(optRetTy, (t) => t.astOfType()); + if (mapsTo !== undefined) { + return { + kind: "type_mapped", + value: paramBiTy.astOfType(), + mapsTo: mapsTo, + loc: createSrcInfo(this), + }; + } + return paramBiTy.astOfType(); + }, + TypeBuiltinParameter(paramBiTy) { + if (paramBiTy.isTerminal()) { + return { + kind: "type_primitive", + value: paramBiTy.sourceString as FuncTypePrimitive, + loc: createSrcInfo(this), + }; + } + return paramBiTy.astOfType(); + }, + TensorParameter(_lparen, retTys, _rparen) { + return { + kind: "type_tensor", + types: retTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TupleParameter(_lparen, retTys, _rparen) { + return { + kind: "type_tuple", + types: retTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TypeVarDecl(varDeclBiTy, _space1, _optMapsTo, _space2, optRetTy) { + const mapsTo = unwrapOptNode(optRetTy, (t) => t.astOfType()); + if (mapsTo !== undefined) { + return { + kind: "type_mapped", + value: varDeclBiTy.astOfType(), + mapsTo: mapsTo, + loc: createSrcInfo(this), + }; + } + return varDeclBiTy.astOfType(); + }, + TypeBuiltinVarDecl(varDeclBiTy) { + return varDeclBiTy.astOfType(); + }, + TypeBuiltinVarDecl_simple(varDeclBiTy) { + if (!varDeclBiTy.isTerminal()) { + return { + kind: "hole", + value: varDeclBiTy.sourceString as "_" | "var", + loc: createSrcInfo(this), + }; + } + return { + kind: "type_primitive", + value: varDeclBiTy.sourceString as FuncTypePrimitive, + loc: createSrcInfo(this), + }; + }, + TensorVarDecl(_lparen, varDeclTys, _rparen) { + return { + kind: "type_tensor", + types: varDeclTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, + TupleVarDecl(_lparen, varDeclTys, _rparen) { + return { + kind: "type_tuple", + types: varDeclTys.asIteration().children.map((x) => x.astOfType()), + loc: createSrcInfo(this), + }; + }, +}); + +// Not a standalone statement, produces a list of statements instead +semantics.addOperation("astOfElseBlock", { + ElseBlock(_elseKwd, _lbrace, stmts, _rbrace) { + return stmts.children.map((x) => x.astOfStatement()); + }, +}); + +// Not a standalone statement +semantics.addOperation("astOfCatchClauseContents", { + CatchClauseContents(node) { + return node.astOfCatchClauseContents(); + }, + CatchClauseContents_unused(_underscore) { + return "_"; + }, + CatchClauseContents_both(exceptionName, _comma, exitCodeName) { + return { + exceptionName: exceptionName.astOfExpression(), + exitCodeName: exitCodeName.astOfExpression(), + }; + }, +}); + +// +// Utility parsing functions +// + +/** If the match wasn't successful, provides error message and interval */ +export type GrammarMatch = + | { ok: false; message: string; interval: RawInterval } + | { ok: true; res: MatchResult }; + +/** + * Checks if the given `src` string of FunC code matches the FunC grammar + * Doesn't throw an error, unlike `parse()` functions + */ +export function match(src: string): GrammarMatch { + const matchResult = FuncGrammar.match(src); + + if (matchResult.failed()) { + return { + ok: false, + message: `Parse error: expected ${(matchResult as any).getExpectedText()}\n`, + interval: matchResult.getInterval(), + }; + } + + return { ok: true, res: matchResult }; +} + +/** + * Checks if the given `src` string of FunC code is parsable, returning an AST in case of success or throwing a `FuncParseError` otherwise + * + * Uses semantic analysis, unlike simple `match()` + */ +export function parse(src: string) { + const matchResult = FuncGrammar.match(src); + + if (matchResult.failed()) { + throwFuncParseError(matchResult, undefined); + } + try { + return semantics(matchResult).astOfModule(); + } finally { + } +} + +/** + * Similar to `parse()`, but also uses provided `path` in error messages + * Unlike `parse()`, wraps its body in a call to `inFile()` with the `path` provided + */ +export function parseFile(src: string, path: string) { + return inFile(path, () => { + const matchResult = FuncGrammar.match(src); + + if (matchResult.failed()) { + throwFuncParseError(matchResult, path); + } + try { + return semantics(matchResult).astOfModule(); + } finally { + } + }); +} diff --git a/src/func/iterators.ts b/src/func/iterators.ts new file mode 100644 index 000000000..3130fa857 --- /dev/null +++ b/src/func/iterators.ts @@ -0,0 +1,183 @@ +import { FuncAstNode, FuncAstExpression } from "./grammar"; +import { throwUnsupportedNodeError } from "./syntaxUtils"; + +/** + * Recursively executes `callback` on each nested expression. + * + * NOTE: It doesn't traverse raw assembly (present as string literals) and + * identifier expressions that represent names. + */ +export function forEachExpression( + node: FuncAstNode, + callback: (expr: FuncAstExpression) => void, +): void { + switch (node.kind) { + case "expression_assign": + callback(node); + forEachExpression(node.left, callback); + forEachExpression(node.right, callback); + break; + case "expression_conditional": + callback(node); + forEachExpression(node.condition, callback); + forEachExpression(node.consequence, callback); + forEachExpression(node.alternative, callback); + break; + case "expression_compare": + callback(node); + forEachExpression(node.left, callback); + forEachExpression(node.right, callback); + break; + case "expression_bitwise_shift": + case "expression_add_bitwise": + case "expression_mul_bitwise": + callback(node); + forEachExpression(node.left, callback); + node.ops.forEach((part) => forEachExpression(part.expr, callback)); + break; + case "expression_unary": + callback(node); + forEachExpression(node.operand, callback); + break; + case "expression_method": + callback(node); + forEachExpression(node.object, callback); + node.calls.forEach((call) => + forEachExpression(call.argument, callback), + ); + break; + case "expression_var_decl": + callback(node); + if ( + node.names.kind === "expression_tensor_var_decl" || + node.names.kind === "expression_tuple_var_decl" + ) { + node.names.names.forEach((name) => + forEachExpression(name, callback), + ); + } else { + forEachExpression(node.names, callback); + } + break; + case "expression_fun_call": + callback(node); + forEachExpression(node.object, callback); + forEachExpression(node.argument, callback); + break; + case "expression_tensor": + case "expression_tuple": + callback(node); + node.expressions.forEach((expr) => + forEachExpression(expr, callback), + ); + break; + case "module": + node.items.forEach((item) => forEachExpression(item, callback)); + break; + case "pragma_literal": + case "pragma_version_range": + case "pragma_version_string": + case "include": + break; + case "global_variables_declaration": + // Do nothing; global variables don't contain initializers + break; + case "constants_definition": + node.constants.forEach((c) => forEachExpression(c.value, callback)); + break; + case "asm_function_definition": + break; + case "function_declaration": + case "function_definition": + forEachExpression(node.name, callback); + node.attributes.forEach((attr) => { + if (attr.kind === "method_id" && attr.value) { + forEachExpression(attr.value, callback); + } + }); + if (node.kind === "function_definition") { + node.statements.forEach((stmt) => + forEachExpression(stmt, callback), + ); + } + break; + case "unit": + break; + case "statement_return": + if (node.expression) forEachExpression(node.expression, callback); + break; + case "statement_block": + node.statements.forEach((stmt) => + forEachExpression(stmt, callback), + ); + break; + case "statement_empty": + break; + case "statement_condition_if": + forEachExpression(node.condition, callback); + node.consequences.forEach((stmt) => + forEachExpression(stmt, callback), + ); + if (node.alternatives) { + node.alternatives.forEach((stmt) => + forEachExpression(stmt, callback), + ); + } + break; + case "statement_condition_elseif": + forEachExpression(node.conditionIf, callback); + node.consequencesIf.forEach((stmt) => + forEachExpression(stmt, callback), + ); + forEachExpression(node.conditionElseif, callback); + node.consequencesElseif.forEach((stmt) => + forEachExpression(stmt, callback), + ); + if (node.alternativesElseif) { + node.alternativesElseif.forEach((stmt) => + forEachExpression(stmt, callback), + ); + } + break; + case "statement_repeat": + forEachExpression(node.iterations, callback); + node.statements.forEach((stmt) => + forEachExpression(stmt, callback), + ); + break; + case "statement_until": + case "statement_while": + forEachExpression(node.condition, callback); + node.statements.forEach((stmt) => + forEachExpression(stmt, callback), + ); + break; + case "statement_try_catch": + node.statementsTry.forEach((stmt) => + forEachExpression(stmt, callback), + ); + node.statementsCatch.forEach((stmt) => + forEachExpression(stmt, callback), + ); + break; + case "statement_expression": + forEachExpression(node.expression, callback); + break; + // No nested nodes for these cases + case "method_id": + case "quoted_id": + case "operator_id": + case "plain_id": + case "unused_id": + case "integer_literal": + case "string_singleline": + case "string_multiline": + case "cr": + case "comment_singleline": + case "comment_multiline": + break; + + default: + throwUnsupportedNodeError(node); + } +} diff --git a/src/func/prettyPrinter.ts b/src/func/prettyPrinter.ts new file mode 100644 index 000000000..cce4662b0 --- /dev/null +++ b/src/func/prettyPrinter.ts @@ -0,0 +1,730 @@ +import { + FuncAstNode, + FuncAstStatement, + FuncAstModule, + FuncAstVersionRange, + FuncAstParameter, + FuncAstConstant, + FuncAstGlobalVariable, + FuncAstFunctionAttribute, + FuncVarDeclPart, + FuncAstId, + FuncAstTypeTensor, + FuncAstMethodId, + FuncAstQuotedId, + FuncAstOperatorId, + FuncAstPlainId, + FuncAstUnusedId, + FuncAstTypeTuple, + FuncAstHole, + FuncAstCR, + FuncAstComment, + FuncAstPragmaLiteral, + FuncAstPragmaVersionRange, + FuncAstPragmaVersionString, + FuncAstInclude, + FuncAstGlobalVariablesDeclaration, + FuncAstConstantsDefinition, + FuncAstAsmFunctionDefinition, + FuncAstFunctionDeclaration, + FuncAstFunctionDefinition, + FuncAstStatementReturn, + FuncAstStatementBlock, + FuncAstStatementConditionIf, + FuncAstStatementConditionElseIf, + FuncAstStatementRepeat, + FuncAstStatementUntil, + FuncAstStatementWhile, + FuncAstStatementTryCatch, + FuncAstStatementExpression, + FuncAstExpressionAssign, + FuncAstExpressionConditional, + FuncAstExpressionCompare, + FuncAstExpressionBitwiseShift, + FuncAstExpressionAddBitwise, + FuncAstExpressionMulBitwise, + FuncAstExpressionUnary, + FuncAstExpressionMethod, + FuncAstExpressionVarDecl, + FuncAstExpressionFunCall, + FuncAstExpressionTensor, + FuncAstExpressionTuple, + FuncAstIntegerLiteral, + FuncAstStringLiteral, + FuncAstType, + FuncAstTypePrimitive, + FuncAstTypeMapped, +} from "./grammar"; +import { throwUnsupportedNodeError } from "./syntaxUtils"; + +export class FuncPrettyPrinter { + private lineLengthLimit: number; + private indent: number; + private currentIndent: number; + + constructor( + params: Partial<{ indent: number; lineLengthLimit: number }> = {}, + ) { + const { indent = 4, lineLengthLimit = 100 } = params; + this.lineLengthLimit = lineLengthLimit; + this.indent = indent; + this.currentIndent = 0; + } + + public prettyPrint(node: FuncAstNode): string { + switch (node.kind) { + case "module": + return this.prettyPrintModule(node as FuncAstModule); + case "pragma_literal": + return this.prettyPrintPragmaLiteral( + node as FuncAstPragmaLiteral, + ); + case "pragma_version_range": + return this.prettyPrintPragmaVersionRange( + node as FuncAstPragmaVersionRange, + ); + case "pragma_version_string": + return this.prettyPrintPragmaVersionString( + node as FuncAstPragmaVersionString, + ); + case "include": + return this.prettyPrintInclude(node as FuncAstInclude); + case "global_variables_declaration": + return this.prettyPrintGlobalVariablesDeclaration( + node as FuncAstGlobalVariablesDeclaration, + ); + case "constants_definition": + return this.prettyPrintConstantsDefinition( + node as FuncAstConstantsDefinition, + ); + case "asm_function_definition": + return this.prettyPrintAsmFunctionDefinition( + node as FuncAstAsmFunctionDefinition, + ); + case "function_declaration": + return this.prettyPrintFunctionDeclaration( + node as FuncAstFunctionDeclaration, + ); + case "function_definition": + return this.prettyPrintFunctionDefinition( + node as FuncAstFunctionDefinition, + ); + case "statement_return": + return this.prettyPrintStatementReturn( + node as FuncAstStatementReturn, + ); + case "statement_block": + return this.prettyPrintStatementBlock( + node as FuncAstStatementBlock, + ); + case "statement_empty": + return ";"; + case "statement_condition_if": + return this.prettyPrintStatementConditionIf( + node as FuncAstStatementConditionIf, + ); + case "statement_condition_elseif": + return this.prettyPrintStatementConditionElseIf( + node as FuncAstStatementConditionElseIf, + ); + case "statement_repeat": + return this.prettyPrintStatementRepeat( + node as FuncAstStatementRepeat, + ); + case "statement_until": + return this.prettyPrintStatementUntil( + node as FuncAstStatementUntil, + ); + case "statement_while": + return this.prettyPrintStatementWhile( + node as FuncAstStatementWhile, + ); + case "statement_try_catch": + return this.prettyPrintStatementTryCatch( + node as FuncAstStatementTryCatch, + ); + case "statement_expression": + return this.prettyPrintStatementExpression( + node as FuncAstStatementExpression, + ); + case "expression_assign": + return this.prettyPrintExpressionAssign( + node as FuncAstExpressionAssign, + ); + case "expression_conditional": + return this.prettyPrintExpressionConditional( + node as FuncAstExpressionConditional, + ); + case "expression_compare": + return this.prettyPrintExpressionCompare( + node as FuncAstExpressionCompare, + ); + case "expression_bitwise_shift": + return this.prettyPrintExpressionBitwiseShift( + node as FuncAstExpressionBitwiseShift, + ); + case "expression_add_bitwise": + return this.prettyPrintExpressionAddBitwise( + node as FuncAstExpressionAddBitwise, + ); + case "expression_mul_bitwise": + return this.prettyPrintExpressionMulBitwise( + node as FuncAstExpressionMulBitwise, + ); + case "expression_unary": + return this.prettyPrintExpressionUnary( + node as FuncAstExpressionUnary, + ); + case "expression_method": + return this.prettyPrintExpressionMethod( + node as FuncAstExpressionMethod, + ); + case "expression_var_decl": + return this.prettyPrintExpressionVarDecl( + node as FuncAstExpressionVarDecl, + ); + case "expression_fun_call": + return this.prettyPrintExpressionFunCall( + node as FuncAstExpressionFunCall, + ); + case "expression_tensor": + return this.prettyPrintExpressionTensor( + node as FuncAstExpressionTensor, + ); + case "expression_tuple": + return this.prettyPrintExpressionTuple( + node as FuncAstExpressionTuple, + ); + case "integer_literal": + return this.prettyPrintIntegerLiteral( + node as FuncAstIntegerLiteral, + ); + case "string_singleline": + case "string_multiline": + return this.prettyPrintStringLiteral( + node as FuncAstStringLiteral, + ); + case "comment_singleline": + case "comment_multiline": + return this.prettyPrintComment(node as FuncAstComment); + case "cr": + return this.prettyPrintCR(node as FuncAstCR); + case "method_id": + return this.prettyPrintMethodId(node as FuncAstMethodId); + case "quoted_id": + return this.prettyPrintQuotedId(node as FuncAstQuotedId); + case "operator_id": + return this.prettyPrintOperatorId(node as FuncAstOperatorId); + case "plain_id": + return this.prettyPrintPlainId(node as FuncAstPlainId); + case "unused_id": + return this.prettyPrintUnusedId(node as FuncAstUnusedId); + case "unit": + return "()"; + default: + throwUnsupportedNodeError(node); + } + } + + public prettyPrintType(ty: FuncAstType): string { + switch (ty.kind) { + case "type_var": + return this.prettyPrint(ty.name as FuncAstId); + case "type_primitive": + return this.prettyPrintTypePrimitive( + ty as FuncAstTypePrimitive, + ); + case "type_tensor": + return this.prettyPrintTypeTensor(ty as FuncAstTypeTensor); + case "type_tuple": + return this.prettyPrintTypeTuple(ty as FuncAstTypeTuple); + case "hole": + return this.prettyPrintTypeHole(ty as FuncAstHole); + case "unit": + return "()"; + case "type_mapped": + return this.prettyPrintTypeMapped(ty as FuncAstTypeMapped); + default: + throwUnsupportedNodeError(ty); + } + } + + public prettyPrintFunctionSignature( + returnType: FuncAstType, + name: FuncAstId, + parameters: FuncAstParameter[], + attributes: FuncAstFunctionAttribute[], + ): string { + const returnTypeStr = this.prettyPrintType(returnType); + const nameStr = this.prettyPrint(name); + const paramsStr = parameters + .map((param) => this.prettyPrintParameter(param)) + .join(", "); + const attrsStr = + attributes.length > 0 + ? ` ${attributes.map((attr) => this.prettyPrintFunctionAttribute(attr)).join(" ")}` + : ""; + return `${returnTypeStr} ${nameStr}(${paramsStr})${attrsStr}`; + } + + public prettyPrintStatements(stmts: FuncAstStatement[]): string { + return this.prettyPrintIndentedBlock( + stmts.map(this.prettyPrint.bind(this)).join("\n"), + ); + } + + private prettyPrintModule(node: FuncAstModule): string { + return node.items + .map((item, index) => { + const previousItem = node.items[index - 1]; + const isSequentialPragmaOrInclude = + previousItem && + previousItem.kind === item.kind && + (item.kind === "include" || + item.kind === "pragma_literal" || + item.kind === "pragma_version_range" || + item.kind === "pragma_version_string"); + const separator = isSequentialPragmaOrInclude ? "\n" : "\n\n"; + return (index > 0 ? separator : "") + this.prettyPrint(item); + }) + .join(""); + } + + private prettyPrintPragmaLiteral(node: FuncAstPragmaLiteral): string { + return `#pragma ${node.literal};`; + } + + private prettyPrintPragmaVersionRange( + node: FuncAstPragmaVersionRange, + ): string { + const allow = node.allow ? "allow" : "not-allow"; + return `#pragma ${allow} ${this.prettyPrintVersionRange(node.range)};`; + } + + private prettyPrintVersionRange(node: FuncAstVersionRange): string { + const op = node.op ? `${node.op} ` : ""; + const major = node.major.toString(); + const minor = node.minor !== undefined ? `.${node.minor}` : ""; + const patch = node.patch !== undefined ? `.${node.patch}` : ""; + return `${op}${major}${minor}${patch}`; + } + + private prettyPrintPragmaVersionString( + node: FuncAstPragmaVersionString, + ): string { + return `#pragma test-version-set "${node.version.value}";`; + } + + private prettyPrintInclude(node: FuncAstInclude): string { + return `#include "${node.path.value}";`; + } + + private prettyPrintGlobalVariablesDeclaration( + node: FuncAstGlobalVariablesDeclaration, + ): string { + const globals = node.globals + .map((g) => this.prettyPrintGlobalVariable(g)) + .join(", "); + return `global ${globals};`; + } + + private prettyPrintGlobalVariable(node: FuncAstGlobalVariable): string { + const typeStr = node.ty ? this.prettyPrintType(node.ty) : "var"; + const nameStr = this.prettyPrint(node.name); + return `${typeStr} ${nameStr};`; + } + + private prettyPrintConstantsDefinition( + node: FuncAstConstantsDefinition, + ): string { + const constants = node.constants + .map((c) => this.prettyPrintConstant(c)) + .join(", "); + return `const ${constants};`; + } + + private prettyPrintConstant(node: FuncAstConstant): string { + const typeStr = node.ty ? node.ty : "var"; + const nameStr = this.prettyPrint(node.name); + const valueStr = this.prettyPrint(node.value); + return `${typeStr} ${nameStr} = ${valueStr};`; + } + + prettyPrintAsmStrings(asmStrings: FuncAstStringLiteral[]): string { + return asmStrings.map(this.prettyPrint.bind(this)) + .join("\n") + } + + private prettyPrintAsmFunctionDefinition( + node: FuncAstAsmFunctionDefinition, + ): string { + const signature = this.prettyPrintFunctionSignature( + node.returnTy, + node.name, + node.parameters, + node.attributes, + ); + const asmBody = this.prettyPrintAsmStrings(node.asmStrings); + return `${signature} asm ${asmBody};`; + } + + private prettyPrintFunctionDeclaration( + node: FuncAstFunctionDeclaration, + ): string { + const signature = this.prettyPrintFunctionSignature( + node.returnTy, + node.name, + node.parameters, + node.attributes, + ); + return `${signature};`; + } + + private prettyPrintFunctionDefinition( + node: FuncAstFunctionDefinition, + ): string { + const signature = this.prettyPrintFunctionSignature( + node.returnTy, + node.name, + node.parameters, + node.attributes, + ); + return `${signature} ${this.prettyPrintStatementBlock(node)}`; + } + + private prettyPrintParameter(param: FuncAstParameter): string { + const typeStr = param.ty ? this.prettyPrintType(param.ty) : "var"; + const nameStr = param.name ? this.prettyPrint(param.name) : ""; + return `${typeStr} ${nameStr}`.trim(); + } + + private prettyPrintFunctionAttribute( + attr: FuncAstFunctionAttribute, + ): string { + switch (attr.kind) { + case "impure": + case "inline": + case "inline_ref": + return attr.kind; + case "method_id": + return attr.value + ? `method_id(${this.prettyPrint(attr.value)})` + : "method_id"; + default: + throwUnsupportedNodeError(attr); + } + } + + private prettyPrintStatementReturn(node: FuncAstStatementReturn): string { + const value = node.expression + ? ` ${this.prettyPrint(node.expression)}` + : ""; + return `return${value};`; + } + + private prettyPrintStatementBlock< + T extends { statements: FuncAstStatement[] }, + >(node: T): string { + return `{\n${this.prettyPrintStatements(node.statements)}\n}`; + } + + private prettyPrintStatementConditionIf( + node: FuncAstStatementConditionIf, + ): string { + const condition = this.prettyPrint(node.condition); + const ifnot = node.positive ? "if" : "ifnot"; + const bodyBlock = this.prettyPrintIndentedBlock( + node.consequences.map(this.prettyPrint.bind(this)).join("\n"), + ); + const elseBlock = node.alternatives + ? ` else {\n${this.prettyPrintIndentedBlock(node.alternatives.map(this.prettyPrint.bind(this)).join("\n"))}\n}` + : ""; + return `${ifnot} (${condition}) {\n${bodyBlock}\n}${elseBlock}`; + } + + private prettyPrintStatementConditionElseIf( + node: FuncAstStatementConditionElseIf, + ): string { + const conditionIf = this.prettyPrint(node.conditionIf); + const conditionElseif = this.prettyPrint(node.conditionElseif); + const ifnotIf = node.positiveIf ? "if" : "ifnot"; + const ifnotElseif = node.positiveElseif ? "elseif" : "elseifnot"; + const bodyBlockIf = this.prettyPrintIndentedBlock( + node.consequencesIf.map(this.prettyPrint.bind(this)).join("\n"), + ); + const bodyBlockElseif = this.prettyPrintIndentedBlock( + node.consequencesElseif.map(this.prettyPrint.bind(this)).join("\n"), + ); + const elseBlock = node.alternativesElseif + ? ` else {\n${this.prettyPrintIndentedBlock(node.alternativesElseif.map(this.prettyPrint.bind(this)).join("\n"))}\n}` + : ""; + return `${ifnotIf} (${conditionIf}) {\n${bodyBlockIf}\n} ${ifnotElseif} (${conditionElseif}) {\n${bodyBlockElseif}\n}${elseBlock}`; + } + + private prettyPrintStatementRepeat(node: FuncAstStatementRepeat): string { + const condition = this.prettyPrint(node.iterations); + const body = this.prettyPrintIndentedBlock( + node.statements.map(this.prettyPrint.bind(this)).join("\n"), + ); + return `repeat ${condition} {\n${body}\n}`; + } + + private prettyPrintStatementUntil(node: FuncAstStatementUntil): string { + const condition = this.prettyPrint(node.condition); + const body = this.prettyPrintIndentedBlock( + node.statements.map(this.prettyPrint.bind(this)).join("\n"), + ); + return `do {\n${body}\n} until ${condition};`; + } + + private prettyPrintStatementWhile(node: FuncAstStatementWhile): string { + const condition = this.prettyPrint(node.condition); + const body = this.prettyPrintIndentedBlock( + node.statements.map(this.prettyPrint.bind(this)).join("\n"), + ); + return `while ${condition} {\n${body}\n}`; + } + + private prettyPrintStatementTryCatch( + node: FuncAstStatementTryCatch, + ): string { + const tryBlock = this.prettyPrintIndentedBlock( + node.statementsTry.map(this.prettyPrint.bind(this)).join("\n"), + ); + const catchStr = + node.catchDefinitions === "_" + ? "_" + : [ + this.prettyPrint(node.catchDefinitions.exceptionName), + this.prettyPrint(node.catchDefinitions.exitCodeName), + ].join(", "); + const catchBlock = this.prettyPrintIndentedBlock( + node.statementsCatch.map(this.prettyPrint.bind(this)).join("\n"), + ); + return `try {\n${tryBlock}\n} catch (${catchStr}) {\n${catchBlock}\n}`; + } + + private prettyPrintStatementExpression( + node: FuncAstStatementExpression, + ): string { + return `${this.prettyPrint(node.expression)};`; + } + + private prettyPrintExpressionAssign(node: FuncAstExpressionAssign): string { + const left = this.prettyPrint(node.left); + const right = this.prettyPrint(node.right); + return `${left} ${node.op} ${right}`; + } + + private prettyPrintExpressionConditional( + node: FuncAstExpressionConditional, + ): string { + const condition = this.prettyPrint(node.condition); + const consequence = this.prettyPrint(node.consequence); + const alternative = this.prettyPrint(node.alternative); + return `${condition} ? ${consequence} : ${alternative}`; + } + + private prettyPrintExpressionCompare( + node: FuncAstExpressionCompare, + ): string { + const left = this.prettyPrint(node.left); + const right = this.prettyPrint(node.right); + return `${left} ${node.op} ${right}`; + } + + private prettyPrintExpressionBitwiseShift( + node: FuncAstExpressionBitwiseShift, + ): string { + const left = this.prettyPrint(node.left); + const ops = node.ops + .map((op) => `${op.op} ${this.prettyPrint(op.expr)}`) + .join(" "); + return `${left} ${ops}`; + } + + private prettyPrintExpressionAddBitwise( + node: FuncAstExpressionAddBitwise, + ): string { + const left = node.negateLeft + ? `-${this.prettyPrint(node.left)}` + : this.prettyPrint(node.left); + const ops = node.ops + .map((op) => `${op.op} ${this.prettyPrint(op.expr)}`) + .join(" "); + return `${left} ${ops}`; + } + + private prettyPrintExpressionMulBitwise( + node: FuncAstExpressionMulBitwise, + ): string { + const left = this.prettyPrint(node.left); + const ops = node.ops + .map((op) => `${op.op} ${this.prettyPrint(op.expr)}`) + .join(" "); + return `${left} ${ops}`; + } + + private prettyPrintExpressionUnary(node: FuncAstExpressionUnary): string { + const operand = this.prettyPrint(node.operand); + return `${node.op}${operand}`; + } + + private prettyPrintExpressionMethod(node: FuncAstExpressionMethod): string { + const object = this.prettyPrint(node.object); + const calls = node.calls + .map( + (call) => + `${this.prettyPrint(call.name)}${this.prettyPrint(call.argument)}`, + ) + .join(""); + return `${object}${calls}`; + } + + private prettyPrintExpressionVarDecl( + node: FuncAstExpressionVarDecl, + ): string { + const type = this.prettyPrintType(node.ty); + const names = this.prettyPrintVarDeclPart(node.names); + return `${type} ${names}`; + } + + private prettyPrintVarDeclPart(node: FuncVarDeclPart): string { + switch (node.kind) { + case "plain_id": + case "quoted_id": + case "method_id": + case "operator_id": + case "unused_id": + return this.prettyPrint(node as FuncAstId); + case "expression_tensor_var_decl": { + const names = node.names + .map((name) => this.prettyPrint(name)) + .join(", "); + return `(${names})`; + } + case "expression_tuple_var_decl": { + const names = node.names + .map((name) => this.prettyPrint(name)) + .join(", "); + return `[${names}]`; + } + default: + throwUnsupportedNodeError(node); + } + } + + private prettyPrintExpressionFunCall( + node: FuncAstExpressionFunCall, + ): string { + const object = this.prettyPrint(node.object); + const tensor = this.prettyPrint(node.argument); + return `${object}${tensor}`; + } + + private prettyPrintExpressionTensor(node: FuncAstExpressionTensor): string { + const expressions = node.expressions + .map((expr) => this.prettyPrint(expr)) + .join(", "); + return `(${expressions})`; + } + + private prettyPrintExpressionTuple(node: FuncAstExpressionTuple): string { + const expressions = node.expressions + .map((expr) => this.prettyPrint(expr)) + .join(", "); + return `[${expressions}]`; + } + + private prettyPrintIntegerLiteral(node: FuncAstIntegerLiteral): string { + return node.isHex + ? `0x${node.value.toString(16)}` + : node.value.toString(); + } + + private prettyPrintStringLiteral(node: FuncAstStringLiteral): string { + const type = node.ty ? node.ty : ""; + const value = + node.kind === "string_singleline" + ? `"${node.value}"` + : `"""${node.value}"""`; + return `${value}${type}`; + } + + private prettyPrintTypePrimitive(node: FuncAstTypePrimitive): string { + return node.value; + } + + private prettyPrintComment(node: FuncAstComment): string { + if (node.kind === "comment_singleline") { + return `;; ${node.line}`; + } else { + const lines = node.lines.join("\n;; "); + return node.style === "{-" ? `{- ${lines} -}` : `;; ${lines}`; + } + } + + private prettyPrintCR(node: FuncAstCR): string { + // Remove an extra newline, since PrettyPrinter adds newlines between AST entries + if (node.lines === 1) { + return ""; + } + return "\n".repeat(node.lines - 1); + } + + private prettyPrintIndentedBlock(content: string): string { + this.currentIndent += this.indent; + const indentedContent = content + .split("\n") + .map((line) => " ".repeat(this.currentIndent) + line) + .join("\n"); + this.currentIndent -= this.indent; + return indentedContent; + } + + private prettyPrintTypeTensor(node: FuncAstTypeTensor): string { + return `(${node.types.map((type) => this.prettyPrintType(type)).join(", ")})`; + } + + private prettyPrintTypeTuple(node: FuncAstTypeTuple): string { + return `[${node.types.map((type) => this.prettyPrintType(type)).join(", ")}]`; + } + + private prettyPrintTypeHole(node: FuncAstHole): string { + return node.value; + } + + private prettyPrintTypeMapped(node: FuncAstTypeMapped): string { + const value = this.prettyPrintType(node.value); + const mapsTo = this.prettyPrintType(node.mapsTo); + return `${value} -> ${mapsTo}`; + } + + private prettyPrintMethodId(node: FuncAstMethodId): string { + return `${node.prefix}${node.value}`; + } + + private prettyPrintQuotedId(node: FuncAstQuotedId): string { + return `\`${node.value}\``; + } + + private prettyPrintOperatorId(node: FuncAstOperatorId): string { + return node.value; + } + + private prettyPrintPlainId(node: FuncAstPlainId): string { + return node.value; + } + + private prettyPrintUnusedId(node: FuncAstUnusedId): string { + return node.value; + } +} + +export function prettyPrint(node: FuncAstNode): string | never { + return (new FuncPrettyPrinter()).prettyPrint(node); +} + +export function prettyPrintType(ty: FuncAstType): string | never { + return (new FuncPrettyPrinter()).prettyPrintType(ty); +} diff --git a/src/func/syntaxConstructors.ts b/src/func/syntaxConstructors.ts new file mode 100644 index 000000000..3d518ec3b --- /dev/null +++ b/src/func/syntaxConstructors.ts @@ -0,0 +1,731 @@ +import { + funcOpCompare, + funcOpBitwiseShift, + funcOpAddBitwise, + funcOpMulBitwise, + FuncAstHole, + FuncAstVersionRange, + FuncAstPragmaVersionRange, + FuncAstStatementExpression, + FuncPragmaLiteralValue, + FuncAstConstantsDefinition, + FuncConstantType, + FuncAstParameter, + FuncAstCR, + FuncAstCommentSingleLine, + FuncAstCommentMultiLine, + FuncAstExpressionVarDecl, + FuncAstUnit, + FuncAstExpressionTensor, + FuncAstExpressionTuple, + FuncAstIntegerLiteral, + FuncOpUnary, + FuncAstExpressionUnary, + FuncAstExpressionFunCall, + FuncAstBinaryExpression, + FuncStringType, + FuncBinaryOp, + FuncAstExpressionAssign, + FuncAstExpressionConditional, + FuncOpAssign, + FuncAstId, + FuncAstStringLiteral, + FuncAstPlainId, + FuncAstStatementReturn, + FuncAstStatementBlock, + FuncAstStatementRepeat, + FuncAstStatementCondition, + FuncAstStatement, + FuncAstStatementUntil, + FuncAstStatementWhile, + FuncAstExpression, + FuncAstStatementTryCatch, + FuncAstType, + FuncAstFunctionAttribute, + FuncAstFunctionDeclaration, + FuncAstFunctionDefinition, + FuncAstAsmFunctionDefinition, + FuncAstComment, + FuncAstInclude, + FuncAstPragma, + FuncAstGlobalVariable, + FuncAstModuleItem, + FuncAstModule, + FuncAstGlobalVariablesDeclaration, + FuncCatchDefinitions, + dummySrcInfo, +} from "./grammar"; +import { dummySrcInfo as tactDummySrcInfo } from "../grammar/grammar"; +import { throwInternalCompilerError } from "../errors"; + +import JSONbig from "json-bigint"; + +function wrapToId(v: T | string): T { + if (typeof v === "string" && v.includes("[object")) { + throw new Error(`Incorrect input: ${JSONbig.stringify(v, null, 2)}`); + } + return typeof v === "string" ? (id(v) as T) : v; +} + +// +// Types +// +export class Type { + public static int(): FuncAstType { + return { kind: "type_primitive", value: "int", loc: dummySrcInfo }; + } + + public static cell(): FuncAstType { + return { kind: "type_primitive", value: "cell", loc: dummySrcInfo }; + } + + public static slice(): FuncAstType { + return { kind: "type_primitive", value: "slice", loc: dummySrcInfo }; + } + + public static builder(): FuncAstType { + return { kind: "type_primitive", value: "builder", loc: dummySrcInfo }; + } + + public static cont(): FuncAstType { + return { kind: "type_primitive", value: "cont", loc: dummySrcInfo }; + } + + public static tuple(): FuncAstType { + return { kind: "type_primitive", value: "tuple", loc: dummySrcInfo }; + } + + public static tensor(...types: FuncAstType[]): FuncAstType { + return { kind: "type_tensor", types, loc: dummySrcInfo }; + } + + public static tuple_values(...types: FuncAstType[]): FuncAstType { + return { kind: "type_tuple", types, loc: dummySrcInfo }; + } + + public static hole(): FuncAstHole { + return { kind: "hole", value: "_", loc: dummySrcInfo }; + } +} + +export class FunAttr { + public static impure(): FuncAstFunctionAttribute { + return { kind: "impure", loc: dummySrcInfo }; + } + + public static inline(): FuncAstFunctionAttribute { + return { kind: "inline", loc: dummySrcInfo }; + } + + public static inline_ref(): FuncAstFunctionAttribute { + return { kind: "inline_ref", loc: dummySrcInfo }; + } + + public static method_id( + value?: bigint | number | string, + ): FuncAstFunctionAttribute { + const literal = + value === undefined + ? undefined + : typeof value === "string" + ? string(value) + : int(value as bigint | number); + return { kind: "method_id", value: literal, loc: dummySrcInfo }; + } +} + +// +// Expressions +// + +function integerLiteral( + num: bigint | number, + isHex: boolean, +): FuncAstIntegerLiteral { + return { + kind: "integer_literal", + value: typeof num === "bigint" ? num : BigInt(num), + isHex, + loc: dummySrcInfo, + }; +} + +export function int(num: bigint | number): FuncAstIntegerLiteral { + return integerLiteral(num, false); +} + +function hexToBigInt(hexString: string): bigint { + if (hexString.startsWith("0x")) { + hexString = hexString.slice(2); + } + return BigInt(`0x${hexString}`); +} + +export function hex(value: string | bigint | number): FuncAstIntegerLiteral { + const num = typeof value === "string" ? hexToBigInt(value) : value; + return integerLiteral(num, true); +} + +export function bool(value: boolean): FuncAstPlainId { + return id(`${value}`) as FuncAstPlainId; +} + +export function string( + value: string, + ty?: FuncStringType, +): FuncAstStringLiteral { + return { + kind: "string_singleline", + value, + ty, + loc: dummySrcInfo, + }; +} + +export function nil(): FuncAstPlainId { + return id("nil") as FuncAstPlainId; +} + +export function id(value: string): FuncAstId { + if (value.length > 1 && (value.startsWith(".") || value.startsWith("~"))) { + return { + kind: "method_id", + value: value.slice(1), + prefix: value[0]! as "." | "~", + loc: dummySrcInfo, + }; + } else { + return { + kind: "plain_id", + value, + loc: dummySrcInfo, + }; + } +} + +export function call( + fun: FuncAstExpression | string, + arg: FuncAstExpressionTensor, + // TODO: doesn't support method calls + params: Partial<{ receiver: FuncAstExpression }> = {}, +): FuncAstExpressionFunCall { + return { + kind: "expression_fun_call", + object: wrapToId(fun) as FuncAstId, + argument: arg, + loc: dummySrcInfo, + }; +} + +export function assign( + left: FuncAstExpression, + right: FuncAstExpression, +): FuncAstExpressionAssign { + return augassign(left, "=", right); +} + +export function augassign( + left: FuncAstExpression, + op: FuncOpAssign, + right: FuncAstExpression, +): FuncAstExpressionAssign { + return { + kind: "expression_assign", + left, + op, + right, + loc: dummySrcInfo, + }; +} + +export function ternary( + cond: FuncAstExpression, + trueExpr: FuncAstExpression, + falseExpr: FuncAstExpression, +): FuncAstExpressionConditional { + return { + kind: "expression_conditional", + condition: cond, + consequence: trueExpr, + alternative: falseExpr, + loc: dummySrcInfo, + }; +} + +function isFuncOp( + str: string, + ops: T, +): str is T[number] { + return (ops as readonly string[]).includes(str); +} + +export function binop( + left: FuncAstExpression, + op: FuncBinaryOp, + right: FuncAstExpression, +): FuncAstBinaryExpression { + if (isFuncOp(op, funcOpCompare)) { + return { + kind: "expression_compare", + left, + op, + right, + loc: dummySrcInfo, + }; + } + if (isFuncOp(op, funcOpBitwiseShift)) { + return { + kind: "expression_bitwise_shift", + left, + ops: [{ op, expr: right }], + loc: dummySrcInfo, + }; + } + if (isFuncOp(op, funcOpAddBitwise)) { + return { + kind: "expression_add_bitwise", + negateLeft: false, + left, + ops: [{ op, expr: right }], + loc: dummySrcInfo, + }; + } + if (isFuncOp(op, funcOpMulBitwise)) { + return { + kind: "expression_mul_bitwise", + left, + ops: [{ op, expr: right }], + loc: dummySrcInfo, + }; + } + + throwInternalCompilerError( + `Unsupported binary operation: ${op}`, + tactDummySrcInfo, + ); +} + +export function unop( + op: FuncOpUnary, + operand: FuncAstExpression, +): FuncAstExpressionUnary { + return { + kind: "expression_unary", + op, + operand, + loc: dummySrcInfo, + }; +} + +export function tuple( + expressions: FuncAstExpression[], +): FuncAstExpressionTuple { + return { + kind: "expression_tuple", + expressions, + loc: dummySrcInfo, + }; +} + +export function tensor( + ...expressions: FuncAstExpression[] +): FuncAstExpressionTensor { + return { + kind: "expression_tensor", + expressions, + loc: dummySrcInfo, + }; +} + +export function unit(): FuncAstUnit { + return { kind: "unit", value: "()", loc: dummySrcInfo }; +} + +export function hole(value: "_" | "var"): FuncAstType { + return { kind: "hole", value, loc: dummySrcInfo }; +} + +// +// Statements +// + +export function vardef( + ty: FuncAstType | "_", + names: string | string[], + init?: FuncAstExpression, +): FuncAstStatement { + if (Array.isArray(names) && names.length === 0) { + throwInternalCompilerError( + `Variable definition cannot have an empty set of names`, + tactDummySrcInfo, + ); + } + const varDecl: FuncAstExpressionVarDecl = { + kind: "expression_var_decl", + ty: ty === "_" ? hole("_") : ty, + names: + typeof names === "string" + ? (id(names) as FuncAstId) + : { + kind: "expression_tensor_var_decl", + names: names.map(id), + loc: dummySrcInfo, + }, + loc: dummySrcInfo, + }; + return expr(init === undefined ? varDecl : assign(varDecl, init)); +} + +export function ret(expression?: FuncAstExpression): FuncAstStatementReturn { + return { + kind: "statement_return", + expression, + loc: dummySrcInfo, + }; +} + +export function block(statements: FuncAstStatement[]): FuncAstStatementBlock { + return { + kind: "statement_block", + statements, + loc: dummySrcInfo, + }; +} + +export function repeat( + iterations: FuncAstExpression, + statements: FuncAstStatement[], +): FuncAstStatementRepeat { + return { + kind: "statement_repeat", + iterations, + statements, + loc: dummySrcInfo, + }; +} + +export function condition( + condition: FuncAstExpression, + bodyStmts: FuncAstStatement[], + elseStmts?: FuncAstStatement[], + params: Partial<{ positive: boolean }> = {}, +): FuncAstStatementCondition { + const { positive = true } = params; + return { + kind: "statement_condition_if", + condition, + positive, + consequences: bodyStmts, + alternatives: elseStmts, + loc: dummySrcInfo, + }; +} + +export function conditionElseif( + conditionIf: FuncAstExpression, + bodyStmts: FuncAstStatement[], + conditionElseif: FuncAstExpression, + elseifStmts: FuncAstStatement[], + elseStmts?: FuncAstStatement[], + params: Partial<{ positiveIf: boolean; positiveElseif: boolean }> = {}, +): FuncAstStatementCondition { + const { positiveIf = false, positiveElseif = false } = params; + return { + kind: "statement_condition_elseif", + positiveIf, + conditionIf, + consequencesIf: bodyStmts, + positiveElseif, + conditionElseif, + consequencesElseif: elseifStmts, + alternativesElseif: elseStmts, + loc: dummySrcInfo, + }; +} + +export function doUntil( + condition: FuncAstExpression, + statements: FuncAstStatement[], +): FuncAstStatementUntil { + return { + kind: "statement_until", + statements, + condition, + loc: dummySrcInfo, + }; +} + +export function while_( + condition: FuncAstExpression, + statements: FuncAstStatement[], +): FuncAstStatementWhile { + return { + kind: "statement_while", + condition, + statements, + loc: dummySrcInfo, + }; +} + +export function expr( + expression: FuncAstExpression, +): FuncAstStatementExpression { + return { + kind: "statement_expression", + expression, + loc: dummySrcInfo, + }; +} + +export function try_( + statementsTry: FuncAstStatement[], +): FuncAstStatementTryCatch { + return { + kind: "statement_try_catch", + statementsTry, + catchDefinitions: "_", + statementsCatch: [], + loc: dummySrcInfo, + }; +} + +export function tryCatch( + statementsTry: FuncAstStatement[], + catchDefinitions: "_" | FuncCatchDefinitions, + statementsCatch: FuncAstStatement[], +): FuncAstStatementTryCatch { + return { + kind: "statement_try_catch", + statementsTry, + catchDefinitions, + statementsCatch, + loc: dummySrcInfo, + }; +} + +// Other top-level elements + +export function comment( + ...args: (string | Partial<{ skipCR: boolean; style: ";" | ";;" }>)[] +): FuncAstComment { + let params: Partial<{ skipCR: boolean; style: ";" | ";;" }> = {}; + let values: string[]; + + if (args.length > 0 && typeof args[args.length - 1] === "object") { + params = args.pop() as Partial<{ skipCR: boolean; style: ";" | ";;" }>; + } + values = args as string[]; + const { skipCR = false, style = ";;" } = params; + return values.length === 1 + ? ({ + kind: "comment_singleline", + line: values[0], + style, + loc: dummySrcInfo, + } as FuncAstCommentSingleLine) + : ({ + kind: "comment_multiline", + lines: values, + skipCR, + style, + loc: dummySrcInfo, + } as FuncAstCommentMultiLine); +} + +export function cr(lines: number = 1): FuncAstCR { + return { + kind: "cr", + lines, + }; +} + +export function constant( + name: string | FuncAstId, + init: FuncAstExpression, + ty?: FuncConstantType, +): FuncAstConstantsDefinition { + return { + kind: "constants_definition", + constants: [ + { + kind: "constant", + ty, + name: wrapToId(name) as FuncAstPlainId, + value: init, + loc: dummySrcInfo, + }, + ], + loc: dummySrcInfo, + }; +} + +export function functionParam( + name: string | FuncAstPlainId, + ty: FuncAstType | undefined, +): FuncAstParameter { + return { + kind: "parameter", + name: wrapToId(name), + ty, + loc: dummySrcInfo, + }; +} + +export function functionDeclaration( + name: string | FuncAstPlainId, + parameters: FuncAstParameter[], + attributes: FuncAstFunctionAttribute[], + returnTy: FuncAstType, +): FuncAstFunctionDeclaration { + return { + kind: "function_declaration", + forall: undefined, + name: wrapToId(name), + parameters, + attributes, + returnTy, + loc: dummySrcInfo, + }; +} + +export type FunParamValue = [string, FuncAstType]; + +function transformFunctionParams( + paramValues: FunParamValue[], +): FuncAstParameter[] { + return paramValues.map( + ([name, ty]) => + ({ + kind: "parameter", + name: wrapToId(name), + ty, + loc: dummySrcInfo, + }) as FuncAstParameter, + ); +} + +export function fun( + name: string | FuncAstId, + paramValues: FunParamValue[], + attributes: FuncAstFunctionAttribute[], + returnTy: FuncAstType, + statements: FuncAstStatement[], +): FuncAstFunctionDefinition { + return { + kind: "function_definition", + forall: undefined, + name: wrapToId(name) as FuncAstPlainId, + attributes, + parameters: transformFunctionParams(paramValues), + returnTy, + statements, + loc: dummySrcInfo, + }; +} + +export function asmfun( + name: string | FuncAstId, + paramValues: FunParamValue[], + attributes: FuncAstFunctionAttribute[], + returnTy: FuncAstType, + asmStrings: string[], +): FuncAstAsmFunctionDefinition { + return { + kind: "asm_function_definition", + forall: undefined, + name: wrapToId(name) as FuncAstPlainId, + attributes, + parameters: transformFunctionParams(paramValues), + returnTy, + arrangement: undefined, + asmStrings: asmStrings.map((a) => string(a)), + loc: dummySrcInfo, + }; +} + +export function toDeclaration( + def: FuncAstFunctionDefinition, +): FuncAstFunctionDeclaration { + return { + kind: "function_declaration", + forall: def.forall, + returnTy: def.returnTy, + attributes: def.attributes, + name: def.name, + parameters: def.parameters, + loc: dummySrcInfo, + }; +} + +export function include(path: string): FuncAstInclude { + return { + kind: "include", + path: string(path), + loc: dummySrcInfo, + }; +} + +export function version( + op: FuncAstVersionRange["op"], + versionString: string, +): FuncAstPragmaVersionRange { + const versionNumbers = versionString.split(".").map(BigInt); + if (versionNumbers.length < 1) { + throwInternalCompilerError( + `Incorrect version: ${versionString}. Expected format: "1.2.3"`, + tactDummySrcInfo, + ); + } + const range: FuncAstVersionRange = { + kind: "version_range", + op, + major: versionNumbers[0]!, + minor: versionNumbers[1], + patch: versionNumbers[2], + loc: dummySrcInfo, + }; + return { + kind: "pragma_version_range", + allow: true, + range, + loc: dummySrcInfo, + }; +} + +export function pragma(value: FuncPragmaLiteralValue): FuncAstPragma { + return { + kind: "pragma_literal", + literal: value, + loc: dummySrcInfo, + }; +} + +export function global( + ty: FuncAstType, + name: string | FuncAstId, +): FuncAstGlobalVariablesDeclaration { + return { + kind: "global_variables_declaration", + globals: [ + { + kind: "global_variable", + name: wrapToId(name), + ty, + loc: dummySrcInfo, + } as FuncAstGlobalVariable, + ], + loc: dummySrcInfo, + }; +} + +export function moduleEntry(entry: FuncAstModuleItem): FuncAstModuleItem { + return entry; +} + +export function mod(...items: FuncAstModuleItem[]): FuncAstModule { + return { + kind: "module", + items, + loc: dummySrcInfo, + }; +} diff --git a/src/func/syntaxUtils.ts b/src/func/syntaxUtils.ts new file mode 100644 index 000000000..d8247f2b7 --- /dev/null +++ b/src/func/syntaxUtils.ts @@ -0,0 +1,30 @@ +import JSONbig from "json-bigint"; +import { throwInternalCompilerError } from "../errors"; +import { dummySrcInfo as tactDummySrcInfo } from "../grammar/grammar"; + +/** + * Provides deep copy that works for AST nodes. + */ +export function deepCopy(obj: T): T { + if (obj === null || typeof obj !== "object") { + return obj; + } + if (Array.isArray(obj)) { + return obj.map((item) => deepCopy(item)) as unknown as T; + } + const copy = {} as T; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + (copy as any)[key] = deepCopy((obj as any)[key]); + } + } + return copy; +} + +// TODO(jubnzv): Refactor and move to errors.ts when merging with `main` +export function throwUnsupportedNodeError(node: any): never { + throwInternalCompilerError( + `Unsupported node: ${JSONbig.stringify(node, null, 2)}`, + tactDummySrcInfo, + ); +} diff --git a/src/generator/Writer.ts b/src/generator/Writer.ts index 7c13eca6d..9feaee525 100644 --- a/src/generator/Writer.ts +++ b/src/generator/Writer.ts @@ -31,36 +31,30 @@ export type WrittenFunction = { export class WriterContext { readonly ctx: CompilerContext; - #name: string; - #functions: Map = new Map(); - #functionsRendering: Set = new Set(); - #pendingWriter: Writer | null = null; - #pendingCode: Body | null = null; - #pendingDepends: Set | null = null; - #pendingName: string | null = null; - #pendingSignature: string | null = null; - #pendingFlags: Set | null = null; - #pendingComment: string | null = null; - #pendingContext: string | null = null; - #nextId = 0; - // #headers: string[] = []; - #rendered: Set = new Set(); + name: string; + functions: Map = new Map(); + functionsRendering: Set = new Set(); + pendingWriter: Writer | null = null; + pendingCode: Body | null = null; + pendingDepends: Set | null = null; + pendingName: string | null = null; + pendingSignature: string | null = null; + pendingFlags: Set | null = null; + pendingComment: string | null = null; + pendingContext: string | null = null; + nextId = 0; + rendered: Set = new Set(); constructor(ctx: CompilerContext, name: string) { this.ctx = ctx; - this.#name = name; - } - - get name() { - return this.#name; + this.name = name; } clone() { - const res = new WriterContext(this.ctx, this.#name); - res.#functions = new Map(this.#functions); - res.#nextId = this.#nextId; - // res.#headers = [...this.#headers]; - res.#rendered = new Set(this.#rendered); + const res = new WriterContext(this.ctx, this.name); + res.functions = new Map(this.functions); + res.nextId = this.nextId; + res.rendered = new Set(this.rendered); return res; } @@ -71,9 +65,9 @@ export class WriterContext { extract(debug: boolean = false) { // Check dependencies const missing: Map = new Map(); - for (const f of this.#functions.values()) { + for (const f of this.functions.values()) { for (const d of f.depends) { - if (!this.#functions.has(d)) { + if (!this.functions.has(d)) { if (!missing.has(d)) { missing.set(d, [f.name]); } else { @@ -91,14 +85,14 @@ export class WriterContext { } // All functions - let all = Array.from(this.#functions.values()); + let all = Array.from(this.functions.values()); // Remove unused if (!debug) { const used: Set = new Set(); const visit = (name: string) => { used.add(name); - const f = this.#functions.get(name)!; + const f = this.functions.get(name)!; for (const d of f.depends) { visit(d); } @@ -109,7 +103,7 @@ export class WriterContext { // Sort functions const sorted = topologicalSort(all, (f) => - Array.from(f.depends).map((v) => this.#functions.get(v)!), + Array.from(f.depends).map((v) => this.functions.get(v)!), ); return sorted; @@ -123,7 +117,7 @@ export class WriterContext { this.fun(name, () => { this.signature(""); this.context("stdlib"); - this.#pendingCode = { kind: "skip" }; + this.pendingCode = { kind: "skip" }; }); } @@ -132,10 +126,10 @@ export class WriterContext { // Duplicates check // - if (this.#functions.has(name)) { + if (this.functions.has(name)) { throw new Error(`Function "${name}" already defined`); // Should not happen } - if (this.#functionsRendering.has(name)) { + if (this.functionsRendering.has(name)) { throw new Error(`Function "${name}" already rendering`); // Should not happen } @@ -143,52 +137,52 @@ export class WriterContext { // Nesting check // - if (this.#pendingName) { - const w = this.#pendingWriter; - const d = this.#pendingDepends; - const n = this.#pendingName; - const s = this.#pendingSignature; - const f = this.#pendingFlags; - const c = this.#pendingCode; - const cc = this.#pendingComment; - const cs = this.#pendingContext; - this.#pendingDepends = null; - this.#pendingWriter = null; - this.#pendingName = null; - this.#pendingSignature = null; - this.#pendingFlags = null; - this.#pendingCode = null; - this.#pendingComment = null; - this.#pendingContext = null; + if (this.pendingName) { + const w = this.pendingWriter; + const d = this.pendingDepends; + const n = this.pendingName; + const s = this.pendingSignature; + const f = this.pendingFlags; + const c = this.pendingCode; + const cc = this.pendingComment; + const cs = this.pendingContext; + this.pendingDepends = null; + this.pendingWriter = null; + this.pendingName = null; + this.pendingSignature = null; + this.pendingFlags = null; + this.pendingCode = null; + this.pendingComment = null; + this.pendingContext = null; this.fun(name, handler); - this.#pendingSignature = s; - this.#pendingDepends = d; - this.#pendingWriter = w; - this.#pendingName = n; - this.#pendingFlags = f; - this.#pendingCode = c; - this.#pendingComment = cc; - this.#pendingContext = cs; + this.pendingSignature = s; + this.pendingDepends = d; + this.pendingWriter = w; + this.pendingName = n; + this.pendingFlags = f; + this.pendingCode = c; + this.pendingComment = cc; + this.pendingContext = cs; return; } // Write function - this.#functionsRendering.add(name); - this.#pendingWriter = null; - this.#pendingDepends = new Set(); - this.#pendingName = name; - this.#pendingSignature = null; - this.#pendingFlags = new Set(); - this.#pendingCode = null; - this.#pendingComment = null; - this.#pendingContext = null; + this.functionsRendering.add(name); + this.pendingWriter = null; + this.pendingDepends = new Set(); + this.pendingName = name; + this.pendingSignature = null; + this.pendingFlags = new Set(); + this.pendingCode = null; + this.pendingComment = null; + this.pendingContext = null; handler(); - const depends = this.#pendingDepends; - const signature = this.#pendingSignature!; - const flags = this.#pendingFlags; - const code = this.#pendingCode; - const comment = this.#pendingComment; - const context = this.#pendingContext; + const depends = this.pendingDepends; + const signature = this.pendingSignature!; + const flags = this.pendingFlags; + const code = this.pendingCode; + const comment = this.pendingComment; + const context = this.pendingContext; if (!signature && name !== "$main") { throw new Error(`Function "${name}" signature not set`); } @@ -196,13 +190,13 @@ export class WriterContext { if (!code) { throw new Error(`Function "${name}" body not set`); } - this.#pendingDepends = null; - this.#pendingWriter = null; - this.#pendingName = null; - this.#pendingSignature = null; - this.#pendingFlags = null; - this.#functionsRendering.delete(name); - this.#functions.set(name, { + this.pendingDepends = null; + this.pendingWriter = null; + this.pendingName = null; + this.pendingSignature = null; + this.pendingFlags = null; + this.functionsRendering.delete(name); + this.functions.set(name, { name, code, depends, @@ -214,8 +208,8 @@ export class WriterContext { } asm(shuffle: string, code: string) { - if (this.#pendingName) { - this.#pendingCode = { + if (this.pendingName) { + this.pendingCode = { kind: "asm", shuffle, code, @@ -226,14 +220,14 @@ export class WriterContext { } body(handler: () => void) { - if (this.#pendingWriter) { + if (this.pendingWriter) { throw new Error(`Body can be set only once`); } - this.#pendingWriter = new Writer(); + this.pendingWriter = new Writer(); handler(); - this.#pendingCode = { + this.pendingCode = { kind: "generic", - code: this.#pendingWriter!.end(), + code: this.pendingWriter!.end(), }; } @@ -246,46 +240,46 @@ export class WriterContext { } signature(sig: string) { - if (this.#pendingName) { - this.#pendingSignature = sig; + if (this.pendingName) { + this.pendingSignature = sig; } else { throw new Error(`Signature can be set only inside function`); } } flag(flag: Flag) { - if (this.#pendingName) { - this.#pendingFlags!.add(flag); + if (this.pendingName) { + this.pendingFlags!.add(flag); } else { throw new Error(`Flag can be set only inside function`); } } used(name: string) { - if (this.#pendingName !== name) { - this.#pendingDepends!.add(name); + if (this.pendingName !== name) { + this.pendingDepends!.add(name); } return name; } comment(src: string) { - if (this.#pendingName) { - this.#pendingComment = escapeUnicodeControlCodes(trimIndent(src)); + if (this.pendingName) { + this.pendingComment = escapeUnicodeControlCodes(trimIndent(src)); } else { throw new Error(`Comment can be set only inside function`); } } context(src: string) { - if (this.#pendingName) { - this.#pendingContext = src; + if (this.pendingName) { + this.pendingContext = src; } else { throw new Error(`Context can be set only inside function`); } } currentContext() { - return this.#pendingName; + return this.pendingName; } // @@ -293,37 +287,29 @@ export class WriterContext { // inIndent = (handler: () => void) => { - this.#pendingWriter!.inIndent(handler); + this.pendingWriter!.inIndent(handler); }; append(src: string = "") { - this.#pendingWriter!.append(src); + this.pendingWriter!.append(src); } write(src: string = "") { - this.#pendingWriter!.write(src); + this.pendingWriter!.write(src); } - // - // Headers - // - - // header(src: string) { - // this.#headers.push(src); - // } - // // Utils // isRendered(key: string) { - return this.#rendered.has(key); + return this.rendered.has(key); } markRendered(key: string) { - if (this.#rendered.has(key)) { + if (this.rendered.has(key)) { throw new Error(`Key "${key}" already rendered`); } - this.#rendered.add(key); + this.rendered.add(key); } } diff --git a/src/generatorNew/Writer.ts b/src/generatorNew/Writer.ts new file mode 100644 index 000000000..dd6f9eca0 --- /dev/null +++ b/src/generatorNew/Writer.ts @@ -0,0 +1,421 @@ +import { CompilerContext } from "../context"; +import { escapeUnicodeControlCodes, trimIndent } from "../utils/text"; +import { topologicalSort } from "../utils/utils"; +import { Writer } from "../utils/Writer"; +import { FuncPrettyPrinter } from "../func/prettyPrinter"; +import { + FuncAstFunctionDefinition, + FuncAstAsmFunctionDefinition, + FuncAstModule, + FuncAstNode, + funcBuiltinFunctions, + parse, +} from "../func/grammar"; +import { forEachExpression } from "../func/iterators"; +import JSONbig from "json-bigint"; + +type Flag = "inline" | "impure" | "inline_ref"; + +type Body = + | { + kind: "generic"; + code: string; + } + | { + kind: "asm"; + shuffle: string; + code: string; + } + | { + kind: "skip"; + }; + +export type WrittenFunction = { + name: string; + code: Body; + signature: string; + flags: Set; + depends: Set; + comment: string | null; + context: string | null; + parsed: boolean; +}; + +export class WriterContext { + readonly ctx: CompilerContext; + name: string; + functions: Map = new Map(); + functionsRendering: Set = new Set(); + pendingWriter: Writer | null = null; + pendingCode: Body | null = null; + pendingDepends: Set | null = null; + pendingName: string | null = null; + pendingSignature: string | null = null; + pendingFlags: Set | null = null; + pendingComment: string | null = null; + pendingContext: string | null = null; + nextId = 0; + rendered: Set = new Set(); + + constructor(ctx: CompilerContext, name: string) { + this.ctx = ctx; + this.name = name; + } + + clone() { + const res = new WriterContext(this.ctx, this.name); + res.functions = new Map(this.functions); + res.nextId = this.nextId; + res.rendered = new Set(this.rendered); + return res; + } + + // + // New FunC backend functionality + // + + /** + * Collects names of the functions the given function calls. + */ + private collectDependencies(fun: FuncAstFunctionDefinition): Set { + const depends = new Set(); + forEachExpression(fun, (expr) => { + // XXX: It doesn't save receivers. But should it? + if ( + expr.kind === "expression_fun_call" && + expr.object.kind === "plain_id" && + !funcBuiltinFunctions.includes(expr.object.value) + ) { + depends.add(expr.object.value); + } + }); + return depends; + } + + /** + * Parses the FunC function definition saving it to this context. + * + * @param src FunC source that includes exactly one function definition. + * @throws If the given source cannot be parsed as a simple function/asm function definition. + */ + public parse< + T extends FuncAstFunctionDefinition | FuncAstAsmFunctionDefinition, + >( + src: string, + { + context = "", + comment = "", + }: Partial<{ context: string; comment: string }>, + ): T | never { + const mod = parse(src) as FuncAstModule; + if ( + mod.items.length !== 1 || + !["function_definition", "asm_function_definition"].includes( + mod.items[0]!.kind, + ) + ) { + console.error(); + throw new Error( + `Incorrect function structure: ${JSONbig.stringify(mod)}`, + ); + } + const fundef = mod.items[0]! as T; + const name = fundef.name.value; + this.throwIfDefined(name); + const pp = new FuncPrettyPrinter(); + const code: Body = + fundef.kind === "function_definition" + ? { + kind: "generic", + code: pp.prettyPrintStatements(fundef.statements), + } + : { + kind: "asm", + shuffle: "", + code: pp.prettyPrintAsmStrings(fundef.asmStrings), + }; + const depends = + fundef.kind === "function_definition" + ? this.collectDependencies(fundef) + : new Set(); + const signature = pp.prettyPrintFunctionSignature( + fundef.returnTy, + fundef.name, + fundef.parameters, + fundef.attributes, + ); + const flags = fundef.attributes.reduce((set, attr) => { + if ( + attr.kind === "inline" || + attr.kind === "impure" || + attr.kind === "inline_ref" + ) { + set.add(attr.kind); + } + return set; + }, new Set()); + this.functions.set(name, { + name, + code, + depends, + signature, + flags, + comment, + context, + parsed: true, + }); + return fundef; + } + + // The same as `append`, but adds AST entries. + appendNode(node: FuncAstNode) { + const pp = new FuncPrettyPrinter(); + this.pendingWriter!.append(pp.prettyPrint(node) + "\n"); + } + + // + // Rendering + // + + extract(debug: boolean = false) { + // Remove unavailable dependencies + for (const f of this.functions.values()) { + const updatedDepends = new Set(); + for (const d of f.depends) { + if (this.functions.has(d)) { + updatedDepends.add(d); + } + } + f.depends = updatedDepends; + } + + // All functions + let all = Array.from(this.functions.values()); + + // Remove unused + if (!debug) { + const used: Set = new Set(); + const visit = (name: string) => { + used.add(name); + const f = this.functions.get(name)!; + for (const d of f.depends) { + visit(d); + } + }; + visit("$main"); + all = all.filter((v) => used.has(v.name)); + } + + // Sort functions + const sorted = topologicalSort(all, (f) => + Array.from(f.depends).map((v) => this.functions.get(v)!), + ); + + return sorted; + } + + // + // Declaration + // + + skip(name: string) { + this.fun(name, () => { + this.signature(""); + this.context("stdlib"); + this.pendingCode = { kind: "skip" }; + }); + } + + private throwIfDefined(name: string): void | never { + if (this.functions.has(name)) { + throw new Error(`Function "${name}" already defined`); + } + if (this.functionsRendering.has(name)) { + throw new Error(`Function "${name}" already rendering`); + } + } + + fun(name: string, handler: () => void) { + this.throwIfDefined(name); + + // + // Nesting check + // + + if (this.pendingName) { + const w = this.pendingWriter; + const d = this.pendingDepends; + const n = this.pendingName; + const s = this.pendingSignature; + const f = this.pendingFlags; + const c = this.pendingCode; + const cc = this.pendingComment; + const cs = this.pendingContext; + this.pendingDepends = null; + this.pendingWriter = null; + this.pendingName = null; + this.pendingSignature = null; + this.pendingFlags = null; + this.pendingCode = null; + this.pendingComment = null; + this.pendingContext = null; + this.fun(name, handler); + this.pendingSignature = s; + this.pendingDepends = d; + this.pendingWriter = w; + this.pendingName = n; + this.pendingFlags = f; + this.pendingCode = c; + this.pendingComment = cc; + this.pendingContext = cs; + return; + } + + // Write function + this.functionsRendering.add(name); + this.pendingWriter = null; + this.pendingDepends = new Set(); + this.pendingName = name; + this.pendingSignature = null; + this.pendingFlags = new Set(); + this.pendingCode = null; + this.pendingComment = null; + this.pendingContext = null; + handler(); + const depends = this.pendingDepends; + const signature = this.pendingSignature!; + const flags = this.pendingFlags; + const code = this.pendingCode; + const comment = this.pendingComment; + const context = this.pendingContext; + if (!signature && name !== "$main") { + throw new Error(`Function "${name}" signature not set`); + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!code) { + throw new Error(`Function "${name}" body not set`); + } + this.pendingDepends = null; + this.pendingWriter = null; + this.pendingName = null; + this.pendingSignature = null; + this.pendingFlags = null; + this.functionsRendering.delete(name); + this.functions.set(name, { + name, + code, + depends, + signature, + flags, + comment, + context, + parsed: false, + }); + } + + asm(shuffle: string, code: string) { + if (this.pendingName) { + this.pendingCode = { + kind: "asm", + shuffle, + code, + }; + } else { + throw new Error(`ASM can be set only inside function`); + } + } + + body(handler: () => void) { + if (this.pendingWriter) { + throw new Error(`Body can be set only once`); + } + this.pendingWriter = new Writer(); + handler(); + this.pendingCode = { + kind: "generic", + code: this.pendingWriter!.end(), + }; + } + + main(handler: () => void) { + this.fun("$main", () => { + this.body(() => { + handler(); + }); + }); + } + + signature(sig: string) { + if (this.pendingName) { + this.pendingSignature = sig; + } else { + throw new Error(`Signature can be set only inside function`); + } + } + + flag(flag: Flag) { + if (this.pendingName) { + this.pendingFlags!.add(flag); + } else { + throw new Error(`Flag can be set only inside function`); + } + } + + used(name: string) { + if (this.pendingName !== name) { + this.pendingDepends!.add(name); + } + return name; + } + + comment(src: string) { + if (this.pendingName) { + this.pendingComment = escapeUnicodeControlCodes(trimIndent(src)); + } else { + throw new Error(`Comment can be set only inside function`); + } + } + + context(src: string) { + if (this.pendingName) { + this.pendingContext = src; + } else { + throw new Error(`Context can be set only inside function`); + } + } + + currentContext() { + return this.pendingName; + } + + // + // Writers + // + + inIndent = (handler: () => void) => { + this.pendingWriter!.inIndent(handler); + }; + + append(src: string = "") { + this.pendingWriter!.append(src); + } + + write(src: string = "") { + this.pendingWriter!.write(src); + } + + // + // Utils + // + + isRendered(key: string) { + return this.rendered.has(key); + } + + markRendered(key: string) { + if (this.rendered.has(key)) { + throw new Error(`Key "${key}" already rendered`); + } + this.rendered.add(key); + } +} diff --git a/src/generatorNew/createABI.ts b/src/generatorNew/createABI.ts new file mode 100644 index 000000000..406606370 --- /dev/null +++ b/src/generatorNew/createABI.ts @@ -0,0 +1,176 @@ +import { ABIGetter, ABIReceiver, ABIType, ContractABI } from "@ton/core"; +import { contractErrors } from "../abi/errors"; +import { CompilerContext } from "../context"; +import { idText } from "../grammar/ast"; +import { getSupportedInterfaces } from "../types/getSupportedInterfaces"; +import { createABITypeRefFromTypeRef } from "../types/resolveABITypeRef"; +import { getAllTypes } from "../types/resolveDescriptors"; +import { getAllErrors } from "../types/resolveErrors"; + +export function createABI(ctx: CompilerContext, name: string): ContractABI { + const allTypes = getAllTypes(ctx); + + // Contract + const contract = allTypes.find((v) => v.name === name); + if (!contract) { + throw Error(`Contract "${name}" not found`); + } + if (contract.kind !== "contract") { + throw Error("Not a contract"); + } + + // Structs + const types: ABIType[] = []; + for (const t of allTypes) { + if (t.kind === "struct") { + types.push({ + name: t.name, + header: Number(t.header?.value), + fields: t.fields.map((v) => v.abi), + }); + } else if (t.kind === "contract") { + types.push({ + name: t.name + "$Data", + header: Number(t.header?.value), + fields: t.fields.map((v) => v.abi), + }); + } + } + + // // Receivers + const receivers: ABIReceiver[] = []; + for (const r of contract.receivers) { + if (r.selector.kind === "internal-binary") { + receivers.push({ + receiver: "internal", + message: { + kind: "typed", + type: r.selector.type, + }, + }); + } else if (r.selector.kind === "external-binary") { + receivers.push({ + receiver: "external", + message: { + kind: "typed", + type: r.selector.type, + }, + }); + } else if (r.selector.kind === "internal-empty") { + receivers.push({ + receiver: "internal", + message: { + kind: "empty", + }, + }); + } else if (r.selector.kind === "external-empty") { + receivers.push({ + receiver: "external", + message: { + kind: "empty", + }, + }); + } else if (r.selector.kind === "internal-comment") { + receivers.push({ + receiver: "internal", + message: { + kind: "text", + text: r.selector.comment, + }, + }); + } else if (r.selector.kind === "external-comment") { + receivers.push({ + receiver: "external", + message: { + kind: "text", + text: r.selector.comment, + }, + }); + } else if (r.selector.kind === "internal-comment-fallback") { + receivers.push({ + receiver: "internal", + message: { + kind: "text", + }, + }); + } else if (r.selector.kind === "external-comment-fallback") { + receivers.push({ + receiver: "external", + message: { + kind: "text", + }, + }); + } else if (r.selector.kind === "internal-fallback") { + receivers.push({ + receiver: "internal", + message: { + kind: "any", + }, + }); + } else if (r.selector.kind === "external-fallback") { + receivers.push({ + receiver: "external", + message: { + kind: "any", + }, + }); + } + } + + // Getters + const getters: ABIGetter[] = []; + for (const f of contract.functions.values()) { + if (f.isGetter) { + getters.push({ + name: f.name, + arguments: f.params.map((v) => ({ + name: idText(v.name), + type: createABITypeRefFromTypeRef(ctx, v.type, v.loc), + })), + returnType: + f.returns.kind !== "void" + ? createABITypeRefFromTypeRef(ctx, f.returns, f.ast.loc) + : null, + }); + } + } + + // Errors + const errors: Record = {}; + errors["2"] = { message: "Stack underflow" }; + errors["3"] = { message: "Stack overflow" }; + errors["4"] = { message: "Integer overflow" }; + errors["5"] = { message: "Integer out of expected range" }; + errors["6"] = { message: "Invalid opcode" }; + errors["7"] = { message: "Type check error" }; + errors["8"] = { message: "Cell overflow" }; + errors["9"] = { message: "Cell underflow" }; + errors["10"] = { message: "Dictionary error" }; + errors["13"] = { message: "Out of gas error" }; + errors["32"] = { message: "Method ID not found" }; + errors["34"] = { message: "Action is invalid or not supported" }; + errors["37"] = { message: "Not enough TON" }; + errors["38"] = { message: "Not enough extra-currencies" }; + for (const e of Object.values(contractErrors)) { + errors[e.id] = { message: e.message }; + } + const codeErrors = getAllErrors(ctx); + for (const c of codeErrors) { + errors[c.id + ""] = { message: c.value }; + } + + // Interfaces + const interfaces = [ + "org.ton.introspection.v0", + ...getSupportedInterfaces(contract, ctx), + ]; + + return { + name: contract.name, + types, + receivers, + getters, + errors, + interfaces, + } as object; +} diff --git a/src/generatorNew/emitter/createPadded.ts b/src/generatorNew/emitter/createPadded.ts new file mode 100644 index 000000000..eff2f620e --- /dev/null +++ b/src/generatorNew/emitter/createPadded.ts @@ -0,0 +1,8 @@ +import { trimIndent } from "../../utils/text"; + +export function createPadded(src: string) { + return trimIndent(src) + .split("\n") + .map((v) => " ".repeat(4) + v) + .join("\n"); +} diff --git a/src/generatorNew/emitter/emit.ts b/src/generatorNew/emitter/emit.ts new file mode 100644 index 000000000..a3d3087c7 --- /dev/null +++ b/src/generatorNew/emitter/emit.ts @@ -0,0 +1,70 @@ +import { Maybe } from "@ton/core/dist/utils/maybe"; +import { trimIndent } from "../../utils/text"; +import { WrittenFunction } from "../Writer"; +import { createPadded } from "./createPadded"; + +export function emit(args: { + header?: Maybe; + functions?: Maybe; +}) { + // Emit header + let res = ""; + if (args.header) { + res = trimIndent(args.header); + } + + // Emit functions + if (args.functions) { + for (const f of args.functions) { + if (f.name === "$main") { + continue; + } else { + if (res !== "") { + res += "\n\n"; + } + if (f.comment) { + for (const s of f.comment.split("\n")) { + res += `;; ${s}\n`; + } + } + if (f.code.kind === "generic") { + let sig = f.signature; + if (!f.parsed) { + if (f.flags.has("impure")) { + sig = `${sig} impure`; + } + if (f.flags.has("inline")) { + sig = `${sig} inline`; + } else { + sig = `${sig} inline_ref`; + } + } + + res += `${sig} {\n${createPadded(f.code.code)}\n}`; + } else if (f.code.kind === "asm") { + let sig = f.signature; + if (!f.parsed && f.flags.has("impure")) { + sig = `${sig} impure`; + } + res += `${sig} asm${f.code.shuffle} "${f.code.code}";`; + } else { + throw new Error(`Unknown function body kind`); + } + } + } + + // Emit main + const m = args.functions.find((v) => v.name === "$main"); + if (m) { + if (m.code.kind !== "generic") { + throw new Error(`Main function should have generic body`); + } + if (res !== "") { + res += "\n\n"; + } + res += m.code.code; + } + } + + return res; +} diff --git a/src/generatorNew/writeProgram.ts b/src/generatorNew/writeProgram.ts new file mode 100644 index 000000000..02d8a4472 --- /dev/null +++ b/src/generatorNew/writeProgram.ts @@ -0,0 +1,411 @@ +import { CompilerContext } from "../context"; +import { getAllocation, getSortedTypes } from "../storage/resolveAllocation"; +import { + getAllStaticFunctions, + getAllTypes, + toBounced, +} from "../types/resolveDescriptors"; +import { WriterContext, WrittenFunction } from "./Writer"; +import { + writeBouncedParser, + writeOptionalParser, + writeOptionalSerializer, + writeParser, + writeSerializer, +} from "./writers/writeSerialization"; +import { writeStdlib } from "./writers/writeStdlib"; +import { writeAccessors } from "./writers/writeAccessors"; +import { ContractABI } from "@ton/core"; +import { writeFunction } from "./writers/writeFunction"; +import { calculateIPFSlink } from "../utils/calculateIPFSlink"; +import { getRawAST } from "../grammar/store"; +import { emit } from "./emitter/emit"; +import { + writeInit, + writeMainContract, + writeStorageOps, +} from "./writers/writeContract"; +import { funcInitIdOf } from "./writers/id"; +import { idToHex } from "../utils/idToHex"; +import { trimIndent } from "../utils/text"; + +export async function writeProgram( + ctx: CompilerContext, + abiSrc: ContractABI, + basename: string, + debug: boolean = false, +) { + // + // Load ABI (required for generator) + // + + const abi = JSON.stringify(abiSrc); + const abiLink = await calculateIPFSlink(Buffer.from(abi)); + + // + // Render contract + // + + const wCtx = new WriterContext(ctx, abiSrc.name!); + writeAll(ctx, wCtx, abiSrc.name!, abiLink); + const functions = wCtx.extract(debug); + + // + // Emit files + // + + const files: { name: string; code: string }[] = []; + const imported: string[] = []; + + // + // Headers + // + + const headers: string[] = []; + headers.push(`;;`); + headers.push(`;; Header files for ${abiSrc.name}`); + headers.push(`;; NOTE: declarations are sorted for optimal order`); + headers.push(`;;`); + headers.push(``); + // const sortedHeaders = [...functions].sort((a, b) => a.name.localeCompare(b.name)); + for (const f of functions) { + if (f.code.kind === "generic" && f.signature) { + headers.push(`;; ${f.name}`); + let sig = f.signature; + if (!f.parsed) { + if (f.flags.has("impure")) { + sig = sig + " impure"; + } + if (f.flags.has("inline")) { + sig = sig + " inline"; + } else { + sig = sig + " inline_ref"; + } + } + headers.push(sig + ";"); + headers.push(""); + } + } + files.push({ + name: basename + ".headers.fc", + code: headers.join("\n"), + }); + + // + // stdlib + // + + const stdlibHeader = trimIndent(` + global (int, slice, int, slice) __tact_context; + global slice __tact_context_sender; + global cell __tact_context_sys; + global int __tact_randomized; + `); + + const stdlibFunctions = tryExtractModule(functions, "stdlib", []); + if (stdlibFunctions) { + imported.push("stdlib"); + } + + const stdlib = emit({ + header: stdlibHeader, + functions: stdlibFunctions, + }); + + files.push({ + name: basename + ".stdlib.fc", + code: stdlib, + }); + + // + // native + // + + const nativeSources = getRawAST(ctx).funcSources; + if (nativeSources.length > 0) { + imported.push("native"); + files.push({ + name: basename + ".native.fc", + code: emit({ + header: [...nativeSources.map((v) => v.code)].join("\n\n"), + }), + }); + } + + // + // constants + // + + const constantsFunctions = tryExtractModule( + functions, + "constants", + imported, + ); + if (constantsFunctions) { + imported.push("constants"); + files.push({ + name: basename + ".constants.fc", + code: emit({ functions: constantsFunctions }), + }); + } + + // + // storage + // + + const emittedTypes: string[] = []; + const types = getSortedTypes(ctx); + for (const t of types) { + const ffs: WrittenFunction[] = []; + if (t.kind === "struct" || t.kind === "contract" || t.kind == "trait") { + const typeFunctions = tryExtractModule( + functions, + "type:" + t.name, + imported, + ); + if (typeFunctions) { + imported.push("type:" + t.name); + ffs.push(...typeFunctions); + } + } + if (t.kind === "contract") { + const typeFunctions = tryExtractModule( + functions, + "type:" + t.name + "$init", + imported, + ); + if (typeFunctions) { + imported.push("type:" + t.name + "$init"); + ffs.push(...typeFunctions); + } + } + if (ffs.length > 0) { + const header: string[] = []; + header.push(";;"); + header.push(`;; Type: ${t.name}`); + if (t.header !== null) { + header.push(`;; Header: 0x${idToHex(Number(t.header.value))}`); + } + if (t.tlb) { + header.push(`;; TLB: ${t.tlb}`); + } + header.push(";;"); + + emittedTypes.push( + emit({ + functions: ffs, + header: header.join("\n"), + }), + ); + } + } + if (emittedTypes.length > 0) { + files.push({ + name: basename + ".storage.fc", + code: [...emittedTypes].join("\n\n"), + }); + } + + // const storageFunctions = tryExtractModule(functions, 'storage', imported); + // if (storageFunctions) { + // imported.push('storage'); + // files.push({ + // name: basename + '.storage.fc', + // code: emit({ functions: storageFunctions }) + // }); + // } + + // + // Remaining + // + + const remainingFunctions = tryExtractModule(functions, null, imported); + const header: string[] = []; + header.push("#pragma version =0.4.4;"); + header.push("#pragma allow-post-modification;"); + header.push("#pragma compute-asm-ltr;"); + header.push(""); + for (const i of files.map((v) => `#include "${v.name}";`)) { + header.push(i); + } + header.push(""); + header.push(";;"); + header.push(`;; Contract ${abiSrc.name} functions`); + header.push(";;"); + header.push(""); + const code = emit({ + header: header.join("\n"), + functions: remainingFunctions, + }); + files.push({ + name: basename + ".code.fc", + code, + }); + + return { + entrypoint: basename + ".code.fc", + files, + abi, + }; +} + +function tryExtractModule( + functions: WrittenFunction[], + context: string | null, + imported: string[], +): WrittenFunction[] | null { + // Put to map + const maps: Map = new Map(); + for (const f of functions) { + maps.set(f.name, f); + } + + // Extract functions of a context + const ctxFunctions: WrittenFunction[] = functions + .filter((v) => v.code.kind !== "skip") + .filter((v) => { + if (context) { + return v.context === context; + } else { + return v.context === null || !imported.includes(v.context); + } + }); + if (ctxFunctions.length === 0) { + return null; + } + + // Check dependencies + // if (context) { + // for (let f of ctxFunctions) { + // for (let d of f.depends) { + // let c = maps.get(d)!.context; + // if (!c) { + // console.warn(`Function ${f.name} depends on ${d} with generic context, but ${context} is needed`); + // return null; // Found dependency to unknown function + // } + // if (c !== context && (c !== null && !imported.includes(c))) { + // console.warn(`Function ${f.name} depends on ${d} with ${c} context, but ${context} is needed`); + // return null; // Found dependency to another context + // } + // } + // } + // } + + return ctxFunctions; +} + +function writeAll( + ctx: CompilerContext, + wCtx: WriterContext, + name: string, + abiLink: string, +) { + // Load all types + const allTypes = getAllTypes(ctx); + const contracts = allTypes.filter((v) => v.kind === "contract"); + const c = contracts.find((v) => v.name === name); + if (!c) { + throw Error(`Contract "${name}" not found`); + } + + // Stdlib + writeStdlib(wCtx); + + // Serializers + const sortedTypes = getSortedTypes(ctx); + for (const t of sortedTypes) { + if (t.kind === "contract" || t.kind === "struct") { + const allocation = getAllocation(ctx, t.name); + const allocationBounced = getAllocation(ctx, toBounced(t.name)); + writeSerializer( + t.name, + t.kind === "contract", + allocation, + t.origin, + wCtx, + ); + writeOptionalSerializer(t.name, t.origin, wCtx); + writeParser( + t.name, + t.kind === "contract", + allocation, + t.origin, + wCtx, + ); + writeOptionalParser(t.name, t.origin, wCtx); + writeBouncedParser( + t.name, + t.kind === "contract", + allocationBounced, + t.origin, + wCtx, + ); + } + } + + // Accessors + for (const t of allTypes) { + if (t.kind === "contract" || t.kind === "struct") { + writeAccessors(t, t.origin, wCtx); + } + } + + // Init serializers + for (const t of sortedTypes) { + if (t.kind === "contract" && t.init) { + const allocation = getAllocation(ctx, funcInitIdOf(t.name)); + writeSerializer( + funcInitIdOf(t.name), + true, + allocation, + t.origin, + wCtx, + ); + writeParser( + funcInitIdOf(t.name), + false, + allocation, + t.origin, + wCtx, + ); + } + } + + // Storage Functions + for (const t of sortedTypes) { + if (t.kind === "contract") { + writeStorageOps(t, t.origin, wCtx); + } + } + + // Static functions + getAllStaticFunctions(ctx).forEach((f) => { + writeFunction(f, wCtx); + }); + + // Extensions + for (const c of allTypes) { + if (c.kind !== "contract" && c.kind !== "trait") { + // We are rendering contract functions separately + for (const f of c.functions.values()) { + writeFunction(f, wCtx); + } + } + } + + // Contract functions + for (const c of contracts) { + // Init + if (c.init) { + writeInit(c, c.init, wCtx); + } + + // Functions + for (const f of c.functions.values()) { + writeFunction(f, wCtx); + } + } + + // Write contract main + writeMainContract(c, abiLink, wCtx); +} diff --git a/src/generatorNew/writeReport.ts b/src/generatorNew/writeReport.ts new file mode 100644 index 000000000..b6f580a85 --- /dev/null +++ b/src/generatorNew/writeReport.ts @@ -0,0 +1,100 @@ +import { ContractABI } from "@ton/core"; +import { CompilerContext } from "../context"; +import { PackageFileFormat } from "../packaging/fileFormat"; +import { getType } from "../types/resolveDescriptors"; +import { Writer } from "../utils/Writer"; +import { TypeDescription } from "../types/types"; + +export function writeReport(ctx: CompilerContext, pkg: PackageFileFormat) { + const w = new Writer(); + const abi = JSON.parse(pkg.abi) as ContractABI; + w.write(` + # TACT Compilation Report + Contract: ${pkg.name} + BOC Size: ${Buffer.from(pkg.code, "base64").length} bytes + `); + w.append(); + + // Types + w.write(`# Types`); + w.write("Total Types: " + abi.types!.length); + w.append(); + for (const t of abi.types!) { + const tt = getType( + ctx, + t.name.endsWith("$Data") ? t.name.slice(0, -5) : t.name, + ); + w.write(`## ${t.name}`); + w.write(`TLB: \`${tt.tlb!}\``); + w.write(`Signature: \`${tt.signature!}\``); + w.append(); + } + + // Get methods + w.write(`# Get Methods`); + w.write("Total Get Methods: " + abi.getters!.length); + w.append(); + for (const t of abi.getters!) { + w.write(`## ${t.name}`); + for (const arg of t.arguments!) { + w.write(`Argument: ${arg.name}`); + } + w.append(); + } + + // Error Codes + w.write(`# Error Codes`); + Object.entries(abi.errors!).forEach(([t, abiError]) => { + w.write(`${t}: ${abiError.message}`); + }); + w.append(); + + const t = getType(ctx, pkg.name); + const writtenEdges: Set = new Set(); + + // Trait Inheritance Diagram + w.write(`# Trait Inheritance Diagram`); + w.append(); + w.write("```mermaid"); + w.write("graph TD"); + function writeTraits(t: TypeDescription) { + for (const trait of t.traits) { + const edge = `${t.name} --> ${trait.name}`; + if (writtenEdges.has(edge)) { + continue; + } + writtenEdges.add(edge); + w.write(edge); + writeTraits(trait); + } + } + w.write(t.name); + writeTraits(t); + w.write("```"); + w.append(); + + writtenEdges.clear(); + + // Contract Dependency Diagram + w.write(`# Contract Dependency Diagram`); + w.append(); + w.write("```mermaid"); + w.write("graph TD"); + function writeDependencies(t: TypeDescription) { + for (const dep of t.dependsOn) { + const edge = `${t.name} --> ${dep.name}`; + if (writtenEdges.has(edge)) { + continue; + } + writtenEdges.add(edge); + w.write(edge); + writeDependencies(dep); + } + } + writtenEdges.clear(); + w.write(t.name); + writeDependencies(t); + w.write("```"); + + return w.end(); +} diff --git a/src/generatorNew/writers/__snapshots__/writeSerialization.spec.ts.snap b/src/generatorNew/writers/__snapshots__/writeSerialization.spec.ts.snap new file mode 100644 index 000000000..b7815dc4f --- /dev/null +++ b/src/generatorNew/writers/__snapshots__/writeSerialization.spec.ts.snap @@ -0,0 +1,10684 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`writeSerialization should write serializer for A 1`] = ` +[ + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_set", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_nop", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_str_to_slice", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_slice_to_str", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_address_to_slice", + "signature": "", + }, + { + "code": { + "code": "throw_unless(136, address.slice_bits() == 267); +var h = address.preload_uint(11); +throw_if(137, h == 1279); +throw_unless(136, h == 1024); +return address;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_verify_address", + "signature": "slice __tact_verify_address(slice address)", + }, + { + "code": { + "code": "slice raw = cs~load_msg_addr(); +return (cs, __tact_verify_address(raw));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_load_address", + "signature": "(slice, slice) __tact_load_address(slice cs)", + }, + { + "code": { + "code": "if (cs.preload_uint(2) != 0) { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); +} else { + cs~skip_bits(2); + return (cs, null()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_load_address_opt", + "signature": "(slice, slice) __tact_load_address_opt(slice cs)", + }, + { + "code": { + "code": "return b.store_slice(__tact_verify_address(address));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_store_address", + "signature": "builder __tact_store_address(builder b, slice address)", + }, + { + "code": { + "code": "if (null?(address)) { + b = b.store_uint(0, 2); + return b; +} else { + return __tact_store_address(b, address); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_store_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_store_address_opt", + "signature": "builder __tact_store_address_opt(builder b, slice address)", + }, + { + "code": { + "code": "var b = begin_cell(); +b = b.store_uint(2, 2); +b = b.store_uint(0, 1); +b = b.store_int(chain, 8); +b = b.store_uint(hash, 256); +var addr = b.end_cell().begin_parse(); +return __tact_verify_address(addr);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_create_address", + "signature": "slice __tact_create_address(int chain, int hash)", + }, + { + "code": { + "code": "var b = begin_cell(); +b = b.store_uint(0, 2); +b = b.store_uint(3, 2); +b = b.store_uint(0, 1); +b = b.store_ref(code); +b = b.store_ref(data); +var hash = cell_hash(b.end_cell()); +return __tact_create_address(chain, hash);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_create_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_compute_contract_address", + "signature": "slice __tact_compute_contract_address(int chain, cell code, cell data)", + }, + { + "code": { + "code": "throw_if(128, null?(x)); return x;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_not_null", + "signature": "forall X -> X __tact_not_null(X x)", + }, + { + "code": { + "code": "DICTDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete", + "signature": "(cell, int) __tact_dict_delete(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTIDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_int", + "signature": "(cell, int) __tact_dict_delete_int(cell dict, int key_len, int index)", + }, + { + "code": { + "code": "DICTUDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_uint", + "signature": "(cell, int) __tact_dict_delete_uint(cell dict, int key_len, int index)", + }, + { + "code": { + "code": "DICTSETREF", + "kind": "asm", + "shuffle": "(value index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_set_ref", + "signature": "((cell), ()) __tact_dict_set_ref(cell dict, int key_len, slice index, cell value)", + }, + { + "code": { + "code": "DICTGET NULLSWAPIFNOT", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_get", + "signature": "(slice, int) __tact_dict_get(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTDELGET NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_get", + "signature": "(cell, (slice, int)) __tact_dict_delete_get(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTGETREF NULLSWAPIFNOT", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_get_ref", + "signature": "(cell, int) __tact_dict_get_ref(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTMIN NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_min", + "signature": "(slice, slice, int) __tact_dict_min(cell dict, int key_len)", + }, + { + "code": { + "code": "DICTMINREF NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_min_ref", + "signature": "(slice, cell, int) __tact_dict_min_ref(cell dict, int key_len)", + }, + { + "code": { + "code": "DICTGETNEXT NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(pivot dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_next", + "signature": "(slice, slice, int) __tact_dict_next(cell dict, int key_len, slice pivot)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(dict, key_len, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set {}, + "name": "__tact_dict_next_ref", + "signature": "(slice, cell, int) __tact_dict_next_ref(cell dict, int key_len, slice pivot)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP s0 DUMP DROP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug", + "signature": "forall X -> () __tact_debug(X value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP STRDUMP DROP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_str", + "signature": "() __tact_debug_str(slice value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "if (value) { + __tact_debug_str("true", debug_print_1, debug_print_2); +} else { + __tact_debug_str("false", debug_print_1, debug_print_2); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_bool", + "signature": "() __tact_debug_bool(int value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "SDSUBSTR", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_preload_offset", + "signature": "(slice) __tact_preload_offset(slice s, int offset, int bits)", + }, + { + "code": { + "code": "slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) +.end_cell().begin_parse(); +int reg = 0; +while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } +} +(int q, int r) = divmod(reg, 256); +return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) +.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline_ref", + }, + "name": "__tact_crc16", + "signature": "(slice) __tact_crc16(slice data)", + }, + { + "code": { + "code": "slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; +builder res = begin_cell(); + +while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); +} + +return res.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_preload_offset", + }, + "flags": Set {}, + "name": "__tact_base64_encode", + "signature": "(slice) __tact_base64_encode(slice data)", + }, + { + "code": { + "code": "(int wc, int hash) = address.parse_std_addr(); + +slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) +.end_cell().begin_parse(); + +slice checksum = __tact_crc16(user_friendly_address); +slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) +.end_cell().begin_parse(); + +return __tact_base64_encode(user_friendly_address_with_checksum);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_crc16", + "__tact_base64_encode", + }, + "flags": Set {}, + "name": "__tact_address_to_user_friendly", + "signature": "(slice) __tact_address_to_user_friendly(slice address)", + }, + { + "code": { + "code": "__tact_debug_str(__tact_address_to_user_friendly(address), debug_print_1, debug_print_2);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + "__tact_address_to_user_friendly", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_address", + "signature": "() __tact_debug_address(slice address, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP DUMPSTK", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_stack", + "signature": "() __tact_debug_stack(slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "return __tact_context;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_context_get", + "signature": "(int, slice, int, slice) __tact_context_get()", + }, + { + "code": { + "code": "return __tact_context_sender;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_context_get_sender", + "signature": "slice __tact_context_get_sender()", + }, + { + "code": { + "code": "if (null?(__tact_randomized)) { + randomize_lt(); + __tact_randomized = true; +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_prepare_random", + "signature": "() __tact_prepare_random()", + }, + { + "code": { + "code": "return b.store_int(v, 1);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_store_bool", + "signature": "builder __tact_store_bool(builder b, int v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_to_tuple", + "signature": "forall X -> tuple __tact_to_tuple(X x)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_from_tuple", + "signature": "forall X -> X __tact_from_tuple(tuple x)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_int", + "signature": "(cell, ()) __tact_dict_set_int_int(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_int", + "signature": "int __tact_dict_get_int_int(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_int", + "signature": "(int, int, int) __tact_dict_min_int_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_int", + "signature": "(int, int, int) __tact_dict_next_int_int(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_uint", + "signature": "(cell, ()) __tact_dict_set_int_uint(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_uint", + "signature": "int __tact_dict_get_int_uint(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_uint", + "signature": "(int, int, int) __tact_dict_min_int_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_uint", + "signature": "(int, int, int) __tact_dict_next_int_uint(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_int", + "signature": "(cell, ()) __tact_dict_set_uint_int(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_int", + "signature": "int __tact_dict_get_uint_int(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_int", + "signature": "(int, int, int) __tact_dict_min_uint_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_int", + "signature": "(int, int, int) __tact_dict_next_uint_int(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_uint", + "signature": "(cell, ()) __tact_dict_set_uint_uint(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_uint", + "signature": "int __tact_dict_get_uint_uint(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_uint", + "signature": "(int, int, int) __tact_dict_min_uint_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_uint", + "signature": "(int, int, int) __tact_dict_next_uint_uint(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_ref(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_cell", + "signature": "(cell, ()) __tact_dict_set_int_cell(cell d, int kl, int k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = idict_get_ref?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_cell", + "signature": "cell __tact_dict_get_int_cell(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min_ref?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_cell", + "signature": "(int, cell, int) __tact_dict_min_int_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_cell", + "signature": "(int, cell, int) __tact_dict_next_int_cell(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_ref(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_cell", + "signature": "(cell, ()) __tact_dict_set_uint_cell(cell d, int kl, int k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = udict_get_ref?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_cell", + "signature": "cell __tact_dict_get_uint_cell(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min_ref?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_cell", + "signature": "(int, cell, int) __tact_dict_min_uint_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_cell", + "signature": "(int, cell, int) __tact_dict_next_uint_cell(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_slice", + "signature": "(cell, ()) __tact_dict_set_int_slice(cell d, int kl, int k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_slice", + "signature": "slice __tact_dict_get_int_slice(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_slice", + "signature": "(int, slice, int) __tact_dict_min_int_slice(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_slice", + "signature": "(int, slice, int) __tact_dict_next_int_slice(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_slice", + "signature": "(cell, ()) __tact_dict_set_uint_slice(cell d, int kl, int k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_slice", + "signature": "slice __tact_dict_get_uint_slice(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_slice", + "signature": "(int, slice, int) __tact_dict_min_uint_slice(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_slice", + "signature": "(int, slice, int) __tact_dict_next_uint_slice(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_int", + "signature": "(cell, ()) __tact_dict_set_slice_int(cell d, int kl, slice k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_int", + "signature": "int __tact_dict_get_slice_int(cell d, int kl, slice k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_int", + "signature": "(slice, int, int) __tact_dict_min_slice_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_int", + "signature": "(slice, int, int) __tact_dict_next_slice_int(cell d, int kl, slice pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_uint", + "signature": "(cell, ()) __tact_dict_set_slice_uint(cell d, int kl, slice k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_uint", + "signature": "int __tact_dict_get_slice_uint(cell d, int kl, slice k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_uint", + "signature": "(slice, int, int) __tact_dict_min_slice_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_uint", + "signature": "(slice, int, int) __tact_dict_next_slice_uint(cell d, int kl, slice pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return __tact_dict_set_ref(d, kl, k, v); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + "__tact_dict_set_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_cell", + "signature": "(cell, ()) __tact_dict_set_slice_cell(cell d, int kl, slice k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get_ref(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_cell", + "signature": "cell __tact_dict_get_slice_cell(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min_ref(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_cell", + "signature": "(slice, cell, int) __tact_dict_min_slice_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_cell", + "signature": "(slice, cell, int) __tact_dict_next_slice_cell(cell d, int kl, slice pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_slice(v)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_slice", + "signature": "(cell, ()) __tact_dict_set_slice_slice(cell d, int kl, slice k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_slice", + "signature": "slice __tact_dict_get_slice_slice(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_slice", + "signature": "(slice, slice, int) __tact_dict_min_slice_slice(cell d, int kl)", + }, + { + "code": { + "code": "return __tact_dict_next(d, kl, pivot);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_slice", + "signature": "(slice, slice, int) __tact_dict_next_slice_slice(cell d, int kl, slice pivot)", + }, + { + "code": { + "code": "return equal_slices_bits(a, b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits", + "signature": "int __tact_slice_eq_bits(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (equal_slices_bits(a, b));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits_nullable_one", + "signature": "int __tact_slice_eq_bits_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( equal_slices_bits(a, b) ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits_nullable", + "signature": "int __tact_slice_eq_bits_nullable(slice a, slice b)", + }, + { + "code": { + "code": "(slice key, slice value, int flag) = __tact_dict_min(a, kl); +while (flag) { + (slice value_b, int flag_b) = b~__tact_dict_delete_get(kl, key); + ifnot (flag_b) { + return 0; + } + ifnot (value.slice_hash() == value_b.slice_hash()) { + return 0; + } + (key, value, flag) = __tact_dict_next(a, kl, key); +} +return null?(b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + "__tact_dict_delete_get", + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_eq", + "signature": "int __tact_dict_eq(cell a, cell b, int kl)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a == b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_eq_nullable_one", + "signature": "int __tact_int_eq_nullable_one(int a, int b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a != b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_neq_nullable_one", + "signature": "int __tact_int_neq_nullable_one(int a, int b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a == b ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_eq_nullable", + "signature": "int __tact_int_eq_nullable(int a, int b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a != b ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_neq_nullable", + "signature": "int __tact_int_neq_nullable(int a, int b)", + }, + { + "code": { + "code": "return (a.cell_hash() == b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq", + "signature": "int __tact_cell_eq(cell a, cell b)", + }, + { + "code": { + "code": "return (a.cell_hash() != b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq", + "signature": "int __tact_cell_neq(cell a, cell b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.cell_hash() == b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq_nullable_one", + "signature": "int __tact_cell_eq_nullable_one(cell a, cell b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.cell_hash() != b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq_nullable_one", + "signature": "int __tact_cell_neq_nullable_one(cell a, cell b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.cell_hash() == b.cell_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq_nullable", + "signature": "int __tact_cell_eq_nullable(cell a, cell b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.cell_hash() != b.cell_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq_nullable", + "signature": "int __tact_cell_neq_nullable(cell a, cell b)", + }, + { + "code": { + "code": "return (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq", + "signature": "int __tact_slice_eq(slice a, slice b)", + }, + { + "code": { + "code": "return (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq", + "signature": "int __tact_slice_neq(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable_one", + "signature": "int __tact_slice_eq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable_one", + "signature": "int __tact_slice_neq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable", + "signature": "int __tact_slice_eq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable", + "signature": "int __tact_slice_neq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "return udict_set_ref(dict, 16, id, code);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_code", + "signature": "cell __tact_dict_set_code(cell dict, int id, cell code)", + }, + { + "code": { + "code": "var (data, ok) = udict_get_ref?(dict, 16, id); +throw_unless(135, ok); +return data;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_code", + "signature": "cell __tact_dict_get_code(cell dict, int id)", + }, + { + "code": { + "code": "NIL", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_0", + "signature": "tuple __tact_tuple_create_0()", + }, + { + "code": { + "code": "return ();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_tuple_destroy_0", + "signature": "() __tact_tuple_destroy_0()", + }, + { + "code": { + "code": "1 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_1", + "signature": "forall X0 -> tuple __tact_tuple_create_1((X0) v)", + }, + { + "code": { + "code": "1 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_1", + "signature": "forall X0 -> (X0) __tact_tuple_destroy_1(tuple v)", + }, + { + "code": { + "code": "2 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_2", + "signature": "forall X0, X1 -> tuple __tact_tuple_create_2((X0, X1) v)", + }, + { + "code": { + "code": "2 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_2", + "signature": "forall X0, X1 -> (X0, X1) __tact_tuple_destroy_2(tuple v)", + }, + { + "code": { + "code": "3 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_3", + "signature": "forall X0, X1, X2 -> tuple __tact_tuple_create_3((X0, X1, X2) v)", + }, + { + "code": { + "code": "3 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_3", + "signature": "forall X0, X1, X2 -> (X0, X1, X2) __tact_tuple_destroy_3(tuple v)", + }, + { + "code": { + "code": "4 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_4", + "signature": "forall X0, X1, X2, X3 -> tuple __tact_tuple_create_4((X0, X1, X2, X3) v)", + }, + { + "code": { + "code": "4 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_4", + "signature": "forall X0, X1, X2, X3 -> (X0, X1, X2, X3) __tact_tuple_destroy_4(tuple v)", + }, + { + "code": { + "code": "5 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_5", + "signature": "forall X0, X1, X2, X3, X4 -> tuple __tact_tuple_create_5((X0, X1, X2, X3, X4) v)", + }, + { + "code": { + "code": "5 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_5", + "signature": "forall X0, X1, X2, X3, X4 -> (X0, X1, X2, X3, X4) __tact_tuple_destroy_5(tuple v)", + }, + { + "code": { + "code": "6 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_6", + "signature": "forall X0, X1, X2, X3, X4, X5 -> tuple __tact_tuple_create_6((X0, X1, X2, X3, X4, X5) v)", + }, + { + "code": { + "code": "6 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_6", + "signature": "forall X0, X1, X2, X3, X4, X5 -> (X0, X1, X2, X3, X4, X5) __tact_tuple_destroy_6(tuple v)", + }, + { + "code": { + "code": "7 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_7", + "signature": "forall X0, X1, X2, X3, X4, X5, X6 -> tuple __tact_tuple_create_7((X0, X1, X2, X3, X4, X5, X6) v)", + }, + { + "code": { + "code": "7 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_7", + "signature": "forall X0, X1, X2, X3, X4, X5, X6 -> (X0, X1, X2, X3, X4, X5, X6) __tact_tuple_destroy_7(tuple v)", + }, + { + "code": { + "code": "8 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_8", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7 -> tuple __tact_tuple_create_8((X0, X1, X2, X3, X4, X5, X6, X7) v)", + }, + { + "code": { + "code": "8 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_8", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7 -> (X0, X1, X2, X3, X4, X5, X6, X7) __tact_tuple_destroy_8(tuple v)", + }, + { + "code": { + "code": "9 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_9", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8 -> tuple __tact_tuple_create_9((X0, X1, X2, X3, X4, X5, X6, X7, X8) v)", + }, + { + "code": { + "code": "9 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_9", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8) __tact_tuple_destroy_9(tuple v)", + }, + { + "code": { + "code": "10 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_10", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9 -> tuple __tact_tuple_create_10((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) v)", + }, + { + "code": { + "code": "10 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_10", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) __tact_tuple_destroy_10(tuple v)", + }, + { + "code": { + "code": "11 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_11", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 -> tuple __tact_tuple_create_11((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) v)", + }, + { + "code": { + "code": "11 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_11", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) __tact_tuple_destroy_11(tuple v)", + }, + { + "code": { + "code": "12 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_12", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11 -> tuple __tact_tuple_create_12((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) v)", + }, + { + "code": { + "code": "12 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_12", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) __tact_tuple_destroy_12(tuple v)", + }, + { + "code": { + "code": "13 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_13", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12 -> tuple __tact_tuple_create_13((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) v)", + }, + { + "code": { + "code": "13 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_13", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) __tact_tuple_destroy_13(tuple v)", + }, + { + "code": { + "code": "14 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_14", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 -> tuple __tact_tuple_create_14((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) v)", + }, + { + "code": { + "code": "14 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_14", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) __tact_tuple_destroy_14(tuple v)", + }, + { + "code": { + "code": "15 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_15", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14 -> tuple __tact_tuple_create_15((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) v)", + }, + { + "code": { + "code": "15 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_15", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) __tact_tuple_destroy_15(tuple v)", + }, + { + "code": { + "code": "return tpush(tpush(empty_tuple(), b), null());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start", + "signature": "tuple __tact_string_builder_start(builder b)", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell().store_uint(0, 32));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_comment", + "signature": "tuple __tact_string_builder_start_comment()", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell().store_uint(0, 8));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_tail_string", + "signature": "tuple __tact_string_builder_start_tail_string()", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_string", + "signature": "tuple __tact_string_builder_start_string()", + }, + { + "code": { + "code": "(builder b, tuple tail) = uncons(builders); +cell c = b.end_cell(); +while(~ null?(tail)) { + (b, tail) = uncons(tail); + c = b.store_ref(c).end_cell(); +} +return c;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_end", + "signature": "cell __tact_string_builder_end(tuple builders)", + }, + { + "code": { + "code": "return __tact_string_builder_end(builders).begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_end", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_end_slice", + "signature": "slice __tact_string_builder_end_slice(tuple builders)", + }, + { + "code": { + "code": "int sliceRefs = slice_refs(sc); +int sliceBits = slice_bits(sc); + +while((sliceBits > 0) | (sliceRefs > 0)) { + + ;; Load the current builder + (builder b, tuple tail) = uncons(builders); + int remBytes = 127 - (builder_bits(b) / 8); + int exBytes = sliceBits / 8; + + ;; Append bits + int amount = min(remBytes, exBytes); + if (amount > 0) { + slice read = sc~load_bits(amount * 8); + b = b.store_slice(read); + } + + ;; Update builders + builders = cons(b, tail); + + ;; Check if we need to add a new cell and continue + if (exBytes - amount > 0) { + var bb = begin_cell(); + builders = cons(bb, builders); + sliceBits = (exBytes - amount) * 8; + } elseif (sliceRefs > 0) { + sc = sc~load_ref().begin_parse(); + sliceRefs = slice_refs(sc); + sliceBits = slice_bits(sc); + } else { + sliceBits = 0; + sliceRefs = 0; + } +} + +return ((builders), ());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_string_builder_append", + "signature": "((tuple), ()) __tact_string_builder_append(tuple builders, slice sc)", + }, + { + "code": { + "code": "builders~__tact_string_builder_append(sc); +return builders;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_append", + }, + "flags": Set {}, + "name": "__tact_string_builder_append_not_mut", + "signature": "(tuple) __tact_string_builder_append_not_mut(tuple builders, slice sc)", + }, + { + "code": { + "code": "var b = begin_cell(); +if (src < 0) { + b = b.store_uint(45, 8); + src = - src; +} + +if (src < 1000000000000000000000000000000) { + int len = 0; + int value = 0; + int mult = 1; + do { + (src, int res) = src.divmod(10); + value = value + (res + 48) * mult; + mult = mult * 256; + len = len + 1; + } until (src == 0); + + b = b.store_uint(value, len * 8); +} else { + tuple t = empty_tuple(); + int len = 0; + do { + int digit = src % 10; + t~tpush(digit); + len = len + 1; + src = src / 10; + } until (src == 0); + + int c = len - 1; + repeat(len) { + int v = t.at(c); + b = b.store_uint(v + 48, 8); + c = c - 1; + } +} +return b.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_int_to_string", + "signature": "slice __tact_int_to_string(int src)", + }, + { + "code": { + "code": "throw_if(134, (digits <= 0) | (digits > 77)); +builder b = begin_cell(); + +if (src < 0) { + b = b.store_uint(45, 8); + src = - src; +} + +;; Process rem part +int skip = true; +int len = 0; +int rem = 0; +tuple t = empty_tuple(); +repeat(digits) { + (src, rem) = src.divmod(10); + if ( ~ ( skip & ( rem == 0 ) ) ) { + skip = false; + t~tpush(rem + 48); + len = len + 1; + } +} + +;; Process dot +if (~ skip) { + t~tpush(46); + len = len + 1; +} + +;; Main +do { + (src, rem) = src.divmod(10); + t~tpush(rem + 48); + len = len + 1; +} until (src == 0); + +;; Assemble +int c = len - 1; +repeat(len) { + int v = t.at(c); + b = b.store_uint(v, 8); + c = c - 1; +} + +;; Result +return b.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_float_to_string", + "signature": "slice __tact_float_to_string(int src, int digits)", + }, + { + "code": { + "code": "throw_unless(5, num > 0); +throw_unless(5, base > 1); +if (num < base) { + return 0; +} +int result = 0; +while (num >= base) { + num /= base; + result += 1; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_log", + "signature": "int __tact_log(int num, int base)", + }, + { + "code": { + "code": "throw_unless(5, exp >= 0); +int result = 1; +repeat (exp) { + result *= base; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_pow", + "signature": "int __tact_pow(int base, int exp)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_int", + "signature": "int __tact_dict_exists_int(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_uint", + "signature": "int __tact_dict_exists_uint(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_slice", + "signature": "int __tact_dict_exists_slice(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +build_0 = build_0.store_int(v'a, 257); +build_0 = build_0.store_int(v'b, 257); +build_0 = ~ null?(v'c) ? build_0.store_int(true, 1).store_int(v'c, 257) : build_0.store_int(false, 1); +build_0 = build_0.store_int(v'd, 1); +build_0 = ~ null?(v'e) ? build_0.store_int(true, 1).store_int(v'e, 1) : build_0.store_int(false, 1); +var build_1 = begin_cell(); +build_1 = build_1.store_int(v'f, 257); +build_1 = build_1.store_int(v'g, 257); +build_0 = store_ref(build_0, build_1.end_cell()); +return build_0;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set {}, + "name": "$A$_store", + "signature": "builder $A$_store(builder build_0, (int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "return $A$_store(begin_cell(), v).end_cell();", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_store", + }, + "flags": Set { + "inline", + }, + "name": "$A$_store_cell", + "signature": "cell $A$_store_cell((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_a", + "signature": "_ $A$_get_a((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_b", + "signature": "_ $A$_get_b((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_c", + "signature": "_ $A$_get_c((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_d", + "signature": "_ $A$_get_d((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_e", + "signature": "_ $A$_get_e((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_f", + "signature": "_ $A$_get_f((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_g", + "signature": "_ $A$_get_g((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set {}, + "name": "$A$_tensor_cast", + "signature": "((int, int, int, int, int, int, int)) $A$_tensor_cast((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (int vvv'a, int vvv'b, int vvv'c, int vvv'd, int vvv'e, int vvv'f, int vvv'g) = __tact_tuple_destroy_7(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_not_null", + "signature": "((int, int, int, int, int, int, int)) $A$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_as_optional", + "signature": "tuple $A$_as_optional((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_tuple", + "signature": "tuple $A$_to_tuple(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $A$_to_tuple($A$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_to_tuple", + "$A$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_opt_tuple", + "signature": "tuple $A$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (int v'a, int v'b, int v'c, int v'd, int v'e, int v'f, int v'g) = __tact_tuple_destroy_7(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_from_tuple", + "signature": "(int, int, int, int, int, int, int) $A$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $A$_as_optional($A$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_as_optional", + "$A$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$A$_from_opt_tuple", + "signature": "tuple $A$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_to_external", + "signature": "(int, int, int, int, int, int, int) $A$_to_external(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "var loaded = $A$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_opt_external", + "signature": "tuple $A$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_a", + "signature": "_ $B$_get_a((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_b", + "signature": "_ $B$_get_b((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_c", + "signature": "_ $B$_get_c((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_d", + "signature": "_ $B$_get_d((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_e", + "signature": "_ $B$_get_e((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_f", + "signature": "_ $B$_get_f((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_g", + "signature": "_ $B$_get_g((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set {}, + "name": "$B$_tensor_cast", + "signature": "((int, int, int, int, int, int, int)) $B$_tensor_cast((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (int vvv'a, int vvv'b, int vvv'c, int vvv'd, int vvv'e, int vvv'f, int vvv'g) = __tact_tuple_destroy_7(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_not_null", + "signature": "((int, int, int, int, int, int, int)) $B$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_as_optional", + "signature": "tuple $B$_as_optional((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_tuple", + "signature": "tuple $B$_to_tuple(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $B$_to_tuple($B$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_to_tuple", + "$B$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_opt_tuple", + "signature": "tuple $B$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (int v'a, int v'b, int v'c, int v'd, int v'e, int v'f, int v'g) = __tact_tuple_destroy_7(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_from_tuple", + "signature": "(int, int, int, int, int, int, int) $B$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $B$_as_optional($B$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_as_optional", + "$B$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$B$_from_opt_tuple", + "signature": "tuple $B$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_to_external", + "signature": "(int, int, int, int, int, int, int) $B$_to_external(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "var loaded = $B$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_opt_external", + "signature": "tuple $B$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_a", + "signature": "_ $C$_get_a((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_b", + "signature": "_ $C$_get_b((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_c", + "signature": "_ $C$_get_c((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_d", + "signature": "_ $C$_get_d((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_e", + "signature": "_ $C$_get_e((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_f", + "signature": "_ $C$_get_f((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_g", + "signature": "_ $C$_get_g((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'h;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_h", + "signature": "_ $C$_get_h((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set {}, + "name": "$C$_tensor_cast", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_tensor_cast((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (cell vvv'a, cell vvv'b, slice vvv'c, slice vvv'd, int vvv'e, int vvv'f, int vvv'g, slice vvv'h) = __tact_tuple_destroy_8(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g, vvv'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_destroy_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_not_null", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return __tact_tuple_create_8(v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_create_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_as_optional", + "signature": "tuple $C$_as_optional((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return __tact_tuple_create_8(v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_create_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_tuple", + "signature": "tuple $C$_to_tuple(((cell, cell, slice, slice, int, int, int, slice)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $C$_to_tuple($C$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_to_tuple", + "$C$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_opt_tuple", + "signature": "tuple $C$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (cell v'a, cell v'b, slice v'c, slice v'd, int v'e, int v'f, int v'g, slice v'h) = __tact_tuple_destroy_8(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g, __tact_verify_address(v'h));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_verify_address", + "__tact_tuple_destroy_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_from_tuple", + "signature": "(cell, cell, slice, slice, int, int, int, slice) $C$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $C$_as_optional($C$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_as_optional", + "$C$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$C$_from_opt_tuple", + "signature": "tuple $C$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_to_external", + "signature": "(cell, cell, slice, slice, int, int, int, slice) $C$_to_external(((cell, cell, slice, slice, int, int, int, slice)) v)", + }, + { + "code": { + "code": "var loaded = $C$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_opt_external", + "signature": "tuple $C$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var v'a = sc_0~load_int(257); +var v'b = sc_0~load_int(257); +var v'c = sc_0~load_int(1) ? sc_0~load_int(257) : null(); +var v'd = sc_0~load_int(1); +var v'e = sc_0~load_int(1) ? sc_0~load_int(1) : null(); +slice sc_1 = sc_0~load_ref().begin_parse(); +var v'f = sc_1~load_int(257); +var v'g = sc_1~load_int(257); +return (sc_0, (v'a, v'b, v'c, v'd, v'e, v'f, v'g));", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set {}, + "name": "$A$_load", + "signature": "(slice, ((int, int, int, int, int, int, int))) $A$_load(slice sc_0)", + }, + { + "code": { + "code": "var r = sc_0~$A$_load(); +sc_0.end_parse(); +return r;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_load", + }, + "flags": Set {}, + "name": "$A$_load_not_mut", + "signature": "((int, int, int, int, int, int, int)) $A$_load_not_mut(slice sc_0)", + }, +] +`; + +exports[`writeSerialization should write serializer for B 1`] = ` +[ + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_set", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_nop", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_str_to_slice", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_slice_to_str", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_address_to_slice", + "signature": "", + }, + { + "code": { + "code": "throw_unless(136, address.slice_bits() == 267); +var h = address.preload_uint(11); +throw_if(137, h == 1279); +throw_unless(136, h == 1024); +return address;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_verify_address", + "signature": "slice __tact_verify_address(slice address)", + }, + { + "code": { + "code": "slice raw = cs~load_msg_addr(); +return (cs, __tact_verify_address(raw));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_load_address", + "signature": "(slice, slice) __tact_load_address(slice cs)", + }, + { + "code": { + "code": "if (cs.preload_uint(2) != 0) { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); +} else { + cs~skip_bits(2); + return (cs, null()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_load_address_opt", + "signature": "(slice, slice) __tact_load_address_opt(slice cs)", + }, + { + "code": { + "code": "return b.store_slice(__tact_verify_address(address));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_store_address", + "signature": "builder __tact_store_address(builder b, slice address)", + }, + { + "code": { + "code": "if (null?(address)) { + b = b.store_uint(0, 2); + return b; +} else { + return __tact_store_address(b, address); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_store_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_store_address_opt", + "signature": "builder __tact_store_address_opt(builder b, slice address)", + }, + { + "code": { + "code": "var b = begin_cell(); +b = b.store_uint(2, 2); +b = b.store_uint(0, 1); +b = b.store_int(chain, 8); +b = b.store_uint(hash, 256); +var addr = b.end_cell().begin_parse(); +return __tact_verify_address(addr);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_create_address", + "signature": "slice __tact_create_address(int chain, int hash)", + }, + { + "code": { + "code": "var b = begin_cell(); +b = b.store_uint(0, 2); +b = b.store_uint(3, 2); +b = b.store_uint(0, 1); +b = b.store_ref(code); +b = b.store_ref(data); +var hash = cell_hash(b.end_cell()); +return __tact_create_address(chain, hash);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_create_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_compute_contract_address", + "signature": "slice __tact_compute_contract_address(int chain, cell code, cell data)", + }, + { + "code": { + "code": "throw_if(128, null?(x)); return x;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_not_null", + "signature": "forall X -> X __tact_not_null(X x)", + }, + { + "code": { + "code": "DICTDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete", + "signature": "(cell, int) __tact_dict_delete(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTIDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_int", + "signature": "(cell, int) __tact_dict_delete_int(cell dict, int key_len, int index)", + }, + { + "code": { + "code": "DICTUDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_uint", + "signature": "(cell, int) __tact_dict_delete_uint(cell dict, int key_len, int index)", + }, + { + "code": { + "code": "DICTSETREF", + "kind": "asm", + "shuffle": "(value index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_set_ref", + "signature": "((cell), ()) __tact_dict_set_ref(cell dict, int key_len, slice index, cell value)", + }, + { + "code": { + "code": "DICTGET NULLSWAPIFNOT", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_get", + "signature": "(slice, int) __tact_dict_get(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTDELGET NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_get", + "signature": "(cell, (slice, int)) __tact_dict_delete_get(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTGETREF NULLSWAPIFNOT", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_get_ref", + "signature": "(cell, int) __tact_dict_get_ref(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTMIN NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_min", + "signature": "(slice, slice, int) __tact_dict_min(cell dict, int key_len)", + }, + { + "code": { + "code": "DICTMINREF NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_min_ref", + "signature": "(slice, cell, int) __tact_dict_min_ref(cell dict, int key_len)", + }, + { + "code": { + "code": "DICTGETNEXT NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(pivot dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_next", + "signature": "(slice, slice, int) __tact_dict_next(cell dict, int key_len, slice pivot)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(dict, key_len, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set {}, + "name": "__tact_dict_next_ref", + "signature": "(slice, cell, int) __tact_dict_next_ref(cell dict, int key_len, slice pivot)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP s0 DUMP DROP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug", + "signature": "forall X -> () __tact_debug(X value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP STRDUMP DROP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_str", + "signature": "() __tact_debug_str(slice value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "if (value) { + __tact_debug_str("true", debug_print_1, debug_print_2); +} else { + __tact_debug_str("false", debug_print_1, debug_print_2); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_bool", + "signature": "() __tact_debug_bool(int value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "SDSUBSTR", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_preload_offset", + "signature": "(slice) __tact_preload_offset(slice s, int offset, int bits)", + }, + { + "code": { + "code": "slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) +.end_cell().begin_parse(); +int reg = 0; +while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } +} +(int q, int r) = divmod(reg, 256); +return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) +.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline_ref", + }, + "name": "__tact_crc16", + "signature": "(slice) __tact_crc16(slice data)", + }, + { + "code": { + "code": "slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; +builder res = begin_cell(); + +while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); +} + +return res.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_preload_offset", + }, + "flags": Set {}, + "name": "__tact_base64_encode", + "signature": "(slice) __tact_base64_encode(slice data)", + }, + { + "code": { + "code": "(int wc, int hash) = address.parse_std_addr(); + +slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) +.end_cell().begin_parse(); + +slice checksum = __tact_crc16(user_friendly_address); +slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) +.end_cell().begin_parse(); + +return __tact_base64_encode(user_friendly_address_with_checksum);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_crc16", + "__tact_base64_encode", + }, + "flags": Set {}, + "name": "__tact_address_to_user_friendly", + "signature": "(slice) __tact_address_to_user_friendly(slice address)", + }, + { + "code": { + "code": "__tact_debug_str(__tact_address_to_user_friendly(address), debug_print_1, debug_print_2);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + "__tact_address_to_user_friendly", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_address", + "signature": "() __tact_debug_address(slice address, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP DUMPSTK", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_stack", + "signature": "() __tact_debug_stack(slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "return __tact_context;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_context_get", + "signature": "(int, slice, int, slice) __tact_context_get()", + }, + { + "code": { + "code": "return __tact_context_sender;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_context_get_sender", + "signature": "slice __tact_context_get_sender()", + }, + { + "code": { + "code": "if (null?(__tact_randomized)) { + randomize_lt(); + __tact_randomized = true; +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_prepare_random", + "signature": "() __tact_prepare_random()", + }, + { + "code": { + "code": "return b.store_int(v, 1);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_store_bool", + "signature": "builder __tact_store_bool(builder b, int v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_to_tuple", + "signature": "forall X -> tuple __tact_to_tuple(X x)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_from_tuple", + "signature": "forall X -> X __tact_from_tuple(tuple x)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_int", + "signature": "(cell, ()) __tact_dict_set_int_int(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_int", + "signature": "int __tact_dict_get_int_int(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_int", + "signature": "(int, int, int) __tact_dict_min_int_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_int", + "signature": "(int, int, int) __tact_dict_next_int_int(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_uint", + "signature": "(cell, ()) __tact_dict_set_int_uint(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_uint", + "signature": "int __tact_dict_get_int_uint(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_uint", + "signature": "(int, int, int) __tact_dict_min_int_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_uint", + "signature": "(int, int, int) __tact_dict_next_int_uint(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_int", + "signature": "(cell, ()) __tact_dict_set_uint_int(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_int", + "signature": "int __tact_dict_get_uint_int(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_int", + "signature": "(int, int, int) __tact_dict_min_uint_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_int", + "signature": "(int, int, int) __tact_dict_next_uint_int(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_uint", + "signature": "(cell, ()) __tact_dict_set_uint_uint(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_uint", + "signature": "int __tact_dict_get_uint_uint(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_uint", + "signature": "(int, int, int) __tact_dict_min_uint_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_uint", + "signature": "(int, int, int) __tact_dict_next_uint_uint(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_ref(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_cell", + "signature": "(cell, ()) __tact_dict_set_int_cell(cell d, int kl, int k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = idict_get_ref?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_cell", + "signature": "cell __tact_dict_get_int_cell(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min_ref?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_cell", + "signature": "(int, cell, int) __tact_dict_min_int_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_cell", + "signature": "(int, cell, int) __tact_dict_next_int_cell(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_ref(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_cell", + "signature": "(cell, ()) __tact_dict_set_uint_cell(cell d, int kl, int k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = udict_get_ref?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_cell", + "signature": "cell __tact_dict_get_uint_cell(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min_ref?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_cell", + "signature": "(int, cell, int) __tact_dict_min_uint_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_cell", + "signature": "(int, cell, int) __tact_dict_next_uint_cell(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_slice", + "signature": "(cell, ()) __tact_dict_set_int_slice(cell d, int kl, int k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_slice", + "signature": "slice __tact_dict_get_int_slice(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_slice", + "signature": "(int, slice, int) __tact_dict_min_int_slice(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_slice", + "signature": "(int, slice, int) __tact_dict_next_int_slice(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_slice", + "signature": "(cell, ()) __tact_dict_set_uint_slice(cell d, int kl, int k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_slice", + "signature": "slice __tact_dict_get_uint_slice(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_slice", + "signature": "(int, slice, int) __tact_dict_min_uint_slice(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_slice", + "signature": "(int, slice, int) __tact_dict_next_uint_slice(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_int", + "signature": "(cell, ()) __tact_dict_set_slice_int(cell d, int kl, slice k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_int", + "signature": "int __tact_dict_get_slice_int(cell d, int kl, slice k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_int", + "signature": "(slice, int, int) __tact_dict_min_slice_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_int", + "signature": "(slice, int, int) __tact_dict_next_slice_int(cell d, int kl, slice pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_uint", + "signature": "(cell, ()) __tact_dict_set_slice_uint(cell d, int kl, slice k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_uint", + "signature": "int __tact_dict_get_slice_uint(cell d, int kl, slice k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_uint", + "signature": "(slice, int, int) __tact_dict_min_slice_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_uint", + "signature": "(slice, int, int) __tact_dict_next_slice_uint(cell d, int kl, slice pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return __tact_dict_set_ref(d, kl, k, v); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + "__tact_dict_set_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_cell", + "signature": "(cell, ()) __tact_dict_set_slice_cell(cell d, int kl, slice k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get_ref(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_cell", + "signature": "cell __tact_dict_get_slice_cell(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min_ref(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_cell", + "signature": "(slice, cell, int) __tact_dict_min_slice_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_cell", + "signature": "(slice, cell, int) __tact_dict_next_slice_cell(cell d, int kl, slice pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_slice(v)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_slice", + "signature": "(cell, ()) __tact_dict_set_slice_slice(cell d, int kl, slice k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_slice", + "signature": "slice __tact_dict_get_slice_slice(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_slice", + "signature": "(slice, slice, int) __tact_dict_min_slice_slice(cell d, int kl)", + }, + { + "code": { + "code": "return __tact_dict_next(d, kl, pivot);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_slice", + "signature": "(slice, slice, int) __tact_dict_next_slice_slice(cell d, int kl, slice pivot)", + }, + { + "code": { + "code": "return equal_slices_bits(a, b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits", + "signature": "int __tact_slice_eq_bits(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (equal_slices_bits(a, b));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits_nullable_one", + "signature": "int __tact_slice_eq_bits_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( equal_slices_bits(a, b) ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits_nullable", + "signature": "int __tact_slice_eq_bits_nullable(slice a, slice b)", + }, + { + "code": { + "code": "(slice key, slice value, int flag) = __tact_dict_min(a, kl); +while (flag) { + (slice value_b, int flag_b) = b~__tact_dict_delete_get(kl, key); + ifnot (flag_b) { + return 0; + } + ifnot (value.slice_hash() == value_b.slice_hash()) { + return 0; + } + (key, value, flag) = __tact_dict_next(a, kl, key); +} +return null?(b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + "__tact_dict_delete_get", + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_eq", + "signature": "int __tact_dict_eq(cell a, cell b, int kl)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a == b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_eq_nullable_one", + "signature": "int __tact_int_eq_nullable_one(int a, int b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a != b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_neq_nullable_one", + "signature": "int __tact_int_neq_nullable_one(int a, int b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a == b ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_eq_nullable", + "signature": "int __tact_int_eq_nullable(int a, int b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a != b ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_neq_nullable", + "signature": "int __tact_int_neq_nullable(int a, int b)", + }, + { + "code": { + "code": "return (a.cell_hash() == b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq", + "signature": "int __tact_cell_eq(cell a, cell b)", + }, + { + "code": { + "code": "return (a.cell_hash() != b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq", + "signature": "int __tact_cell_neq(cell a, cell b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.cell_hash() == b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq_nullable_one", + "signature": "int __tact_cell_eq_nullable_one(cell a, cell b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.cell_hash() != b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq_nullable_one", + "signature": "int __tact_cell_neq_nullable_one(cell a, cell b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.cell_hash() == b.cell_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq_nullable", + "signature": "int __tact_cell_eq_nullable(cell a, cell b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.cell_hash() != b.cell_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq_nullable", + "signature": "int __tact_cell_neq_nullable(cell a, cell b)", + }, + { + "code": { + "code": "return (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq", + "signature": "int __tact_slice_eq(slice a, slice b)", + }, + { + "code": { + "code": "return (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq", + "signature": "int __tact_slice_neq(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable_one", + "signature": "int __tact_slice_eq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable_one", + "signature": "int __tact_slice_neq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable", + "signature": "int __tact_slice_eq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable", + "signature": "int __tact_slice_neq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "return udict_set_ref(dict, 16, id, code);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_code", + "signature": "cell __tact_dict_set_code(cell dict, int id, cell code)", + }, + { + "code": { + "code": "var (data, ok) = udict_get_ref?(dict, 16, id); +throw_unless(135, ok); +return data;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_code", + "signature": "cell __tact_dict_get_code(cell dict, int id)", + }, + { + "code": { + "code": "NIL", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_0", + "signature": "tuple __tact_tuple_create_0()", + }, + { + "code": { + "code": "return ();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_tuple_destroy_0", + "signature": "() __tact_tuple_destroy_0()", + }, + { + "code": { + "code": "1 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_1", + "signature": "forall X0 -> tuple __tact_tuple_create_1((X0) v)", + }, + { + "code": { + "code": "1 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_1", + "signature": "forall X0 -> (X0) __tact_tuple_destroy_1(tuple v)", + }, + { + "code": { + "code": "2 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_2", + "signature": "forall X0, X1 -> tuple __tact_tuple_create_2((X0, X1) v)", + }, + { + "code": { + "code": "2 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_2", + "signature": "forall X0, X1 -> (X0, X1) __tact_tuple_destroy_2(tuple v)", + }, + { + "code": { + "code": "3 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_3", + "signature": "forall X0, X1, X2 -> tuple __tact_tuple_create_3((X0, X1, X2) v)", + }, + { + "code": { + "code": "3 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_3", + "signature": "forall X0, X1, X2 -> (X0, X1, X2) __tact_tuple_destroy_3(tuple v)", + }, + { + "code": { + "code": "4 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_4", + "signature": "forall X0, X1, X2, X3 -> tuple __tact_tuple_create_4((X0, X1, X2, X3) v)", + }, + { + "code": { + "code": "4 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_4", + "signature": "forall X0, X1, X2, X3 -> (X0, X1, X2, X3) __tact_tuple_destroy_4(tuple v)", + }, + { + "code": { + "code": "5 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_5", + "signature": "forall X0, X1, X2, X3, X4 -> tuple __tact_tuple_create_5((X0, X1, X2, X3, X4) v)", + }, + { + "code": { + "code": "5 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_5", + "signature": "forall X0, X1, X2, X3, X4 -> (X0, X1, X2, X3, X4) __tact_tuple_destroy_5(tuple v)", + }, + { + "code": { + "code": "6 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_6", + "signature": "forall X0, X1, X2, X3, X4, X5 -> tuple __tact_tuple_create_6((X0, X1, X2, X3, X4, X5) v)", + }, + { + "code": { + "code": "6 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_6", + "signature": "forall X0, X1, X2, X3, X4, X5 -> (X0, X1, X2, X3, X4, X5) __tact_tuple_destroy_6(tuple v)", + }, + { + "code": { + "code": "7 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_7", + "signature": "forall X0, X1, X2, X3, X4, X5, X6 -> tuple __tact_tuple_create_7((X0, X1, X2, X3, X4, X5, X6) v)", + }, + { + "code": { + "code": "7 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_7", + "signature": "forall X0, X1, X2, X3, X4, X5, X6 -> (X0, X1, X2, X3, X4, X5, X6) __tact_tuple_destroy_7(tuple v)", + }, + { + "code": { + "code": "8 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_8", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7 -> tuple __tact_tuple_create_8((X0, X1, X2, X3, X4, X5, X6, X7) v)", + }, + { + "code": { + "code": "8 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_8", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7 -> (X0, X1, X2, X3, X4, X5, X6, X7) __tact_tuple_destroy_8(tuple v)", + }, + { + "code": { + "code": "9 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_9", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8 -> tuple __tact_tuple_create_9((X0, X1, X2, X3, X4, X5, X6, X7, X8) v)", + }, + { + "code": { + "code": "9 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_9", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8) __tact_tuple_destroy_9(tuple v)", + }, + { + "code": { + "code": "10 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_10", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9 -> tuple __tact_tuple_create_10((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) v)", + }, + { + "code": { + "code": "10 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_10", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) __tact_tuple_destroy_10(tuple v)", + }, + { + "code": { + "code": "11 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_11", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 -> tuple __tact_tuple_create_11((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) v)", + }, + { + "code": { + "code": "11 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_11", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) __tact_tuple_destroy_11(tuple v)", + }, + { + "code": { + "code": "12 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_12", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11 -> tuple __tact_tuple_create_12((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) v)", + }, + { + "code": { + "code": "12 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_12", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) __tact_tuple_destroy_12(tuple v)", + }, + { + "code": { + "code": "13 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_13", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12 -> tuple __tact_tuple_create_13((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) v)", + }, + { + "code": { + "code": "13 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_13", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) __tact_tuple_destroy_13(tuple v)", + }, + { + "code": { + "code": "14 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_14", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 -> tuple __tact_tuple_create_14((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) v)", + }, + { + "code": { + "code": "14 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_14", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) __tact_tuple_destroy_14(tuple v)", + }, + { + "code": { + "code": "15 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_15", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14 -> tuple __tact_tuple_create_15((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) v)", + }, + { + "code": { + "code": "15 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_15", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) __tact_tuple_destroy_15(tuple v)", + }, + { + "code": { + "code": "return tpush(tpush(empty_tuple(), b), null());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start", + "signature": "tuple __tact_string_builder_start(builder b)", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell().store_uint(0, 32));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_comment", + "signature": "tuple __tact_string_builder_start_comment()", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell().store_uint(0, 8));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_tail_string", + "signature": "tuple __tact_string_builder_start_tail_string()", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_string", + "signature": "tuple __tact_string_builder_start_string()", + }, + { + "code": { + "code": "(builder b, tuple tail) = uncons(builders); +cell c = b.end_cell(); +while(~ null?(tail)) { + (b, tail) = uncons(tail); + c = b.store_ref(c).end_cell(); +} +return c;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_end", + "signature": "cell __tact_string_builder_end(tuple builders)", + }, + { + "code": { + "code": "return __tact_string_builder_end(builders).begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_end", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_end_slice", + "signature": "slice __tact_string_builder_end_slice(tuple builders)", + }, + { + "code": { + "code": "int sliceRefs = slice_refs(sc); +int sliceBits = slice_bits(sc); + +while((sliceBits > 0) | (sliceRefs > 0)) { + + ;; Load the current builder + (builder b, tuple tail) = uncons(builders); + int remBytes = 127 - (builder_bits(b) / 8); + int exBytes = sliceBits / 8; + + ;; Append bits + int amount = min(remBytes, exBytes); + if (amount > 0) { + slice read = sc~load_bits(amount * 8); + b = b.store_slice(read); + } + + ;; Update builders + builders = cons(b, tail); + + ;; Check if we need to add a new cell and continue + if (exBytes - amount > 0) { + var bb = begin_cell(); + builders = cons(bb, builders); + sliceBits = (exBytes - amount) * 8; + } elseif (sliceRefs > 0) { + sc = sc~load_ref().begin_parse(); + sliceRefs = slice_refs(sc); + sliceBits = slice_bits(sc); + } else { + sliceBits = 0; + sliceRefs = 0; + } +} + +return ((builders), ());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_string_builder_append", + "signature": "((tuple), ()) __tact_string_builder_append(tuple builders, slice sc)", + }, + { + "code": { + "code": "builders~__tact_string_builder_append(sc); +return builders;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_append", + }, + "flags": Set {}, + "name": "__tact_string_builder_append_not_mut", + "signature": "(tuple) __tact_string_builder_append_not_mut(tuple builders, slice sc)", + }, + { + "code": { + "code": "var b = begin_cell(); +if (src < 0) { + b = b.store_uint(45, 8); + src = - src; +} + +if (src < 1000000000000000000000000000000) { + int len = 0; + int value = 0; + int mult = 1; + do { + (src, int res) = src.divmod(10); + value = value + (res + 48) * mult; + mult = mult * 256; + len = len + 1; + } until (src == 0); + + b = b.store_uint(value, len * 8); +} else { + tuple t = empty_tuple(); + int len = 0; + do { + int digit = src % 10; + t~tpush(digit); + len = len + 1; + src = src / 10; + } until (src == 0); + + int c = len - 1; + repeat(len) { + int v = t.at(c); + b = b.store_uint(v + 48, 8); + c = c - 1; + } +} +return b.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_int_to_string", + "signature": "slice __tact_int_to_string(int src)", + }, + { + "code": { + "code": "throw_if(134, (digits <= 0) | (digits > 77)); +builder b = begin_cell(); + +if (src < 0) { + b = b.store_uint(45, 8); + src = - src; +} + +;; Process rem part +int skip = true; +int len = 0; +int rem = 0; +tuple t = empty_tuple(); +repeat(digits) { + (src, rem) = src.divmod(10); + if ( ~ ( skip & ( rem == 0 ) ) ) { + skip = false; + t~tpush(rem + 48); + len = len + 1; + } +} + +;; Process dot +if (~ skip) { + t~tpush(46); + len = len + 1; +} + +;; Main +do { + (src, rem) = src.divmod(10); + t~tpush(rem + 48); + len = len + 1; +} until (src == 0); + +;; Assemble +int c = len - 1; +repeat(len) { + int v = t.at(c); + b = b.store_uint(v, 8); + c = c - 1; +} + +;; Result +return b.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_float_to_string", + "signature": "slice __tact_float_to_string(int src, int digits)", + }, + { + "code": { + "code": "throw_unless(5, num > 0); +throw_unless(5, base > 1); +if (num < base) { + return 0; +} +int result = 0; +while (num >= base) { + num /= base; + result += 1; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_log", + "signature": "int __tact_log(int num, int base)", + }, + { + "code": { + "code": "throw_unless(5, exp >= 0); +int result = 1; +repeat (exp) { + result *= base; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_pow", + "signature": "int __tact_pow(int base, int exp)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_int", + "signature": "int __tact_dict_exists_int(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_uint", + "signature": "int __tact_dict_exists_uint(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_slice", + "signature": "int __tact_dict_exists_slice(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +build_0 = build_0.store_int(v'a, 257); +build_0 = build_0.store_int(v'b, 257); +build_0 = ~ null?(v'c) ? build_0.store_int(true, 1).store_int(v'c, 257) : build_0.store_int(false, 1); +build_0 = build_0.store_int(v'd, 1); +build_0 = ~ null?(v'e) ? build_0.store_int(true, 1).store_int(v'e, 1) : build_0.store_int(false, 1); +var build_1 = begin_cell(); +build_1 = build_1.store_int(v'f, 257); +build_1 = build_1.store_int(v'g, 257); +build_0 = store_ref(build_0, build_1.end_cell()); +return build_0;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set {}, + "name": "$B$_store", + "signature": "builder $B$_store(builder build_0, (int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "return $B$_store(begin_cell(), v).end_cell();", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_store", + }, + "flags": Set { + "inline", + }, + "name": "$B$_store_cell", + "signature": "cell $B$_store_cell((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_a", + "signature": "_ $A$_get_a((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_b", + "signature": "_ $A$_get_b((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_c", + "signature": "_ $A$_get_c((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_d", + "signature": "_ $A$_get_d((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_e", + "signature": "_ $A$_get_e((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_f", + "signature": "_ $A$_get_f((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_g", + "signature": "_ $A$_get_g((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set {}, + "name": "$A$_tensor_cast", + "signature": "((int, int, int, int, int, int, int)) $A$_tensor_cast((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (int vvv'a, int vvv'b, int vvv'c, int vvv'd, int vvv'e, int vvv'f, int vvv'g) = __tact_tuple_destroy_7(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_not_null", + "signature": "((int, int, int, int, int, int, int)) $A$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_as_optional", + "signature": "tuple $A$_as_optional((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_tuple", + "signature": "tuple $A$_to_tuple(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $A$_to_tuple($A$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_to_tuple", + "$A$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_opt_tuple", + "signature": "tuple $A$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (int v'a, int v'b, int v'c, int v'd, int v'e, int v'f, int v'g) = __tact_tuple_destroy_7(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_from_tuple", + "signature": "(int, int, int, int, int, int, int) $A$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $A$_as_optional($A$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_as_optional", + "$A$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$A$_from_opt_tuple", + "signature": "tuple $A$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_to_external", + "signature": "(int, int, int, int, int, int, int) $A$_to_external(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "var loaded = $A$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_opt_external", + "signature": "tuple $A$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_a", + "signature": "_ $B$_get_a((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_b", + "signature": "_ $B$_get_b((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_c", + "signature": "_ $B$_get_c((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_d", + "signature": "_ $B$_get_d((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_e", + "signature": "_ $B$_get_e((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_f", + "signature": "_ $B$_get_f((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_g", + "signature": "_ $B$_get_g((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set {}, + "name": "$B$_tensor_cast", + "signature": "((int, int, int, int, int, int, int)) $B$_tensor_cast((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (int vvv'a, int vvv'b, int vvv'c, int vvv'd, int vvv'e, int vvv'f, int vvv'g) = __tact_tuple_destroy_7(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_not_null", + "signature": "((int, int, int, int, int, int, int)) $B$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_as_optional", + "signature": "tuple $B$_as_optional((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_tuple", + "signature": "tuple $B$_to_tuple(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $B$_to_tuple($B$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_to_tuple", + "$B$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_opt_tuple", + "signature": "tuple $B$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (int v'a, int v'b, int v'c, int v'd, int v'e, int v'f, int v'g) = __tact_tuple_destroy_7(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_from_tuple", + "signature": "(int, int, int, int, int, int, int) $B$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $B$_as_optional($B$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_as_optional", + "$B$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$B$_from_opt_tuple", + "signature": "tuple $B$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_to_external", + "signature": "(int, int, int, int, int, int, int) $B$_to_external(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "var loaded = $B$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_opt_external", + "signature": "tuple $B$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_a", + "signature": "_ $C$_get_a((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_b", + "signature": "_ $C$_get_b((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_c", + "signature": "_ $C$_get_c((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_d", + "signature": "_ $C$_get_d((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_e", + "signature": "_ $C$_get_e((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_f", + "signature": "_ $C$_get_f((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_g", + "signature": "_ $C$_get_g((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'h;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_h", + "signature": "_ $C$_get_h((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set {}, + "name": "$C$_tensor_cast", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_tensor_cast((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (cell vvv'a, cell vvv'b, slice vvv'c, slice vvv'd, int vvv'e, int vvv'f, int vvv'g, slice vvv'h) = __tact_tuple_destroy_8(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g, vvv'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_destroy_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_not_null", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return __tact_tuple_create_8(v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_create_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_as_optional", + "signature": "tuple $C$_as_optional((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return __tact_tuple_create_8(v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_create_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_tuple", + "signature": "tuple $C$_to_tuple(((cell, cell, slice, slice, int, int, int, slice)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $C$_to_tuple($C$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_to_tuple", + "$C$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_opt_tuple", + "signature": "tuple $C$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (cell v'a, cell v'b, slice v'c, slice v'd, int v'e, int v'f, int v'g, slice v'h) = __tact_tuple_destroy_8(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g, __tact_verify_address(v'h));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_verify_address", + "__tact_tuple_destroy_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_from_tuple", + "signature": "(cell, cell, slice, slice, int, int, int, slice) $C$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $C$_as_optional($C$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_as_optional", + "$C$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$C$_from_opt_tuple", + "signature": "tuple $C$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_to_external", + "signature": "(cell, cell, slice, slice, int, int, int, slice) $C$_to_external(((cell, cell, slice, slice, int, int, int, slice)) v)", + }, + { + "code": { + "code": "var loaded = $C$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_opt_external", + "signature": "tuple $C$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var v'a = sc_0~load_int(257); +var v'b = sc_0~load_int(257); +var v'c = sc_0~load_int(1) ? sc_0~load_int(257) : null(); +var v'd = sc_0~load_int(1); +var v'e = sc_0~load_int(1) ? sc_0~load_int(1) : null(); +slice sc_1 = sc_0~load_ref().begin_parse(); +var v'f = sc_1~load_int(257); +var v'g = sc_1~load_int(257); +return (sc_0, (v'a, v'b, v'c, v'd, v'e, v'f, v'g));", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set {}, + "name": "$B$_load", + "signature": "(slice, ((int, int, int, int, int, int, int))) $B$_load(slice sc_0)", + }, + { + "code": { + "code": "var r = sc_0~$B$_load(); +sc_0.end_parse(); +return r;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_load", + }, + "flags": Set {}, + "name": "$B$_load_not_mut", + "signature": "((int, int, int, int, int, int, int)) $B$_load_not_mut(slice sc_0)", + }, +] +`; + +exports[`writeSerialization should write serializer for C 1`] = ` +[ + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_set", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_nop", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_str_to_slice", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_slice_to_str", + "signature": "", + }, + { + "code": { + "kind": "skip", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_address_to_slice", + "signature": "", + }, + { + "code": { + "code": "throw_unless(136, address.slice_bits() == 267); +var h = address.preload_uint(11); +throw_if(137, h == 1279); +throw_unless(136, h == 1024); +return address;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_verify_address", + "signature": "slice __tact_verify_address(slice address)", + }, + { + "code": { + "code": "slice raw = cs~load_msg_addr(); +return (cs, __tact_verify_address(raw));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_load_address", + "signature": "(slice, slice) __tact_load_address(slice cs)", + }, + { + "code": { + "code": "if (cs.preload_uint(2) != 0) { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); +} else { + cs~skip_bits(2); + return (cs, null()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_load_address_opt", + "signature": "(slice, slice) __tact_load_address_opt(slice cs)", + }, + { + "code": { + "code": "return b.store_slice(__tact_verify_address(address));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_store_address", + "signature": "builder __tact_store_address(builder b, slice address)", + }, + { + "code": { + "code": "if (null?(address)) { + b = b.store_uint(0, 2); + return b; +} else { + return __tact_store_address(b, address); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_store_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_store_address_opt", + "signature": "builder __tact_store_address_opt(builder b, slice address)", + }, + { + "code": { + "code": "var b = begin_cell(); +b = b.store_uint(2, 2); +b = b.store_uint(0, 1); +b = b.store_int(chain, 8); +b = b.store_uint(hash, 256); +var addr = b.end_cell().begin_parse(); +return __tact_verify_address(addr);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_verify_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_create_address", + "signature": "slice __tact_create_address(int chain, int hash)", + }, + { + "code": { + "code": "var b = begin_cell(); +b = b.store_uint(0, 2); +b = b.store_uint(3, 2); +b = b.store_uint(0, 1); +b = b.store_ref(code); +b = b.store_ref(data); +var hash = cell_hash(b.end_cell()); +return __tact_create_address(chain, hash);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_create_address", + }, + "flags": Set { + "inline", + }, + "name": "__tact_compute_contract_address", + "signature": "slice __tact_compute_contract_address(int chain, cell code, cell data)", + }, + { + "code": { + "code": "throw_if(128, null?(x)); return x;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_not_null", + "signature": "forall X -> X __tact_not_null(X x)", + }, + { + "code": { + "code": "DICTDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete", + "signature": "(cell, int) __tact_dict_delete(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTIDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_int", + "signature": "(cell, int) __tact_dict_delete_int(cell dict, int key_len, int index)", + }, + { + "code": { + "code": "DICTUDEL", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_uint", + "signature": "(cell, int) __tact_dict_delete_uint(cell dict, int key_len, int index)", + }, + { + "code": { + "code": "DICTSETREF", + "kind": "asm", + "shuffle": "(value index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_set_ref", + "signature": "((cell), ()) __tact_dict_set_ref(cell dict, int key_len, slice index, cell value)", + }, + { + "code": { + "code": "DICTGET NULLSWAPIFNOT", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_get", + "signature": "(slice, int) __tact_dict_get(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTDELGET NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_delete_get", + "signature": "(cell, (slice, int)) __tact_dict_delete_get(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTGETREF NULLSWAPIFNOT", + "kind": "asm", + "shuffle": "(index dict key_len)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_get_ref", + "signature": "(cell, int) __tact_dict_get_ref(cell dict, int key_len, slice index)", + }, + { + "code": { + "code": "DICTMIN NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_min", + "signature": "(slice, slice, int) __tact_dict_min(cell dict, int key_len)", + }, + { + "code": { + "code": "DICTMINREF NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_min_ref", + "signature": "(slice, cell, int) __tact_dict_min_ref(cell dict, int key_len)", + }, + { + "code": { + "code": "DICTGETNEXT NULLSWAPIFNOT2", + "kind": "asm", + "shuffle": "(pivot dict key_len -> 1 0 2)", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_dict_next", + "signature": "(slice, slice, int) __tact_dict_next(cell dict, int key_len, slice pivot)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(dict, key_len, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set {}, + "name": "__tact_dict_next_ref", + "signature": "(slice, cell, int) __tact_dict_next_ref(cell dict, int key_len, slice pivot)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP s0 DUMP DROP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug", + "signature": "forall X -> () __tact_debug(X value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP STRDUMP DROP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_str", + "signature": "() __tact_debug_str(slice value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "if (value) { + __tact_debug_str("true", debug_print_1, debug_print_2); +} else { + __tact_debug_str("false", debug_print_1, debug_print_2); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_bool", + "signature": "() __tact_debug_bool(int value, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "SDSUBSTR", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_preload_offset", + "signature": "(slice) __tact_preload_offset(slice s, int offset, int bits)", + }, + { + "code": { + "code": "slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) +.end_cell().begin_parse(); +int reg = 0; +while (~ new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } +} +(int q, int r) = divmod(reg, 256); +return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) +.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline_ref", + }, + "name": "__tact_crc16", + "signature": "(slice) __tact_crc16(slice data)", + }, + { + "code": { + "code": "slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; +builder res = begin_cell(); + +while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); +} + +return res.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_preload_offset", + }, + "flags": Set {}, + "name": "__tact_base64_encode", + "signature": "(slice) __tact_base64_encode(slice data)", + }, + { + "code": { + "code": "(int wc, int hash) = address.parse_std_addr(); + +slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) +.end_cell().begin_parse(); + +slice checksum = __tact_crc16(user_friendly_address); +slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) +.end_cell().begin_parse(); + +return __tact_base64_encode(user_friendly_address_with_checksum);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_crc16", + "__tact_base64_encode", + }, + "flags": Set {}, + "name": "__tact_address_to_user_friendly", + "signature": "(slice) __tact_address_to_user_friendly(slice address)", + }, + { + "code": { + "code": "__tact_debug_str(__tact_address_to_user_friendly(address), debug_print_1, debug_print_2);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_debug_str", + "__tact_address_to_user_friendly", + }, + "flags": Set { + "impure", + }, + "name": "__tact_debug_address", + "signature": "() __tact_debug_address(slice address, slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "STRDUMP DROP STRDUMP DROP DUMPSTK", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + }, + "name": "__tact_debug_stack", + "signature": "() __tact_debug_stack(slice debug_print_1, slice debug_print_2)", + }, + { + "code": { + "code": "return __tact_context;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_context_get", + "signature": "(int, slice, int, slice) __tact_context_get()", + }, + { + "code": { + "code": "return __tact_context_sender;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_context_get_sender", + "signature": "slice __tact_context_get_sender()", + }, + { + "code": { + "code": "if (null?(__tact_randomized)) { + randomize_lt(); + __tact_randomized = true; +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "impure", + "inline", + }, + "name": "__tact_prepare_random", + "signature": "() __tact_prepare_random()", + }, + { + "code": { + "code": "return b.store_int(v, 1);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_store_bool", + "signature": "builder __tact_store_bool(builder b, int v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_to_tuple", + "signature": "forall X -> tuple __tact_to_tuple(X x)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_from_tuple", + "signature": "forall X -> X __tact_from_tuple(tuple x)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_int", + "signature": "(cell, ()) __tact_dict_set_int_int(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_int", + "signature": "int __tact_dict_get_int_int(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_int", + "signature": "(int, int, int) __tact_dict_min_int_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_int", + "signature": "(int, int, int) __tact_dict_next_int_int(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_uint", + "signature": "(cell, ()) __tact_dict_set_int_uint(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_uint", + "signature": "int __tact_dict_get_int_uint(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_uint", + "signature": "(int, int, int) __tact_dict_min_int_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_uint", + "signature": "(int, int, int) __tact_dict_next_int_uint(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_int", + "signature": "(cell, ()) __tact_dict_set_uint_int(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_int", + "signature": "int __tact_dict_get_uint_int(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_int", + "signature": "(int, int, int) __tact_dict_min_uint_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_int", + "signature": "(int, int, int) __tact_dict_next_uint_int(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_uint", + "signature": "(cell, ()) __tact_dict_set_uint_uint(cell d, int kl, int k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_uint", + "signature": "int __tact_dict_get_uint_uint(cell d, int kl, int k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_uint", + "signature": "(int, int, int) __tact_dict_min_uint_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_uint", + "signature": "(int, int, int) __tact_dict_next_uint_uint(cell d, int kl, int pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set_ref(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_cell", + "signature": "(cell, ()) __tact_dict_set_int_cell(cell d, int kl, int k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = idict_get_ref?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_cell", + "signature": "cell __tact_dict_get_int_cell(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min_ref?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_cell", + "signature": "(int, cell, int) __tact_dict_min_int_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_cell", + "signature": "(int, cell, int) __tact_dict_next_int_cell(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set_ref(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_cell", + "signature": "(cell, ()) __tact_dict_set_uint_cell(cell d, int kl, int k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = udict_get_ref?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_cell", + "signature": "cell __tact_dict_get_uint_cell(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min_ref?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_cell", + "signature": "(int, cell, int) __tact_dict_min_uint_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_cell", + "signature": "(int, cell, int) __tact_dict_next_uint_cell(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); +} else { + return (idict_set(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_int_slice", + "signature": "(cell, ()) __tact_dict_set_int_slice(cell d, int kl, int k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_int_slice", + "signature": "slice __tact_dict_get_int_slice(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_min?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_int_slice", + "signature": "(int, slice, int) __tact_dict_min_int_slice(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = idict_get_next?(d, kl, pivot); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_int_slice", + "signature": "(int, slice, int) __tact_dict_next_int_slice(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); +} else { + return (udict_set(d, kl, k, v), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_uint_slice", + "signature": "(cell, ()) __tact_dict_set_uint_slice(cell d, int kl, int k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_uint_slice", + "signature": "slice __tact_dict_get_uint_slice(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_min?(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_uint_slice", + "signature": "(int, slice, int) __tact_dict_min_uint_slice(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = udict_get_next?(d, kl, pivot); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_uint_slice", + "signature": "(int, slice, int) __tact_dict_next_uint_slice(cell d, int kl, int pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_int", + "signature": "(cell, ()) __tact_dict_set_slice_int(cell d, int kl, slice k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r~load_int(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_int", + "signature": "int __tact_dict_get_slice_int(cell d, int kl, slice k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_int", + "signature": "(slice, int, int) __tact_dict_min_slice_int(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_int(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_int", + "signature": "(slice, int, int) __tact_dict_next_slice_int(cell d, int kl, slice pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_uint", + "signature": "(cell, ()) __tact_dict_set_slice_uint(cell d, int kl, slice k, int v, int vl)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r~load_uint(vl); +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_uint", + "signature": "int __tact_dict_get_slice_uint(cell d, int kl, slice k, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_uint", + "signature": "(slice, int, int) __tact_dict_min_slice_uint(cell d, int kl, int vl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_uint(vl), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_uint", + "signature": "(slice, int, int) __tact_dict_next_slice_uint(cell d, int kl, slice pivot, int vl)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return __tact_dict_set_ref(d, kl, k, v); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + "__tact_dict_set_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_cell", + "signature": "(cell, ()) __tact_dict_set_slice_cell(cell d, int kl, slice k, cell v)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get_ref(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_cell", + "signature": "cell __tact_dict_get_slice_cell(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min_ref(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min_ref", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_cell", + "signature": "(slice, cell, int) __tact_dict_min_slice_cell(cell d, int kl)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_next(d, kl, pivot); +if (flag) { + return (key, value~load_ref(), flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_cell", + "signature": "(slice, cell, int) __tact_dict_next_slice_cell(cell d, int kl, slice pivot)", + }, + { + "code": { + "code": "if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); +} else { + return (dict_set_builder(d, kl, k, begin_cell().store_slice(v)), ()); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_delete", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_slice_slice", + "signature": "(cell, ()) __tact_dict_set_slice_slice(cell d, int kl, slice k, slice v)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +if (ok) { + return r; +} else { + return null(); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_slice_slice", + "signature": "slice __tact_dict_get_slice_slice(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (key, value, flag) = __tact_dict_min(d, kl); +if (flag) { + return (key, value, flag); +} else { + return (null(), null(), flag); +}", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_min_slice_slice", + "signature": "(slice, slice, int) __tact_dict_min_slice_slice(cell d, int kl)", + }, + { + "code": { + "code": "return __tact_dict_next(d, kl, pivot);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_next_slice_slice", + "signature": "(slice, slice, int) __tact_dict_next_slice_slice(cell d, int kl, slice pivot)", + }, + { + "code": { + "code": "return equal_slices_bits(a, b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits", + "signature": "int __tact_slice_eq_bits(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (equal_slices_bits(a, b));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits_nullable_one", + "signature": "int __tact_slice_eq_bits_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( equal_slices_bits(a, b) ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_bits_nullable", + "signature": "int __tact_slice_eq_bits_nullable(slice a, slice b)", + }, + { + "code": { + "code": "(slice key, slice value, int flag) = __tact_dict_min(a, kl); +while (flag) { + (slice value_b, int flag_b) = b~__tact_dict_delete_get(kl, key); + ifnot (flag_b) { + return 0; + } + ifnot (value.slice_hash() == value_b.slice_hash()) { + return 0; + } + (key, value, flag) = __tact_dict_next(a, kl, key); +} +return null?(b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_min", + "__tact_dict_delete_get", + "__tact_dict_next", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_eq", + "signature": "int __tact_dict_eq(cell a, cell b, int kl)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a == b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_eq_nullable_one", + "signature": "int __tact_int_eq_nullable_one(int a, int b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a != b);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_neq_nullable_one", + "signature": "int __tact_int_neq_nullable_one(int a, int b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a == b ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_eq_nullable", + "signature": "int __tact_int_eq_nullable(int a, int b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a != b ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_int_neq_nullable", + "signature": "int __tact_int_neq_nullable(int a, int b)", + }, + { + "code": { + "code": "return (a.cell_hash() == b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq", + "signature": "int __tact_cell_eq(cell a, cell b)", + }, + { + "code": { + "code": "return (a.cell_hash() != b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq", + "signature": "int __tact_cell_neq(cell a, cell b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.cell_hash() == b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq_nullable_one", + "signature": "int __tact_cell_eq_nullable_one(cell a, cell b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.cell_hash() != b.cell_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq_nullable_one", + "signature": "int __tact_cell_neq_nullable_one(cell a, cell b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.cell_hash() == b.cell_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_eq_nullable", + "signature": "int __tact_cell_eq_nullable(cell a, cell b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.cell_hash() != b.cell_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_cell_neq_nullable", + "signature": "int __tact_cell_neq_nullable(cell a, cell b)", + }, + { + "code": { + "code": "return (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq", + "signature": "int __tact_slice_eq(slice a, slice b)", + }, + { + "code": { + "code": "return (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq", + "signature": "int __tact_slice_neq(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable_one", + "signature": "int __tact_slice_eq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable_one", + "signature": "int __tact_slice_neq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable", + "signature": "int __tact_slice_eq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable", + "signature": "int __tact_slice_neq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "return udict_set_ref(dict, 16, id, code);", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_set_code", + "signature": "cell __tact_dict_set_code(cell dict, int id, cell code)", + }, + { + "code": { + "code": "var (data, ok) = udict_get_ref?(dict, 16, id); +throw_unless(135, ok); +return data;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_get_code", + "signature": "cell __tact_dict_get_code(cell dict, int id)", + }, + { + "code": { + "code": "NIL", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_0", + "signature": "tuple __tact_tuple_create_0()", + }, + { + "code": { + "code": "return ();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_tuple_destroy_0", + "signature": "() __tact_tuple_destroy_0()", + }, + { + "code": { + "code": "1 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_1", + "signature": "forall X0 -> tuple __tact_tuple_create_1((X0) v)", + }, + { + "code": { + "code": "1 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_1", + "signature": "forall X0 -> (X0) __tact_tuple_destroy_1(tuple v)", + }, + { + "code": { + "code": "2 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_2", + "signature": "forall X0, X1 -> tuple __tact_tuple_create_2((X0, X1) v)", + }, + { + "code": { + "code": "2 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_2", + "signature": "forall X0, X1 -> (X0, X1) __tact_tuple_destroy_2(tuple v)", + }, + { + "code": { + "code": "3 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_3", + "signature": "forall X0, X1, X2 -> tuple __tact_tuple_create_3((X0, X1, X2) v)", + }, + { + "code": { + "code": "3 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_3", + "signature": "forall X0, X1, X2 -> (X0, X1, X2) __tact_tuple_destroy_3(tuple v)", + }, + { + "code": { + "code": "4 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_4", + "signature": "forall X0, X1, X2, X3 -> tuple __tact_tuple_create_4((X0, X1, X2, X3) v)", + }, + { + "code": { + "code": "4 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_4", + "signature": "forall X0, X1, X2, X3 -> (X0, X1, X2, X3) __tact_tuple_destroy_4(tuple v)", + }, + { + "code": { + "code": "5 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_5", + "signature": "forall X0, X1, X2, X3, X4 -> tuple __tact_tuple_create_5((X0, X1, X2, X3, X4) v)", + }, + { + "code": { + "code": "5 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_5", + "signature": "forall X0, X1, X2, X3, X4 -> (X0, X1, X2, X3, X4) __tact_tuple_destroy_5(tuple v)", + }, + { + "code": { + "code": "6 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_6", + "signature": "forall X0, X1, X2, X3, X4, X5 -> tuple __tact_tuple_create_6((X0, X1, X2, X3, X4, X5) v)", + }, + { + "code": { + "code": "6 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_6", + "signature": "forall X0, X1, X2, X3, X4, X5 -> (X0, X1, X2, X3, X4, X5) __tact_tuple_destroy_6(tuple v)", + }, + { + "code": { + "code": "7 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_7", + "signature": "forall X0, X1, X2, X3, X4, X5, X6 -> tuple __tact_tuple_create_7((X0, X1, X2, X3, X4, X5, X6) v)", + }, + { + "code": { + "code": "7 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_7", + "signature": "forall X0, X1, X2, X3, X4, X5, X6 -> (X0, X1, X2, X3, X4, X5, X6) __tact_tuple_destroy_7(tuple v)", + }, + { + "code": { + "code": "8 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_8", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7 -> tuple __tact_tuple_create_8((X0, X1, X2, X3, X4, X5, X6, X7) v)", + }, + { + "code": { + "code": "8 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_8", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7 -> (X0, X1, X2, X3, X4, X5, X6, X7) __tact_tuple_destroy_8(tuple v)", + }, + { + "code": { + "code": "9 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_9", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8 -> tuple __tact_tuple_create_9((X0, X1, X2, X3, X4, X5, X6, X7, X8) v)", + }, + { + "code": { + "code": "9 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_9", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8) __tact_tuple_destroy_9(tuple v)", + }, + { + "code": { + "code": "10 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_10", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9 -> tuple __tact_tuple_create_10((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) v)", + }, + { + "code": { + "code": "10 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_10", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) __tact_tuple_destroy_10(tuple v)", + }, + { + "code": { + "code": "11 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_11", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 -> tuple __tact_tuple_create_11((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) v)", + }, + { + "code": { + "code": "11 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_11", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) __tact_tuple_destroy_11(tuple v)", + }, + { + "code": { + "code": "12 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_12", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11 -> tuple __tact_tuple_create_12((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) v)", + }, + { + "code": { + "code": "12 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_12", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) __tact_tuple_destroy_12(tuple v)", + }, + { + "code": { + "code": "13 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_13", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12 -> tuple __tact_tuple_create_13((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) v)", + }, + { + "code": { + "code": "13 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_13", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) __tact_tuple_destroy_13(tuple v)", + }, + { + "code": { + "code": "14 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_14", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 -> tuple __tact_tuple_create_14((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) v)", + }, + { + "code": { + "code": "14 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_14", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) __tact_tuple_destroy_14(tuple v)", + }, + { + "code": { + "code": "15 TUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_create_15", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14 -> tuple __tact_tuple_create_15((X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) v)", + }, + { + "code": { + "code": "15 UNTUPLE", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_tuple_destroy_15", + "signature": "forall X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14 -> (X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) __tact_tuple_destroy_15(tuple v)", + }, + { + "code": { + "code": "return tpush(tpush(empty_tuple(), b), null());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start", + "signature": "tuple __tact_string_builder_start(builder b)", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell().store_uint(0, 32));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_comment", + "signature": "tuple __tact_string_builder_start_comment()", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell().store_uint(0, 8));", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_tail_string", + "signature": "tuple __tact_string_builder_start_tail_string()", + }, + { + "code": { + "code": "return __tact_string_builder_start(begin_cell());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_start", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_start_string", + "signature": "tuple __tact_string_builder_start_string()", + }, + { + "code": { + "code": "(builder b, tuple tail) = uncons(builders); +cell c = b.end_cell(); +while(~ null?(tail)) { + (b, tail) = uncons(tail); + c = b.store_ref(c).end_cell(); +} +return c;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_end", + "signature": "cell __tact_string_builder_end(tuple builders)", + }, + { + "code": { + "code": "return __tact_string_builder_end(builders).begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_end", + }, + "flags": Set { + "inline", + }, + "name": "__tact_string_builder_end_slice", + "signature": "slice __tact_string_builder_end_slice(tuple builders)", + }, + { + "code": { + "code": "int sliceRefs = slice_refs(sc); +int sliceBits = slice_bits(sc); + +while((sliceBits > 0) | (sliceRefs > 0)) { + + ;; Load the current builder + (builder b, tuple tail) = uncons(builders); + int remBytes = 127 - (builder_bits(b) / 8); + int exBytes = sliceBits / 8; + + ;; Append bits + int amount = min(remBytes, exBytes); + if (amount > 0) { + slice read = sc~load_bits(amount * 8); + b = b.store_slice(read); + } + + ;; Update builders + builders = cons(b, tail); + + ;; Check if we need to add a new cell and continue + if (exBytes - amount > 0) { + var bb = begin_cell(); + builders = cons(bb, builders); + sliceBits = (exBytes - amount) * 8; + } elseif (sliceRefs > 0) { + sc = sc~load_ref().begin_parse(); + sliceRefs = slice_refs(sc); + sliceBits = slice_bits(sc); + } else { + sliceBits = 0; + sliceRefs = 0; + } +} + +return ((builders), ());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_string_builder_append", + "signature": "((tuple), ()) __tact_string_builder_append(tuple builders, slice sc)", + }, + { + "code": { + "code": "builders~__tact_string_builder_append(sc); +return builders;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_string_builder_append", + }, + "flags": Set {}, + "name": "__tact_string_builder_append_not_mut", + "signature": "(tuple) __tact_string_builder_append_not_mut(tuple builders, slice sc)", + }, + { + "code": { + "code": "var b = begin_cell(); +if (src < 0) { + b = b.store_uint(45, 8); + src = - src; +} + +if (src < 1000000000000000000000000000000) { + int len = 0; + int value = 0; + int mult = 1; + do { + (src, int res) = src.divmod(10); + value = value + (res + 48) * mult; + mult = mult * 256; + len = len + 1; + } until (src == 0); + + b = b.store_uint(value, len * 8); +} else { + tuple t = empty_tuple(); + int len = 0; + do { + int digit = src % 10; + t~tpush(digit); + len = len + 1; + src = src / 10; + } until (src == 0); + + int c = len - 1; + repeat(len) { + int v = t.at(c); + b = b.store_uint(v + 48, 8); + c = c - 1; + } +} +return b.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_int_to_string", + "signature": "slice __tact_int_to_string(int src)", + }, + { + "code": { + "code": "throw_if(134, (digits <= 0) | (digits > 77)); +builder b = begin_cell(); + +if (src < 0) { + b = b.store_uint(45, 8); + src = - src; +} + +;; Process rem part +int skip = true; +int len = 0; +int rem = 0; +tuple t = empty_tuple(); +repeat(digits) { + (src, rem) = src.divmod(10); + if ( ~ ( skip & ( rem == 0 ) ) ) { + skip = false; + t~tpush(rem + 48); + len = len + 1; + } +} + +;; Process dot +if (~ skip) { + t~tpush(46); + len = len + 1; +} + +;; Main +do { + (src, rem) = src.divmod(10); + t~tpush(rem + 48); + len = len + 1; +} until (src == 0); + +;; Assemble +int c = len - 1; +repeat(len) { + int v = t.at(c); + b = b.store_uint(v, 8); + c = c - 1; +} + +;; Result +return b.end_cell().begin_parse();", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_float_to_string", + "signature": "slice __tact_float_to_string(int src, int digits)", + }, + { + "code": { + "code": "throw_unless(5, num > 0); +throw_unless(5, base > 1); +if (num < base) { + return 0; +} +int result = 0; +while (num >= base) { + num /= base; + result += 1; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_log", + "signature": "int __tact_log(int num, int base)", + }, + { + "code": { + "code": "throw_unless(5, exp >= 0); +int result = 1; +repeat (exp) { + result *= base; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_pow", + "signature": "int __tact_pow(int base, int exp)", + }, + { + "code": { + "code": "var (r, ok) = idict_get?(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_int", + "signature": "int __tact_dict_exists_int(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (r, ok) = udict_get?(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_uint", + "signature": "int __tact_dict_exists_uint(cell d, int kl, int k)", + }, + { + "code": { + "code": "var (r, ok) = __tact_dict_get(d, kl, k); +return ok;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set { + "__tact_dict_get", + }, + "flags": Set { + "inline", + }, + "name": "__tact_dict_exists_slice", + "signature": "int __tact_dict_exists_slice(cell d, int kl, slice k)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +build_0 = build_0.store_ref(v'a); +build_0 = ~ null?(v'b) ? build_0.store_int(true, 1).store_ref(v'b) : build_0.store_int(false, 1); +var build_1 = begin_cell(); +build_1 = ~ null?(v'c) ? build_1.store_int(true, 1).store_ref(begin_cell().store_slice(v'c).end_cell()) : build_1.store_int(false, 1); +build_1 = ~ null?(v'd) ? build_1.store_int(true, 1).store_ref(begin_cell().store_slice(v'd).end_cell()) : build_1.store_int(false, 1); +build_1 = build_1.store_int(v'e, 1); +build_1 = build_1.store_int(v'f, 257); +build_1 = build_1.store_int(v'g, 257); +build_1 = __tact_store_address(build_1, v'h); +build_0 = store_ref(build_0, build_1.end_cell()); +return build_0;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_store_address", + }, + "flags": Set {}, + "name": "$C$_store", + "signature": "builder $C$_store(builder build_0, (cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "return $C$_store(begin_cell(), v).end_cell();", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_store", + }, + "flags": Set { + "inline", + }, + "name": "$C$_store_cell", + "signature": "cell $C$_store_cell((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_a", + "signature": "_ $A$_get_a((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_b", + "signature": "_ $A$_get_b((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_c", + "signature": "_ $A$_get_c((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_d", + "signature": "_ $A$_get_d((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_e", + "signature": "_ $A$_get_e((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_f", + "signature": "_ $A$_get_f((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_get_g", + "signature": "_ $A$_get_g((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set {}, + "name": "$A$_tensor_cast", + "signature": "((int, int, int, int, int, int, int)) $A$_tensor_cast((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (int vvv'a, int vvv'b, int vvv'c, int vvv'd, int vvv'e, int vvv'f, int vvv'g) = __tact_tuple_destroy_7(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_not_null", + "signature": "((int, int, int, int, int, int, int)) $A$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_as_optional", + "signature": "tuple $A$_as_optional((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_tuple", + "signature": "tuple $A$_to_tuple(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $A$_to_tuple($A$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_to_tuple", + "$A$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_opt_tuple", + "signature": "tuple $A$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (int v'a, int v'b, int v'c, int v'd, int v'e, int v'f, int v'g) = __tact_tuple_destroy_7(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$A$_from_tuple", + "signature": "(int, int, int, int, int, int, int) $A$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $A$_as_optional($A$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_as_optional", + "$A$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$A$_from_opt_tuple", + "signature": "tuple $A$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$A$_to_external", + "signature": "(int, int, int, int, int, int, int) $A$_to_external(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "var loaded = $A$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:A", + "depends": Set { + "$A$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$A$_to_opt_external", + "signature": "tuple $A$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_a", + "signature": "_ $B$_get_a((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_b", + "signature": "_ $B$_get_b((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_c", + "signature": "_ $B$_get_c((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_d", + "signature": "_ $B$_get_d((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_e", + "signature": "_ $B$_get_e((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_f", + "signature": "_ $B$_get_f((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_get_g", + "signature": "_ $B$_get_g((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set {}, + "name": "$B$_tensor_cast", + "signature": "((int, int, int, int, int, int, int)) $B$_tensor_cast((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (int vvv'a, int vvv'b, int vvv'c, int vvv'd, int vvv'e, int vvv'f, int vvv'g) = __tact_tuple_destroy_7(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_not_null", + "signature": "((int, int, int, int, int, int, int)) $B$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_as_optional", + "signature": "tuple $B$_as_optional((int, int, int, int, int, int, int) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return __tact_tuple_create_7(v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_create_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_tuple", + "signature": "tuple $B$_to_tuple(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $B$_to_tuple($B$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_to_tuple", + "$B$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_opt_tuple", + "signature": "tuple $B$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (int v'a, int v'b, int v'c, int v'd, int v'e, int v'f, int v'g) = __tact_tuple_destroy_7(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "__tact_tuple_destroy_7", + }, + "flags": Set { + "inline", + }, + "name": "$B$_from_tuple", + "signature": "(int, int, int, int, int, int, int) $B$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $B$_as_optional($B$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_as_optional", + "$B$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$B$_from_opt_tuple", + "signature": "tuple $B$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g);", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$B$_to_external", + "signature": "(int, int, int, int, int, int, int) $B$_to_external(((int, int, int, int, int, int, int)) v)", + }, + { + "code": { + "code": "var loaded = $B$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:B", + "depends": Set { + "$B$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$B$_to_opt_external", + "signature": "tuple $B$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'a;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_a", + "signature": "_ $C$_get_a((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'b;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_b", + "signature": "_ $C$_get_b((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'c;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_c", + "signature": "_ $C$_get_c((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'd;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_d", + "signature": "_ $C$_get_d((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'e;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_e", + "signature": "_ $C$_get_e((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'f;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_f", + "signature": "_ $C$_get_f((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'g;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_g", + "signature": "_ $C$_get_g((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return v'h;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_get_h", + "signature": "_ $C$_get_h((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "NOP", + "kind": "asm", + "shuffle": "", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set {}, + "name": "$C$_tensor_cast", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_tensor_cast((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "throw_if(128, null?(v)); +var (cell vvv'a, cell vvv'b, slice vvv'c, slice vvv'd, int vvv'e, int vvv'f, int vvv'g, slice vvv'h) = __tact_tuple_destroy_8(v); +return (vvv'a, vvv'b, vvv'c, vvv'd, vvv'e, vvv'f, vvv'g, vvv'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_destroy_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_not_null", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_not_null(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return __tact_tuple_create_8(v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_create_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_as_optional", + "signature": "tuple $C$_as_optional((cell, cell, slice, slice, int, int, int, slice) v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return __tact_tuple_create_8(v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_tuple_create_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_tuple", + "signature": "tuple $C$_to_tuple(((cell, cell, slice, slice, int, int, int, slice)) v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $C$_to_tuple($C$_not_null(v)); ", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_to_tuple", + "$C$_not_null", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_opt_tuple", + "signature": "tuple $C$_to_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (cell v'a, cell v'b, slice v'c, slice v'd, int v'e, int v'f, int v'g, slice v'h) = __tact_tuple_destroy_8(v); +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g, __tact_verify_address(v'h));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_verify_address", + "__tact_tuple_destroy_8", + }, + "flags": Set { + "inline", + }, + "name": "$C$_from_tuple", + "signature": "(cell, cell, slice, slice, int, int, int, slice) $C$_from_tuple(tuple v)", + }, + { + "code": { + "code": "if (null?(v)) { return null(); } +return $C$_as_optional($C$_from_tuple(v));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_as_optional", + "$C$_from_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$C$_from_opt_tuple", + "signature": "tuple $C$_from_opt_tuple(tuple v)", + }, + { + "code": { + "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; +return (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h);", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "$C$_to_external", + "signature": "(cell, cell, slice, slice, int, int, int, slice) $C$_to_external(((cell, cell, slice, slice, int, int, int, slice)) v)", + }, + { + "code": { + "code": "var loaded = $C$_to_opt_tuple(v); +if (null?(loaded)) { + return null(); +} else { + return (loaded); +}", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_to_opt_tuple", + }, + "flags": Set { + "inline", + }, + "name": "$C$_to_opt_external", + "signature": "tuple $C$_to_opt_external(tuple v)", + }, + { + "code": { + "code": "var v'a = sc_0~load_ref(); +var v'b = sc_0~load_int(1) ? sc_0~load_ref() : null(); +slice sc_1 = sc_0~load_ref().begin_parse(); +var v'c = sc_1~load_int(1) ? sc_1~load_ref().begin_parse() : null(); +var v'd = sc_1~load_int(1) ? sc_1~load_ref().begin_parse() : null(); +var v'e = sc_1~load_int(1); +var v'f = sc_1~load_int(257); +var v'g = sc_1~load_int(257); +var v'h = sc_1~__tact_load_address(); +return (sc_0, (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h));", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "__tact_load_address", + }, + "flags": Set {}, + "name": "$C$_load", + "signature": "(slice, ((cell, cell, slice, slice, int, int, int, slice))) $C$_load(slice sc_0)", + }, + { + "code": { + "code": "var r = sc_0~$C$_load(); +sc_0.end_parse(); +return r;", + "kind": "generic", + }, + "comment": null, + "context": "type:C", + "depends": Set { + "$C$_load", + }, + "flags": Set {}, + "name": "$C$_load_not_mut", + "signature": "((cell, cell, slice, slice, int, int, int, slice)) $C$_load_not_mut(slice sc_0)", + }, +] +`; diff --git a/src/generatorNew/writers/cast.ts b/src/generatorNew/writers/cast.ts new file mode 100644 index 000000000..4c098e455 --- /dev/null +++ b/src/generatorNew/writers/cast.ts @@ -0,0 +1,24 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; +import { ops } from "./ops"; + +export function cast( + from: TypeRef, + to: TypeRef, + expression: string, + ctx: WriterContext, +) { + if (from.kind === "ref" && to.kind === "ref") { + if (from.name !== to.name) { + throw Error("Impossible"); + } + if (!from.optional && to.optional) { + const type = getType(ctx.ctx, from.name); + if (type.kind === "struct") { + return `${ops.typeAsOptional(type.name, ctx)}(${expression})`; + } + } + } + return expression; +} diff --git a/src/generatorNew/writers/freshIdentifier.ts b/src/generatorNew/writers/freshIdentifier.ts new file mode 100644 index 000000000..dcbdafdd7 --- /dev/null +++ b/src/generatorNew/writers/freshIdentifier.ts @@ -0,0 +1,9 @@ +import { funcIdOf } from "./id"; + +let counter = 0; + +export function freshIdentifier(prefix: string): string { + const fresh = `fresh$${prefix}_${counter}`; + counter += 1; + return funcIdOf(fresh); +} diff --git a/src/generatorNew/writers/id.ts b/src/generatorNew/writers/id.ts new file mode 100644 index 000000000..50c55372d --- /dev/null +++ b/src/generatorNew/writers/id.ts @@ -0,0 +1,15 @@ +import { AstId, idText } from "../../grammar/ast"; + +export function funcIdOf(ident: AstId | string): string { + if (typeof ident === "string") { + return "$" + ident; + } + return "$" + idText(ident); +} + +export function funcInitIdOf(ident: AstId | string): string { + if (typeof ident === "string") { + return ident + "$init"; + } + return idText(ident) + "$init"; +} diff --git a/src/generatorNew/writers/ops.ts b/src/generatorNew/writers/ops.ts new file mode 100644 index 000000000..6f4f28865 --- /dev/null +++ b/src/generatorNew/writers/ops.ts @@ -0,0 +1,82 @@ +import { WriterContext } from "../Writer"; + +function used(name: string, ctx: WriterContext) { + const c = ctx.currentContext(); + if (c) { + ctx.used(name); + } + return name; +} + +export const ops = { + // Type operations + writer: (type: string, ctx: WriterContext) => used(`$${type}$_store`, ctx), + writerCell: (type: string, ctx: WriterContext) => + used(`$${type}$_store_cell`, ctx), + writerCellOpt: (type: string, ctx: WriterContext) => + used(`$${type}$_store_opt`, ctx), + reader: (type: string, ctx: WriterContext) => used(`$${type}$_load`, ctx), + readerNonModifying: (type: string, ctx: WriterContext) => + used(`$${type}$_load_not_mut`, ctx), + readerBounced: (type: string, ctx: WriterContext) => + used(`$${type}$_load_bounced`, ctx), + readerOpt: (type: string, ctx: WriterContext) => + used(`$${type}$_load_opt`, ctx), + typeField: (type: string, name: string, ctx: WriterContext) => + used(`$${type}$_get_${name}`, ctx), + typeTensorCast: (type: string, ctx: WriterContext) => + used(`$${type}$_tensor_cast`, ctx), + typeNotNull: (type: string, ctx: WriterContext) => + used(`$${type}$_not_null`, ctx), + typeAsOptional: (type: string, ctx: WriterContext) => + used(`$${type}$_as_optional`, ctx), + typeToTuple: (type: string, ctx: WriterContext) => + used(`$${type}$_to_tuple`, ctx), + typeToOptTuple: (type: string, ctx: WriterContext) => + used(`$${type}$_to_opt_tuple`, ctx), + typeFromTuple: (type: string, ctx: WriterContext) => + used(`$${type}$_from_tuple`, ctx), + typeFromOptTuple: (type: string, ctx: WriterContext) => + used(`$${type}$_from_opt_tuple`, ctx), + typeToExternal: (type: string, ctx: WriterContext) => + used(`$${type}$_to_external`, ctx), + typeToOptExternal: (type: string, ctx: WriterContext) => + used(`$${type}$_to_opt_external`, ctx), + typeConstructor: (type: string, fields: string[], ctx: WriterContext) => + used(`$${type}$_constructor_${fields.join("_")}`, ctx), + + // Contract operations + contractInit: (type: string, ctx: WriterContext) => + used(`$${type}$_contract_init`, ctx), + contractInitChild: (type: string, ctx: WriterContext) => + used(`$${type}$_init_child`, ctx), + contractLoad: (type: string, ctx: WriterContext) => + used(`$${type}$_contract_load`, ctx), + contractStore: (type: string, ctx: WriterContext) => + used(`$${type}$_contract_store`, ctx), + contractRouter: (type: string, kind: "internal" | "external") => + `$${type}$_contract_router_${kind}`, // Not rendered as dependency + + // Router operations + receiveEmpty: (type: string, kind: "internal" | "external") => + `%$${type}$_${kind}_empty`, + receiveType: (type: string, kind: "internal" | "external", msg: string) => + `$${type}$_${kind}_binary_${msg}`, + receiveAnyText: (type: string, kind: "internal" | "external") => + `$${type}$_${kind}_any_text`, + receiveText: (type: string, kind: "internal" | "external", hash: string) => + `$${type}$_${kind}_text_${hash}`, + receiveAny: (type: string, kind: "internal" | "external") => + `$${type}$_${kind}_any`, + receiveTypeBounce: (type: string, msg: string) => + `$${type}$_receive_binary_bounce_${msg}`, + receiveBounceAny: (type: string) => `$${type}$_receive_bounce`, + + // Functions + extension: (type: string, name: string) => `$${type}$_fun_${name}`, + global: (name: string) => `$global_${name}`, + nonModifying: (name: string) => `${name}$not_mut`, + + // Constants + str: (id: string, ctx: WriterContext) => used(`__gen_str_${id}`, ctx), +}; diff --git a/src/generatorNew/writers/resolveFuncFlatPack.ts b/src/generatorNew/writers/resolveFuncFlatPack.ts new file mode 100644 index 000000000..f0480bc5a --- /dev/null +++ b/src/generatorNew/writers/resolveFuncFlatPack.ts @@ -0,0 +1,58 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; + +export function resolveFuncFlatPack( + descriptor: TypeRef | TypeDescription | string, + name: string, + ctx: WriterContext, + optional: boolean = false, +): string[] { + // String + if (typeof descriptor === "string") { + return resolveFuncFlatPack(getType(ctx.ctx, descriptor), name, ctx); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncFlatPack( + getType(ctx.ctx, descriptor.name), + name, + ctx, + descriptor.optional, + ); + } + if (descriptor.kind === "map") { + return [name]; + } + if (descriptor.kind === "ref_bounced") { + throw Error("Unimplemented"); + } + if (descriptor.kind === "void") { + throw Error("Void type is not allowed in function arguments: " + name); + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + return [name]; + } else if (descriptor.kind === "struct") { + if (optional || descriptor.fields.length === 0) { + return [name]; + } else { + return descriptor.fields.flatMap((v) => + resolveFuncFlatPack(v.type, name + `'` + v.name, ctx), + ); + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return [name]; + } else { + return descriptor.fields.flatMap((v) => + resolveFuncFlatPack(v.type, name + `'` + v.name, ctx), + ); + } + } + + // Unreachable + throw Error("Unknown type: " + descriptor.kind); +} diff --git a/src/generatorNew/writers/resolveFuncFlatTypes.ts b/src/generatorNew/writers/resolveFuncFlatTypes.ts new file mode 100644 index 000000000..2f829a2ba --- /dev/null +++ b/src/generatorNew/writers/resolveFuncFlatTypes.ts @@ -0,0 +1,57 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; +import { resolveFuncType } from "./resolveFuncType"; + +export function resolveFuncFlatTypes( + descriptor: TypeRef | TypeDescription | string, + ctx: WriterContext, + optional: boolean = false, +): string[] { + // String + if (typeof descriptor === "string") { + return resolveFuncFlatTypes(getType(ctx.ctx, descriptor), ctx); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncFlatTypes( + getType(ctx.ctx, descriptor.name), + ctx, + descriptor.optional, + ); + } + if (descriptor.kind === "map") { + return ["cell"]; + } + if (descriptor.kind === "ref_bounced") { + throw Error("Unimplemented"); + } + if (descriptor.kind === "void") { + throw Error("Void type is not allowed in function arguments"); + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + return [resolveFuncType(descriptor, ctx)]; + } else if (descriptor.kind === "struct") { + if (optional || descriptor.fields.length === 0) { + return ["tuple"]; + } else { + return descriptor.fields.flatMap((v) => + resolveFuncFlatTypes(v.type, ctx), + ); + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return ["tuple"]; + } else { + return descriptor.fields.flatMap((v) => + resolveFuncFlatTypes(v.type, ctx), + ); + } + } + + // Unreachable + throw Error("Unknown type: " + descriptor.kind); +} diff --git a/src/generatorNew/writers/resolveFuncPrimitive.ts b/src/generatorNew/writers/resolveFuncPrimitive.ts new file mode 100644 index 000000000..0d815f358 --- /dev/null +++ b/src/generatorNew/writers/resolveFuncPrimitive.ts @@ -0,0 +1,57 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; + +export function resolveFuncPrimitive( + descriptor: TypeRef | TypeDescription | string, + ctx: WriterContext, +): boolean { + // String + if (typeof descriptor === "string") { + return resolveFuncPrimitive(getType(ctx.ctx, descriptor), ctx); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncPrimitive(getType(ctx.ctx, descriptor.name), ctx); + } + if (descriptor.kind === "map") { + return true; + } + if (descriptor.kind === "ref_bounced") { + throw Error("Unimplemented"); + } + if (descriptor.kind === "void") { + return true; + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return true; + } else if (descriptor.name === "Bool") { + return true; + } else if (descriptor.name === "Slice") { + return true; + } else if (descriptor.name === "Cell") { + return true; + } else if (descriptor.name === "Builder") { + return true; + } else if (descriptor.name === "Address") { + return true; + } else if (descriptor.name === "String") { + return true; + } else if (descriptor.name === "StringBuilder") { + return true; + } else { + throw Error("Unknown primitive type: " + descriptor.name); + } + } else if (descriptor.kind === "struct") { + return false; + } else if (descriptor.kind === "contract") { + return false; + } + + // Unreachable + throw Error("Unknown type: " + descriptor.kind); +} diff --git a/src/generatorNew/writers/resolveFuncTupleType.ts b/src/generatorNew/writers/resolveFuncTupleType.ts new file mode 100644 index 000000000..70483cebc --- /dev/null +++ b/src/generatorNew/writers/resolveFuncTupleType.ts @@ -0,0 +1,55 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; + +export function resolveFuncTupleType( + descriptor: TypeRef | TypeDescription | string, + ctx: WriterContext, +): string { + // String + if (typeof descriptor === "string") { + return resolveFuncTupleType(getType(ctx.ctx, descriptor), ctx); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncTupleType(getType(ctx.ctx, descriptor.name), ctx); + } + if (descriptor.kind === "map") { + return "cell"; + } + if (descriptor.kind === "ref_bounced") { + throw Error("Unimplemented"); + } + if (descriptor.kind === "void") { + return "()"; + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return "int"; + } else if (descriptor.name === "Bool") { + return "int"; + } else if (descriptor.name === "Slice") { + return "slice"; + } else if (descriptor.name === "Cell") { + return "cell"; + } else if (descriptor.name === "Builder") { + return "builder"; + } else if (descriptor.name === "Address") { + return "slice"; + } else if (descriptor.name === "String") { + return "slice"; + } else if (descriptor.name === "StringBuilder") { + return "tuple"; + } else { + throw Error("Unknown primitive type: " + descriptor.name); + } + } else if (descriptor.kind === "struct" || descriptor.kind === "contract") { + return "tuple"; + } + + // Unreachable + throw Error("Unknown type: " + descriptor.kind); +} diff --git a/src/generatorNew/writers/resolveFuncType.spec.ts b/src/generatorNew/writers/resolveFuncType.spec.ts new file mode 100644 index 000000000..362f00667 --- /dev/null +++ b/src/generatorNew/writers/resolveFuncType.spec.ts @@ -0,0 +1,176 @@ +import { __DANGER_resetNodeId } from "../../grammar/ast"; +import { resolveDescriptors } from "../../types/resolveDescriptors"; +import { WriterContext } from "../Writer"; +import { resolveFuncType } from "./resolveFuncType"; +import { openContext } from "../../grammar/store"; +import { CompilerContext } from "../../context"; + +const primitiveCode = ` +primitive Int; +primitive Bool; +primitive Builder; +primitive Cell; +primitive Slice; + +trait BaseTrait { + +} + +struct Struct1 { + a1: Int; + a2: Int; +} + +struct Struct2 { + b1: Int; +} + +contract Contract1 { + c: Int; + c2: Int; + + init() { + + } +} + +contract Contract2 { + d: Int; + e: Struct1; + + init() { + + } +} +`; + +describe("resolveFuncType", () => { + beforeEach(() => { + __DANGER_resetNodeId(); + }); + + it("should process primitive types", () => { + let ctx = openContext( + new CompilerContext(), + [{ code: primitiveCode, path: "", origin: "user" }], + [], + ); + ctx = resolveDescriptors(ctx); + const wCtx = new WriterContext(ctx, "Contract1"); + expect( + resolveFuncType( + { kind: "ref", name: "Int", optional: false }, + wCtx, + ), + ).toBe("int"); + expect( + resolveFuncType( + { kind: "ref", name: "Bool", optional: false }, + wCtx, + ), + ).toBe("int"); + expect( + resolveFuncType( + { kind: "ref", name: "Cell", optional: false }, + wCtx, + ), + ).toBe("cell"); + expect( + resolveFuncType( + { kind: "ref", name: "Slice", optional: false }, + wCtx, + ), + ).toBe("slice"); + expect( + resolveFuncType( + { kind: "ref", name: "Builder", optional: false }, + wCtx, + ), + ).toBe("builder"); + expect( + resolveFuncType({ kind: "ref", name: "Int", optional: true }, wCtx), + ).toBe("int"); + expect( + resolveFuncType( + { kind: "ref", name: "Bool", optional: true }, + wCtx, + ), + ).toBe("int"); + expect( + resolveFuncType( + { kind: "ref", name: "Cell", optional: true }, + wCtx, + ), + ).toBe("cell"); + expect( + resolveFuncType( + { kind: "ref", name: "Slice", optional: true }, + wCtx, + ), + ).toBe("slice"); + expect( + resolveFuncType( + { kind: "ref", name: "Builder", optional: true }, + wCtx, + ), + ).toBe("builder"); + }); + + it("should process contract and struct types", () => { + let ctx = openContext( + new CompilerContext(), + [{ code: primitiveCode, path: "", origin: "user" }], + [], + ); + ctx = resolveDescriptors(ctx); + const wCtx = new WriterContext(ctx, "Contract1"); + expect( + resolveFuncType( + { kind: "ref", name: "Struct1", optional: false }, + wCtx, + ), + ).toBe("(int, int)"); + expect( + resolveFuncType( + { kind: "ref", name: "Struct2", optional: false }, + wCtx, + ), + ).toBe("(int)"); + expect( + resolveFuncType( + { kind: "ref", name: "Contract1", optional: false }, + wCtx, + ), + ).toBe("(int, int)"); + expect( + resolveFuncType( + { kind: "ref", name: "Contract2", optional: false }, + wCtx, + ), + ).toBe("(int, (int, int))"); + expect( + resolveFuncType( + { kind: "ref", name: "Struct1", optional: true }, + wCtx, + ), + ).toBe("tuple"); + expect( + resolveFuncType( + { kind: "ref", name: "Struct2", optional: true }, + wCtx, + ), + ).toBe("tuple"); + expect( + resolveFuncType( + { kind: "ref", name: "Contract1", optional: true }, + wCtx, + ), + ).toBe("tuple"); + expect( + resolveFuncType( + { kind: "ref", name: "Contract2", optional: true }, + wCtx, + ), + ).toBe("tuple"); + }); +}); diff --git a/src/generatorNew/writers/resolveFuncType.ts b/src/generatorNew/writers/resolveFuncType.ts new file mode 100644 index 000000000..a5b920636 --- /dev/null +++ b/src/generatorNew/writers/resolveFuncType.ts @@ -0,0 +1,101 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; + +export function resolveFuncType( + descriptor: TypeRef | TypeDescription | string, + ctx: WriterContext, + optional: boolean = false, + usePartialFields: boolean = false, +): string { + // String + if (typeof descriptor === "string") { + return resolveFuncType( + getType(ctx.ctx, descriptor), + ctx, + false, + usePartialFields, + ); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncType( + getType(ctx.ctx, descriptor.name), + ctx, + descriptor.optional, + usePartialFields, + ); + } + if (descriptor.kind === "map") { + return "cell"; + } + if (descriptor.kind === "ref_bounced") { + return resolveFuncType( + getType(ctx.ctx, descriptor.name), + ctx, + false, + true, + ); + } + if (descriptor.kind === "void") { + return "()"; + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return "int"; + } else if (descriptor.name === "Bool") { + return "int"; + } else if (descriptor.name === "Slice") { + return "slice"; + } else if (descriptor.name === "Cell") { + return "cell"; + } else if (descriptor.name === "Builder") { + return "builder"; + } else if (descriptor.name === "Address") { + return "slice"; + } else if (descriptor.name === "String") { + return "slice"; + } else if (descriptor.name === "StringBuilder") { + return "tuple"; + } else { + throw Error("Unknown primitive type: " + descriptor.name); + } + } else if (descriptor.kind === "struct") { + const fieldsToUse = usePartialFields + ? descriptor.fields.slice(0, descriptor.partialFieldCount) + : descriptor.fields; + if (optional || fieldsToUse.length === 0) { + return "tuple"; + } else { + return ( + "(" + + fieldsToUse + .map((v) => + resolveFuncType(v.type, ctx, false, usePartialFields), + ) + .join(", ") + + ")" + ); + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return "tuple"; + } else { + return ( + "(" + + descriptor.fields + .map((v) => + resolveFuncType(v.type, ctx, false, usePartialFields), + ) + .join(", ") + + ")" + ); + } + } + + // Unreachable + throw Error("Unknown type: " + descriptor.kind); +} diff --git a/src/generatorNew/writers/resolveFuncTypeFromAbi.ts b/src/generatorNew/writers/resolveFuncTypeFromAbi.ts new file mode 100644 index 000000000..c45b7b5da --- /dev/null +++ b/src/generatorNew/writers/resolveFuncTypeFromAbi.ts @@ -0,0 +1,55 @@ +import { ABITypeRef } from "@ton/core"; +import { getType } from "../../types/resolveDescriptors"; +import { WriterContext } from "../Writer"; + +export function resolveFuncTypeFromAbi( + fields: ABITypeRef[], + ctx: WriterContext, +): string { + if (fields.length === 0) { + return "tuple"; + } + const res: string[] = []; + for (const f of fields) { + switch (f.kind) { + case "dict": + { + res.push("cell"); + } + break; + case "simple": { + if ( + f.type === "int" || + f.type === "uint" || + f.type === "bool" + ) { + res.push("int"); + } else if (f.type === "cell") { + res.push("cell"); + } else if (f.type === "slice") { + res.push("slice"); + } else if (f.type === "builder") { + res.push("builder"); + } else if (f.type === "address") { + res.push("slice"); + } else if (f.type === "fixed-bytes") { + res.push("slice"); + } else if (f.type === "string") { + res.push("slice"); + } else { + const t = getType(ctx.ctx, f.type); + if (t.kind !== "struct") { + throw Error("Unsupported type: " + t.kind); + } + if (f.optional ?? t.fields.length === 0) { + res.push("tuple"); + } else { + const loaded = t.fields.map((v) => v.abi.type); + res.push(resolveFuncTypeFromAbi(loaded, ctx)); + } + } + } + } + } + return `(${res.join(", ")})`; +} diff --git a/src/generatorNew/writers/resolveFuncTypeFromAbiUnpack.ts b/src/generatorNew/writers/resolveFuncTypeFromAbiUnpack.ts new file mode 100644 index 000000000..4652570d7 --- /dev/null +++ b/src/generatorNew/writers/resolveFuncTypeFromAbiUnpack.ts @@ -0,0 +1,61 @@ +import { ABITypeRef } from "@ton/core"; +import { getType } from "../../types/resolveDescriptors"; +import { WriterContext } from "../Writer"; + +export function resolveFuncTypeFromAbiUnpack( + name: string, + fields: { name: string; type: ABITypeRef }[], + ctx: WriterContext, +): string { + if (fields.length === 0) { + return name; + } + const res: string[] = []; + for (const f of fields) { + switch (f.type.kind) { + case "dict": + { + res.push(`${name}'${f.name}`); + } + break; + case "simple": + { + if ( + f.type.type === "int" || + f.type.type === "uint" || + f.type.type === "bool" + ) { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "cell") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "slice") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "builder") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "address") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "fixed-bytes") { + res.push(`${name}'${f.name}`); + } else if (f.type.type === "string") { + res.push(`${name}'${f.name}`); + } else { + const t = getType(ctx.ctx, f.type.type); + if (f.type.optional ?? t.fields.length === 0) { + res.push(`${name}'${f.name}`); + } else { + const loaded = t.fields.map((v) => v.abi); + res.push( + resolveFuncTypeFromAbiUnpack( + `${name}'${f.name}`, + loaded, + ctx, + ), + ); + } + } + } + break; + } + } + return `(${res.join(", ")})`; +} diff --git a/src/generatorNew/writers/resolveFuncTypeNew.ts b/src/generatorNew/writers/resolveFuncTypeNew.ts new file mode 100644 index 000000000..b79f06f7e --- /dev/null +++ b/src/generatorNew/writers/resolveFuncTypeNew.ts @@ -0,0 +1,93 @@ +import { WriterContext } from "../Writer"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { getType } from "../../types/resolveDescriptors"; +import { FuncAstType } from "../../func/grammar"; +import { Type, unit } from "../../func/syntaxConstructors"; + +/** + * Generates Func type from the Tact type. + */ +export function resolveFuncType( + descriptor: TypeRef | TypeDescription | string, + optional: boolean = false, + usePartialFields: boolean = false, + ctx: WriterContext, +): FuncAstType { + // String + if (typeof descriptor === "string") { + return resolveFuncType( + getType(ctx.ctx, descriptor), + false, + usePartialFields, + ctx, + ); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncType( + getType(ctx.ctx, descriptor.name), + descriptor.optional, + usePartialFields, + ctx, + ); + } + if (descriptor.kind === "map") { + return Type.cell(); + } + if (descriptor.kind === "ref_bounced") { + return resolveFuncType(getType(ctx.ctx, descriptor.name), false, true, ctx); + } + if (descriptor.kind === "void") { + return unit(); + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return Type.int(); + } else if (descriptor.name === "Bool") { + return Type.int(); + } else if (descriptor.name === "Slice") { + return Type.slice(); + } else if (descriptor.name === "Cell") { + return Type.cell(); + } else if (descriptor.name === "Builder") { + return Type.builder(); + } else if (descriptor.name === "Address") { + return Type.slice(); + } else if (descriptor.name === "String") { + return Type.slice(); + } else if (descriptor.name === "StringBuilder") { + return Type.tuple(); + } else { + throw Error(`Unknown primitive type: ${descriptor.name}`); + } + } else if (descriptor.kind === "struct") { + const fieldsToUse = usePartialFields + ? descriptor.fields.slice(0, descriptor.partialFieldCount) + : descriptor.fields; + if (optional || fieldsToUse.length === 0) { + return Type.tuple(); + } else { + return Type.tensor( + ...fieldsToUse.map((v) => + resolveFuncType(v.type, false, usePartialFields, ctx), + ), + ); + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return Type.tuple(); + } else { + return Type.tensor( + ...descriptor.fields.map((v) => + resolveFuncType(v.type, false, usePartialFields, ctx), + ), + ); + } + } + + // Unreachable + throw Error(`Unknown type: ${descriptor.kind}`); +} diff --git a/src/generatorNew/writers/resolveFuncTypeUnpack.ts b/src/generatorNew/writers/resolveFuncTypeUnpack.ts new file mode 100644 index 000000000..dd088d8b0 --- /dev/null +++ b/src/generatorNew/writers/resolveFuncTypeUnpack.ts @@ -0,0 +1,99 @@ +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription, TypeRef } from "../../types/types"; +import { WriterContext } from "../Writer"; + +export function resolveFuncTypeUnpack( + descriptor: TypeRef | TypeDescription | string, + name: string, + ctx: WriterContext, + optional: boolean = false, + usePartialFields: boolean = false, +): string { + // String + if (typeof descriptor === "string") { + return resolveFuncTypeUnpack( + getType(ctx.ctx, descriptor), + name, + ctx, + false, + usePartialFields, + ); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncTypeUnpack( + getType(ctx.ctx, descriptor.name), + name, + ctx, + descriptor.optional, + usePartialFields, + ); + } + if (descriptor.kind === "map") { + return name; + } + if (descriptor.kind === "ref_bounced") { + return resolveFuncTypeUnpack( + getType(ctx.ctx, descriptor.name), + name, + ctx, + false, + true, + ); + } + if (descriptor.kind === "void") { + throw Error("Void type is not allowed in function arguments: " + name); + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + return name; + } else if (descriptor.kind === "struct") { + const fieldsToUse = usePartialFields + ? descriptor.fields.slice(0, descriptor.partialFieldCount) + : descriptor.fields; + if (optional || fieldsToUse.length === 0) { + return name; + } else { + return ( + "(" + + fieldsToUse + .map((v) => + resolveFuncTypeUnpack( + v.type, + name + `'` + v.name, + ctx, + false, + usePartialFields, + ), + ) + .join(", ") + + ")" + ); + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return name; + } else { + return ( + "(" + + descriptor.fields + .map((v) => + resolveFuncTypeUnpack( + v.type, + name + `'` + v.name, + ctx, + false, + usePartialFields, + ), + ) + .join(", ") + + ")" + ); + } + } + + // Unreachable + throw Error("Unknown type: " + descriptor.kind); +} diff --git a/src/generatorNew/writers/writeAccessors.ts b/src/generatorNew/writers/writeAccessors.ts new file mode 100644 index 000000000..df210313c --- /dev/null +++ b/src/generatorNew/writers/writeAccessors.ts @@ -0,0 +1,318 @@ +import { contractErrors } from "../../abi/errors"; +import { maxTupleSize } from "../../bindings/typescript/writeStruct"; +import { ItemOrigin } from "../../grammar/grammar"; +import { getType } from "../../types/resolveDescriptors"; +import { TypeDescription } from "../../types/types"; +import { WriterContext } from "../Writer"; +import { ops } from "./ops"; +import { resolveFuncFlatPack } from "./resolveFuncFlatPack"; +import { resolveFuncFlatTypes } from "./resolveFuncFlatTypes"; +import { resolveFuncType } from "./resolveFuncType"; +import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; + +function chainVars(vars: string[]): string[] { + // let's say we have vars = ['v1', 'v2, ..., 'v32'] + // we need to split it into chunks of size maxTupleSize - 1 + const chunks: string[][] = []; + while (vars.length > 0) { + chunks.push(vars.splice(0, maxTupleSize - 1)); + } + // and now chain them into a string like this: [v1, v2, ..., v14, [v15, v16, ..., v28, [v29, v30, ..., v32]] + while (chunks.length > 1) { + const a = chunks.pop()!; + chunks[chunks.length - 1]!.push(`[${a.join(", ")}]`); + } + return chunks[0]!; +} + +export function writeAccessors( + type: TypeDescription, + origin: ItemOrigin, + ctx: WriterContext, +) { + // Getters + for (const f of type.fields) { + ctx.fun(ops.typeField(type.name, f.name, ctx), () => { + ctx.signature( + `_ ${ops.typeField(type.name, f.name, ctx)}(${resolveFuncType(type, ctx)} v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append( + `var (${type.fields.map((v) => `v'${v.name}`).join(", ")}) = v;`, + ); + ctx.append(`return v'${f.name};`); + }); + }); + } + + // Tensor cast + ctx.fun(ops.typeTensorCast(type.name, ctx), () => { + ctx.signature( + `(${resolveFuncType(type, ctx)}) ${ops.typeTensorCast(type.name, ctx)}(${resolveFuncType(type, ctx)} v)`, + ); + ctx.context("type:" + type.name); + ctx.asm("", "NOP"); + }); + + // Not null + ctx.fun(ops.typeNotNull(type.name, ctx), () => { + ctx.signature( + `(${resolveFuncType(type, ctx)}) ${ops.typeNotNull(type.name, ctx)}(tuple v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append(`throw_if(${contractErrors.null.id}, null?(v));`); + const flatPack = resolveFuncFlatPack(type, "vvv", ctx); + const flatTypes = resolveFuncFlatTypes(type, ctx); + if (flatPack.length !== flatTypes.length) + throw Error("Flat pack and flat types length mismatch"); + const pairs = flatPack.map((v, i) => `${flatTypes[i]} ${v}`); + if (flatPack.length <= maxTupleSize) { + ctx.used(`__tact_tuple_destroy_${flatPack.length}`); + ctx.append( + `var (${pairs.join(", ")}) = __tact_tuple_destroy_${flatPack.length}(v);`, + ); + } else { + flatPack.splice(0, maxTupleSize - 1); + const pairsBatch = pairs.splice(0, maxTupleSize - 1); + ctx.used(`__tact_tuple_destroy_${maxTupleSize}`); + ctx.append( + `var (${pairsBatch.join(", ")}, next) = __tact_tuple_destroy_${maxTupleSize}(v);`, + ); + while (flatPack.length >= maxTupleSize) { + flatPack.splice(0, maxTupleSize - 1); + const pairsBatch = pairs.splice(0, maxTupleSize - 1); + ctx.append( + `var (${pairsBatch.join(", ")}, next) = __tact_tuple_destroy_${maxTupleSize}(next);`, + ); + } + ctx.used(`__tact_tuple_destroy_${flatPack.length}`); + ctx.append( + `var (${pairs.join(", ")}) = __tact_tuple_destroy_${flatPack.length}(next);`, + ); + } + ctx.append(`return ${resolveFuncTypeUnpack(type, "vvv", ctx)};`); + }); + }); + + // As optional + ctx.fun(ops.typeAsOptional(type.name, ctx), () => { + ctx.signature( + `tuple ${ops.typeAsOptional(type.name, ctx)}(${resolveFuncType(type, ctx)} v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append(`var ${resolveFuncTypeUnpack(type, "v", ctx)} = v;`); + const flatPack = resolveFuncFlatPack(type, "v", ctx); + if (flatPack.length <= maxTupleSize) { + ctx.used(`__tact_tuple_create_${flatPack.length}`); + ctx.append( + `return __tact_tuple_create_${flatPack.length}(${flatPack.join(", ")});`, + ); + } else { + const longTupleFlatPack = chainVars(flatPack); + ctx.used(`__tact_tuple_create_${longTupleFlatPack.length}`); + ctx.append( + `return __tact_tuple_create_${longTupleFlatPack.length}(${longTupleFlatPack.join(", ")});`, + ); + } + }); + }); + + // + // Convert to and from tuple representation + // + + ctx.fun(ops.typeToTuple(type.name, ctx), () => { + ctx.signature( + `tuple ${ops.typeToTuple(type.name, ctx)}((${resolveFuncType(type, ctx)}) v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append( + `var (${type.fields.map((v) => `v'${v.name}`).join(", ")}) = v;`, + ); + const vars: string[] = []; + for (const f of type.fields) { + if (f.type.kind === "ref") { + const t = getType(ctx.ctx, f.type.name); + if (t.kind === "struct") { + if (f.type.optional) { + vars.push( + `${ops.typeToOptTuple(f.type.name, ctx)}(v'${f.name})`, + ); + } else { + vars.push( + `${ops.typeToTuple(f.type.name, ctx)}(v'${f.name})`, + ); + } + continue; + } + } + vars.push(`v'${f.name}`); + } + if (vars.length <= maxTupleSize) { + ctx.used(`__tact_tuple_create_${vars.length}`); + ctx.append( + `return __tact_tuple_create_${vars.length}(${vars.join(", ")});`, + ); + } else { + const longTupleVars = chainVars(vars); + ctx.used(`__tact_tuple_create_${longTupleVars.length}`); + ctx.append( + `return __tact_tuple_create_${longTupleVars.length}(${longTupleVars.join(", ")});`, + ); + } + }); + }); + + ctx.fun(ops.typeToOptTuple(type.name, ctx), () => { + ctx.signature(`tuple ${ops.typeToOptTuple(type.name, ctx)}(tuple v)`); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append(`if (null?(v)) { return null(); } `); + ctx.append( + `return ${ops.typeToTuple(type.name, ctx)}(${ops.typeNotNull(type.name, ctx)}(v)); `, + ); + }); + }); + + ctx.fun(ops.typeFromTuple(type.name, ctx), () => { + ctx.signature( + `(${type.fields.map((v) => resolveFuncType(v.type, ctx)).join(", ")}) ${ops.typeFromTuple(type.name, ctx)}(tuple v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + // Resolve vars + const vars: string[] = []; + const out: string[] = []; + for (const f of type.fields) { + if (f.type.kind === "ref") { + const t = getType(ctx.ctx, f.type.name); + if (t.kind === "struct") { + vars.push(`tuple v'${f.name}`); + if (f.type.optional) { + out.push( + `${ops.typeFromOptTuple(f.type.name, ctx)}(v'${f.name})`, + ); + } else { + out.push( + `${ops.typeFromTuple(f.type.name, ctx)}(v'${f.name})`, + ); + } + continue; + } else if ( + t.kind === "primitive_type_decl" && + t.name === "Address" + ) { + if (f.type.optional) { + vars.push( + `${resolveFuncType(f.type, ctx)} v'${f.name}`, + ); + out.push( + `null?(v'${f.name}) ? null() : ${ctx.used(`__tact_verify_address`)}(v'${f.name})`, + ); + } else { + vars.push( + `${resolveFuncType(f.type, ctx)} v'${f.name}`, + ); + out.push( + `${ctx.used(`__tact_verify_address`)}(v'${f.name})`, + ); + } + continue; + } + } + vars.push(`${resolveFuncType(f.type, ctx)} v'${f.name}`); + out.push(`v'${f.name}`); + } + if (vars.length <= maxTupleSize) { + ctx.used(`__tact_tuple_destroy_${vars.length}`); + ctx.append( + `var (${vars.join(", ")}) = __tact_tuple_destroy_${vars.length}(v);`, + ); + } else { + const batch = vars.splice(0, maxTupleSize - 1); + ctx.used(`__tact_tuple_destroy_${maxTupleSize}`); + ctx.append( + `var (${batch.join(", ")}, next) = __tact_tuple_destroy_${maxTupleSize}(v);`, + ); + while (vars.length >= maxTupleSize) { + const batch = vars.splice(0, maxTupleSize - 1); + ctx.used(`__tact_tuple_destroy_${maxTupleSize}`); + ctx.append( + `var (${batch.join(", ")}, next) = __tact_tuple_destroy_${maxTupleSize}(next);`, + ); + } + ctx.used(`__tact_tuple_destroy_${vars.length}`); + ctx.append( + `var (${batch.join(", ")}) = __tact_tuple_destroy_${vars.length}(next);`, + ); + } + ctx.append(`return (${out.join(", ")});`); + }); + }); + + ctx.fun(ops.typeFromOptTuple(type.name, ctx), () => { + ctx.signature(`tuple ${ops.typeFromOptTuple(type.name, ctx)}(tuple v)`); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append(`if (null?(v)) { return null(); } `); + ctx.append( + `return ${ops.typeAsOptional(type.name, ctx)}(${ops.typeFromTuple(type.name, ctx)}(v));`, + ); + }); + }); + + // + // Convert to and from external representation + // + + ctx.fun(ops.typeToExternal(type.name, ctx), () => { + ctx.signature( + `(${type.fields.map((f) => resolveFuncType(f.type, ctx)).join(", ")}) ${ops.typeToExternal(type.name, ctx)}((${resolveFuncType(type, ctx)}) v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append( + `var (${type.fields.map((v) => `v'${v.name}`).join(", ")}) = v; `, + ); + const vars: string[] = []; + for (const f of type.fields) { + vars.push(`v'${f.name}`); + } + ctx.append(`return (${vars.join(", ")});`); + }); + }); + + ctx.fun(ops.typeToOptExternal(type.name, ctx), () => { + ctx.signature( + `tuple ${ops.typeToOptExternal(type.name, ctx)}(tuple v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + ctx.append( + `var loaded = ${ops.typeToOptTuple(type.name, ctx)}(v);`, + ); + ctx.append(`if (null?(loaded)) {`); + ctx.inIndent(() => { + ctx.append(`return null();`); + }); + ctx.append(`} else {`); + ctx.inIndent(() => { + ctx.append(`return (loaded);`); + }); + ctx.append(`}`); + }); + }); +} diff --git a/src/generatorNew/writers/writeConstant.ts b/src/generatorNew/writers/writeConstant.ts new file mode 100644 index 000000000..7b45bdee6 --- /dev/null +++ b/src/generatorNew/writers/writeConstant.ts @@ -0,0 +1,84 @@ +import { Address, beginCell, Cell, Slice } from "@ton/core"; +import { WriterContext } from "../Writer"; + +export function writeString(str: string, ctx: WriterContext) { + const cell = beginCell().storeStringTail(str).endCell(); + return writeRawSlice("string", `String "${str}"`, cell, ctx); +} + +export function writeComment(str: string, ctx: WriterContext) { + const cell = beginCell().storeUint(0, 32).storeStringTail(str).endCell(); + return writeRawCell("comment", `Comment "${str}"`, cell, ctx); +} + +export function writeAddress(address: Address, ctx: WriterContext) { + return writeRawSlice( + "address", + address.toString(), + beginCell().storeAddress(address).endCell(), + ctx, + ); +} + +export function writeCell(cell: Cell, ctx: WriterContext) { + return writeRawCell( + "cell", + "Cell " + cell.hash().toString("base64"), + cell, + ctx, + ); +} + +export function writeSlice(slice: Slice, ctx: WriterContext) { + const cell = slice.asCell(); + return writeRawSlice( + "slice", + "Slice " + cell.hash().toString("base64"), + cell, + ctx, + ); +} + +function writeRawSlice( + prefix: string, + comment: string, + cell: Cell, + ctx: WriterContext, +) { + const h = cell.hash().toString("hex"); + const t = cell.toBoc({ idx: false }).toString("hex"); + const k = "slice:" + prefix + ":" + h; + if (ctx.isRendered(k)) { + return `__gen_slice_${prefix}_${h}`; + } + ctx.markRendered(k); + ctx.fun(`__gen_slice_${prefix}_${h}`, () => { + ctx.signature(`slice __gen_slice_${prefix}_${h}()`); + ctx.comment(comment); + ctx.context("constants"); + ctx.asm("", `B{${t}} B>boc { + ctx.signature(`cell __gen_cell_${prefix}_${h}()`); + ctx.comment(comment); + ctx.context("constants"); + ctx.asm("", `B{${t}} B>boc PUSHREF`); + }); + return `__gen_cell_${prefix}_${h}`; +} diff --git a/src/generatorNew/writers/writeContract.ts b/src/generatorNew/writers/writeContract.ts new file mode 100644 index 000000000..642682052 --- /dev/null +++ b/src/generatorNew/writers/writeContract.ts @@ -0,0 +1,374 @@ +import { contractErrors } from "../../abi/errors"; +import { + enabledInline, + enabledInterfacesGetter, + enabledIpfsAbiGetter, + enabledMasterchain, +} from "../../config/features"; +import { ItemOrigin } from "../../grammar/grammar"; +import { InitDescription, TypeDescription } from "../../types/types"; +import { WriterContext } from "../Writer"; +import { funcIdOf, funcInitIdOf } from "./id"; +import { ops } from "./ops"; +import { resolveFuncPrimitive } from "./resolveFuncPrimitive"; +import { resolveFuncType } from "./resolveFuncType"; +import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; +import { writeValue } from "./writeExpression"; +import { writeGetter, writeStatement } from "./writeFunction"; +import { writeInterfaces } from "./writeInterfaces"; +import { writeReceiver, writeRouter } from "./writeRouter"; + +export function writeStorageOps( + type: TypeDescription, + origin: ItemOrigin, + ctx: WriterContext, +) { + // Load function + ctx.fun(ops.contractLoad(type.name, ctx), () => { + ctx.signature( + `${resolveFuncType(type, ctx)} ${ops.contractLoad(type.name, ctx)}()`, + ); + ctx.flag("impure"); + // ctx.flag('inline'); + ctx.context("type:" + type.name + "$init"); + ctx.body(() => { + // Load data slice + ctx.append(`slice $sc = get_data().begin_parse();`); + + // Load context + ctx.append(`__tact_context_sys = $sc~load_ref();`); + ctx.append(`int $loaded = $sc~load_int(1);`); + + // Load data + ctx.append(`if ($loaded) {`); + ctx.inIndent(() => { + if (type.fields.length > 0) { + ctx.append(`return $sc~${ops.reader(type.name, ctx)}();`); + } else { + ctx.append(`return null();`); + } + }); + ctx.append(`} else {`); + ctx.inIndent(() => { + // Allow only workchain deployments + if (!enabledMasterchain(ctx.ctx)) { + ctx.write(`;; Allow only workchain deployments`); + ctx.write( + `throw_unless(${contractErrors.masterchainNotEnabled.id}, my_address().preload_uint(11) == 1024);`, + ); + } + + // Load arguments + if (type.init!.params.length > 0) { + ctx.append( + `(${type.init!.params.map((v) => resolveFuncType(v.type, ctx) + " " + funcIdOf(v.name)).join(", ")}) = $sc~${ops.reader(funcInitIdOf(type.name), ctx)}();`, + ); + ctx.append(`$sc.end_parse();`); + } + + // Execute init function + ctx.append( + `return ${ops.contractInit(type.name, ctx)}(${[...type.init!.params.map((v) => funcIdOf(v.name))].join(", ")});`, + ); + }); + + ctx.append(`}`); + }); + }); + + // Store function + ctx.fun(ops.contractStore(type.name, ctx), () => { + const sig = `() ${ops.contractStore(type.name, ctx)}(${resolveFuncType(type, ctx)} v)`; + ctx.signature(sig); + ctx.flag("impure"); + ctx.flag("inline"); + ctx.context("type:" + type.name + "$init"); + ctx.body(() => { + ctx.append(`builder b = begin_cell();`); + + // Persist system cell + ctx.append(`b = b.store_ref(__tact_context_sys);`); + + // Persist deployment flag + ctx.append(`b = b.store_int(true, 1);`); + + // Build data + if (type.fields.length > 0) { + ctx.append(`b = ${ops.writer(type.name, ctx)}(b, v);`); + } + + // Persist data + ctx.append(`set_data(b.end_cell());`); + }); + }); +} + +export function writeInit( + t: TypeDescription, + init: InitDescription, + ctx: WriterContext, +) { + ctx.fun(ops.contractInit(t.name, ctx), () => { + const args = init.params.map( + (v) => resolveFuncType(v.type, ctx) + " " + funcIdOf(v.name), + ); + const sig = `${resolveFuncType(t, ctx)} ${ops.contractInit(t.name, ctx)}(${args.join(", ")})`; + ctx.signature(sig); + ctx.flag("impure"); + ctx.body(() => { + // Unpack parameters + for (const a of init.params) { + if (!resolveFuncPrimitive(a.type, ctx)) { + ctx.append( + `var (${resolveFuncTypeUnpack(a.type, funcIdOf(a.name), ctx)}) = ${funcIdOf(a.name)};`, + ); + } + } + + // Generate self initial tensor + const initValues: string[] = []; + t.fields.forEach((tField) => { + let init = "null()"; + if (tField.default !== undefined) { + init = writeValue(tField.default!, ctx); + } + initValues.push(init); + }); + if (initValues.length > 0) { + // Special case for empty contracts + ctx.append( + `var (${resolveFuncTypeUnpack(t, funcIdOf("self"), ctx)}) = (${initValues.join(", ")});`, + ); + } else { + ctx.append(`tuple ${funcIdOf("self")} = null();`); + } + + // Generate statements + const returns = resolveFuncTypeUnpack(t, funcIdOf("self"), ctx); + for (const s of init.ast.statements) { + if (s.kind === "statement_return") { + ctx.append(`return ${returns};`); + } else { + writeStatement(s, returns, null, ctx); + } + } + + // Return result + if ( + init.ast.statements.length === 0 || + init.ast.statements[init.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return ${returns};`); + } + }); + }); + + ctx.fun(ops.contractInitChild(t.name, ctx), () => { + const args = [ + `cell sys'`, + ...init.params.map( + (v) => resolveFuncType(v.type, ctx) + " " + funcIdOf(v.name), + ), + ]; + const sig = `(cell, cell) ${ops.contractInitChild(t.name, ctx)}(${args.join(", ")})`; + ctx.signature(sig); + if (enabledInline(ctx.ctx)) { + ctx.flag("inline"); + } + ctx.context("type:" + t.name + "$init"); + ctx.body(() => { + ctx.write(` + slice sc' = sys'.begin_parse(); + cell source = sc'~load_dict(); + cell contracts = new_dict(); + + ;; Contract Code: ${t.name} + cell mine = ${ctx.used(`__tact_dict_get_code`)}(source, ${t.uid}); + contracts = ${ctx.used(`__tact_dict_set_code`)}(contracts, ${t.uid}, mine); + `); + + // Copy contracts code + for (const c of t.dependsOn) { + ctx.append(); + ctx.write(` + ;; Contract Code: ${c.name} + cell code_${c.uid} = __tact_dict_get_code(source, ${c.uid}); + contracts = ${ctx.used(`__tact_dict_set_code`)}(contracts, ${c.uid}, code_${c.uid}); + `); + } + + // Build cell + ctx.append(); + ctx.append(`;; Build cell`); + ctx.append(`builder b = begin_cell();`); + ctx.append( + `b = b.store_ref(begin_cell().store_dict(contracts).end_cell());`, + ); + ctx.append(`b = b.store_int(false, 1);`); + const args = + t.init!.params.length > 0 + ? [ + "b", + "(" + + t + .init!.params.map((a) => funcIdOf(a.name)) + .join(", ") + + ")", + ].join(", ") + : "b, null()"; + ctx.append( + `b = ${ops.writer(funcInitIdOf(t.name), ctx)}(${args});`, + ); + ctx.append(`return (mine, b.end_cell());`); + }); + }); +} + +export function writeMainContract( + type: TypeDescription, + abiLink: string, + ctx: WriterContext, +) { + // Main field + ctx.main(() => { + // Comments + ctx.append(`;;`); + ctx.append(`;; Receivers of a Contract ${type.name}`); + ctx.append(`;;`); + ctx.append(``); + + // Write receivers + for (const r of type.receivers) { + writeReceiver(type, r, ctx); + } + + // Comments + ctx.append(`;;`); + ctx.append(`;; Get methods of a Contract ${type.name}`); + ctx.append(`;;`); + ctx.append(``); + + // Getters + for (const f of type.functions.values()) { + if (f.isGetter) { + writeGetter(f, ctx); + } + } + + // Interfaces + if (enabledInterfacesGetter(ctx.ctx)) { + writeInterfaces(type, ctx); + } + + // ABI + if (enabledIpfsAbiGetter(ctx.ctx)) { + ctx.append(`_ get_abi_ipfs() method_id {`); + ctx.inIndent(() => { + ctx.append(`return "${abiLink}";`); + }); + ctx.append(`}`); + ctx.append(); + } + + // Deployed + ctx.append(`_ lazy_deployment_completed() method_id {`); + ctx.inIndent(() => { + ctx.append(`return get_data().begin_parse().load_int(1);`); + }); + ctx.append(`}`); + ctx.append(); + + // Comments + ctx.append(`;;`); + ctx.append(`;; Routing of a Contract ${type.name}`); + ctx.append(`;;`); + ctx.append(``); + + // Render body + const hasExternal = type.receivers.find((v) => + v.selector.kind.startsWith("external-"), + ); + writeRouter(type, "internal", ctx); + if (hasExternal) { + writeRouter(type, "external", ctx); + } + + // Render internal receiver + ctx.append( + `() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {`, + ); + ctx.inIndent(() => { + // Load context + ctx.append(); + ctx.append(`;; Context`); + ctx.append(`var cs = in_msg_cell.begin_parse();`); + ctx.append(`var msg_flags = cs~load_uint(4);`); // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + ctx.append(`var msg_bounced = -(msg_flags & 1);`); + ctx.append( + `slice msg_sender_addr = ${ctx.used("__tact_verify_address")}(cs~load_msg_addr());`, + ); + ctx.append( + `__tact_context = (msg_bounced, msg_sender_addr, msg_value, cs);`, + ); + ctx.append(`__tact_context_sender = msg_sender_addr;`); + ctx.append(); + + // Load self + ctx.append(`;; Load contract data`); + ctx.append(`var self = ${ops.contractLoad(type.name, ctx)}();`); + ctx.append(); + + // Process operation + ctx.append(`;; Handle operation`); + ctx.append( + `int handled = self~${ops.contractRouter(type.name, "internal")}(msg_bounced, in_msg);`, + ); + ctx.append(); + + // Throw if not handled + ctx.append(`;; Throw if not handled`); + ctx.append( + `throw_unless(${contractErrors.invalidMessage.id}, handled);`, + ); + ctx.append(); + + // Persist state + ctx.append(`;; Persist state`); + ctx.append(`${ops.contractStore(type.name, ctx)}(self);`); + }); + ctx.append("}"); + ctx.append(); + + // Render external receiver + if (hasExternal) { + ctx.append(`() recv_external(slice in_msg) impure {`); + ctx.inIndent(() => { + // Load self + ctx.append(`;; Load contract data`); + ctx.append(`var self = ${ops.contractLoad(type.name, ctx)}();`); + ctx.append(); + + // Process operation + ctx.append(`;; Handle operation`); + ctx.append( + `int handled = self~${ops.contractRouter(type.name, "external")}(in_msg);`, + ); + ctx.append(); + + // Throw if not handled + ctx.append(`;; Throw if not handled`); + ctx.append( + `throw_unless(${contractErrors.invalidMessage.id}, handled);`, + ); + ctx.append(); + + // Persist state + ctx.append(`;; Persist state`); + ctx.append(`${ops.contractStore(type.name, ctx)}(self);`); + }); + ctx.append("}"); + ctx.append(); + } + }); +} diff --git a/src/generatorNew/writers/writeExpression.spec.ts b/src/generatorNew/writers/writeExpression.spec.ts new file mode 100644 index 000000000..19b9bd17f --- /dev/null +++ b/src/generatorNew/writers/writeExpression.spec.ts @@ -0,0 +1,102 @@ +import { __DANGER_resetNodeId } from "../../grammar/ast"; +import { + getStaticFunction, + resolveDescriptors, +} from "../../types/resolveDescriptors"; +import { WriterContext } from "../Writer"; +import { writeExpression } from "./writeExpression"; +import { openContext } from "../../grammar/store"; +import { resolveStatements } from "../../types/resolveStatements"; +import { CompilerContext } from "../../context"; + +const code = ` + +primitive Int; +primitive Bool; +primitive Builder; +primitive Cell; +primitive Slice; + +fun f1(a: Int): Int { + return a; +} + +struct A { + a: Int; + b: Int; +} + +fun main() { + let a: Int = 1; + let b: Int = 2; + let c: Int = a + b; + let d: Int = a + b * c; + let e: Int = a + b / c; + let f: Bool = true; + let g: Bool = false; + let h: Bool = a > 1 || b < 2 && c == 3 || !(d != 4 && true && !false); + let i: Int = f1(a); + let j: A = A{a: 1, b: 2}; + let k: Int = j.a; + let l: Int = A{a: 1, b}.b; + let m: Int = -j.b + a; + let n: Int = -j.b + a + (+b); + let o: Int? = null; + let p: Int? = o!! + 1; + let q: Cell = j.toCell(); +} +`; + +const golden: string[] = [ + "1", + "2", + "($a + $b)", + "($a + ($b * $c))", + "($a + ($b / $c))", + "true", + "false", + "( (( (($a > 1)) ? (true) : (( (($b < 2)) ? (($c == 3)) : (false) )) )) ? (true) : ((~ ( (( (($d != 4)) ? (true) : (false) )) ? (true) : (false) ))) )", + "$global_f1($a)", + "$A$_constructor_a_b(1, 2)", + `$j'a`, + "$A$_get_b($A$_constructor_a_b(1, $b))", + `((- $j'b) + $a)`, + `(((- $j'b) + $a) + (+ $b))`, + "null()", + "(__tact_not_null($o) + 1)", + `$A$_store_cell(($j'a, $j'b))`, +]; + +describe("writeExpression", () => { + beforeEach(() => { + __DANGER_resetNodeId(); + }); + it("should write expression", () => { + let ctx = openContext( + new CompilerContext(), + [{ code: code, path: "", origin: "user" }], + [], + ); + ctx = resolveDescriptors(ctx); + ctx = resolveStatements(ctx); + const main = getStaticFunction(ctx, "main"); + if (main.ast.kind !== "function_def") { + throw Error("Unexpected function kind"); + } + let i = 0; + for (const s of main.ast.statements) { + if (s.kind !== "statement_let") { + throw Error("Unexpected statement kind"); + } + const wCtx = new WriterContext(ctx, "Contract1"); + wCtx.fun("$main", () => { + wCtx.body(() => { + expect(writeExpression(s.expression, wCtx)).toBe( + golden[i]!, + ); + }); + }); + i++; + } + }); +}); diff --git a/src/generatorNew/writers/writeExpression.ts b/src/generatorNew/writers/writeExpression.ts new file mode 100644 index 000000000..937d3144c --- /dev/null +++ b/src/generatorNew/writers/writeExpression.ts @@ -0,0 +1,692 @@ +import { + AstExpression, + AstId, + eqNames, + idText, + tryExtractPath, +} from "../../grammar/ast"; +import { + idTextErr, + TactConstEvalError, + throwCompilationError, +} from "../../errors"; +import { getExpType } from "../../types/resolveExpression"; +import { + getStaticConstant, + getStaticFunction, + getType, + hasStaticConstant, +} from "../../types/resolveDescriptors"; +import { + FieldDescription, + printTypeRef, + TypeDescription, + CommentValue, + Value, +} from "../../types/types"; +import { WriterContext } from "../Writer"; +import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; +import { MapFunctions } from "../../abi/map"; +import { GlobalFunctions } from "../../abi/global"; +import { funcIdOf } from "./id"; +import { StructFunctions } from "../../abi/struct"; +import { resolveFuncType } from "./resolveFuncType"; +import { Address, Cell, Slice } from "@ton/core"; +import { + writeAddress, + writeCell, + writeComment, + writeSlice, + writeString, +} from "./writeConstant"; +import { ops } from "./ops"; +import { writeCastedExpression } from "./writeFunction"; +import { evalConstantExpression } from "../../constEval"; +import { isLvalue } from "../../types/resolveStatements"; + +function isNull(f: AstExpression): boolean { + return f.kind === "null"; +} + +function writeStructConstructor( + type: TypeDescription, + args: string[], + ctx: WriterContext, +) { + // Check for duplicates + const name = ops.typeConstructor(type.name, args, ctx); + const renderKey = "$constructor$" + type.name + "$" + args.join(","); + if (ctx.isRendered(renderKey)) { + return name; + } + ctx.markRendered(renderKey); + + // Generate constructor + ctx.fun(name, () => { + const funcType = resolveFuncType(type, ctx); + // rename a struct constructor formal parameter to avoid + // name clashes with FunC keywords, e.g. `struct Foo {type: Int}` + // is a perfectly fine Tact structure, but its constructor would + // have the wrong parameter name: `$Foo$_constructor_type(int type)` + const avoidFunCKeywordNameClash = (p: string) => `$${p}`; + const sig = `(${funcType}) ${name}(${args.map((v) => resolveFuncType(type.fields.find((v2) => v2.name === v)!.type, ctx) + " " + avoidFunCKeywordNameClash(v)).join(", ")})`; + ctx.signature(sig); + ctx.flag("inline"); + ctx.context("type:" + type.name); + ctx.body(() => { + // Create expressions + const expressions = type.fields.map((v) => { + const arg = args.find((v2) => v2 === v.name); + if (arg) { + return avoidFunCKeywordNameClash(arg); + } else if (v.default !== undefined) { + return writeValue(v.default, ctx); + } else { + throw Error( + `Missing argument for field "${v.name}" in struct "${type.name}"`, + ); // Must not happen + } + }, ctx); + + if (expressions.length === 0 && funcType === "tuple") { + ctx.append(`return empty_tuple();`); + } else { + ctx.append(`return (${expressions.join(", ")});`); + } + }); + }); + return name; +} + +export function writeValue(val: Value, wCtx: WriterContext): string { + if (typeof val === "bigint") { + return val.toString(10); + } + if (typeof val === "string") { + const id = writeString(val, wCtx); + wCtx.used(id); + return `${id}()`; + } + if (typeof val === "boolean") { + return val ? "true" : "false"; + } + if (Address.isAddress(val)) { + const res = writeAddress(val, wCtx); + wCtx.used(res); + return res + "()"; + } + if (val instanceof Cell) { + const res = writeCell(val, wCtx); + wCtx.used(res); + return `${res}()`; + } + if (val instanceof Slice) { + const res = writeSlice(val, wCtx); + wCtx.used(res); + return `${res}()`; + } + if (val === null) { + return "null()"; + } + if (val instanceof CommentValue) { + const id = writeComment(val.comment, wCtx); + wCtx.used(id); + return `${id}()`; + } + if (typeof val === "object" && "$tactStruct" in val) { + // this is a struct value + const structDescription = getType( + wCtx.ctx, + val["$tactStruct"] as string, + ); + const fields = structDescription.fields.map((field) => field.name); + const id = writeStructConstructor(structDescription, fields, wCtx); + wCtx.used(id); + const fieldValues = structDescription.fields.map((field) => { + if (field.name in val) { + if (field.type.kind === "ref" && field.type.optional) { + const ft = getType(wCtx.ctx, field.type.name); + if (ft.kind === "struct" && val[field.name] !== null) { + return `${ops.typeAsOptional(ft.name, wCtx)}(${writeValue(val[field.name]!, wCtx)})`; + } + } + return writeValue(val[field.name]!, wCtx); + } else { + throw Error( + `Struct value is missing a field: ${field.name}`, + val, + ); + } + }); + return `${id}(${fieldValues.join(", ")})`; + } + throw Error("Invalid value", val); +} + +export function writePathExpression(path: AstId[]): string { + return [funcIdOf(idText(path[0]!)), ...path.slice(1).map(idText)].join(`'`); +} + +export function writeExpression(f: AstExpression, wCtx: WriterContext): string { + // literals and constant expressions are covered here + try { + const value = evalConstantExpression(f, wCtx.ctx); + return writeValue(value, wCtx); + } catch (error) { + if (!(error instanceof TactConstEvalError) || error.fatal) throw error; + } + + // + // ID Reference + // + + if (f.kind === "id") { + const t = getExpType(wCtx.ctx, f); + + // Handle packed type + if (t.kind === "ref") { + const tt = getType(wCtx.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { + return resolveFuncTypeUnpack(t, funcIdOf(f.text), wCtx); + } + } + + if (t.kind === "ref_bounced") { + const tt = getType(wCtx.ctx, t.name); + if (tt.kind === "struct") { + return resolveFuncTypeUnpack( + t, + funcIdOf(f.text), + wCtx, + false, + true, + ); + } + } + + // Handle constant + if (hasStaticConstant(wCtx.ctx, f.text)) { + const c = getStaticConstant(wCtx.ctx, f.text); + return writeValue(c.value!, wCtx); + } + + return funcIdOf(f.text); + } + + // NOTE: We always wrap in parentheses to avoid operator precedence issues + if (f.kind === "op_binary") { + // Special case for non-integer types and nullable + if (f.op === "==" || f.op === "!=") { + if (isNull(f.left) && isNull(f.right)) { + if (f.op === "==") { + return "true"; + } else { + return "false"; + } + } else if (isNull(f.left) && !isNull(f.right)) { + if (f.op === "==") { + return `null?(${writeExpression(f.right, wCtx)})`; + } else { + return `(~ null?(${writeExpression(f.right, wCtx)}))`; + } + } else if (!isNull(f.left) && isNull(f.right)) { + if (f.op === "==") { + return `null?(${writeExpression(f.left, wCtx)})`; + } else { + return `(~ null?(${writeExpression(f.left, wCtx)}))`; + } + } + } + + // Special case for address + const lt = getExpType(wCtx.ctx, f.left); + const rt = getExpType(wCtx.ctx, f.right); + + // Case for addresses equality + if ( + lt.kind === "ref" && + rt.kind === "ref" && + lt.name === "Address" && + rt.name === "Address" + ) { + let prefix = ""; + if (f.op == "!=") { + prefix = "~ "; + } + if (lt.optional && rt.optional) { + wCtx.used(`__tact_slice_eq_bits_nullable`); + return `( ${prefix}__tact_slice_eq_bits_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)}) )`; + } + if (lt.optional && !rt.optional) { + wCtx.used(`__tact_slice_eq_bits_nullable_one`); + return `( ${prefix}__tact_slice_eq_bits_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)}) )`; + } + if (!lt.optional && rt.optional) { + wCtx.used(`__tact_slice_eq_bits_nullable_one`); + return `( ${prefix}__tact_slice_eq_bits_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)}) )`; + } + wCtx.used(`__tact_slice_eq_bits`); + return `( ${prefix}__tact_slice_eq_bits(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)}) )`; + } + + // Case for cells equality + if ( + lt.kind === "ref" && + rt.kind === "ref" && + lt.name === "Cell" && + rt.name === "Cell" + ) { + const op = f.op === "==" ? "eq" : "neq"; + if (lt.optional && rt.optional) { + wCtx.used(`__tact_cell_${op}_nullable`); + return `__tact_cell_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + if (lt.optional && !rt.optional) { + wCtx.used(`__tact_cell_${op}_nullable_one`); + return `__tact_cell_${op}_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + if (!lt.optional && rt.optional) { + wCtx.used(`__tact_cell_${op}_nullable_one`); + return `__tact_cell_${op}_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + } + wCtx.used(`__tact_cell_${op}`); + return `__tact_cell_${op}(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + } + + // Case for slices and strings equality + if ( + lt.kind === "ref" && + rt.kind === "ref" && + lt.name === rt.name && + (lt.name === "Slice" || lt.name === "String") + ) { + const op = f.op === "==" ? "eq" : "neq"; + if (lt.optional && rt.optional) { + wCtx.used(`__tact_slice_${op}_nullable`); + return `__tact_slice_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + if (lt.optional && !rt.optional) { + wCtx.used(`__tact_slice_${op}_nullable_one`); + return `__tact_slice_${op}_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + if (!lt.optional && rt.optional) { + wCtx.used(`__tact_slice_${op}_nullable_one`); + return `__tact_slice_${op}_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + } + wCtx.used(`__tact_slice_${op}`); + return `__tact_slice_${op}(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + } + + // Case for maps equality + if (lt.kind === "map" && rt.kind === "map") { + const op = f.op === "==" ? "eq" : "neq"; + wCtx.used(`__tact_cell_${op}_nullable`); + return `__tact_cell_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + + // Check for int or boolean types + if ( + lt.kind !== "ref" || + rt.kind !== "ref" || + (lt.name !== "Int" && lt.name !== "Bool") || + (rt.name !== "Int" && rt.name !== "Bool") + ) { + const file = f.loc.file; + const loc_info = f.loc.interval.getLineAndColumn(); + throw Error( + `(Internal Compiler Error) Invalid types for binary operation: ${file}:${loc_info.lineNum}:${loc_info.colNum}`, + ); // Should be unreachable + } + + // Case for ints equality + if (f.op === "==" || f.op === "!=") { + const op = f.op === "==" ? "eq" : "neq"; + if (lt.optional && rt.optional) { + wCtx.used(`__tact_int_${op}_nullable`); + return `__tact_int_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + if (lt.optional && !rt.optional) { + wCtx.used(`__tact_int_${op}_nullable_one`); + return `__tact_int_${op}_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + } + if (!lt.optional && rt.optional) { + wCtx.used(`__tact_int_${op}_nullable_one`); + return `__tact_int_${op}_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + } + if (f.op === "==") { + return `(${writeExpression(f.left, wCtx)} == ${writeExpression(f.right, wCtx)})`; + } else { + return `(${writeExpression(f.left, wCtx)} != ${writeExpression(f.right, wCtx)})`; + } + } + + // Case for "&&" operator + if (f.op === "&&") { + return `( (${writeExpression(f.left, wCtx)}) ? (${writeExpression(f.right, wCtx)}) : (false) )`; + } + + // Case for "||" operator + if (f.op === "||") { + return `( (${writeExpression(f.left, wCtx)}) ? (true) : (${writeExpression(f.right, wCtx)}) )`; + } + + // Other ops + return ( + "(" + + writeExpression(f.left, wCtx) + + " " + + f.op + + " " + + writeExpression(f.right, wCtx) + + ")" + ); + } + + // + // Unary operations: !, -, +, !! + // NOTE: We always wrap in parenthesis to avoid operator precedence issues + // + + if (f.kind === "op_unary") { + // NOTE: Logical not is written as a bitwise not + switch (f.op) { + case "!": { + return "(~ " + writeExpression(f.operand, wCtx) + ")"; + } + + case "~": { + return "(~ " + writeExpression(f.operand, wCtx) + ")"; + } + + case "-": { + return "(- " + writeExpression(f.operand, wCtx) + ")"; + } + + case "+": { + return "(+ " + writeExpression(f.operand, wCtx) + ")"; + } + + // NOTE: Assert function that ensures that the value is not null + case "!!": { + const t = getExpType(wCtx.ctx, f.operand); + if (t.kind === "ref") { + const tt = getType(wCtx.ctx, t.name); + if (tt.kind === "struct") { + return `${ops.typeNotNull(tt.name, wCtx)}(${writeExpression(f.operand, wCtx)})`; + } + } + + wCtx.used("__tact_not_null"); + return `${wCtx.used("__tact_not_null")}(${writeExpression(f.operand, wCtx)})`; + } + } + } + + // + // Field Access + // NOTE: this branch resolves "a.b", where "a" is an expression and "b" is a field name + // + + if (f.kind === "field_access") { + // Resolve the type of the expression + const src = getExpType(wCtx.ctx, f.aggregate); + if ( + (src.kind !== "ref" || src.optional) && + src.kind !== "ref_bounced" + ) { + throwCompilationError( + `Cannot access field of non-struct type: "${printTypeRef(src)}"`, + f.loc, + ); + } + const srcT = getType(wCtx.ctx, src.name); + + // Resolve field + let fields: FieldDescription[]; + + fields = srcT.fields; + if (src.kind === "ref_bounced") { + fields = fields.slice(0, srcT.partialFieldCount); + } + + const field = fields.find((v) => eqNames(v.name, f.field)); + const cst = srcT.constants.find((v) => eqNames(v.name, f.field)); + if (!field && !cst) { + throwCompilationError( + `Cannot find field ${idTextErr(f.field)} in struct ${idTextErr(srcT.name)}`, + f.field.loc, + ); + } + + if (field) { + // Trying to resolve field as a path + const path = tryExtractPath(f); + if (path) { + // Prepare path + const idd = writePathExpression(path); + + // Special case for structs + if (field.type.kind === "ref") { + const ft = getType(wCtx.ctx, field.type.name); + if (ft.kind === "struct" || ft.kind === "contract") { + return resolveFuncTypeUnpack(field.type, idd, wCtx); + } + } + + return idd; + } + + // Getter instead of direct field access + return `${ops.typeField(srcT.name, field.name, wCtx)}(${writeExpression(f.aggregate, wCtx)})`; + } else { + return writeValue(cst!.value!, wCtx); + } + } + + // + // Static Function Call + // + + if (f.kind === "static_call") { + // Check global functions + if (GlobalFunctions.has(idText(f.function))) { + return GlobalFunctions.get(idText(f.function))!.generate( + wCtx, + f.args.map((v) => getExpType(wCtx.ctx, v)), + f.args, + f.loc, + ); + } + + const sf = getStaticFunction(wCtx.ctx, idText(f.function)); + let n = ops.global(idText(f.function)); + if (sf.ast.kind === "native_function_decl") { + n = idText(sf.ast.nativeName); + if (n.startsWith("__tact")) { + wCtx.used(n); + } + } else { + wCtx.used(n); + } + return ( + n + + "(" + + f.args + .map((a, i) => + writeCastedExpression(a, sf.params[i]!.type, wCtx), + ) + .join(", ") + + ")" + ); + } + + // + // Struct Constructor + // + + if (f.kind === "struct_instance") { + const src = getType(wCtx.ctx, f.type); + + // Write a constructor + const id = writeStructConstructor( + src, + f.args.map((v) => idText(v.field)), + wCtx, + ); + wCtx.used(id); + + // Write an expression + const expressions = f.args.map( + (v) => + writeCastedExpression( + v.initializer, + src.fields.find((v2) => eqNames(v2.name, v.field))!.type, + wCtx, + ), + wCtx, + ); + return `${id}(${expressions.join(", ")})`; + } + + // + // Object-based function call + // + + if (f.kind === "method_call") { + // Resolve source type + const selfTyRef = getExpType(wCtx.ctx, f.self); + + // Reference type + if (selfTyRef.kind === "ref") { + if (selfTyRef.optional) { + throwCompilationError( + `Cannot call function of non - direct type: "${printTypeRef(selfTyRef)}"`, + f.loc, + ); + } + + // Render function call + const selfTy = getType(wCtx.ctx, selfTyRef.name); + + // Check struct ABI + if (selfTy.kind === "struct") { + if (StructFunctions.has(idText(f.method))) { + const abi = StructFunctions.get(idText(f.method))!; + return abi.generate( + wCtx, + [ + selfTyRef, + ...f.args.map((v) => getExpType(wCtx.ctx, v)), + ], + [f.self, ...f.args], + f.loc, + ); + } + } + + // Resolve function + const methodDescr = selfTy.functions.get(idText(f.method))!; + let name = ops.extension(selfTyRef.name, idText(f.method)); + if ( + methodDescr.ast.kind === "function_def" || + methodDescr.ast.kind === "function_decl" || + methodDescr.ast.kind === "asm_function_def" + ) { + wCtx.used(name); + } else { + name = idText(methodDescr.ast.nativeName); + if (name.startsWith("__tact")) { + wCtx.used(name); + } + } + + // Render arguments + let renderedArguments = f.args.map((a, i) => + writeCastedExpression(a, methodDescr.params[i]!.type, wCtx), + ); + + // Hack to replace a single struct argument to a tensor wrapper since otherwise + // func would convert (int) type to just int and break mutating functions + if (methodDescr.isMutating) { + if (f.args.length === 1) { + const t = getExpType(wCtx.ctx, f.args[0]!); + if (t.kind === "ref") { + const tt = getType(wCtx.ctx, t.name); + if ( + (tt.kind === "contract" || tt.kind === "struct") && + methodDescr.params[0]!.type.kind === "ref" && + !methodDescr.params[0]!.type.optional + ) { + renderedArguments = [ + `${ops.typeTensorCast(tt.name, wCtx)}(${renderedArguments[0]})`, + ]; + } + } + } + } + + // Render + const s = writeExpression(f.self, wCtx); + if (methodDescr.isMutating) { + // check if it's an l-value + const path = tryExtractPath(f.self); + if (path !== null && isLvalue(path, wCtx.ctx)) { + return `${s}~${name}(${renderedArguments.join(", ")})`; + } else { + return `${wCtx.used(ops.nonModifying(name))}(${[s, ...renderedArguments].join(", ")})`; + } + } else { + return `${name}(${[s, ...renderedArguments].join(", ")})`; + } + } + + // Map types + if (selfTyRef.kind === "map") { + if (!MapFunctions.has(idText(f.method))) { + throwCompilationError( + `Map function "${idText(f.method)}" not found`, + f.loc, + ); + } + const abf = MapFunctions.get(idText(f.method))!; + return abf.generate( + wCtx, + [selfTyRef, ...f.args.map((v) => getExpType(wCtx.ctx, v))], + [f.self, ...f.args], + f.loc, + ); + } + + if (selfTyRef.kind === "ref_bounced") { + throw Error("Unimplemented"); + } + + throwCompilationError( + `Cannot call function of non - direct type: "${printTypeRef(selfTyRef)}"`, + f.loc, + ); + } + + // + // Init of + // + + if (f.kind === "init_of") { + const type = getType(wCtx.ctx, f.contract); + return `${ops.contractInitChild(idText(f.contract), wCtx)}(${["__tact_context_sys", ...f.args.map((a, i) => writeCastedExpression(a, type.init!.params[i]!.type, wCtx))].join(", ")})`; + } + + // + // Ternary operator + // + + if (f.kind === "conditional") { + return `(${writeExpression(f.condition, wCtx)} ? ${writeExpression(f.thenBranch, wCtx)} : ${writeExpression(f.elseBranch, wCtx)})`; + } + + // + // Unreachable + // + + throw Error("Unknown expression"); +} diff --git a/src/generatorNew/writers/writeFunction.ts b/src/generatorNew/writers/writeFunction.ts new file mode 100644 index 000000000..cb2727018 --- /dev/null +++ b/src/generatorNew/writers/writeFunction.ts @@ -0,0 +1,726 @@ +import { enabledInline } from "../../config/features"; +import { + AstAsmShuffle, + AstCondition, + AstExpression, + AstStatement, + idOfText, + idText, + isWildcard, + tryExtractPath, +} from "../../grammar/ast"; +import { getType, resolveTypeRef } from "../../types/resolveDescriptors"; +import { getExpType } from "../../types/resolveExpression"; +import { FunctionDescription, TypeRef } from "../../types/types"; +import { getMethodId } from "../../utils/utils"; +import { WriterContext } from "../Writer"; +import { resolveFuncPrimitive } from "./resolveFuncPrimitive"; +import { resolveFuncType } from "./resolveFuncType"; +import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; +import { funcIdOf } from "./id"; +import { writeExpression, writePathExpression } from "./writeExpression"; +import { cast } from "./cast"; +import { resolveFuncTupleType } from "./resolveFuncTupleType"; +import { ops } from "./ops"; +import { freshIdentifier } from "./freshIdentifier"; +import { idTextErr, throwInternalCompilerError } from "../../errors"; +import { prettyPrintAsmShuffle } from "../../prettyPrinter"; + +export function writeCastedExpression( + expression: AstExpression, + to: TypeRef, + ctx: WriterContext, +) { + const expr = getExpType(ctx.ctx, expression); + return cast(expr, to, writeExpression(expression, ctx), ctx); // Cast for nullable +} + +function unwrapExternal( + targetName: string, + sourceName: string, + type: TypeRef, + ctx: WriterContext, +) { + if (type.kind === "ref") { + const t = getType(ctx.ctx, type.name); + if (t.kind === "struct" || t.kind === "contract") { + if (type.optional) { + ctx.append( + `${resolveFuncType(type, ctx)} ${targetName} = ${ops.typeFromOptTuple(t.name, ctx)}(${sourceName});`, + ); + } else { + ctx.append( + `${resolveFuncType(type, ctx)} ${targetName} = ${ops.typeFromTuple(t.name, ctx)}(${sourceName});`, + ); + } + return; + } else if (t.kind === "primitive_type_decl" && t.name === "Address") { + if (type.optional) { + ctx.append( + `${resolveFuncType(type, ctx)} ${targetName} = null?(${sourceName}) ? null() : ${ctx.used(`__tact_verify_address`)}(${sourceName});`, + ); + } else { + ctx.append( + `${resolveFuncType(type, ctx)} ${targetName} = ${ctx.used(`__tact_verify_address`)}(${sourceName});`, + ); + } + return; + } + } + ctx.append(`${resolveFuncType(type, ctx)} ${targetName} = ${sourceName};`); +} + +export function writeStatement( + f: AstStatement, + self: string | null, + returns: TypeRef | null, + ctx: WriterContext, +) { + switch (f.kind) { + case "statement_return": { + if (f.expression) { + // Format expression + const result = writeCastedExpression( + f.expression, + returns!, + ctx, + ); + + // Return + if (self) { + ctx.append(`return (${self}, ${result});`); + } else { + ctx.append(`return ${result};`); + } + } else { + if (self) { + ctx.append(`return (${self}, ());`); + } else { + ctx.append(`return ();`); + } + } + return; + } + case "statement_let": { + // Underscore name case + if (isWildcard(f.name)) { + ctx.append(`${writeExpression(f.expression, ctx)};`); + return; + } + + // Contract/struct case + const t = + f.type === null + ? getExpType(ctx.ctx, f.expression) + : resolveTypeRef(ctx.ctx, f.type); + + if (t.kind === "ref") { + const tt = getType(ctx.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { + if (t.optional) { + ctx.append( + `tuple ${funcIdOf(f.name)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + } else { + ctx.append( + `var ${resolveFuncTypeUnpack(t, funcIdOf(f.name), ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + } + return; + } + } + + ctx.append( + `${resolveFuncType(t, ctx)} ${funcIdOf(f.name)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + return; + } + case "statement_assign": { + // Prepare lvalue + const lvaluePath = tryExtractPath(f.path); + if (lvaluePath === null) { + // typechecker is supposed to catch this + throwInternalCompilerError( + `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + f.path.loc, + ); + } + const path = writePathExpression(lvaluePath); + + // Contract/struct case + const t = getExpType(ctx.ctx, f.path); + if (t.kind === "ref") { + const tt = getType(ctx.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { + ctx.append( + `${resolveFuncTypeUnpack(t, path, ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + return; + } + } + + ctx.append( + `${path} = ${writeCastedExpression(f.expression, t, ctx)};`, + ); + return; + } + case "statement_augmentedassign": { + const lvaluePath = tryExtractPath(f.path); + if (lvaluePath === null) { + // typechecker is supposed to catch this + throwInternalCompilerError( + `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + f.path.loc, + ); + } + const path = writePathExpression(lvaluePath); + const t = getExpType(ctx.ctx, f.path); + ctx.append( + `${path} = ${cast(t, t, `${path} ${f.op} ${writeExpression(f.expression, ctx)}`, ctx)};`, + ); + return; + } + case "statement_condition": { + writeCondition(f, self, false, returns, ctx); + return; + } + case "statement_expression": { + const exp = writeExpression(f.expression, ctx); + ctx.append(`${exp};`); + return; + } + case "statement_while": { + ctx.append(`while (${writeExpression(f.condition, ctx)}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`}`); + return; + } + case "statement_until": { + ctx.append(`do {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`} until (${writeExpression(f.condition, ctx)});`); + return; + } + case "statement_repeat": { + ctx.append(`repeat (${writeExpression(f.iterations, ctx)}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`}`); + return; + } + case "statement_try": { + ctx.append(`try {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append("} catch (_) { }"); + return; + } + case "statement_try_catch": { + ctx.append(`try {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + }); + if (isWildcard(f.catchName)) { + ctx.append(`} catch (_) {`); + } else { + ctx.append(`} catch (_, ${funcIdOf(f.catchName)}) {`); + } + ctx.inIndent(() => { + for (const s of f.catchStatements) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`}`); + return; + } + case "statement_foreach": { + const mapPath = tryExtractPath(f.map); + if (mapPath === null) { + // typechecker is supposed to catch this + throwInternalCompilerError( + `foreach is only allowed over maps that are path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + f.map.loc, + ); + } + const path = writePathExpression(mapPath); + + const t = getExpType(ctx.ctx, f.map); + if (t.kind !== "map") { + throw Error("Unknown map type"); + } + + const flag = freshIdentifier("flag"); + const key = isWildcard(f.keyName) + ? freshIdentifier("underscore") + : funcIdOf(f.keyName); + const value = isWildcard(f.valueName) + ? freshIdentifier("underscore") + : funcIdOf(f.valueName); + + // Handle Int key + if (t.key === "Int") { + let bits = 257; + let kind = "int"; + if (t.keyAs?.startsWith("int")) { + bits = parseInt(t.keyAs.slice(3), 10); + } else if (t.keyAs?.startsWith("uint")) { + bits = parseInt(t.keyAs.slice(4), 10); + kind = "uint"; + } + if (t.value === "Int") { + let vBits = 257; + let vKind = "int"; + if (t.valueAs?.startsWith("int")) { + vBits = parseInt(t.valueAs.slice(3), 10); + } else if (t.valueAs?.startsWith("uint")) { + vBits = parseInt(t.valueAs.slice(4), 10); + vKind = "uint"; + } + + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_${vKind}`)}(${path}, ${bits}, ${vBits});`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_${vKind}`)}(${path}, ${bits}, ${key}, ${vBits});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Bool") { + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_int`)}(${path}, ${bits}, 1);`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_int`)}(${path}, ${bits}, ${key}, 1);`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Cell") { + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Address") { + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_slice`)}(${path}, ${bits});`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_slice`)}(${path}, ${bits}, ${key});`, + ); + }); + ctx.append(`}`); + } else { + // value is struct + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + ctx.append( + `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + ); + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + ); + }); + ctx.append(`}`); + } + } + + // Handle address key + if (t.key === "Address") { + if (t.value === "Int") { + let vBits = 257; + let vKind = "int"; + if (t.valueAs?.startsWith("int")) { + vBits = parseInt(t.valueAs.slice(3), 10); + } else if (t.valueAs?.startsWith("uint")) { + vBits = parseInt(t.valueAs.slice(4), 10); + vKind = "uint"; + } + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_${vKind}`)}(${path}, 267, ${vBits});`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_${vKind}`)}(${path}, 267, ${key}, ${vBits});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Bool") { + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_int`)}(${path}, 267, 1);`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_int`)}(${path}, 267, ${key}, 1);`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Cell") { + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + ); + }); + ctx.append(`}`); + } else if (t.value === "Address") { + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_slice`)}(${path}, 267);`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_slice`)}(${path}, 267, ${key});`, + ); + }); + ctx.append(`}`); + } else { + // value is struct + ctx.append( + `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, + ); + ctx.append(`while (${flag}) {`); + ctx.inIndent(() => { + ctx.append( + `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + ); + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + ctx.append( + `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + ); + }); + ctx.append(`}`); + } + } + + return; + } + } + + throw Error("Unknown statement kind"); +} + +function writeCondition( + f: AstCondition, + self: string | null, + elseif: boolean, + returns: TypeRef | null, + ctx: WriterContext, +) { + ctx.append( + `${elseif ? "} else" : ""}if (${writeExpression(f.condition, ctx)}) {`, + ); + ctx.inIndent(() => { + for (const s of f.trueStatements) { + writeStatement(s, self, returns, ctx); + } + }); + if (f.falseStatements && f.falseStatements.length > 0) { + ctx.append(`} else {`); + ctx.inIndent(() => { + for (const s of f.falseStatements!) { + writeStatement(s, self, returns, ctx); + } + }); + ctx.append(`}`); + } else if (f.elseif) { + writeCondition(f.elseif, self, true, returns, ctx); + } else { + ctx.append(`}`); + } +} + +export function writeFunction(f: FunctionDescription, ctx: WriterContext) { + // Resolve self + const self = f.self ? getType(ctx.ctx, f.self) : null; + + // Write function header + let returns: string = resolveFuncType(f.returns, ctx); + const returnsOriginal = returns; + let returnsStr: string | null; + if (self && f.isMutating) { + if (f.returns.kind !== "void") { + returns = `(${resolveFuncType(self, ctx)}, ${returns})`; + } else { + returns = `(${resolveFuncType(self, ctx)}, ())`; + } + returnsStr = resolveFuncTypeUnpack(self, funcIdOf("self"), ctx); + } + + // Resolve function descriptor + const params: string[] = []; + if (self) { + params.push(resolveFuncType(self, ctx) + " " + funcIdOf("self")); + } + for (const a of f.params) { + params.push(resolveFuncType(a.type, ctx) + " " + funcIdOf(a.name)); + } + + const fAst = f.ast; + switch (fAst.kind) { + case "native_function_decl": { + const name = idText(fAst.nativeName); + if (f.isMutating && !ctx.isRendered(name)) { + writeNonMutatingFunction( + f, + name, + params, + returnsOriginal, + false, + ctx, + ); + ctx.markRendered(name); + } + return; + } + + case "asm_function_def": { + const name = self + ? ops.extension(self.name, f.name) + : ops.global(f.name); + ctx.fun(name, () => { + ctx.signature(`${returns} ${name}(${params.join(", ")})`); + ctx.flag("impure"); + if (f.origin === "stdlib") { + ctx.context("stdlib"); + } + // we need to do some renames (prepending $ to identifiers) + const asmShuffleEscaped: AstAsmShuffle = { + ...fAst.shuffle, + args: fAst.shuffle.args.map((id) => idOfText(funcIdOf(id))), + }; + ctx.asm( + prettyPrintAsmShuffle(asmShuffleEscaped), + fAst.instructions.join(" "), + ); + }); + if (f.isMutating) { + writeNonMutatingFunction( + f, + name, + params, + returnsOriginal, + true, + ctx, + ); + } + return; + } + + case "function_def": { + const name = self + ? ops.extension(self.name, f.name) + : ops.global(f.name); + + ctx.fun(name, () => { + ctx.signature(`${returns} ${name}(${params.join(", ")})`); + ctx.flag("impure"); + if (enabledInline(ctx.ctx) || f.isInline) { + ctx.flag("inline"); + } + if (f.origin === "stdlib") { + ctx.context("stdlib"); + } + ctx.body(() => { + // Unpack self + if (self) { + ctx.append( + `var (${resolveFuncTypeUnpack(self, funcIdOf("self"), ctx)}) = ${funcIdOf("self")};`, + ); + } + for (const a of f.ast.params) { + if ( + !resolveFuncPrimitive( + resolveTypeRef(ctx.ctx, a.type), + ctx, + ) + ) { + ctx.append( + `var (${resolveFuncTypeUnpack(resolveTypeRef(ctx.ctx, a.type), funcIdOf(a.name), ctx)}) = ${funcIdOf(a.name)};`, + ); + } + } + + // Process statements + for (const s of fAst.statements) { + writeStatement(s, returnsStr, f.returns, ctx); + } + + // Auto append return + if (f.self && f.returns.kind === "void" && f.isMutating) { + if ( + fAst.statements.length === 0 || + fAst.statements[fAst.statements.length - 1]! + .kind !== "statement_return" + ) { + ctx.append(`return (${returnsStr}, ());`); + } + } + }); + }); + + if (f.isMutating) { + writeNonMutatingFunction( + f, + name, + params, + returnsOriginal, + true, + ctx, + ); + } + return; + } + default: { + throwInternalCompilerError( + `Unknown function kind: ${idTextErr(fAst.name)}`, + fAst.loc, + ); + } + } +} + +// Write a function in non-mutating form +function writeNonMutatingFunction( + f: FunctionDescription, + name: string, + params: string[], + returnsOriginal: string, + markUsedName: boolean, + ctx: WriterContext, +) { + const nonMutName = ops.nonModifying(name); + ctx.fun(nonMutName, () => { + ctx.signature(`${returnsOriginal} ${nonMutName}(${params.join(", ")})`); + ctx.flag("impure"); + if (enabledInline(ctx.ctx) || f.isInline) { + ctx.flag("inline"); + } + if (f.origin === "stdlib") { + ctx.context("stdlib"); + } + ctx.body(() => { + ctx.append( + `return ${funcIdOf("self")}~${markUsedName ? ctx.used(name) : name}(${f.ast.params + .slice(1) + .map((arg) => funcIdOf(arg.name)) + .join(", ")});`, + ); + }); + }); +} + +export function writeGetter(f: FunctionDescription, ctx: WriterContext) { + // Render tensors + const self = f.self !== null ? getType(ctx.ctx, f.self) : null; + if (!self) { + throw new Error(`No self type for getter ${idTextErr(f.name)}`); // Impossible + } + ctx.append( + `_ %${f.name}(${f.params.map((v) => resolveFuncTupleType(v.type, ctx) + " " + funcIdOf(v.name)).join(", ")}) method_id(${getMethodId(f.name)}) {`, + ); + ctx.inIndent(() => { + // Unpack parameters + for (const param of f.params) { + unwrapExternal( + funcIdOf(param.name), + funcIdOf(param.name), + param.type, + ctx, + ); + } + + // Load contract state + ctx.append(`var self = ${ops.contractLoad(self.name, ctx)}();`); + + // Execute get method + ctx.append( + `var res = self~${ctx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`, + ); + + // Pack if needed + if (f.returns.kind === "ref") { + const t = getType(ctx.ctx, f.returns.name); + if (t.kind === "struct" || t.kind === "contract") { + if (f.returns.optional) { + ctx.append( + `return ${ops.typeToOptExternal(t.name, ctx)}(res);`, + ); + } else { + ctx.append( + `return ${ops.typeToExternal(t.name, ctx)}(res);`, + ); + } + return; + } + } + + // Return result + ctx.append(`return res;`); + }); + ctx.append(`}`); + ctx.append(); +} diff --git a/src/generatorNew/writers/writeInterfaces.ts b/src/generatorNew/writers/writeInterfaces.ts new file mode 100644 index 000000000..5173461ae --- /dev/null +++ b/src/generatorNew/writers/writeInterfaces.ts @@ -0,0 +1,26 @@ +import { getSupportedInterfaces } from "../../types/getSupportedInterfaces"; +import { TypeDescription } from "../../types/types"; +import { WriterContext } from "../Writer"; + +export function writeInterfaces(type: TypeDescription, ctx: WriterContext) { + ctx.append(`_ supported_interfaces() method_id {`); + ctx.inIndent(() => { + ctx.append(`return (`); + ctx.inIndent(() => { + // Build interfaces list + const interfaces: string[] = []; + interfaces.push("org.ton.introspection.v0"); + interfaces.push(...getSupportedInterfaces(type, ctx.ctx)); + + // Render interfaces + for (let i = 0; i < interfaces.length; i++) { + ctx.append( + `"${interfaces[i]}"H >> 128${i < interfaces.length - 1 ? "," : ""}`, + ); + } + }); + ctx.append(`);`); + }); + ctx.append(`}`); + ctx.append(); +} diff --git a/src/generatorNew/writers/writeRouter.ts b/src/generatorNew/writers/writeRouter.ts new file mode 100644 index 000000000..99ab2d585 --- /dev/null +++ b/src/generatorNew/writers/writeRouter.ts @@ -0,0 +1,613 @@ +import { beginCell } from "@ton/core"; +import { getType } from "../../types/resolveDescriptors"; +import { ReceiverDescription, TypeDescription } from "../../types/types"; +import { WriterContext } from "../Writer"; +import { funcIdOf } from "./id"; +import { ops } from "./ops"; +import { resolveFuncType } from "./resolveFuncTypeNew"; +import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; +import { writeStatement } from "./writeFunction"; +import { AstNumber } from "../../grammar/ast"; +import { prettyPrintType } from "../../func/prettyPrinter"; +import { + cr, + comment, + assign, + expr, + call, + binop, + bool, + int, + hex, +fun, + ret, + tensor, + Type, + FunAttr, + vardef, + condition, + id, +} from "../../func/syntaxConstructors"; +import {FuncAstFunctionAttribute, FuncAstType, FuncAstStatement} from "../../func/grammar"; + +export function commentPseudoOpcode(comment: string): string { + return beginCell() + .storeUint(0, 32) + .storeBuffer(Buffer.from(comment, "utf8")) + .endCell() + .hash() + .toString("hex", 0, 64); +} + +export function writeRouter( + type: TypeDescription, + kind: "internal" | "external", + ctx: WriterContext, +): void { + const internal = kind === "internal"; + const attrs: FuncAstFunctionAttribute[] = [ + FunAttr.impure(), + FunAttr.inline_ref(), + ]; + const name = ops.contractRouter(type.name, kind); + const returnTy = Type.tensor( + resolveFuncType(type, false, false, ctx), + Type.int(), + ); + const paramValues: [string, FuncAstType][] = internal + ? [ + ["self", resolveFuncType(type, false, false, ctx)], + ["msg_bounced", Type.int()], + ["in_msg", Type.slice()], + ] + : [ + ["self", resolveFuncType(type, false, false, ctx)], + ["in_msg", Type.slice()], + ]; + const functionBody: FuncAstStatement[] = []; + + // ;; Handle bounced messages + // if (msg_bounced) { + // ... + // } + if (internal) { + functionBody.push(comment("Handle bounced messages")); + const body: FuncAstStatement[] = []; + const bounceReceivers = type.receivers.filter((r) => { + return r.selector.kind === "bounce-binary"; + }); + + const fallbackReceiver = type.receivers.find((r) => { + return r.selector.kind === "bounce-fallback"; + }); + + const condBody: FuncAstStatement[] = []; + if (fallbackReceiver ?? bounceReceivers.length > 0) { + // ;; Skip 0xFFFFFFFF + // in_msg~skip_bits(32); + condBody.push(comment("Skip 0xFFFFFFFF")); + condBody.push(expr(call("in_msg~skip_bits", tensor(int(32))))); + } + + if (bounceReceivers.length > 0) { + // ;; Parse op + // int op = 0; + // if (slice_bits(in_msg) >= 32) { + // op = in_msg.preload_uint(32); + // } + condBody.push(comment("Parse op")); + condBody.push(vardef(Type.int(), "op", int(0))); + condBody.push( + condition( + binop( + call("slice_bits", tensor(id("in_msg"))), + ">=", + int(30), + ), + [ + expr( + assign( + id("op"), + call(id("in_msg.preload_uint"), tensor(int(32))), + ), + ), + ], + ), + ); + } + + for (const r of bounceReceivers) { + const selector = r.selector; + if (selector.kind !== "bounce-binary") + throw Error(`Invalid selector type: ${selector.kind}`); // Should not happen + body.push( + comment(`Bounced handler for ${selector.type} message`), + ); + // XXX: We assert `header` to be non-null only in the new backend; otherwise it could be a compiler bug + body.push( + condition( + binop( + id("op"), + "==", + int(getType(ctx.ctx, selector.type).header!.value), + ), + [ + vardef( + "_", + "msg", + call( + id( + `in_msg~${selector.bounced ? ops.readerBounced(selector.type, ctx) : ops.reader(selector.type, ctx)}`, + ), + tensor(), + ), + ), + expr( + call( + id( + `self~${ops.receiveTypeBounce(type.name, selector.type)}`, + ), + tensor(id("msg")), + ), + ), + ret(tensor(id("self"), bool(true))), + ], + ), + ); + } + + if (fallbackReceiver) { + const selector = fallbackReceiver.selector; + if (selector.kind !== "bounce-fallback") + throw Error("Invalid selector type: " + selector.kind); + body.push(comment("Fallback bounce receiver")); + body.push( + expr( + call(id(`self~${ops.receiveBounceAny(type.name)}`), tensor( + id("in_msg"), + )), + ), + ); + body.push(ret(tensor(id("self"), bool(true)))); + } else { + body.push(ret(tensor(id("self"), bool(true)))); + } + const cond = condition(id("msg_bounced"), body); + functionBody.push(cond); + functionBody.push(cr()); + } + + // ;; Parse incoming message + // int op = 0; + // if (slice_bits(in_msg) >= 32) { + // op = in_msg.preload_uint(32); + // } + functionBody.push(comment("Parse incoming message")); + functionBody.push(vardef(Type.int(), "op", int(0))); + functionBody.push( + condition( + binop(call(id("slice_bits"), tensor(id("in_msg"))), ">=", int(32)), + [ + expr( + assign( + id("op"), + call("in_msg.preload_uint", tensor(int(32))), + ), + ), + ], + ), + ); + functionBody.push(cr()); + + // Non-empty receivers + for (const f of type.receivers) { + const selector = f.selector; + + // Generic receiver + if ( + selector.kind === + (internal ? "internal-binary" : "external-binary") + ) { + const allocation = getType(ctx.ctx, selector.type); + if (!allocation.header) { + throw Error(`Invalid allocation: ${selector.type}`); + } + functionBody.push(comment(`Receive ${selector.type} message`)); + functionBody.push( + condition(binop(id("op"), "==", int(allocation.header.value)), [ + vardef( + "_", + "msg", + call(`in_msg~${ops.reader(selector.type, ctx)}`, tensor()), + ), + expr( + call( + `self~${ops.receiveType(type.name, kind, selector.type)}`, + tensor(id("msg")), + ), + ), + ]), + ); + } + + if ( + selector.kind === + (internal ? "internal-empty" : "external-empty") + ) { + // ;; Receive empty message + // if ((op == 0) & (slice_bits(in_msg) <= 32)) { + // self~${ops.receiveEmpty(type.name, kind)}(); + // return (self, true); + // } + functionBody.push(comment("Receive empty message")); + functionBody.push( + condition( + binop( + binop(id("op"), "==", int(0)), + "&", + binop( + call("slice_bits", tensor(id("in_msg"))), + "<=", + int(32), + ), + ), + [ + expr( + call( + `self~${ops.receiveEmpty(type.name, kind)}`, + tensor(), + ), + ), + ret(tensor(id("self"), bool(true))), + ], + ), + ); + functionBody.push(cr()); + } + } + + // Text resolvers + const hasComments = !!type.receivers.find((v) => + internal + ? v.selector.kind === "internal-comment" || + v.selector.kind === "internal-comment-fallback" + : v.selector.kind === "external-comment" || + v.selector.kind === "external-comment-fallback", + ); + if (hasComments) { + // ;; Text Receivers + // if (op == 0) { + // ... + // } + functionBody.push(comment("Text Receivers")); + const cond = binop(id("op"), "==", int(0)); + const condBody: FuncAstStatement[] = []; + if ( + type.receivers.find( + (v) => + v.selector.kind === + (internal ? "internal-comment" : "external-comment"), + ) + ) { + // var text_op = slice_hash(in_msg); + condBody.push( + vardef("_", "text_op", call("slice_hash", tensor(id("in_msg")))), + ); + for (const r of type.receivers) { + if ( + r.selector.kind === + (internal ? "internal-comment" : "external-comment") + ) { + // ;; Receive "increment" message + // if (text_op == 0xc4f8d72312edfdef5b7bec7833bdbb162d1511bd78a912aed0f2637af65572ae) { + // self~$A$_internal_text_c4f8d72312edfdef5b7bec7833bdbb162d1511bd78a912aed0f2637af65572ae(); + // return (self, true); + // } + const hash = commentPseudoOpcode( + r.selector.comment, + ); + condBody.push( + comment(`Receive "${r.selector.comment}" message`), + ); + condBody.push( + condition(binop(id("text_op"), "==", hex(hash)), [ + expr( + call( + `self~${ops.receiveText(type.name, kind, hash)}`, + tensor(), + ), + ), + ret(tensor(id("self"), bool(true))), + ]), + ); + } + } + } + + // Comment fallback resolver + const fallback = type.receivers.find( + (v) => + v.selector.kind === + (internal + ? "internal-comment-fallback" + : "external-comment-fallback"), + ); + if (fallback) { + condBody.push( + condition( + binop( + call("slice_bits", tensor(id("in_msg"))), + ">=", + int(32), + ), + [ + expr( + call( + id( + `self~${ops.receiveAnyText(type.name, kind)}`, + ), + tensor(call("in_msg.skip_bits", tensor(int(32)))), + ), + ), + ret(tensor(id("self"), bool(true))), + ], + ), + ); + } + functionBody.push(condition(cond, condBody)); + functionBody.push(cr()); + } + + // Fallback + const fallbackReceiver = type.receivers.find( + (v) => + v.selector.kind === + (internal ? "internal-fallback" : "external-fallback"), + ); + if (fallbackReceiver) { + // ;; Receiver fallback + // self~${ops.receiveAny(type.name, kind)}(in_msg); + // return (self, true); + functionBody.push(comment("Receiver fallback")); + functionBody.push( + expr( + call(`self~${ops.receiveAny(type.name, kind)}`, tensor( + id("in_msg"), + )), + ), + ); + functionBody.push(ret(tensor(id("self"), bool(true)))); + } else { + // return (self, false); + functionBody.push(ret(tensor(id("self"), bool(false)))); + } + + const receiver = fun(name, paramValues, attrs, returnTy, functionBody); + ctx.appendNode(receiver); +} + +function messageOpcode(n: AstNumber): string { + // FunC does not support binary and octal numerals + switch (n.base) { + case 10: + return n.value.toString(n.base); + case 2: + case 8: + case 16: + return `0x${n.value.toString(n.base)}`; + } +} + +export function writeReceiver( + self: TypeDescription, + f: ReceiverDescription, + ctx: WriterContext, +) { + const selector = f.selector; + const selfRes = resolveFuncTypeUnpack(self, funcIdOf("self"), ctx); + const selfType = prettyPrintType(resolveFuncType(self, false, false, ctx)); + const selfUnpack = `var ${resolveFuncTypeUnpack(self, funcIdOf("self"), ctx)} = ${funcIdOf("self")};`; + + // Binary receiver + if ( + selector.kind === "internal-binary" || + selector.kind === "external-binary" + ) { + const args = [ + selfType + " " + funcIdOf("self"), + prettyPrintType(resolveFuncType(selector.type, false, false, ctx)) + " " + funcIdOf(selector.name), + ]; + ctx.append( + `((${selfType}), ()) ${ops.receiveType(self.name, selector.kind === "internal-binary" ? "internal" : "external", selector.type)}(${args.join(", ")}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + ctx.append( + `var ${resolveFuncTypeUnpack(selector.type, funcIdOf(selector.name), ctx)} = ${funcIdOf(selector.name)};`, + ); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } + + // Empty receiver + if ( + selector.kind === "internal-empty" || + selector.kind === "external-empty" + ) { + ctx.append( + `((${selfType}), ()) ${ops.receiveEmpty(self.name, selector.kind === "internal-empty" ? "internal" : "external")}(${selfType + " " + funcIdOf("self")}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } + + // Comment receiver + if ( + selector.kind === "internal-comment" || + selector.kind === "external-comment" + ) { + const hash = commentPseudoOpcode(selector.comment); + ctx.append( + `(${selfType}, ()) ${ops.receiveText(self.name, selector.kind === "internal-comment" ? "internal" : "external", hash)}(${selfType + " " + funcIdOf("self")}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } + + // Fallback + if ( + selector.kind === "internal-comment-fallback" || + selector.kind === "external-comment-fallback" + ) { + ctx.append( + `(${selfType}, ()) ${ops.receiveAnyText(self.name, selector.kind === "internal-comment-fallback" ? "internal" : "external")}(${[selfType + " " + funcIdOf("self"), "slice " + funcIdOf(selector.name)].join(", ")}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } + + // Fallback + if (selector.kind === "internal-fallback") { + ctx.append( + `(${selfType}, ()) ${ops.receiveAny(self.name, "internal")}(${selfType} ${funcIdOf("self")}, slice ${funcIdOf(selector.name)}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } + + // Bounced + if (selector.kind === "bounce-fallback") { + ctx.append( + `(${selfType}, ()) ${ops.receiveBounceAny(self.name)}(${selfType} ${funcIdOf("self")}, slice ${funcIdOf(selector.name)}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } + + if (selector.kind === "bounce-binary") { + const args = [ + selfType + " " + funcIdOf("self"), + prettyPrintType(resolveFuncType(selector.type,false, selector.bounced, ctx)) + + " " + + funcIdOf(selector.name), + ]; + ctx.append( + `((${selfType}), ()) ${ops.receiveTypeBounce(self.name, selector.type)}(${args.join(", ")}) impure inline {`, + ); + ctx.inIndent(() => { + ctx.append(selfUnpack); + ctx.append( + `var ${resolveFuncTypeUnpack(selector.type, funcIdOf(selector.name), ctx, false, selector.bounced)} = ${funcIdOf(selector.name)};`, + ); + + for (const s of f.ast.statements) { + writeStatement(s, selfRes, null, ctx); + } + + if ( + f.ast.statements.length === 0 || + f.ast.statements[f.ast.statements.length - 1]!.kind !== + "statement_return" + ) { + ctx.append(`return (${selfRes}, ());`); + } + }); + ctx.append(`}`); + ctx.append(); + return; + } +} diff --git a/src/generatorNew/writers/writeSerialization.spec.ts b/src/generatorNew/writers/writeSerialization.spec.ts new file mode 100644 index 000000000..cafbd3307 --- /dev/null +++ b/src/generatorNew/writers/writeSerialization.spec.ts @@ -0,0 +1,96 @@ +import { __DANGER_resetNodeId } from "../../grammar/ast"; +import { CompilerContext } from "../../context"; +import { + getAllocation, + resolveAllocations, +} from "../../storage/resolveAllocation"; +import { + getAllTypes, + getType, + resolveDescriptors, +} from "../../types/resolveDescriptors"; +import { WriterContext } from "../Writer"; +import { writeParser, writeSerializer } from "./writeSerialization"; +import { writeStdlib } from "./writeStdlib"; +import { openContext } from "../../grammar/store"; +import { writeAccessors } from "./writeAccessors"; + +const code = ` +primitive Int; +primitive Bool; +primitive Builder; +primitive Cell; +primitive Slice; +primitive Address; + +struct A { + a: Int; + b: Int; + c: Int?; + d: Bool; + e: Bool?; + f: Int; + g: Int; +} + +struct B { + a: Int; + b: Int; + c: Int?; + d: Bool; + e: Bool?; + f: Int; + g: Int; +} + +struct C { + a: Cell; + b: Cell?; + c: Slice?; + d: Slice?; + e: Bool; + f: Int; + g: Int; + h: Address; +} +`; + +describe("writeSerialization", () => { + beforeEach(() => { + __DANGER_resetNodeId(); + }); + for (const s of ["A", "B", "C"]) { + it("should write serializer for " + s, () => { + let ctx = openContext( + new CompilerContext(), + [{ code, path: "", origin: "user" }], + [], + ); + ctx = resolveDescriptors(ctx); + ctx = resolveAllocations(ctx); + const wCtx = new WriterContext(ctx, s); + writeStdlib(wCtx); + writeSerializer( + getType(ctx, s).name, + false, + getAllocation(ctx, s), + "user", + wCtx, + ); + for (const t of getAllTypes(ctx)) { + if (t.kind === "contract" || t.kind === "struct") { + writeAccessors(t, "user", wCtx); + } + } + writeParser( + getType(ctx, s).name, + false, + getAllocation(ctx, s), + "user", + wCtx, + ); + const extracted = wCtx.extract(true); + expect(extracted).toMatchSnapshot(); + }); + } +}); diff --git a/src/generatorNew/writers/writeSerialization.ts b/src/generatorNew/writers/writeSerialization.ts new file mode 100644 index 000000000..aca186622 --- /dev/null +++ b/src/generatorNew/writers/writeSerialization.ts @@ -0,0 +1,647 @@ +import { contractErrors } from "../../abi/errors"; +import { throwInternalCompilerError } from "../../errors"; +import { dummySrcInfo, ItemOrigin } from "../../grammar/grammar"; +import { AllocationCell, AllocationOperation } from "../../storage/operation"; +import { StorageAllocation } from "../../storage/StorageAllocation"; +import { getType } from "../../types/resolveDescriptors"; +import { WriterContext } from "../Writer"; +import { ops } from "./ops"; +import { resolveFuncTypeFromAbi } from "./resolveFuncTypeFromAbi"; +import { resolveFuncTypeFromAbiUnpack } from "./resolveFuncTypeFromAbiUnpack"; + +const SMALL_STRUCT_MAX_FIELDS = 5; + +// +// Serializer +// + +export function writeSerializer( + name: string, + forceInline: boolean, + allocation: StorageAllocation, + origin: ItemOrigin, + ctx: WriterContext, +) { + const isSmall = allocation.ops.length <= SMALL_STRUCT_MAX_FIELDS; + + // Write to builder + ctx.fun(ops.writer(name, ctx), () => { + ctx.signature( + `builder ${ops.writer(name, ctx)}(builder build_0, ${resolveFuncTypeFromAbi( + allocation.ops.map((v) => v.type), + ctx, + )} v)`, + ); + if (forceInline || isSmall) { + ctx.flag("inline"); + } + ctx.context("type:" + name); + ctx.body(() => { + if (allocation.ops.length > 0) { + ctx.append( + `var ${resolveFuncTypeFromAbiUnpack(`v`, allocation.ops, ctx)} = v;`, + ); + } + if (allocation.header) { + ctx.append( + `build_0 = store_uint(build_0, ${allocation.header.value}, ${allocation.header.bits});`, + ); + } + writeSerializerCell(allocation.root, 0, ctx); + ctx.append(`return build_0;`); + }); + }); + + // Write to cell + ctx.fun(ops.writerCell(name, ctx), () => { + ctx.signature( + `cell ${ops.writerCell(name, ctx)}(${resolveFuncTypeFromAbi( + allocation.ops.map((v) => v.type), + ctx, + )} v)`, + ); + ctx.flag("inline"); + ctx.context("type:" + name); + ctx.body(() => { + ctx.append( + `return ${ops.writer(name, ctx)}(begin_cell(), v).end_cell();`, + ); + }); + }); +} + +export function writeOptionalSerializer( + name: string, + origin: ItemOrigin, + ctx: WriterContext, +) { + ctx.fun(ops.writerCellOpt(name, ctx), () => { + ctx.signature(`cell ${ops.writerCellOpt(name, ctx)}(tuple v)`); + ctx.flag("inline"); + ctx.context("type:" + name); + ctx.body(() => { + ctx.write(` + if (null?(v)) { + return null(); + } + return ${ops.writerCell(name, ctx)}(${ops.typeNotNull(name, ctx)}(v)); + `); + }); + }); +} + +function writeSerializerCell( + cell: AllocationCell, + gen: number, + ctx: WriterContext, +) { + // Write fields + for (const f of cell.ops) { + writeSerializerField(f, gen, ctx); + } + + // Tail + if (cell.next) { + ctx.append(`var build_${gen + 1} = begin_cell();`); + writeSerializerCell(cell.next, gen + 1, ctx); + ctx.append( + `build_${gen} = store_ref(build_${gen}, build_${gen + 1}.end_cell());`, + ); + } +} + +function writeSerializerField( + f: AllocationOperation, + gen: number, + ctx: WriterContext, +) { + const fieldName = `v'${f.name}`; + const op = f.op; + + switch (op.kind) { + case "int": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_int(${fieldName}, ${op.bits}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_int(${fieldName}, ${op.bits});`, + ); + } + return; + } + case "uint": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_uint(${fieldName}, ${op.bits}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_uint(${fieldName}, ${op.bits});`, + ); + } + return; + } + case "coins": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_coins(${fieldName}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_coins(${fieldName});`, + ); + } + return; + } + case "boolean": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_int(${fieldName}, 1) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_int(${fieldName}, 1);`, + ); + } + return; + } + case "address": { + if (op.optional) { + ctx.used(`__tact_store_address_opt`); + ctx.append( + `build_${gen} = __tact_store_address_opt(build_${gen}, ${fieldName});`, + ); + } else { + ctx.used(`__tact_store_address`); + ctx.append( + `build_${gen} = __tact_store_address(build_${gen}, ${fieldName});`, + ); + } + return; + } + case "cell": { + switch (op.format) { + case "default": + { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(${fieldName}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(${fieldName});`, + ); + } + } + break; + case "remainder": + { + if (op.optional) { + throw Error("Impossible"); + } + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName}.begin_parse());`, + ); + } + break; + } + return; + } + case "slice": { + switch (op.format) { + case "default": + { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}).end_cell()) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}).end_cell());`, + ); + } + } + break; + case "remainder": { + if (op.optional) { + throw Error("Impossible"); + } + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName});`, + ); + } + } + return; + } + case "builder": { + switch (op.format) { + case "default": + { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}.end_cell().begin_parse()).end_cell()) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}.end_cell().begin_parse()).end_cell());`, + ); + } + } + break; + case "remainder": { + if (op.optional) { + throw Error("Impossible"); + } + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName}.end_cell().begin_parse());`, + ); + } + } + return; + } + case "string": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_ref(begin_cell().store_slice(${fieldName}).end_cell()) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_ref(begin_cell().store_slice(${fieldName}).end_cell());`, + ); + } + return; + } + case "fixed-bytes": { + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).store_slice(${fieldName}) : build_${gen}.store_int(false, 1);`, + ); + } else { + ctx.append( + `build_${gen} = build_${gen}.store_slice(${fieldName});`, + ); + } + return; + } + case "map": { + ctx.append(`build_${gen} = build_${gen}.store_dict(${fieldName});`); + return; + } + case "struct": { + if (op.ref) { + throw Error("Not implemented"); + } + if (op.optional) { + ctx.append( + `build_${gen} = ~ null?(${fieldName}) ? build_${gen}.store_int(true, 1).${ops.writer(op.type, ctx)}(${ops.typeNotNull(op.type, ctx)}(${fieldName})) : build_${gen}.store_int(false, 1);`, + ); + } else { + const ff = getType(ctx.ctx, op.type).fields.map((f) => f.abi); + ctx.append( + `build_${gen} = ${ops.writer(op.type, ctx)}(build_${gen}, ${resolveFuncTypeFromAbiUnpack(fieldName, ff, ctx)});`, + ); + } + return; + } + } + + throwInternalCompilerError(`Unsupported field kind`, dummySrcInfo); +} + +// +// Parser +// + +export function writeParser( + name: string, + forceInline: boolean, + allocation: StorageAllocation, + origin: ItemOrigin, + ctx: WriterContext, +) { + const isSmall = allocation.ops.length <= SMALL_STRUCT_MAX_FIELDS; + + ctx.fun(ops.reader(name, ctx), () => { + ctx.signature( + `(slice, (${resolveFuncTypeFromAbi( + allocation.ops.map((v) => v.type), + ctx, + )})) ${ops.reader(name, ctx)}(slice sc_0)`, + ); + if (forceInline || isSmall) { + ctx.flag("inline"); + } + ctx.context("type:" + name); + ctx.body(() => { + // Check prefix + if (allocation.header) { + ctx.append( + `throw_unless(${contractErrors.invalidPrefix.id}, sc_0~load_uint(${allocation.header.bits}) == ${allocation.header.value});`, + ); + } + + // Write cell parser + writeCellParser(allocation.root, 0, ctx); + + // Compile tuple + if (allocation.ops.length === 0) { + ctx.append(`return (sc_0, null());`); + } else { + ctx.append( + `return (sc_0, (${allocation.ops.map((v) => `v'${v.name}`).join(", ")}));`, + ); + } + }); + }); + + // Write non-modifying variant + + ctx.fun(ops.readerNonModifying(name, ctx), () => { + ctx.signature( + `(${resolveFuncTypeFromAbi( + allocation.ops.map((v) => v.type), + ctx, + )}) ${ops.readerNonModifying(name, ctx)}(slice sc_0)`, + ); + if (forceInline || isSmall) { + ctx.flag("inline"); + } + ctx.context("type:" + name); + ctx.body(() => { + ctx.append(`var r = sc_0~${ops.reader(name, ctx)}();`); + ctx.append(`sc_0.end_parse();`); + ctx.append(`return r;`); + }); + }); +} + +export function writeBouncedParser( + name: string, + forceInline: boolean, + allocation: StorageAllocation, + origin: ItemOrigin, + ctx: WriterContext, +) { + const isSmall = allocation.ops.length <= SMALL_STRUCT_MAX_FIELDS; + + ctx.fun(ops.readerBounced(name, ctx), () => { + ctx.signature( + `(slice, (${resolveFuncTypeFromAbi( + allocation.ops.map((v) => v.type), + ctx, + )})) ${ops.readerBounced(name, ctx)}(slice sc_0)`, + ); + if (forceInline || isSmall) { + ctx.flag("inline"); + } + ctx.context("type:" + name); + ctx.body(() => { + // Check prefix + if (allocation.header) { + ctx.append( + `throw_unless(${contractErrors.invalidPrefix.id}, sc_0~load_uint(${allocation.header.bits}) == ${allocation.header.value});`, + ); + } + + // Write cell parser + writeCellParser(allocation.root, 0, ctx); + + // Compile tuple + if (allocation.ops.length === 0) { + ctx.append(`return (sc_0, null());`); + } else { + ctx.append( + `return (sc_0, (${allocation.ops.map((v) => `v'${v.name}`).join(", ")}));`, + ); + } + }); + }); +} + +export function writeOptionalParser( + name: string, + origin: ItemOrigin, + ctx: WriterContext, +) { + ctx.fun(ops.readerOpt(name, ctx), () => { + ctx.signature(`tuple ${ops.readerOpt(name, ctx)}(cell cl)`); + ctx.flag("inline"); + ctx.context("type:" + name); + ctx.body(() => { + ctx.write(` + if (null?(cl)) { + return null(); + } + var sc = cl.begin_parse(); + return ${ops.typeAsOptional(name, ctx)}(sc~${ops.reader(name, ctx)}()); + `); + }); + }); +} + +function writeCellParser( + cell: AllocationCell, + gen: number, + ctx: WriterContext, +): number { + // Write current fields + for (const f of cell.ops) { + writeFieldParser(f, gen, ctx); + } + + // Handle next cell + if (cell.next) { + ctx.append(`slice sc_${gen + 1} = sc_${gen}~load_ref().begin_parse();`); + return writeCellParser(cell.next, gen + 1, ctx); + } else { + return gen; + } +} + +function writeFieldParser( + f: AllocationOperation, + gen: number, + ctx: WriterContext, +) { + const op = f.op; + const varName = `var v'${f.name}`; + + switch (op.kind) { + case "int": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_int(${op.bits}) : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_int(${op.bits});`); + } + return; + } + case "uint": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_uint(${op.bits}) : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_uint(${op.bits});`); + } + return; + } + case "coins": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_coins() : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_coins();`); + } + return; + } + case "boolean": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_int(1) : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_int(1);`); + } + return; + } + case "address": { + if (op.optional) { + ctx.used(`__tact_load_address_opt`); + ctx.append(`${varName} = sc_${gen}~__tact_load_address_opt();`); + } else { + ctx.used(`__tact_load_address`); + ctx.append(`${varName} = sc_${gen}~__tact_load_address();`); + } + return; + } + case "cell": { + if (op.optional) { + if (op.format !== "default") { + throw new Error(`Impossible`); + } + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref() : null();`, + ); + } else { + switch (op.format) { + case "default": + { + ctx.append(`${varName} = sc_${gen}~load_ref();`); + } + break; + case "remainder": { + ctx.append( + `${varName} = begin_cell().store_slice(sc_${gen}).end_cell();`, + ); + } + } + } + return; + } + case "slice": { + if (op.optional) { + if (op.format !== "default") { + throw new Error(`Impossible`); + } + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref().begin_parse() : null();`, + ); + } else { + switch (op.format) { + case "default": + { + ctx.append( + `${varName} = sc_${gen}~load_ref().begin_parse();`, + ); + } + break; + case "remainder": + { + ctx.append(`${varName} = sc_${gen};`); + } + break; + } + } + return; + } + case "builder": { + if (op.optional) { + if (op.format !== "default") { + throw new Error(`Impossible`); + } + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? begin_cell().store_slice(sc_${gen}~load_ref().begin_parse()) : null();`, + ); + } else { + switch (op.format) { + case "default": + { + ctx.append( + `${varName} = begin_cell().store_slice(sc_${gen}~load_ref().begin_parse());`, + ); + } + break; + case "remainder": + { + ctx.append( + `${varName} = begin_cell().store_slice(sc_${gen});`, + ); + } + break; + } + } + return; + } + case "string": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_ref().begin_parse() : null();`, + ); + } else { + ctx.append(`${varName} = sc_${gen}~load_ref().begin_parse();`); + } + return; + } + case "fixed-bytes": { + if (op.optional) { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? sc_${gen}~load_bits(${op.bytes * 8}) : null();`, + ); + } else { + ctx.append( + `${varName} = sc_${gen}~load_bits(${op.bytes * 8});`, + ); + } + return; + } + case "map": { + ctx.append(`${varName} = sc_${gen}~load_dict();`); + return; + } + case "struct": { + if (op.optional) { + if (op.ref) { + throw Error("Not implemented"); + } else { + ctx.append( + `${varName} = sc_${gen}~load_int(1) ? ${ops.typeAsOptional(op.type, ctx)}(sc_${gen}~${ops.reader(op.type, ctx)}()) : null();`, + ); + } + } else { + if (op.ref) { + throw Error("Not implemented"); + } else { + ctx.append( + `${varName} = sc_${gen}~${ops.reader(op.type, ctx)}();`, + ); + } + } + return; + } + } +} diff --git a/src/generatorNew/writers/writeStdlib.ts b/src/generatorNew/writers/writeStdlib.ts new file mode 100644 index 000000000..7d563f8c6 --- /dev/null +++ b/src/generatorNew/writers/writeStdlib.ts @@ -0,0 +1,1226 @@ +import { WriterContext } from "../Writer"; +import { contractErrors } from "../../abi/errors"; +import { enabledMasterchain } from "../../config/features"; + +export function writeStdlib(ctx: WriterContext): void { + const parse = (code: string) => + ctx.parse(code, { context: "stdlib" }); + + // + // stdlib extension functions + // + // + ctx.skip("__tact_set"); + ctx.skip("__tact_nop"); + ctx.skip("__tact_str_to_slice"); + ctx.skip("__tact_slice_to_str"); + ctx.skip("__tact_address_to_slice"); + + // + // Addresses + // + + parse( + `slice __tact_verify_address(slice address) impure inline { + throw_unless(${contractErrors.invalidAddress.id}, address.slice_bits() == 267); + var h = address.preload_uint(11); + + ${ + enabledMasterchain(ctx.ctx) + ? ` + throw_unless(${contractErrors.invalidAddress.id}, (h == 1024) | (h == 1279)); + ` + : ` + throw_if(${contractErrors.masterchainNotEnabled.id}, h == 1279); + throw_unless(${contractErrors.invalidAddress.id}, h == 1024); + ` + } + + return address; + }`, + ); + + parse(`(slice, int) __tact_load_bool(slice s) asm(s -> 1 0) "1 LDI";`); + + parse( + `(slice, slice) __tact_load_address(slice cs) inline { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); + }`, + ); + + parse( + `(slice, slice) __tact_load_address_opt(slice cs) inline { + if (cs.preload_uint(2) != 0) { + slice raw = cs~load_msg_addr(); + return (cs, __tact_verify_address(raw)); + } else { + cs~skip_bits(2); + return (cs, null()); + } + }`, + ); + + parse( + `builder __tact_store_address(builder b, slice address) inline { + return b.store_slice(__tact_verify_address(address)); + }`, + ); + + parse( + `builder __tact_store_address_opt(builder b, slice address) inline { + if (null?(address)) { + b = b.store_uint(0, 2); + return b; + } else { + return __tact_store_address(b, address); + } + }`, + ); + + parse( + `slice __tact_create_address(int chain, int hash) inline { + var b = begin_cell(); + b = b.store_uint(2, 2); + b = b.store_uint(0, 1); + b = b.store_int(chain, 8); + b = b.store_uint(hash, 256); + var addr = b.end_cell().begin_parse(); + return __tact_verify_address(addr); + }`, + ); + + parse( + `slice __tact_compute_contract_address(int chain, cell code, cell data) inline { + var b = begin_cell(); + b = b.store_uint(0, 2); + b = b.store_uint(3, 2); + b = b.store_uint(0, 1); + b = b.store_ref(code); + b = b.store_ref(data); + var hash = cell_hash(b.end_cell()); + return __tact_create_address(chain, hash); + }`, + ); + + parse( + `int __tact_my_balance() inline { + return pair_first(get_balance()); + }`, + ); + + parse( + `forall X -> X __tact_not_null(X x) inline { + throw_if(${contractErrors.null.id}, null?(x)); + return x; + }`, + ); + + parse( + `(cell, int) __tact_dict_delete(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL";`, + ); + + parse( + `(cell, int) __tact_dict_delete_int(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL";`, + ); + + parse( + `(cell, int) __tact_dict_delete_uint(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL";`, + ); + + parse( + `((cell), ()) __tact_dict_set_ref(cell dict, int key_len, slice index, cell value) asm(value index dict key_len) "DICTSETREF";`, + ); + + parse( + `(slice, int) __tact_dict_get(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT";`, + ); + + parse( + `(cell, int) __tact_dict_get_ref(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGETREF" "NULLSWAPIFNOT";`, + ); + + parse( + `(slice, slice, int) __tact_dict_min(cell dict, int key_len) asm(dict key_len -> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2";`, + ); + + parse( + `(slice, cell, int) __tact_dict_min_ref(cell dict, int key_len) asm(dict key_len -> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2";`, + ); + + parse( + `(slice, slice, int) __tact_dict_next(cell dict, int key_len, slice pivot) asm(pivot dict key_len -> 1 0 2) "DICTGETNEXT" "NULLSWAPIFNOT2";`, + ); + + parse( + `(slice, cell, int) __tact_dict_next_ref(cell dict, int key_len, slice pivot) inline { + var (key, value, flag) = __tact_dict_next(dict, key_len, pivot); + if (flag) { + return (key, value~load_ref(), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `forall X -> () __tact_debug(X value, slice debug_print) impure asm "STRDUMP" "DROP" "s0 DUMP" "DROP";`, + ); + + parse( + `() __tact_debug_str(slice value, slice debug_print) impure asm "STRDUMP" "DROP" "STRDUMP" "DROP";`, + ); + + parse( + `() __tact_debug_bool(int value, slice debug_print) impure { + if (value) { + __tact_debug_str("true", debug_print); + } else { + __tact_debug_str("false", debug_print); + } + }`, + ); + + parse( + `(slice) __tact_preload_offset(slice s, int offset, int bits) inline asm "SDSUBSTR";`, + ); + + parse( + `(slice) __tact_crc16(slice data) inline_ref { + slice new_data = begin_cell() + .store_slice(data) + .store_slice("0000"s) + .end_cell().begin_parse(); + int reg = 0; + while (~new_data.slice_data_empty?()) { + int byte = new_data~load_uint(8); + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (byte & mask) { + reg += 1; + } + mask >>= 1; + if (reg > 0xffff) { + reg &= 0xffff; + reg ^= 0x1021; + } + } + } + (int q, int r) = divmod(reg, 256); + return begin_cell() + .store_uint(q, 8) + .store_uint(r, 8) + .end_cell().begin_parse(); + }`, + ); + + parse( + `(slice) __tact_base64_encode(slice data) inline { + slice chars = "4142434445464748494A4B4C4D4E4F505152535455565758595A6162636465666768696A6B6C6D6E6F707172737475767778797A303132333435363738392D5F"s; + builder res = begin_cell(); + + while (data.slice_bits() >= 24) { + (int bs1, int bs2, int bs3) = (data~load_uint(8), data~load_uint(8), data~load_uint(8)); + + int n = (bs1 << 16) | (bs2 << 8) | bs3; + + res = res + .store_slice(__tact_preload_offset(chars, ((n >> 18) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 12) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n >> 6) & 63) * 8, 8)) + .store_slice(__tact_preload_offset(chars, ((n ) & 63) * 8, 8)); + } + + return res.end_cell().begin_parse(); + }`, + ); + + parse( + `(slice) __tact_address_to_user_friendly(slice address) inline { + (int wc, int hash) = address.parse_std_addr(); + + slice user_friendly_address = begin_cell() + .store_slice("11"s) + .store_uint((wc + 0x100) % 0x100, 8) + .store_uint(hash, 256) + .end_cell().begin_parse(); + + slice checksum = __tact_crc16(user_friendly_address); + slice user_friendly_address_with_checksum = begin_cell() + .store_slice(user_friendly_address) + .store_slice(checksum) + .end_cell().begin_parse(); + + return __tact_base64_encode(user_friendly_address_with_checksum); + }`, + ); + + parse( + `() __tact_debug_address(slice address, slice debug_print) impure { + __tact_debug_str(__tact_address_to_user_friendly(address), debug_print); + }`, + ); + + parse( + `() __tact_debug_stack(slice debug_print) impure asm "STRDUMP" "DROP" "DUMPSTK";`, + ); + + parse( + `(int, slice, int, slice) __tact_context_get() inline { + return __tact_context; + }`, + ); + + parse( + `slice __tact_context_get_sender() inline { + return __tact_context_sender; + }`, + ); + + parse( + `() __tact_prepare_random() impure inline { + if (null?(__tact_randomized)) { + randomize_lt(); + __tact_randomized = true; + } + }`, + ); + + parse( + `builder __tact_store_bool(builder b, int v) inline { + return b.store_int(v, 1); + }`, + ); + + parse(`forall X -> tuple __tact_to_tuple(X x) asm "NOP";`); + + parse(`forall X -> X __tact_from_tuple(tuple x) asm "NOP";`); + + // + // Dict Int -> Int + // + + parse( + `(cell, ()) __tact_dict_set_int_int(cell d, int kl, int k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } + }`, + ); + + parse( + `int __tact_dict_get_int_int(cell d, int kl, int k, int vl) inline { + var (r, ok) = idict_get?(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } + }`, + ); + + parse( + `(int, int, int) __tact_dict_min_int_int(cell d, int kl, int vl) inline { + var (key, value, flag) = idict_get_min?(d, kl); + if (flag) { + return (key, value~load_int(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, int, int) __tact_dict_next_int_int(cell d, int kl, int pivot, int vl) inline { + var (key, value, flag) = idict_get_next?(d, kl, pivot); + if (flag) { + return (key, value~load_int(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Int -> Uint + // + + parse( + `(cell, ()) __tact_dict_set_uint_int(cell d, int kl, int k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); + } else { + return (udict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } + }`, + ); + + parse( + `int __tact_dict_get_uint_int(cell d, int kl, int k, int vl) inline { + var (r, ok) = udict_get?(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } + }`, + ); + + parse( + `(int, int, int) __tact_dict_min_uint_int(cell d, int kl, int vl) inline { + var (key, value, flag) = udict_get_min?(d, kl); + if (flag) { + return (key, value~load_int(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, int, int) __tact_dict_next_uint_int(cell d, int kl, int pivot, int vl) inline { + var (key, value, flag) = udict_get_next?(d, kl, pivot); + if (flag) { + return (key, value~load_int(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Uint -> Uint + // + + parse( + `(cell, ()) __tact_dict_set_uint_uint(cell d, int kl, int k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); + } else { + return (udict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); + } + }`, + ); + + parse( + `int __tact_dict_get_uint_uint(cell d, int kl, int k, int vl) inline { + var (r, ok) = udict_get?(d, kl, k); + if (ok) { + return r~load_uint(vl); + } else { + return null(); + } + }`, + ); + + parse( + `(int, int, int) __tact_dict_min_uint_uint(cell d, int kl, int vl) inline { + var (key, value, flag) = udict_get_min?(d, kl); + if (flag) { + return (key, value~load_uint(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, int, int) __tact_dict_next_uint_uint(cell d, int kl, int pivot, int vl) inline { + var (key, value, flag) = udict_get_next?(d, kl, pivot); + if (flag) { + return (key, value~load_uint(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Int -> Cell + // + + parse( + `(cell, ()) __tact_dict_set_int_cell(cell d, int kl, int k, cell v) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set_ref(d, kl, k, v), ()); + } + }`, + ); + + parse( + `cell __tact_dict_get_int_cell(cell d, int kl, int k) inline { + var (r, ok) = idict_get_ref?(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } + }`, + ); + + parse( + `(int, cell, int) __tact_dict_min_int_cell(cell d, int kl) inline { + var (key, value, flag) = idict_get_min_ref?(d, kl); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, cell, int) __tact_dict_next_int_cell(cell d, int kl, int pivot) inline { + var (key, value, flag) = idict_get_next?(d, kl, pivot); + if (flag) { + return (key, value~load_ref(), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Uint -> Cell + // + + parse( + `(cell, ()) __tact_dict_set_uint_cell(cell d, int kl, int k, cell v) inline { + if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); + } else { + return (udict_set_ref(d, kl, k, v), ()); + } + }`, + ); + + parse( + `cell __tact_dict_get_uint_cell(cell d, int kl, int k) inline { + var (r, ok) = udict_get_ref?(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } + }`, + ); + + parse( + `(int, cell, int) __tact_dict_min_uint_cell(cell d, int kl) inline { + var (key, value, flag) = udict_get_min_ref?(d, kl); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, cell, int) __tact_dict_next_uint_cell(cell d, int kl, int pivot) inline { + var (key, value, flag) = udict_get_next?(d, kl, pivot); + if (flag) { + return (key, value~load_ref(), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Int -> Slice + // + + parse( + `(cell, ()) __tact_dict_set_int_slice(cell d, int kl, int k, slice v) inline { + if (null?(v)) { + var (r, ok) = idict_delete?(d, kl, k); + return (r, ()); + } else { + return (idict_set(d, kl, k, v), ()); + } + }`, + ); + + parse( + `slice __tact_dict_get_int_slice(cell d, int kl, int k) inline { + var (r, ok) = idict_get?(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } + }`, + ); + + parse( + `(int, slice, int) __tact_dict_min_int_slice(cell d, int kl) inline { + var (key, value, flag) = idict_get_min?(d, kl); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, slice, int) __tact_dict_next_int_slice(cell d, int kl, int pivot) inline { + var (key, value, flag) = idict_get_next?(d, kl, pivot); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Uint -> Slice + // + + parse( + `(cell, ()) __tact_dict_set_uint_slice(cell d, int kl, int k, slice v) inline { + if (null?(v)) { + var (r, ok) = udict_delete?(d, kl, k); + return (r, ()); + } else { + return (udict_set(d, kl, k, v), ()); + } + }`, + ); + + parse( + `slice __tact_dict_get_uint_slice(cell d, int kl, int k) inline { + var (r, ok) = udict_get?(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } + }`, + ); + + parse( + `(int, slice, int) __tact_dict_min_uint_slice(cell d, int kl) inline { + var (key, value, flag) = udict_get_min?(d, kl); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(int, slice, int) __tact_dict_next_uint_slice(cell d, int kl, int pivot) inline { + var (key, value, flag) = udict_get_next?(d, kl, pivot); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Slice -> Int + // + + parse( + `(cell, ()) __tact_dict_set_slice_int(cell d, int kl, slice k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return (dict_set_builder(d, kl, k, begin_cell().store_int(v, vl)), ()); + } + }`, + ); + + parse( + `int __tact_dict_get_slice_int(cell d, int kl, slice k, int vl) inline { + var (r, ok) = __tact_dict_get(d, kl, k); + if (ok) { + return r~load_int(vl); + } else { + return null(); + } + }`, + ); + + parse( + `(slice, int, int) __tact_dict_min_slice_int(cell d, int kl, int vl) inline { + var (key, value, flag) = __tact_dict_min(d, kl); + if (flag) { + return (key, value~load_int(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(slice, int, int) __tact_dict_next_slice_int(cell d, int kl, slice pivot, int vl) inline { + var (key, value, flag) = __tact_dict_next(d, kl, pivot); + if (flag) { + return (key, value~load_int(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Slice -> UInt + // + + parse( + `(cell, ()) __tact_dict_set_slice_uint(cell d, int kl, slice k, int v, int vl) inline { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return (dict_set_builder(d, kl, k, begin_cell().store_uint(v, vl)), ()); + } + }`, + ); + + parse( + `int __tact_dict_get_slice_uint(cell d, int kl, slice k, int vl) inline { + var (r, ok) = __tact_dict_get(d, kl, k); + if (ok) { + return r~load_uint(vl); + } else { + return null(); + } + }`, + ); + + parse( + `(slice, int, int) __tact_dict_min_slice_uint(cell d, int kl, int vl) inline { + var (key, value, flag) = __tact_dict_min(d, kl); + if (flag) { + return (key, value~load_uint(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(slice, int, int) __tact_dict_next_slice_uint(cell d, int kl, slice pivot, int vl) inline { + var (key, value, flag) = __tact_dict_next(d, kl, pivot); + if (flag) { + return (key, value~load_uint(vl), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Slice -> Cell + // + + parse( + `(cell, ()) __tact_dict_set_slice_cell(cell d, int kl, slice k, cell v) inline { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return __tact_dict_set_ref(d, kl, k, v); + } + }`, + ); + + parse( + `cell __tact_dict_get_slice_cell(cell d, int kl, slice k) inline { + var (r, ok) = __tact_dict_get_ref(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } + }`, + ); + + parse( + `(slice, cell, int) __tact_dict_min_slice_cell(cell d, int kl) inline { + var (key, value, flag) = __tact_dict_min_ref(d, kl); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(slice, cell, int) __tact_dict_next_slice_cell(cell d, int kl, slice pivot) inline { + var (key, value, flag) = __tact_dict_next(d, kl, pivot); + if (flag) { + return (key, value~load_ref(), flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + // + // Dict Slice -> Slice + // + + parse( + `(cell, ()) __tact_dict_set_slice_slice(cell d, int kl, slice k, slice v) inline { + if (null?(v)) { + var (r, ok) = __tact_dict_delete(d, kl, k); + return (r, ()); + } else { + return (dict_set_builder(d, kl, k, begin_cell().store_slice(v)), ()); + } + }`, + ); + + parse( + `slice __tact_dict_get_slice_slice(cell d, int kl, slice k) inline { + var (r, ok) = __tact_dict_get(d, kl, k); + if (ok) { + return r; + } else { + return null(); + } + }`, + ); + + parse( + `(slice, slice, int) __tact_dict_min_slice_slice(cell d, int kl) inline { + var (key, value, flag) = __tact_dict_min(d, kl); + if (flag) { + return (key, value, flag); + } else { + return (null(), null(), flag); + } + }`, + ); + + parse( + `(slice, slice, int) __tact_dict_next_slice_slice(cell d, int kl, slice pivot) inline { + return __tact_dict_next(d, kl, pivot); + }`, + ); + + // + // Address + // + + parse( + `int __tact_slice_eq_bits(slice a, slice b) inline { + return equal_slice_bits(a, b); + }`, + ); + + parse( + `int __tact_slice_eq_bits_nullable_one(slice a, slice b) inline { + return (null?(a)) ? (false) : (equal_slice_bits(a, b)); + }`, + ); + + parse( + `int __tact_slice_eq_bits_nullable(slice a, slice b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (true) : ((~a_is_null) & (~b_is_null)) ? (equal_slice_bits(a, b)) : (false); + }`, + ); + + // + // Int Eq + // + + parse( + `int __tact_int_eq_nullable_one(int a, int b) inline { + return (null?(a)) ? (false) : (a == b); + }`, + ); + + parse( + `int __tact_int_neq_nullable_one(int a, int b) inline { + return (null?(a)) ? (true) : (a != b); + }`, + ); + + parse( + `int __tact_int_eq_nullable(int a, int b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (true) : ((~a_is_null) & (~b_is_null)) ? (a == b) : (false); + }`, + ); + + parse( + `int __tact_int_neq_nullable(int a, int b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (false) : ((~a_is_null) & (~b_is_null)) ? (a != b) : (true); + }`, + ); + + // + // Cell Eq + // + + parse( + `int __tact_cell_eq(cell a, cell b) inline { + return (a.cell_hash() == b.cell_hash()); + }`, + ); + + parse( + `int __tact_cell_neq(cell a, cell b) inline { + return (a.cell_hash() != b.cell_hash()); + }`, + ); + + parse( + `int __tact_cell_eq_nullable_one(cell a, cell b) inline { + return (null?(a)) ? (false) : (a.cell_hash() == b.cell_hash()); + }`, + ); + + parse( + `int __tact_cell_neq_nullable_one(cell a, cell b) inline { + return (null?(a)) ? (true) : (a.cell_hash() != b.cell_hash()); + }`, + ); + + parse( + `int __tact_cell_eq_nullable(cell a, cell b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (true) : ((~a_is_null) & (~b_is_null)) ? (a.cell_hash() == b.cell_hash()) : (false); + }`, + ); + + parse( + `int __tact_cell_neq_nullable(cell a, cell b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (false) : ((~a_is_null) & (~b_is_null)) ? (a.cell_hash() != b.cell_hash()) : (true); + }`, + ); + + // + // Slice Eq + // + + parse( + `int __tact_slice_eq(slice a, slice b) inline { + return (a.slice_hash() == b.slice_hash()); + }`, + ); + + parse( + `int __tact_slice_neq(slice a, slice b) inline { + return (a.slice_hash() != b.slice_hash()); + }`, + ); + + parse( + `int __tact_slice_eq_nullable_one(slice a, slice b) inline { + return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash()); + }`, + ); + + parse( + `int __tact_slice_neq_nullable_one(slice a, slice b) inline { + return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash()); + }`, + ); + + parse( + `int __tact_slice_eq_nullable(slice a, slice b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (true) : ((~a_is_null) & (~b_is_null)) ? (a.slice_hash() == b.slice_hash()) : (false); + }`, + ); + + parse( + `int __tact_slice_neq_nullable(slice a, slice b) inline { + var a_is_null = null?(a); + var b_is_null = null?(b); + return (a_is_null & b_is_null) ? (false) : ((~a_is_null) & (~b_is_null)) ? (a.slice_hash() != b.slice_hash()) : (true); + }`, + ); + + // + // Sys Dict + // + + parse( + `cell __tact_dict_set_code(cell dict, int id, cell code) inline { + return udict_set_ref(dict, 16, id, code); + }`, + ); + + parse( + `cell __tact_dict_get_code(cell dict, int id) inline { + var (data, ok) = udict_get_ref?(dict, 16, id); + throw_unless(${contractErrors.codeNotFound.id}, ok); + return data; + }`, + ); + + // + // Tuples + // + + parse(`tuple __tact_tuple_create_0() asm "NIL";`); + + parse( + `() __tact_tuple_destroy_0() inline { + return (); + }`, + ); + + for (let i = 1; i < 64; i++) { + const args: string[] = []; + for (let j = 0; j < i; j++) { + args.push(`X${j}`); + } + + parse( + `forall ${args.join(", ")} -> tuple __tact_tuple_create_${i}((${args.join(", ")}) v) asm "${i} TUPLE";`, + ); + + parse( + `forall ${args.join(", ")} -> (${args.join(", ")}) __tact_tuple_destroy_${i}(tuple v) asm "${i} UNTUPLE";`, + ); + } + + // + // Strings + // + + parse( + `tuple __tact_string_builder_start_comment() inline { + return __tact_string_builder_start(begin_cell().store_uint(0, 32)); + }`, + ); + + parse( + `tuple __tact_string_builder_start_tail_string() inline { + return __tact_string_builder_start(begin_cell().store_uint(0, 8)); + }`, + ); + + parse( + `tuple __tact_string_builder_start_string() inline { + return __tact_string_builder_start(begin_cell()); + }`, + ); + + parse( + `tuple __tact_string_builder_start(builder b) inline { + return tpush(tpush(empty_tuple(), b), null()); + }`, + ); + + parse( + `cell __tact_string_builder_end(tuple builders) inline { + (builder b, tuple tail) = uncons(builders); + cell c = b.end_cell(); + while (~null?(tail)) { + (b, tail) = uncons(tail); + c = b.store_ref(c).end_cell(); + } + return c; + }`, + ); + + parse( + `slice __tact_string_builder_end_slice(tuple builders) inline { + return __tact_string_builder_end(builders).begin_parse(); + }`, + ); + + parse( + `((tuple), ()) __tact_string_builder_append(tuple builders, slice sc) { + int sliceRefs = slice_refs(sc); + int sliceBits = slice_bits(sc); + + while ((sliceBits > 0) | (sliceRefs > 0)) { + ;; Load the current builder + (builder b, tuple tail) = uncons(builders); + int remBytes = 127 - (builder_bits(b) / 8); + int exBytes = sliceBits / 8; + + ;; Append bits + int amount = min(remBytes, exBytes); + if (amount > 0) { + slice read = sc~load_bits(amount * 8); + b = b.store_slice(read); + } + + ;; Update builders + builders = cons(b, tail); + + ;; Check if we need to add a new cell and continue + if (exBytes - amount > 0) { + var bb = begin_cell(); + builders = cons(bb, builders); + sliceBits = (exBytes - amount) * 8; + } elseif (sliceRefs > 0) { + sc = sc~load_ref().begin_parse(); + sliceRefs = slice_refs(sc); + sliceBits = slice_bits(sc); + } else { + sliceBits = 0; + sliceRefs = 0; + } + } + + return ((builders), ()); + }`, + ); + + parse( + `(tuple) __tact_string_builder_append_not_mut(tuple builders, slice sc) { + builders~__tact_string_builder_append(sc); + return builders; + }`, + ); + + parse( + `slice __tact_int_to_string(int src) { + var b = begin_cell(); + if (src < 0) { + b = b.store_uint(45, 8); + src = -src; + } + + if (src < ${(10n ** 30n).toString(10)}) { + int len = 0; + int value = 0; + int mult = 1; + do { + (src, int res) = src.divmod(10); + value = value + (res + 48) * mult; + mult = mult * 256; + len = len + 1; + } until (src == 0); + + b = b.store_uint(value, len * 8); + } else { + tuple t = empty_tuple(); + int len = 0; + do { + int digit = src % 10; + t~tpush(digit); + len = len + 1; + src = src / 10; + } until (src == 0); + + int c = len - 1; + repeat(len) { + int v = t.at(c); + b = b.store_uint(v + 48, 8); + c = c - 1; + } + } + return b.end_cell().begin_parse(); + }`, + ); + + parse( + `slice __tact_float_to_string(int src, int digits) { + throw_if(${contractErrors.invalidArgument.id}, (digits <= 0) | (digits > 77)); + builder b = begin_cell(); + + if (src < 0) { + b = b.store_uint(45, 8); + src = -src; + } + + ;; Process rem part + int skip = true; + int len = 0; + int rem = 0; + tuple t = empty_tuple(); + repeat(digits) { + (src, rem) = src.divmod(10); + if (~ (skip & (rem == 0))) { + skip = false; + t~tpush(rem + 48); + len = len + 1; + } + } + + ;; Process dot + if (~skip) { + t~tpush(46); + len = len + 1; + } + + ;; Main + do { + (src, rem) = src.divmod(10); + t~tpush(rem + 48); + len = len + 1; + } until (src == 0); + + ;; Assemble + int c = len - 1; + repeat(len) { + int v = t.at(c); + b = b.store_uint(v, 8); + c = c - 1; + } + + ;; Result + return b.end_cell().begin_parse(); + }`, + ); + + parse(`int __tact_log2(int num) asm "DUP 5 THROWIFNOT UBITSIZE DEC";`); + + parse( + `int __tact_log(int num, int base) { + throw_unless(5, num > 0); + throw_unless(5, base > 1); + if (num < base) { + return 0; + } + int result = 0; + while (num >= base) { + num /= base; + result += 1; + } + return result; + }`, + ); + + parse( + `int __tact_pow(int base, int exp) { + throw_unless(5, exp >= 0); + int result = 1; + repeat(exp) { + result *= base; + } + return result; + }`, + ); + + parse(`int __tact_pow2(int exp) asm "POW2";`); +} diff --git a/src/pipeline/build.ts b/src/pipeline/build.ts index 91d93131a..2a848f99e 100644 --- a/src/pipeline/build.ts +++ b/src/pipeline/build.ts @@ -125,7 +125,7 @@ export async function build(args: { const res = await compile( ctx, contract, - config.name + "_" + contract, + `${config.name}_${contract}`, ); for (const files of res.output.files) { const ffc = project.resolve(config.output, files.name); diff --git a/src/pipeline/compile.ts b/src/pipeline/compile.ts index 84fde8302..41cae3827 100644 --- a/src/pipeline/compile.ts +++ b/src/pipeline/compile.ts @@ -1,14 +1,49 @@ import { CompilerContext } from "../context"; import { createABI } from "../generator/createABI"; import { writeProgram } from "../generator/writeProgram"; +import { writeProgram as writeProgramNew } from "../generatorNew/writeProgram"; +export type CompilationOutput = { + entrypoint: string; + files: { + name: string; + code: string; + }[]; + abi: string; +}; + +export type CompilationResults = { + output: CompilationOutput; + ctx: CompilerContext; +}; + +function printOutput(contractName: string, output: CompilationOutput) { + const sep = "---------------\n"; + console.log(`Contract ${contractName} output:\n`); + output.files.forEach((o) => + console.log(`${sep}\nFile ${o.name}:\n${o.code}\n`), + ); +} + +/** + * Compiles the given contract to Func. + */ export async function compile( ctx: CompilerContext, - name: string, - basename: string, -) { - const abi = createABI(ctx, name); - const output = await writeProgram(ctx, abi, basename); - const cOutput = output; - return { output: cOutput, ctx }; + contractName: string, + abiName: string, + backend: "new" | "old" = "old", +): Promise { + const abi = createABI(ctx, contractName); + let output: CompilationOutput; + const debug = process.env.DEBUG === "1"; + if (backend === "new" || process.env.NEW_CODEGEN === "1") { + output = await writeProgramNew(ctx, abi, abiName, debug); + } else { + output = await writeProgram(ctx, abi, abiName, debug); + } + if (process.env.PRINT_FUNC === "1") { + printOutput(contractName, output); + } + return { output, ctx }; } diff --git a/src/test/new-codegen/codegen.spec.ts b/src/test/new-codegen/codegen.spec.ts new file mode 100644 index 000000000..68d577c16 --- /dev/null +++ b/src/test/new-codegen/codegen.spec.ts @@ -0,0 +1,185 @@ +import { __DANGER_resetNodeId } from "../../grammar/ast"; +import { compile } from "../../pipeline/compile"; +import { precompile } from "../../pipeline/precompile"; +import { getContracts } from "../../types/resolveDescriptors"; +import { CompilationOutput, CompilationResults } from "../../pipeline/compile"; +import { createNodeFileSystem } from "../../vfs/createNodeFileSystem"; +import { CompilerContext } from "../../context"; +import * as fs from "fs"; +import * as path from "path"; + +const CONTRACTS_DIR = path.join(__dirname, "./contracts/"); + +function capitalize(str: string): string { + if (str.length === 0) return str; + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +} + +/** + * Generates a Tact configuration file for the given contract (imported from Misti). + * @returns Path to entrypoint + */ +export function generateConfig(contractPath: string, contractName: string): string { + const config = { + projects: [ + { + name: `${contractName}`, + path: contractPath, + output: `./output`, + options: {}, + }, + ], + }; + const configPath = path.join(CONTRACTS_DIR, `${contractName}.config.json`); + fs.writeFileSync(configPath, JSON.stringify(config), { + encoding: "utf8", + flag: "w", + }); + return config.projects[0]!.path; +} + +/** + * Compiles the contract on the given filepath to CompilationResults replicating the Tact compiler pipeline. + */ +async function compileContract( + backend: "new" | "old", + contractPath: string, + contractName: string, +): Promise { + const entrypointPath = generateConfig(contractPath, contractName); + + // see: pipeline/build.ts + const project = createNodeFileSystem(CONTRACTS_DIR, false); + const stdlib = createNodeFileSystem( + path.resolve(__dirname, "..", "..", "..", "stdlib"), + false, + ); + let ctx: CompilerContext = new CompilerContext(); + ctx = precompile(ctx, project, stdlib, entrypointPath); + + return await Promise.all( + getContracts(ctx).map(async (contract) => { + const res = await compile( + ctx, + contract, + `${contractName}_${contract}`, + backend, + ); + return res; + }), + ); +} + +function stripEmptyLines(code: string): string { + return code + .split("\n") + .filter((line) => line.trim() !== "") + .join("\n"); +} + +/** + * @returns True iff compilation outputs are the same, ignoring empty lines. + */ +function compilationOutputsEq( + newOut: CompilationOutput, + oldOut: CompilationOutput, +): boolean { + const errors: string[] = []; + + if (newOut === undefined || oldOut === undefined) { + errors.push("One of the outputs is undefined."); + } else { + try { + expect(newOut.entrypoint).toBe(oldOut.entrypoint); + } catch (error) { + if (error instanceof Error) { + errors.push(`Entrypoint mismatch: ${error.message}`); + } else { + errors.push(`Entrypoint mismatch: ${String(error)}`); + } + } + + try { + expect(newOut.abi).toBe(oldOut.abi); + } catch (error) { + if (error instanceof Error) { + errors.push(`ABI mismatch: ${error.message}`); + } else { + errors.push(`ABI mismatch: ${String(error)}`); + } + } + + const unmatchedFiles = new Set(oldOut.files.map((file) => file.name)); + + for (const newFile of newOut.files) { + const oldFile = oldOut.files.find( + (file) => file.name === newFile.name, + ); + if (oldFile) { + unmatchedFiles.delete(oldFile.name); + try { + expect(stripEmptyLines(newFile.code)).toBe( + stripEmptyLines(oldFile.code), + ); + } catch (error) { + if (error instanceof Error) { + errors.push( + `Code mismatch in file ${newFile.name}: ${error.message}`, + ); + } else { + errors.push( + `Code mismatch in file ${newFile.name}: ${String(error)}`, + ); + } + } + } else { + errors.push( + `File ${newFile.name} is missing in the old output.`, + ); + } + } + + for (const missingFile of unmatchedFiles) { + errors.push(`File ${missingFile} is missing in the new output.`); + } + } + + if (errors.length > 0) { + console.error(errors.join("\n")); + return false; + } + return true; +} + +describe("codegen", () => { + beforeEach(async () => { + __DANGER_resetNodeId(); + }); + + fs.readdirSync(CONTRACTS_DIR).forEach((file) => { + if (!file.endsWith(".tact")) { + return; + } + const contractName = capitalize(file).replace(/\.[^/.]+$/, ""); + // Differential tests with the old backend + it(`Should compile the ${file} contract`, async () => { + Promise.all([ + compileContract("new", file, contractName), + compileContract("old", file, contractName), + ]).then(([resultsNew, resultsOld]) => { + if (resultsNew.length !== resultsOld.length) { + throw new Error("Not all contracts have been compiled"); + } + const zipped = resultsNew.map((value, idx) => [ + value, + resultsOld[idx], + ]); + zipped.forEach(([newRes, oldRes]) => { + expect( + compilationOutputsEq(newRes!.output, oldRes!.output), + ).toBe(true); + }); + }); + }); + }); +}); diff --git a/src/test/new-codegen/contracts/Simple.tact b/src/test/new-codegen/contracts/Simple.tact new file mode 100644 index 000000000..4e06edb30 --- /dev/null +++ b/src/test/new-codegen/contracts/Simple.tact @@ -0,0 +1,5 @@ +contract A { + get fun foo(): Int { + return 1; + } +} diff --git a/src/test/new-codegen/contracts/Simple.tact.config.json b/src/test/new-codegen/contracts/Simple.tact.config.json new file mode 100644 index 000000000..d5e4321f2 --- /dev/null +++ b/src/test/new-codegen/contracts/Simple.tact.config.json @@ -0,0 +1 @@ +{"projects":[{"name":"Simple.tact","path":"./Simple.tact","output":"./output","options":{}}]} diff --git a/src/utils/loadCases.ts b/src/utils/loadCases.ts index afe948bb8..b297650d2 100644 --- a/src/utils/loadCases.ts +++ b/src/utils/loadCases.ts @@ -1,12 +1,18 @@ import fs from "fs"; -export function loadCases(src: string) { +/** + * @param src Path to the directory with files + * @param ext Optional extension of the file, without the dot prefix + */ +export function loadCases(src: string, ext?: string) { const recs = fs.readdirSync(src); const res: { name: string; code: string }[] = []; + const extWithDot = `.${ext ? ext.toLowerCase() : "tact"}`; + for (const r of recs) { - if (r.endsWith(".tact")) { + if (r.endsWith(extWithDot)) { res.push({ - name: r.slice(0, r.length - ".tact".length), + name: r.slice(0, r.length - extWithDot.length), code: fs.readFileSync(src + r, "utf8"), }); } diff --git a/yarn.lock b/yarn.lock index 5083f8fdb..fe30606b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1809,7 +1809,7 @@ before-after-hook@^2.2.0: bignumber.js@^9.0.0: version "9.1.2" - resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== bl@^4.1.0: