├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .ocamlformat
├── LICENSE
├── README.md
├── bin
├── dune
├── main.ml
└── stdlib.fc
├── dune-project
├── examples
└── wallet_v3.tact
├── js
├── dune
└── tact_js.ml
├── lib
├── attributes.ml
├── builtin.ml
├── codegen_func.ml
├── compiler.ml
├── config.ml
├── discriminator.ml
├── dune
├── errors.ml
├── func.ml
├── interpreter.ml
├── lang.ml
├── lang_types.ml
├── located.ml
├── parser.ml
├── partial_evaluator.ml
├── show.ml
├── std
│ └── std.tact
├── syntax.ml
├── type_check.ml
└── zint.ml
├── tact.opam
├── test
├── builtin.ml
├── builtin_basics.ml
├── codegen_func.ml
├── dune
├── errors.ml
├── immediacy_check.ml
├── lang.ml
├── lang_types.ml
├── shared.ml
├── std.ml
└── syntax.ml
├── tonvm.opam
└── tonvm
├── bitstr.ml
├── cp.ml
├── dune
├── instr.ml
├── instr_null.ml
├── instr_null_test.ml
├── instr_stack.ml
├── instr_stack_test.ml
├── tests.ml
├── tonvm.ml
├── vm.ml
└── zint.ml
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | schedule:
7 | # Prime the caches every Monday
8 | - cron: 0 1 * * MON
9 |
10 | jobs:
11 | lint-fmt:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v3
16 |
17 | - name: Use OCaml 4.14.x
18 | uses: ocaml/setup-ocaml@v2
19 | with:
20 | ocaml-compiler: 4.14.x
21 | dune-cache: true
22 |
23 | - name: Lint fmt
24 | uses: ocaml/setup-ocaml/lint-fmt@v2
25 | build:
26 | strategy:
27 | fail-fast: false
28 | matrix:
29 | os:
30 | - macos-latest
31 | - ubuntu-latest
32 | ocaml-compiler:
33 | - 4.14.x
34 |
35 | runs-on: ${{ matrix.os }}
36 |
37 | steps:
38 | - name: Checkout code
39 | uses: actions/checkout@v3
40 |
41 | - name: Use OCaml ${{ matrix.ocaml-compiler }}
42 | uses: ocaml/setup-ocaml@v2
43 | with:
44 | ocaml-compiler: ${{ matrix.ocaml-compiler }}
45 |
46 | - run: opam install . --deps-only --with-test
47 |
48 | - run: opam exec -- dune build
49 |
50 | - run: opam exec -- dune test
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 | _opam
3 | *.swp
4 | .vscode
5 | .conflicts
6 | .DS_Store
7 | *.fc
8 | !bin/stdlib.fc
9 | *.fif
10 | *.cell
11 |
--------------------------------------------------------------------------------
/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = ocamlformat
2 | break-separators = after
3 | field-space = loose
4 | version = 0.23.0
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tact
2 |
10 |
11 | ## Building from source
12 |
13 | * Install `opam` using this [guide](https://ocaml.org/learn/tutorials/up_and_running.html)
14 | * Run following commands:
15 | ```
16 | opam init
17 | opam switch create $(pwd) --with-test -y
18 | eval $(opam env)
19 | dune build && dune install
20 | ```
21 |
22 | You can now use `tact /path/to/file.tact`
23 |
24 | ## Development instructions
25 |
26 | ### Updating dependencies
27 |
28 | Run `opam install . --working-dir --deps-only --with-test`
29 |
30 | ### Running REPL
31 |
32 | Run `dune utop lib`
33 |
34 | ### Running tests
35 |
36 | Run `dune test`
37 |
38 | ### Formatting code
39 |
40 | Run `dune build @fmt --auto-promote` (or no `--auto-promote` if you want to review changes, followed
41 | by `dune promote`)
42 |
43 | ### Working with expect-based tests
44 |
45 | `ppx_expect`
46 | [ppx_expect](https://github.com/janestreet/ppx_expect) tests allow to check output against an expectation
47 | and upon failure, they produce a diff, which can be easily applied to the original source code by running
48 | `dune promote` if the output is deemed to be correct. This allows us to write:
49 |
50 | ```ocaml
51 | pp source ; [%expect {||}]
52 | ```
53 |
54 | to fill in the blanks. In this particular case, it's useful to run `dune test --auto-promote`
55 |
56 | ### JavaScript bindings
57 |
58 | Can be built during `dune build` or `dune build js` and will be available in
59 | ` _build/default/js/tact_js.bc.js`
60 |
61 | In order to build a (smaller) release build, one should run:
62 |
63 | ```
64 | dune build js --profile=release
65 | ```
66 |
--------------------------------------------------------------------------------
/bin/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (public_name tact)
3 | (package tact)
4 | (name main)
5 | (modules main)
6 | (preprocess
7 | (pps ppx_blob))
8 | (libraries core tact linenoise base bos cmdliner ppx_blob))
9 |
--------------------------------------------------------------------------------
/bin/main.ml:
--------------------------------------------------------------------------------
1 | open Core
2 | open Bos
3 | module Show = Tact.Compiler.Show
4 | module Lang = Tact.Compiler.Lang
5 | module Syntax = Tact.Compiler.Syntax
6 | module Builtin = Tact.Compiler.Builtin
7 |
8 | let toolchain_available () =
9 | let exists f =
10 | match OS.Cmd.exists @@ Cmd.v f with Ok true -> true | _ -> false
11 | in
12 | match (exists "func", exists "fift") with
13 | | _, _ ->
14 | Option.is_some @@ Sys.getenv "FIFTPATH"
15 | | exception _ ->
16 | false
17 |
18 | let stdlib_fc = [%blob "stdlib.fc"]
19 |
20 | let compile ?(target = `Cell) filename program =
21 | let ext =
22 | match target with `Cell -> ".cell" | `Func -> ".fc" | `Fift -> ".fif"
23 | and is_func = match target with `Func -> true | _ -> false
24 | and is_fift = match target with `Fift -> true | _ -> false in
25 | let out_file =
26 | (Filename.chop_extension filename |> Filename.basename) ^ ext
27 | in
28 | if is_func then (
29 | ignore
30 | @@ OS.File.with_oc
31 | (Option.value_exn @@ Result.ok @@ Fpath.of_string out_file)
32 | (fun out () ->
33 | Stdio.Out_channel.(output_string out program ; flush out) ;
34 | Ok () )
35 | () ;
36 | Caml.Format.print_string ("Compiled to " ^ out_file) ;
37 | Caml.Format.print_newline () )
38 | else
39 | ignore
40 | @@ OS.File.with_tmp_oc
41 | (format_of_string "tact_%s")
42 | (fun path out () ->
43 | Stdio.Out_channel.(
44 | output_string out stdlib_fc ;
45 | output_string out program ;
46 | flush out ) ;
47 | let output =
48 | OS.Cmd.(
49 | run_out Cmd.(v "func" % "-APS" % Fpath.to_string path)
50 | |> to_string )
51 | in
52 | match output with
53 | | Ok fift ->
54 | let fift = fift ^ "\nboc>B \"" ^ out_file ^ "\" B>file\n" in
55 | ( if is_fift then
56 | ignore
57 | @@ OS.File.with_oc
58 | (Option.value_exn @@ Result.ok @@ Fpath.of_string out_file)
59 | (fun out () ->
60 | Stdio.Out_channel.(output_string out fift ; flush out) ;
61 | Ok () )
62 | ()
63 | else
64 | ignore
65 | @@ OS.Cmd.(in_string fift |> run_io Cmd.(v "fift") |> to_null)
66 | ) ;
67 | Caml.Format.print_string ("Compiled to " ^ out_file) ;
68 | Caml.Format.print_newline ()
69 | | _ ->
70 | Caml.Format.print_string "Failed to use the toolchain" ;
71 | Caml.Format.print_newline () )
72 | () ;
73 | ()
74 |
75 | let interpret_file ~target filename =
76 | match Tact.Compiler.compile_with_std ~filename (Caml.open_in filename) with
77 | | Ok program -> (
78 | let is_toolchain_available = toolchain_available () in
79 | match (is_toolchain_available, target) with
80 | | _, `Func ->
81 | compile ~target filename program
82 | | false, _ ->
83 | Caml.Format.print_string
84 | "Can't compile for this target without a toolchain"
85 | | true, target ->
86 | compile ~target filename program )
87 | | Error errors ->
88 | Caml.Format.print_string errors
89 |
90 | let prompt = "# "
91 |
92 | let default_program () =
93 | let program = Lang.default_program () in
94 | Result.ok_or_failwith @@ Result.map ~f:fst
95 | @@ Tact.Compiler.compile_to_ir' ~filename:"std.tact" ~prev_program:program
96 | Builtin.std
97 |
98 | let rec repl ?(program = default_program ()) ?(prompt = prompt) () =
99 | match LNoise.linenoise prompt with
100 | | None ->
101 | ()
102 | | Some input -> (
103 | let bindings = program#bindings in
104 | ignore @@ LNoise.history_add input ;
105 | match
106 | Tact.Compiler.eval_stmt ~constructor:program ~filename:"" input
107 | with
108 | | Ok result when not @@ Lang.equal_value result Void ->
109 | Show.pp_value Caml.Format.std_formatter result ;
110 | Caml.Format.print_newline () ;
111 | Caml.Format.print_flush () ;
112 | repl ~program ~prompt ()
113 | | Ok _ ->
114 | List.iter program#bindings ~f:(fun (name, value) ->
115 | if
116 | Option.is_none
117 | @@ List.find bindings ~f:(fun (name', value') ->
118 | Syntax.equal_located String.equal name name'
119 | && Syntax.equal_located Lang.equal_expr_kind value value' )
120 | then (
121 | Caml.Format.print_string @@ Syntax.value name ;
122 | Caml.Format.print_string " = " ;
123 | Show.pp_expr Caml.Format.std_formatter value ;
124 | Caml.Format.print_newline () ) ) ;
125 | Caml.Format.print_flush () ;
126 | repl ~program ~prompt ()
127 | | Error errors ->
128 | Caml.Format.print_string errors ;
129 | Caml.Format.print_flush () ;
130 | repl ~prompt () )
131 |
132 | open Cmdliner
133 |
134 | let tact target filename =
135 | match (target, filename) with
136 | | _, None ->
137 | LNoise.set_multiline true ;
138 | ignore @@ LNoise.history_set ~max_length:1000 ;
139 | repl ()
140 | | target, Some filename ->
141 | interpret_file ~target filename
142 |
143 | let tact_t =
144 | let target =
145 | let doc = "Output target [cell, func, fift] (ignored for REPL)" in
146 | Arg.(
147 | value
148 | & opt (enum [("cell", `Cell); ("func", `Func); ("fift", `Fift)]) `Cell
149 | & info ["t"; "target"] ~doc )
150 | and filename =
151 | let doc = "Input filename, if none given, will start REPL" in
152 | Arg.(
153 | value
154 | & pos ~rev:true 0 (some non_dir_file) None
155 | & info [] ~doc ~docv:"FILE" )
156 | in
157 | Term.(const tact $ target $ filename)
158 |
159 | let cmd =
160 | let doc = "compile Tact smart contract" in
161 | let info = Cmd.info "tact" ~doc in
162 | Cmd.v info tact_t
163 |
164 | let main () = exit (Cmd.eval cmd)
165 |
166 | let () = main ()
167 |
--------------------------------------------------------------------------------
/bin/stdlib.fc:
--------------------------------------------------------------------------------
1 | ;; Standard library for funC
2 | ;;
3 |
4 | forall X -> tuple cons(X head, tuple tail) asm "CONS";
5 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS";
6 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS";
7 | forall X -> X car(tuple list) asm "CAR";
8 | tuple cdr(tuple list) asm "CDR";
9 | tuple empty_tuple() asm "NIL";
10 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH";
11 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH";
12 | forall X -> [X] single(X x) asm "SINGLE";
13 | forall X -> X unsingle([X] t) asm "UNSINGLE";
14 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR";
15 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR";
16 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE";
17 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE";
18 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE";
19 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE";
20 | forall X -> X first(tuple t) asm "FIRST";
21 | forall X -> X second(tuple t) asm "SECOND";
22 | forall X -> X third(tuple t) asm "THIRD";
23 | forall X -> X fourth(tuple t) asm "3 INDEX";
24 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST";
25 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND";
26 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST";
27 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND";
28 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD";
29 | forall X -> X null() asm "PUSHNULL";
30 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP";
31 |
32 | int now() asm "NOW";
33 | slice my_address() asm "MYADDR";
34 | [int, cell] get_balance() asm "BALANCE";
35 | int cur_lt() asm "LTIME";
36 | int block_lt() asm "BLOCKLT";
37 |
38 | int cell_hash(cell c) asm "HASHCU";
39 | int slice_hash(slice s) asm "HASHSU";
40 | int string_hash(slice s) asm "SHA256U";
41 |
42 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU";
43 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS";
44 |
45 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE";
46 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE";
47 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
48 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
49 |
50 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF";
51 |
52 | () dump_stack() impure asm "DUMPSTK";
53 |
54 | cell get_data() asm "c4 PUSH";
55 | () set_data(cell c) impure asm "c4 POP";
56 | cont get_c3() impure asm "c3 PUSH";
57 | () set_c3(cont c) impure asm "c3 POP";
58 | cont bless(slice s) impure asm "BLESS";
59 |
60 | () accept_message() impure asm "ACCEPT";
61 | () set_gas_limit(int limit) impure asm "SETGASLIMIT";
62 | () commit() impure asm "COMMIT";
63 | () buy_gas(int gram) impure asm "BUYGAS";
64 |
65 | int min(int x, int y) asm "MIN";
66 | int max(int x, int y) asm "MAX";
67 | (int, int) minmax(int x, int y) asm "MINMAX";
68 | int abs(int x) asm "ABS";
69 |
70 | slice begin_parse(cell c) asm "CTOS";
71 | () end_parse(slice s) impure asm "ENDS";
72 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
73 | cell preload_ref(slice s) asm "PLDREF";
74 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX";
75 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX";
76 | ;; int preload_int(slice s, int len) asm "PLDIX";
77 | ;; int preload_uint(slice s, int len) asm "PLDUX";
78 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
79 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX";
80 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS";
81 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST";
82 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST";
83 | slice first_bits(slice s, int len) asm "SDCUTFIRST";
84 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST";
85 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST";
86 | slice slice_last(slice s, int len) asm "SDCUTLAST";
87 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT";
88 | cell preload_dict(slice s) asm "PLDDICT";
89 | slice skip_dict(slice s) asm "SKIPDICT";
90 |
91 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF";
92 | cell preload_maybe_ref(slice s) asm "PLDOPTREF";
93 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF";
94 |
95 | int cell_depth(cell c) asm "CDEPTH";
96 |
97 | int slice_refs(slice s) asm "SREFS";
98 | int slice_bits(slice s) asm "SBITS";
99 | (int, int) slice_bits_refs(slice s) asm "SBITREFS";
100 | int slice_empty?(slice s) asm "SEMPTY";
101 | int slice_data_empty?(slice s) asm "SDEMPTY";
102 | int slice_refs_empty?(slice s) asm "SREMPTY";
103 | int slice_depth(slice s) asm "SDEPTH";
104 |
105 | int builder_refs(builder b) asm "BREFS";
106 | int builder_bits(builder b) asm "BBITS";
107 | int builder_depth(builder b) asm "BDEPTH";
108 |
109 | builder begin_cell() asm "NEWC";
110 | cell end_cell(builder b) asm "ENDC";
111 | builder store_ref(builder b, cell c) asm(c b) "STREF";
112 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX";
113 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX";
114 | builder store_slice(builder b, slice s) asm "STSLICER";
115 | builder store_grams(builder b, int x) asm "STGRAMS";
116 | builder store_dict(builder b, cell c) asm(c b) "STDICT";
117 |
118 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR";
119 | tuple parse_addr(slice s) asm "PARSEMSGADDR";
120 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR";
121 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR";
122 |
123 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
124 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
125 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF";
126 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF";
127 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF";
128 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF";
129 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF";
130 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF";
131 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF";
132 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL";
133 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL";
134 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT";
135 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT";
136 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
137 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
138 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
139 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
140 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
141 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
142 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
143 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
144 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
145 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
146 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD";
147 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE";
148 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD";
149 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE";
150 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
151 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
152 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
153 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
154 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB";
155 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB";
156 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB";
157 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB";
158 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB";
159 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB";
160 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
161 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
162 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
163 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
164 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
165 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
166 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
167 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
168 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
169 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
170 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
171 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
172 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
173 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
174 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
175 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
176 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
177 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
178 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
179 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
180 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2";
181 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2";
182 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2";
183 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
184 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2";
185 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2";
186 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2";
187 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2";
188 | cell new_dict() asm "NEWDICT";
189 | int dict_empty?(cell c) asm "DICTEMPTY";
190 |
191 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2";
192 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET";
193 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL";
194 |
195 | cell config_param(int x) asm "CONFIGOPTPARAM";
196 | int cell_null?(cell c) asm "ISNULL";
197 |
198 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE";
199 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX";
200 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG";
201 | () set_code(cell new_code) impure asm "SETCODE";
202 |
203 | int random() impure asm "RANDU256";
204 | int rand(int range) impure asm "RAND";
205 | int get_seed() impure asm "RANDSEED";
206 | int set_seed() impure asm "SETRAND";
207 | () randomize(int x) impure asm "ADDRAND";
208 | () randomize_lt() impure asm "LTIME" "ADDRAND";
209 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 2.9)
2 | (generate_opam_files true)
3 |
4 | (name tact)
5 | (source (github tact-lang/tact))
6 | (license Apache-2.0)
7 | (authors "cmxog@protonmail.com" "dmytro.polunin@gmail.com")
8 | (maintainers "cmxog@protonmail.com" "dmytro.polunin@gmail.com")
9 |
10 | (package
11 | (name tonvm)
12 | (synopsis "TON Virtual Machine implementation")
13 | (depends
14 | (ocaml (>= 4.14.0))
15 | (base (>= 0.15.0))
16 | (bitstring (>= 4.1.0))
17 | (ppx_bitstring (>= 4.1.0))
18 | (zarith (>= 1.12))
19 | (stdint (>= 0.7.0))
20 | (ppx_show (>= 0.2.1))
21 | (sexplib (>= 0.15.0))
22 | (ppx_sexp_conv (>= 0.15.1))
23 | (dune (and :build (>= 3.1.1)))
24 | (ppx_expect (and :with-test (>= 0.15.0)))))
25 |
26 | (package
27 | (name tact)
28 | (synopsis "TON Tact Language")
29 | (depends
30 | (ocaml (>= 4.14.0))
31 | (ppx_show (>= 0.2.1))
32 | (ppx_make (>= 0.3.0))
33 | (ppx_blob (>= 0.7.2))
34 | (js_of_ocaml-ppx (>= 4.0.0))
35 | (zarith_stubs_js (>= 0.15.0))
36 | (dune (and :build (>= 3.1.1)))
37 | (js_of_ocaml (>= 4.0.0))
38 | (mparser (>= 1.3))
39 | (zarith (>= 1.12))
40 | (base (>= 0.15.0))
41 | (ppx_jane (>= 0.15.0))
42 | (visitors (= 20210608))
43 | (containers (>= 3.8.0))
44 | (linenoise (>= 1.3.1))
45 | (bos (>= 0.2.1))
46 | (cmdliner (>= 1.1.1))
47 | (core (and :with-test (>= 0.15.0)))
48 | (ppx_expect (and :with-test (>= 0.15.0)))
49 | (ppx_matches (and :with-test (>= 0.1)))
50 | (alcotest (and :with-test (>= 1.5.0)))
51 | (ppx_inline_alcotest (and :with-test (>= 1.0.0)))
52 | (utop (and :build (>= 2.9.1)))
53 | (ocamlformat (and :build (= 0.23.0))))
54 | )
55 |
--------------------------------------------------------------------------------
/examples/wallet_v3.tact:
--------------------------------------------------------------------------------
1 | struct MsgBody {
2 | val subwallet: Uint32
3 | val valid_until: Uint32
4 | val seqno: Uint32
5 |
6 | @derive impl Deserialize {}
7 | }
8 |
9 | struct WalletState {
10 | val seqno: Uint32
11 | val subwallet: Uint32
12 | val public_key: Uint256
13 |
14 | @derive impl Deserialize {}
15 | @derive impl Serialize {}
16 | }
17 |
18 | struct NextMessage {
19 | val cell: RefCell
20 | val flags: SendRawMsgFlags
21 |
22 | @derive impl Deserialize {}
23 | }
24 |
25 | fn recv_internal(_: Slice) {}
26 |
27 | fn recv_external(input: Slice) {
28 | let {value as signed, slice} = SignedBody[MsgBody].deserialize(input);
29 | let state = WalletState.deserialize(Slice.parse(Globals.load_state())).value;
30 |
31 | let body = signed.verify_body(state.public_key);
32 |
33 | if (body.valid_until <= Globals.get_now()) { thrown(35) }
34 | if (body.seqno != state.seqno) { thrown(33) }
35 | if (body.subwallet != state.subwallet) { thrown(34) }
36 |
37 | builtin_accept_message();
38 |
39 | while (slice.refs_count() != 0) {
40 | let {value as next, slice as new_slice} = NextMessage.deserialize(slice);
41 | slice = new_slice;
42 | send_raw_msg(next.cell.inner, next.flags);
43 | }
44 |
45 | state.seqno = state.seqno + 1;
46 | let new_state = state.serialize(Builder.new()).build();
47 | Globals.save_state(new_state);
48 | }
49 |
--------------------------------------------------------------------------------
/js/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name tact_js)
3 | (preprocess
4 | (pps js_of_ocaml-ppx))
5 | (libraries tact js_of_ocaml zarith_stubs_js)
6 | (modules tact_js)
7 | (modes js))
8 |
--------------------------------------------------------------------------------
/js/tact_js.ml:
--------------------------------------------------------------------------------
1 | open Js_of_ocaml
2 | open Base
3 |
4 | let _ =
5 | Js.export "Tact"
6 | (object%js (_self)
7 | method parse (src : Js.js_string Js.t) =
8 | let src = Js.to_string src in
9 | match Tact.Compiler.compile_from_string src with
10 | | Ok program ->
11 | Js.string program
12 | | Error errors ->
13 | Js_error.raise_ @@ Js_error.of_error
14 | @@ new%js Js.error_constr (Js.string errors)
15 | end )
16 |
--------------------------------------------------------------------------------
/lib/attributes.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | module Make =
4 | functor
5 | (Config : Config.T)
6 | ->
7 | struct
8 | open Lang_types.Make (Config)
9 |
10 | open struct
11 | let bl = Config.builtin_located
12 |
13 | let derive_executor p binds _exprs = function
14 | | ImplAttrTarget {impl; self_ty} -> (
15 | match impl.mk_impl_interface.value with
16 | (* Serialize intf *)
17 | | ResolvedReference (_, {value = Value (Type (InterfaceType -1)); _})
18 | | Value (Type (InterfaceType -1)) ->
19 | let builder_ty =
20 | find_comptime "Builder" binds
21 | |> Option.value_exn |> Result.ok |> Option.value_exn
22 | |> expr_to_type p
23 | in
24 | let self_serializer =
25 | FunctionCall
26 | ( bl @@ Reference (bl "serializer", HoleType),
27 | [bl @@ Value (Type self_ty)],
28 | true )
29 | in
30 | let fun_body =
31 | bl
32 | @@ Return
33 | ( bl
34 | @@ FunctionCall
35 | ( bl @@ self_serializer,
36 | [ bl @@ Reference (bl "self", self_ty);
37 | bl @@ Reference (bl "b", builder_ty) ],
38 | false ) )
39 | in
40 | let function_signature =
41 | bl
42 | @@ { function_params =
43 | [(bl "self", self_ty); (bl "b", builder_ty)];
44 | function_is_type = false;
45 | function_returns = builder_ty;
46 | function_attributes = [] }
47 | in
48 | let method_ =
49 | bl
50 | @@ MkFunction
51 | (bl {function_signature; function_impl = Fn fun_body})
52 | in
53 | let impl =
54 | { mk_impl_interface = impl.mk_impl_interface;
55 | mk_impl_attributes = [];
56 | mk_impl_methods = [(bl "serialize", method_)] }
57 | in
58 | ImplAttrTarget {impl; self_ty}
59 | (* Deserialize intf *)
60 | | ResolvedReference (_, {value = Value (Type (InterfaceType -2)); _})
61 | | Value (Type (InterfaceType -2)) ->
62 | let slice_ty =
63 | find_comptime "Slice" binds
64 | |> Option.value_exn |> Result.ok |> Option.value_exn
65 | |> expr_to_type p
66 | in
67 | let load_result_f =
68 | find_comptime "LoadResult" binds
69 | |> Option.value_exn |> Result.ok |> Option.value_exn
70 | in
71 | let self_deserializer =
72 | FunctionCall
73 | ( bl @@ Reference (bl "deserializer", HoleType),
74 | [bl @@ Value (Type self_ty)],
75 | true )
76 | in
77 | let fun_body =
78 | bl
79 | @@ Return
80 | ( bl
81 | @@ FunctionCall
82 | ( bl @@ self_deserializer,
83 | [bl @@ Reference (bl "s", slice_ty)],
84 | false ) )
85 | in
86 | let ret_ty =
87 | TypeCall
88 | {func = load_result_f; args = [bl @@ Value (Type self_ty)]}
89 | in
90 | let function_signature =
91 | bl
92 | @@ { function_params = [(bl "s", slice_ty)];
93 | function_is_type = false;
94 | function_returns = ret_ty;
95 | function_attributes = [] }
96 | in
97 | let method_ =
98 | bl
99 | @@ MkFunction
100 | (bl {function_signature; function_impl = Fn fun_body})
101 | in
102 | let impl =
103 | { mk_impl_interface = impl.mk_impl_interface;
104 | mk_impl_attributes = [];
105 | mk_impl_methods = [(bl "deserialize", method_)] }
106 | in
107 | ImplAttrTarget {impl; self_ty}
108 | | _ ->
109 | raise
110 | (Errors.InternalCompilerError
111 | "Currently, only `Serialize` and `Deserialize` interfaces \
112 | are supported" ) )
113 | end
114 |
115 | let attr_executors = [("derive", derive_executor)]
116 | end
117 |
--------------------------------------------------------------------------------
/lib/codegen_func.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | module Make =
4 | functor
5 | (Config : Config.T)
6 | ->
7 | struct
8 | module T = Lang_types.Make (Config)
9 | module F = Func
10 | include Errors
11 | open Config
12 |
13 | exception Invalid
14 |
15 | exception Unsupported
16 |
17 | class constructor (program : T.program) =
18 | object (self)
19 | val mutable struct_representations : (T.struct_ * F.type_) list = []
20 |
21 | val mutable fn_name_counter = 0
22 |
23 | val mutable functions : (T.function_ * F.function_) list = []
24 |
25 | val mutable helpers : F.function_ list = []
26 |
27 | method cg_Fn body = F.Fn (List.concat (Option.value_exn body))
28 |
29 | method build_Integer : Zint.t -> F.expr = fun i -> F.Integer i
30 |
31 | method cg_Let : _ -> F.stmt =
32 | fun bindings ->
33 | F.Vars
34 | (List.map bindings ~f:(fun (name, expr) ->
35 | let expr = self#cg_expr expr in
36 | (F.type_of expr, name, expr) ) )
37 |
38 | method cg_Assignment
39 | ({assignment_lvalue; assignment_expr; _} : T.assignment) =
40 | match assignment_lvalue.value with
41 | | T.ReferenceLvalue assignment_ident ->
42 | F.Assignment (value assignment_ident, self#cg_expr assignment_expr)
43 | | T.FieldAccessLvalue {ref; ref_ty; fields = [field_ident]} -> (
44 | let build_updating ~(tensor : int option) struct_ty field field_ty
45 | =
46 | match tensor with
47 | | None ->
48 | if field >= 16 then
49 | ice "Only structs with 16 or less fields are allowed" ;
50 | let fun_name = "update" ^ Int.to_string field in
51 | ( if
52 | not
53 | @@ List.exists helpers ~f:(fun x ->
54 | String.equal x.function_name fun_name )
55 | then
56 | let fn : F.function_ =
57 | { function_name = fun_name;
58 | function_forall = ["F"; "T"];
59 | function_body =
60 | AsmFn (Int.to_string field ^ " SETINDEX");
61 | function_args =
62 | [("t", UnknownTuple); ("elem", NamedType "F")];
63 | function_returns = F.NamedType "T";
64 | function_is_impure = false }
65 | in
66 | helpers <- fn :: helpers ) ;
67 | F.Assignment
68 | ( value ref,
69 | F.FunctionCall
70 | ( fun_name,
71 | [ F.FunctionCall
72 | ( "func_believe_me",
73 | [ Reference
74 | (ref.value, self#lang_type_to_type ref_ty)
75 | ],
76 | F.UnknownTuple );
77 | struct_ty ],
78 | field_ty ) )
79 | | Some _ ->
80 | ice "Updating tensor values is not supported by codegen."
81 | in
82 | match ref_ty with
83 | | StructType s -> (
84 | let s = T.Program.get_struct program s in
85 | match s.struct_fields with
86 | | [_] ->
87 | F.Assignment (value ref, self#cg_expr assignment_expr)
88 | | _ ->
89 | let field_id, (_, field) =
90 | Option.value_exn
91 | (List.findi s.struct_fields ~f:(fun _ (name, _) ->
92 | equal_string name.value field_ident.value ) )
93 | in
94 | build_updating
95 | ~tensor:
96 | ( if s.tensor then Some (List.length s.struct_fields)
97 | else None )
98 | (self#cg_expr assignment_expr)
99 | field_id
100 | (self#lang_type_to_type field.field_type) )
101 | | _ ->
102 | raise Invalid )
103 | | _ ->
104 | ice "Nested field updating is not supported by codegen"
105 |
106 | method cg_DestructuringLet : T.destructuring_let -> F.stmt =
107 | fun let_ ->
108 | let expr = let_.destructuring_let_expr in
109 | match T.type_of program expr with
110 | | StructType id ->
111 | let struct_ = T.Program.get_struct program id in
112 | let expr' = self#cg_expr expr in
113 | let fields =
114 | List.map struct_.struct_fields
115 | ~f:(fun (field_name, {field_type; _}) ->
116 | match
117 | List.Assoc.find let_.destructuring_let
118 | ~equal:(equal_located String.equal)
119 | field_name
120 | with
121 | | Some new_name ->
122 | ( Some (self#lang_type_to_type field_type),
123 | new_name.value )
124 | | None ->
125 | (None, "_") )
126 | in
127 | F.DestructuringBinding (fields, expr')
128 | | _x ->
129 | T.print_sexp (T.sexp_of_type_ _x) ;
130 | raise Invalid
131 |
132 | method cg_Struct : T.struct_ * (string * T.expr) list -> F.expr =
133 | function
134 | | _, [(_, expr)] ->
135 | self#cg_expr expr
136 | | _, args ->
137 | F.Tuple (List.map args ~f:(fun (_, expr) -> self#cg_expr expr))
138 |
139 | method cg_expr : T.expr -> F.expr =
140 | fun expr ->
141 | match expr.value with
142 | | Value (Integer i) ->
143 | F.Integer i
144 | | Value (Bool true) ->
145 | F.Integer (Zint.of_int (-1))
146 | | Value (Bool false) ->
147 | F.Integer Zint.zero
148 | | StructField x ->
149 | self#cg_StructField x
150 | | Value (Struct ({value = Value (Type (StructType id)); _}, inst))
151 | ->
152 | self#cg_Struct (T.Program.get_struct program id, inst)
153 | | ResolvedReference s ->
154 | self#cg_ResolvedReference s
155 | | Reference (name, ty) ->
156 | F.Reference (name.value, self#lang_type_to_type ty)
157 | | Primitive p ->
158 | self#cg_Primitive p
159 | | Value (Function f) ->
160 | let f' = self#add_function f in
161 | F.Reference (f'.function_name, F.FunctionType f')
162 | | FunctionCall (func, args, _is_ty) -> (
163 | (* TODO: if is_ty then ice "Type function in runtime context." ;*)
164 | let args = List.map args ~f:self#cg_expr in
165 | match self#cg_expr func with
166 | | Reference (name, F.FunctionType f) ->
167 | F.FunctionCall (name, args, f.function_returns)
168 | | _ ->
169 | raise Invalid )
170 | | MakeUnionVariant (expr, union) ->
171 | self#cg_union_variant expr union
172 | | Value (UnionVariant (v, u)) ->
173 | self#cg_union_variant {value = Value v; span = expr.span} u
174 | | _ ->
175 | (* T.print_sexp @@ T.sexp_of_expr expr ; *)
176 | raise Unsupported
177 |
178 | method cg_union_variant expr union =
179 | let e_ty = T.type_of program expr in
180 | let expr = self#cg_expr expr in
181 | let union =
182 | List.Assoc.find_exn program.unions union ~equal:equal_int
183 | in
184 | let (T.Discriminator {discr; _}) =
185 | List.Assoc.find_exn union.cases e_ty ~equal:T.equal_type_
186 | in
187 | F.Tuple [F.Integer (Z.of_int discr); expr]
188 | |> fun t -> F.FunctionCall ("func_believe_me", [t], F.UnknownTuple)
189 |
190 | method get_discriminator : T.union -> T.type_ -> int =
191 | fun union ty ->
192 | let (T.Discriminator {discr; _}) =
193 | List.Assoc.find_exn union.cases ty ~equal:T.equal_type_
194 | in
195 | discr
196 |
197 | method cg_stmt : T.stmt -> F.stmt =
198 | fun stmt ->
199 | match stmt.value with
200 | | Let bindings ->
201 | self#cg_Let
202 | @@ List.map bindings ~f:(fun (n, ex) -> (n.value, ex))
203 | | DestructuringLet let_ ->
204 | self#cg_DestructuringLet let_
205 | | Assignment assignment ->
206 | self#cg_Assignment assignment
207 | | Return expr ->
208 | F.Return (self#cg_expr expr)
209 | | Expr e ->
210 | F.Expr (self#cg_expr e)
211 | | Block stmts ->
212 | F.Block (List.map stmts ~f:self#cg_stmt)
213 | | If {if_condition; if_then; if_else} ->
214 | F.If
215 | ( self#cg_expr if_condition,
216 | self#cg_stmt if_then,
217 | Option.map if_else ~f:self#cg_stmt )
218 | | Break stmt ->
219 | self#cg_stmt stmt (* FIXME: this is unlikely to be correct *)
220 | | Switch s ->
221 | self#cg_switch s
222 | | WhileLoop {while_cond; while_body} ->
223 | F.While
224 | { cond = self#cg_expr while_cond;
225 | body = self#cg_stmt while_body }
226 | | _ ->
227 | raise Unsupported
228 |
229 | method cg_switch switch =
230 | let f_cond =
231 | F.Vars
232 | [(F.UnknownTuple, "temp", self#cg_expr switch.switch_condition)]
233 | in
234 | let f_discr =
235 | F.Vars
236 | [ ( F.IntType,
237 | "discr",
238 | F.FunctionCall
239 | ("first", [Reference ("temp", F.UnknownTuple)], F.IntType)
240 | ) ]
241 | in
242 | let union =
243 | match T.type_of program switch.switch_condition with
244 | | UnionType u ->
245 | List.Assoc.find_exn program.unions u ~equal:equal_int
246 | | _ ->
247 | ice "Type-check error"
248 | in
249 | let branches =
250 | List.fold (List.rev switch.branches)
251 | ~init:
252 | (F.Block
253 | [ F.Expr
254 | (F.FunctionCall
255 | ("thrown", [F.Integer (Z.of_int 90)], F.InferType) );
256 | F.Return
257 | (F.FunctionCall
258 | ("func_believe_me", [F.Tuple []], F.InferType) ) ] )
259 | ~f:(fun acc b ->
260 | let ty_discr = self#get_discriminator union b.value.branch_ty in
261 | let cond =
262 | F.Operator
263 | ( F.Reference ("discr", F.IntType),
264 | EqualityOperator,
265 | F.Integer (Z.of_int ty_discr) )
266 | in
267 | let inner =
268 | F.Block
269 | [ F.Vars
270 | [ ( self#lang_type_to_type b.value.branch_ty,
271 | b.value.branch_var.value,
272 | F.FunctionCall
273 | ( "second",
274 | [Reference ("temp", F.UnknownTuple)],
275 | F.IntType ) ) ];
276 | self#cg_stmt b.value.branch_stmt ]
277 | in
278 | F.Block [F.If (cond, inner, Some acc)] )
279 | in
280 | F.Block [f_cond; f_discr; branches]
281 |
282 | method cg_function_ : string -> T.function_ -> F.function_ =
283 | fun name fn ->
284 | let body =
285 | match fn.value.function_impl with
286 | | Fn {value = Block stmts; _}
287 | | UniversalFn ({value = Block stmts; _}, _) ->
288 | stmts
289 | | Fn stmt | UniversalFn (stmt, _) ->
290 | [stmt]
291 | | _ ->
292 | []
293 | in
294 | { function_forall = [];
295 | function_name = name;
296 | function_args =
297 | List.map fn.value.function_signature.value.function_params
298 | ~f:(fun (name, ty) -> (name.value, self#lang_type_to_type ty));
299 | function_returns =
300 | self#lang_type_to_type
301 | fn.value.function_signature.value.function_returns;
302 | function_body = F.Fn (List.map body ~f:self#cg_stmt);
303 | function_is_impure = true }
304 |
305 | method cg_top_level_stmt : string -> T.expr -> F.top_level_expr option =
306 | fun name expr ->
307 | match expr.value with
308 | | Value (Function f) -> (
309 | try Some (F.Function (self#add_function f ~name:(Some name)))
310 | with ex ->
311 | if equal_string name "test_req_builder" then raise ex else None
312 | )
313 | | _ ->
314 | None
315 |
316 | method cg_program : T.program -> F.program =
317 | fun program ->
318 | let _ =
319 | List.filter_map (List.rev program.bindings)
320 | ~f:(fun (name, top_level_stmt) ->
321 | self#cg_top_level_stmt name.value top_level_stmt )
322 | and make_tensor_accessors (sz : int) =
323 | let typeargs =
324 | List.map ~f:(fun i -> "Value" ^ Int.to_string i)
325 | @@ List.range ~start:`inclusive ~stop:`inclusive 1 sz
326 | in
327 | List.map ~f:(fun i ->
328 | let ret = List.nth_exn typeargs (i - 1)
329 | and tensor_type =
330 | F.TensorType
331 | (List.map typeargs ~f:(fun ident -> F.NamedType ident))
332 | in
333 | F.Function
334 | { function_forall = typeargs;
335 | function_name =
336 | "tensor" ^ Int.to_string sz ^ "_value" ^ Int.to_string i;
337 | function_args = [("tensor", tensor_type)];
338 | function_returns = NamedType ret;
339 | function_body =
340 | Fn
341 | [ DestructuringBinding
342 | ( List.mapi typeargs ~f:(fun i' typearg ->
343 | if Int.equal i (i' + 1) then
344 | (Some (F.NamedType typearg), "value")
345 | else (None, "_") ),
346 | Reference ("tensor", tensor_type) );
347 | Return (Reference ("value", NamedType ret)) ];
348 | function_is_impure = false } )
349 | @@ List.range ~start:`inclusive ~stop:`inclusive 1 sz
350 | and default_functions : F.top_level_expr list =
351 | [ { function_name = "func_believe_me";
352 | function_forall = ["A"; "B"];
353 | function_args = [("i", NamedType "A")];
354 | function_returns = NamedType "B";
355 | function_body = AsmFn "NOP";
356 | function_is_impure = false };
357 | { function_name = "func_bit_not";
358 | function_forall = [];
359 | function_args = [("a", IntType)];
360 | function_returns = IntType;
361 | function_body =
362 | Fn [Return (UnaryOp ("~", Reference ("a", IntType)))];
363 | function_is_impure = false } ]
364 | |> List.map ~f:(fun (x : F.function_) -> F.Function x)
365 | in
366 | make_tensor_accessors 2 @ default_functions
367 | @ List.map helpers ~f:(fun x -> F.Function x)
368 | @ List.map (List.rev functions) ~f:(fun (_, f) -> F.Function f)
369 | |> List.map ~f:(fun x ->
370 | match x with
371 | | F.Function f ->
372 | if String.equal f.function_name "believe_me" then
373 | F.Function
374 | { function_name = "believe_me";
375 | function_forall = ["A"; "B"];
376 | function_args = [("i", NamedType "A")];
377 | function_returns = NamedType "B";
378 | function_body = AsmFn "NOP";
379 | function_is_impure = false }
380 | else F.Function f
381 | | x ->
382 | x )
383 |
384 | method cg_StructField : T.expr * string located * T.type_ -> _ =
385 | fun (from_expr, field, _) ->
386 | let build_access ~(tensor : int option) struct_ty field field_ty =
387 | match tensor with
388 | | None ->
389 | if field >= 16 then
390 | ice "Only structs with 16 or less fields are allowed" ;
391 | let fun_name = "get" ^ Int.to_string field in
392 | ( if
393 | not
394 | @@ List.exists helpers ~f:(fun x ->
395 | String.equal x.function_name fun_name )
396 | then
397 | let fn : F.function_ =
398 | { function_name = fun_name;
399 | function_forall = ["A"];
400 | function_body = AsmFn (Int.to_string field ^ " INDEX");
401 | function_args = [("t", UnknownTuple)];
402 | function_returns = NamedType "A";
403 | function_is_impure = false }
404 | in
405 | helpers <- fn :: helpers ) ;
406 | let converted_value =
407 | F.FunctionCall ("func_believe_me", [struct_ty], UnknownTuple)
408 | in
409 | F.FunctionCall (fun_name, [converted_value], field_ty)
410 | | Some arity ->
411 | let name =
412 | "tensor" ^ Int.to_string arity ^ "_value"
413 | ^ Int.to_string (field + 1)
414 | in
415 | F.FunctionCall (name, [struct_ty], field_ty)
416 | in
417 | match T.type_of program from_expr with
418 | | StructType s -> (
419 | let s = T.Program.get_struct program s in
420 | match s.struct_fields with
421 | | [_] ->
422 | self#cg_expr from_expr
423 | | _ ->
424 | let field_id, (_, field) =
425 | Option.value_exn
426 | (List.findi s.struct_fields ~f:(fun _ (name, _) ->
427 | equal_string name.value field.value ) )
428 | in
429 | build_access
430 | ~tensor:
431 | ( if s.tensor then Some (List.length s.struct_fields)
432 | else None )
433 | (self#cg_expr from_expr) field_id
434 | (self#lang_type_to_type field.field_type) )
435 | | _ ->
436 | raise Invalid
437 |
438 | method cg_ResolvedReference : 'a. 'a * T.expr -> F.expr =
439 | fun (_, expr) -> self#cg_expr expr
440 |
441 | method cg_Primitive : T.primitive -> F.expr =
442 | function
443 | | Prim {name; exprs; out_ty} ->
444 | F.FunctionCall
445 | ( name,
446 | List.map exprs ~f:self#cg_expr,
447 | self#lang_type_to_type out_ty )
448 |
449 | method private lang_type_to_type : T.type_ -> F.type_ =
450 | function
451 | | IntegerType ->
452 | F.IntType
453 | | BoolType ->
454 | F.IntType
455 | | StructType s ->
456 | self#struct_to_ty (T.Program.get_struct program s)
457 | | UnionType _ ->
458 | F.UnknownTuple
459 | (* self#create_ty_from_union
460 | (List.Assoc.find_exn program.unions u ~equal:equal_int) *)
461 | | BuiltinType "Builder" ->
462 | F.BuilderType
463 | | BuiltinType "Cell" ->
464 | F.CellType
465 | | BuiltinType "Slice" ->
466 | F.SliceType
467 | | HoleType | VoidType ->
468 | F.InferType
469 | | _ ->
470 | raise Invalid
471 |
472 | method private struct_to_ty : T.struct_ -> F.type_ =
473 | fun s ->
474 | match
475 | List.find struct_representations ~f:(fun (s', _) ->
476 | T.equal_struct_ s s' )
477 | with
478 | | Some (_, ty) ->
479 | ty
480 | | None ->
481 | let ty = self#create_ty_from_struct s in
482 | struct_representations <- (s, ty) :: struct_representations ;
483 | ty
484 |
485 | method private create_ty_from_struct : T.struct_ -> F.type_ =
486 | function
487 | | {struct_fields = [(_, {field_type})]; _} ->
488 | self#lang_type_to_type field_type
489 | | {struct_fields; tensor; _} ->
490 | let types =
491 | List.map struct_fields ~f:(fun (_, {field_type}) ->
492 | self#lang_type_to_type field_type )
493 | in
494 | if tensor then TensorType types else TupleType types
495 |
496 | method private create_ty_from_union : T.union -> F.type_ =
497 | function _ -> UnknownTuple
498 |
499 | method private add_function
500 | : ?name:string option -> T.function_ -> F.function_ =
501 | fun ?(name = None) fn ->
502 | let default () =
503 | let name =
504 | Option.value_or_thunk name ~default:(fun () ->
505 | self#generate_func_name )
506 | in
507 | let fn' = self#cg_function_ name fn in
508 | functions <- (fn, fn') :: functions ;
509 | fn'
510 | in
511 | List.Assoc.find functions ~equal:T.equal_function_ fn
512 | |> Option.value_or_thunk ~default
513 |
514 | method private generate_func_name =
515 | let num = fn_name_counter in
516 | fn_name_counter <- fn_name_counter + 1 ;
517 | "f" ^ Printf.sprintf "%d" num
518 | end
519 |
520 | let codegen program =
521 | let constructor = new constructor program in
522 | constructor#cg_program program
523 | end
524 |
--------------------------------------------------------------------------------
/lib/compiler.ml:
--------------------------------------------------------------------------------
1 | (* Compiler frontend *)
2 |
3 | module Config = Located.Enabled
4 | module Lang = Lang.Make (Config)
5 | module Show = Show.Make (Config)
6 | module Syntax = Syntax.Make (Config)
7 | module Parser = Parser.Make (Config)
8 | module Codegen_func = Codegen_func.Make (Config)
9 | module Builtin = Builtin.Make (Config)
10 |
11 | let rec compile ?(codegen_impl = Codegen_func.codegen) ?(filename = "")
12 | ch =
13 | let text = really_input_string ch (in_channel_length ch) in
14 | close_in ch ;
15 | compile_from_string ~codegen_impl ~filename text
16 |
17 | and compile_from_string ?(codegen_impl = Codegen_func.codegen)
18 | ?(filename = "") src =
19 | codegen ~codegen_impl (compile_to_ir ~filename src)
20 |
21 | and codegen ?(codegen_impl = Codegen_func.codegen) program =
22 | match program with
23 | | Ok program' ->
24 | let generated_code = codegen_impl program' and buffer = Buffer.create 0 in
25 | let formatter = Caml.Format.formatter_of_buffer buffer in
26 | Func.pp_program formatter generated_code ;
27 | Ok (Buffer.contents buffer)
28 | | Error e ->
29 | Error e
30 |
31 | and eval_stmt ~(constructor : _ Lang.constructor) ~filename text =
32 | ignore filename ;
33 | match
34 | MParser.(
35 | parse_string
36 | Parser.(
37 | handle_errors
38 | ( attempt (locate stmt)
39 | <|> ( locate expr
40 | |>> fun s -> Syntax.map_located s ~f:(fun _ -> Syntax.Expr s) )
41 | <|> locate (return (Syntax.CodeBlock []))
42 | <<< eof ) ) )
43 | text ()
44 | with
45 | | Success stx -> (
46 | let errors = constructor#get_errors in
47 | let result =
48 | try
49 | let stmt =
50 | constructor#visit_located constructor#visit_stmt Lang.default_ctx
51 | stx
52 | in
53 | (constructor#make_interpreter stmt.span)#interpret_stmt
54 | (Syntax.map_located
55 | ~f:(function Lang.Expr e -> Lang.Return e | stmt -> stmt)
56 | stmt )
57 | []
58 | with Lang.Skip -> Void
59 | in
60 | match errors#to_result () with
61 | | Error _ ->
62 | Error (errors#show_errors text)
63 | | Ok _ ->
64 | Ok result )
65 | | MParser.Failed (msg, _) ->
66 | Error msg
67 |
68 | and construct ?(prev_program = Lang.default_program ()) ~filename text =
69 | ignore filename ;
70 | let stx = Parser.parse text in
71 | let errors = new Errors.errors Show.show_error in
72 | (stx, new Lang.constructor ~program:prev_program errors)
73 |
74 | and compile_to_ir' ?(prev_program = Lang.default_program ()) ~filename text =
75 | match construct ~prev_program ~filename text with
76 | | stx, constructor -> (
77 | let program = constructor#visit_program Lang.default_ctx stx in
78 | let errors = constructor#get_errors in
79 | match errors#to_result () with
80 | | Error _ ->
81 | Error (errors#show_errors text)
82 | | Ok _ ->
83 | Ok (constructor, program) )
84 | | exception Parser.Error (msg, _) ->
85 | Error msg
86 |
87 | and compile_to_ir ?(prev_program = Lang.default_program ()) ~filename text =
88 | Result.map snd @@ compile_to_ir' ~prev_program ~filename text
89 |
90 | let compile_with_std ?(codegen_impl = Codegen_func.codegen)
91 | ?(filename = "") ch =
92 | let prev_program =
93 | compile_to_ir ~filename:"std.tact" Builtin.std |> Result.get_ok
94 | in
95 | let text = really_input_string ch (in_channel_length ch) in
96 | close_in ch ;
97 | codegen ~codegen_impl (compile_to_ir ~prev_program ~filename text)
98 |
--------------------------------------------------------------------------------
/lib/config.ml:
--------------------------------------------------------------------------------
1 | module type T = sig
2 | include Located.T
3 | end
4 |
--------------------------------------------------------------------------------
/lib/discriminator.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | module Make =
4 | functor
5 | (Config : Config.T)
6 | ->
7 | struct
8 | open Lang_types.Make (Config)
9 |
10 | module LocalDiscriminators = struct
11 | type t = unit
12 |
13 | open struct
14 | let get_discr_from_attrs attrs =
15 | List.find_map attrs ~f:(fun {attribute_ident; attribute_exprs} ->
16 | match (attribute_ident.value, attribute_exprs) with
17 | | "discriminator", [{value = Value (Integer x); _}] ->
18 | Some (Discriminator {discr = Z.to_int x; bits = None})
19 | | ( "discriminator",
20 | [ {value = Value (Integer x); _};
21 | {value = Value (Integer bits); _} ] ) ->
22 | Some
23 | (Discriminator
24 | {discr = Z.to_int x; bits = Some (Z.to_int bits)} )
25 | | _ ->
26 | None )
27 | end
28 |
29 | let choose_discriminators :
30 | t ->
31 | int ->
32 | (type_ * attribute list) list ->
33 | (type_ * discriminator) list =
34 | fun _ _ cases ->
35 | List.mapi (List.rev cases) ~f:(fun id (case, attrs) ->
36 | ( case,
37 | Option.value_or_thunk (get_discr_from_attrs attrs)
38 | ~default:(fun _ -> Discriminator {discr = id; bits = None}) ) )
39 | |> List.rev
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (libraries
3 | base
4 | ppx_show.runtime
5 | zarith
6 | visitors.runtime
7 | ppx_sexp_conv
8 | sexplib
9 | containers
10 | mparser)
11 | (preprocess
12 | (pps
13 | ppx_show
14 | ppx_make
15 | ppx_compare
16 | visitors.ppx
17 | ppx_sexp_conv
18 | ppx_blob
19 | ppx_hash))
20 | (preprocessor_deps
21 | (file std/std.tact))
22 | (name tact))
23 |
--------------------------------------------------------------------------------
/lib/errors.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | exception InternalCompilerError of string
4 |
5 | let unreachable () = raise (InternalCompilerError "unreachable")
6 |
7 | let ice msg = raise (InternalCompilerError msg)
8 |
9 | class ['a, 's, 'e, 'm] errors (show_error : string -> 'e -> string) =
10 | object (s : _)
11 | val mutable errors = []
12 |
13 | val show_error = show_error
14 |
15 | method report : 's -> 'e -> 'm -> unit =
16 | fun severity error meta -> errors <- (severity, error, meta) :: errors
17 |
18 | method errors = List.rev errors
19 |
20 | method to_result : 'a -> ('a, _) Result.t =
21 | fun value -> if List.is_empty errors then Ok value else Error s#errors
22 |
23 | method show_errors code =
24 | List.fold_left (List.rev errors) ~init:"" ~f:(fun s (_, e, _) ->
25 | s ^ show_error code e ^ "\n" )
26 | end
27 |
--------------------------------------------------------------------------------
/lib/func.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | exception Unsupported
4 |
5 | (* Subset of FunC used by FunC codegen *)
6 |
7 | type function_ =
8 | { function_name : ident;
9 | function_args : (ident * type_) list;
10 | function_returns : type_;
11 | function_body : function_body;
12 | function_forall : ident list;
13 | function_is_impure : bool }
14 |
15 | and function_body = AsmFn of string | Fn of stmt list [@sexp.list]
16 |
17 | and stmt =
18 | | Vars of (type_ * ident * expr) list
19 | | DestructuringBinding of (type_ option * ident) list * expr
20 | | Assignment of (ident * expr)
21 | | Return of expr
22 | | Expr of expr
23 | | Block of stmt list
24 | | If of (expr * stmt * stmt option)
25 | | While of {cond : expr; body : stmt}
26 |
27 | and expr =
28 | | Integer of Zint.t
29 | | Reference of (ident * type_)
30 | | Tuple of expr list
31 | | FunctionCall of (ident * expr list * type_)
32 | | Operator of (expr * operator * expr)
33 | | UnaryOp of (string * expr)
34 |
35 | and operator = EqualityOperator
36 |
37 | and ident = string
38 |
39 | and type_ =
40 | | IntType
41 | | CellType
42 | | SliceType
43 | | BuilderType
44 | | TupleType of type_ list
45 | | UnknownTuple
46 | | TensorType of type_ list
47 | | FunctionType of function_
48 | | ContType
49 | | InferType
50 | | NamedType of ident
51 |
52 | and top_level_expr = Function of function_ | Global of type_ * ident
53 |
54 | and program = top_level_expr list [@@deriving sexp_of]
55 |
56 | exception UnknownType
57 |
58 | let rec type_of = function
59 | | Integer _ ->
60 | IntType
61 | | Reference (_, ty) ->
62 | ty
63 | | Tuple exprs ->
64 | TupleType (List.map exprs ~f:type_of)
65 | | FunctionCall (_, _, ty) ->
66 | ty
67 | | Operator (_, EqualityOperator, _) ->
68 | IntType
69 | | UnaryOp (_, _) ->
70 | IntType
71 |
72 | open Caml.Format
73 |
74 | let list_iter ~f ~flast l =
75 | match (List.drop_last l, List.last l) with
76 | | Some rest, Some last ->
77 | List.iter rest ~f ; flast last
78 | | _ ->
79 | ()
80 |
81 | let indentation = " "
82 |
83 | open struct
84 | let string_repeat s n =
85 | let s = Bytes.of_string s in
86 | let len = Bytes.length s in
87 | let res = Bytes.create (n * len) in
88 | for i = 0 to Int.pred n do
89 | Bytes.blit ~src:s ~src_pos:0 ~dst:res ~dst_pos:(i * len) ~len
90 | done ;
91 | Bytes.to_string res
92 | end
93 |
94 | let pp_indentation f level = pp_print_string f (string_repeat " " (level * 2))
95 |
96 | let boxed ?(box_size = 2) f ~func =
97 | pp_open_box f box_size ; func f ; pp_close_box f ()
98 |
99 | let rec pp_program f program =
100 | let prev_margin = pp_get_margin f () in
101 | let prev_indent = pp_get_max_indent f () in
102 | pp_set_margin f 80 ;
103 | pp_set_max_indent f 40 ;
104 | List.iter program ~f:(function
105 | | Function fn ->
106 | pp_function 0 f fn ; pp_print_newline f ()
107 | | Global _ ->
108 | () ) ;
109 | pp_set_margin f prev_margin ;
110 | pp_set_max_indent f prev_indent
111 |
112 | and pp_function level f fn =
113 | pp_open_box f 2 ;
114 | ( match fn.function_forall with
115 | | [] ->
116 | ()
117 | | typeargs ->
118 | pp_print_string f "forall" ;
119 | pp_print_space f () ;
120 | list_iter typeargs
121 | ~f:(fun ident -> pp_ident f ident ; pp_print_string f ", ")
122 | ~flast:(pp_ident f) ;
123 | pp_print_space f () ;
124 | pp_print_string f "->" ;
125 | pp_print_space f () ) ;
126 | pp_type f fn.function_returns ;
127 | pp_print_space f () ;
128 | pp_ident f fn.function_name ;
129 | pp_print_string f "(" ;
130 | list_iter fn.function_args
131 | ~f:(fun (name, t) ->
132 | pp_type f t ;
133 | pp_print_space f () ;
134 | pp_ident f name ;
135 | pp_print_string f ", " )
136 | ~flast:(fun (name, t) -> pp_type f t ; pp_print_space f () ; pp_ident f name) ;
137 | pp_print_string f ")" ;
138 | pp_print_space f () ;
139 | if fn.function_is_impure then (
140 | pp_print_string f "impure" ; pp_print_space f () ) ;
141 | pp_function_body (level + 1) f fn.function_body ;
142 | pp_close_box f ()
143 |
144 | and pp_function_body level f = function
145 | | Fn stmts ->
146 | pp_print_string f "{" ;
147 | pp_print_newline f () ;
148 | List.iter stmts ~f:(fun stmt ->
149 | pp_indentation f level ;
150 | pp_open_box f 2 ;
151 | pp_stmt level f stmt ;
152 | pp_close_box f () ) ;
153 | pp_print_string f "}"
154 | | AsmFn str ->
155 | pp_print_string f "asm" ;
156 | pp_print_space f () ;
157 | pp_print_string f @@ "\"" ^ str ^ "\"" ;
158 | pp_print_string f ";"
159 |
160 | and pp_stmt level f = function
161 | | Vars vars ->
162 | List.iter vars ~f:(fun (t, n, expr) ->
163 | boxed f ~func:(fun f ->
164 | pp_type f t ;
165 | pp_print_space f () ;
166 | pp_ident f n ;
167 | pp_print_space f () ;
168 | pp_print_string f "=" ;
169 | pp_print_space f () ;
170 | pp_expr f expr ;
171 | pp_print_string f ";" ;
172 | pp_close_box f () ;
173 | pp_print_newline f () ) )
174 | | DestructuringBinding (vars, expr) ->
175 | let tensor =
176 | match expr with Reference (_, TensorType _) -> true | _ -> false
177 | in
178 | boxed f ~func:(fun f ->
179 | pp_print_string f (if tensor then "(" else "[") ;
180 | list_iter vars
181 | ~f:(fun (t, n) ->
182 | ( match t with
183 | | Some t ->
184 | pp_type f t ; pp_print_space f ()
185 | | None ->
186 | () ) ;
187 | pp_ident f n ; pp_print_string f "," ; pp_print_space f () )
188 | ~flast:(fun (t, n) ->
189 | ( match t with
190 | | Some t ->
191 | pp_type f t ; pp_print_space f ()
192 | | None ->
193 | () ) ;
194 | pp_ident f n ) ;
195 | pp_print_string f (if tensor then ")" else "]") ;
196 | pp_print_space f () ;
197 | pp_print_string f "=" ;
198 | pp_print_space f () ;
199 | pp_expr f expr ;
200 | pp_print_string f ";" ;
201 | pp_print_newline f () )
202 | | Assignment (ident, expr) ->
203 | boxed f ~func:(fun f ->
204 | pp_ident f ident ;
205 | pp_print_space f () ;
206 | pp_print_string f "=" ;
207 | pp_print_space f () ;
208 | pp_expr f expr ;
209 | pp_print_string f ";" ;
210 | pp_print_newline f () )
211 | | Return expr ->
212 | boxed f ~func:(fun f ->
213 | pp_print_string f "return" ;
214 | pp_print_space f () ;
215 | pp_expr f expr ;
216 | pp_print_string f ";" ;
217 | pp_print_newline f () )
218 | | Expr expr ->
219 | pp_expr f expr ; pp_print_string f ";" ; pp_print_newline f ()
220 | | Block [stmt] ->
221 | pp_print_string f "{" ;
222 | pp_print_newline f () ;
223 | boxed f ~func:(fun f ->
224 | pp_indentation f (level + 1) ;
225 | pp_stmt (level + 1) f stmt ) ;
226 | pp_indentation f level ;
227 | pp_print_string f "}" ;
228 | pp_print_newline f ()
229 | | Block stmts ->
230 | pp_print_string f "{" ;
231 | pp_print_newline f () ;
232 | boxed f ~func:(fun f ->
233 | List.iter stmts ~f:(fun x ->
234 | pp_indentation f (level + 1) ;
235 | pp_stmt (level + 1) f x ) ) ;
236 | pp_indentation f level ;
237 | pp_print_string f "}" ;
238 | pp_print_newline f ()
239 | | If (condition, then_, else_) ->
240 | boxed f ~func:(fun f ->
241 | pp_print_string f "if" ;
242 | pp_print_space f () ;
243 | pp_print_string f "(" ;
244 | pp_expr f condition ;
245 | pp_print_string f ")" ;
246 | pp_print_space f () ;
247 | pp_stmt level f then_ ;
248 | Option.iter else_ ~f:(fun e ->
249 | boxed f ~func:(fun f ->
250 | pp_indentation f level ;
251 | pp_print_string f "else" ;
252 | pp_print_space f () ;
253 | pp_stmt level f e ) ) )
254 | | While {cond; body} ->
255 | pp_print_string f "while" ;
256 | pp_print_space f () ;
257 | pp_print_string f "(" ;
258 | pp_expr f cond ;
259 | pp_print_string f ")" ;
260 | pp_print_space f () ;
261 | pp_stmt (level + 1) f body
262 |
263 | and pp_expr f = function
264 | | Integer i ->
265 | pp_print_string f (Zint.to_string i)
266 | | Reference (ref, _) ->
267 | pp_ident f ref
268 | | FunctionCall (name, args, _) ->
269 | pp_print_string f name ;
270 | pp_print_string f "(" ;
271 | list_iter args
272 | ~f:(fun t -> pp_expr f t ; pp_print_string f ", ")
273 | ~flast:(pp_expr f) ;
274 | pp_print_string f ")"
275 | | Tuple tuple ->
276 | pp_print_string f "[" ;
277 | list_iter tuple
278 | ~f:(fun t -> pp_expr f t ; pp_print_string f ", ")
279 | ~flast:(pp_expr f) ;
280 | pp_print_string f "]"
281 | | Operator (left, op, right) ->
282 | pp_expr f left ;
283 | pp_print_space f () ;
284 | pp_operator f op ;
285 | pp_print_space f () ;
286 | pp_expr f right
287 | | UnaryOp (op, expr) ->
288 | pp_print_string f op ; pp_print_space f () ; pp_expr f expr
289 |
290 | and pp_operator f = function EqualityOperator -> pp_print_string f "=="
291 |
292 | and pp_type f = function
293 | | IntType ->
294 | pp_print_string f "int"
295 | | CellType ->
296 | pp_print_string f "cell"
297 | | SliceType ->
298 | pp_print_string f "slice"
299 | | BuilderType ->
300 | pp_print_string f "builder"
301 | | ContType ->
302 | pp_print_string f "cont"
303 | | TupleType tuple ->
304 | pp_print_string f "[" ;
305 | list_iter tuple
306 | ~f:(fun (t : type_) -> pp_type f t ; pp_print_string f ", ")
307 | ~flast:(pp_type f) ;
308 | pp_print_string f "]"
309 | | UnknownTuple ->
310 | pp_print_string f "tuple"
311 | | TensorType tuple ->
312 | pp_print_string f "(" ;
313 | list_iter tuple
314 | ~f:(fun t -> pp_type f t ; pp_print_string f ", ")
315 | ~flast:(pp_type f) ;
316 | pp_print_string f ")"
317 | | InferType ->
318 | pp_print_string f "_"
319 | | FunctionType _ ->
320 | raise UnknownType
321 | | NamedType name ->
322 | pp_print_string f name
323 |
324 | and pp_ident f i =
325 | match i with
326 | | "int" | "cell" | "slice" | "builder" | "cont" | "tuple " ->
327 | pp_print_string f (i ^ "_")
328 | | _ ->
329 | pp_print_string f i
330 |
--------------------------------------------------------------------------------
/lib/located.ml:
--------------------------------------------------------------------------------
1 | module Lexing' = struct
2 | open Base
3 | open Caml.Format
4 |
5 | let equal_string = String.equal
6 |
7 | let equal_int = Int.equal
8 |
9 | include Lexing
10 |
11 | let equal_position p1 p2 =
12 | String.equal p1.pos_fname p2.pos_fname
13 | && equal p1.pos_lnum p2.pos_lnum
14 | && equal p1.pos_bol p2.pos_bol
15 | && equal p1.pos_cnum p2.pos_cnum
16 |
17 | let sexp_of_position _pos = Sexplib.Sexp.(Atom "pos")
18 |
19 | type pos = position [@@deriving equal, sexp_of]
20 |
21 | let pp_pos f p =
22 | pp_print_string f p.pos_fname ;
23 | pp_print_string f ":" ;
24 | pp_print_int f p.pos_lnum ;
25 | pp_print_string f "," ;
26 | pp_print_int f p.pos_cnum
27 | end
28 |
29 | type pos = Lexing'.pos [@@deriving show {with_path = false}, equal]
30 |
31 | let sexp_of_pos = Lexing'.sexp_of_pos
32 |
33 | type span_concrete = pos * pos [@@deriving show, equal, sexp_of]
34 |
35 | let merge_spans_concrete : span_concrete -> span_concrete -> span_concrete =
36 | fun (s1, _) (_, e2) -> (s1, e2)
37 |
38 | let merge_spans_concrete_list list =
39 | let open Base in
40 | match list with
41 | | [] ->
42 | (Lexing'.dummy_pos, Lexing'.dummy_pos)
43 | | _ ->
44 | let hd = List.hd_exn list in
45 | let tl = List.tl_exn list in
46 | let rec merge_spans_list_inner left = function
47 | | [] ->
48 | left
49 | | x :: xs ->
50 | merge_spans_list_inner (merge_spans_concrete left x) xs
51 | in
52 | merge_spans_list_inner hd tl
53 |
54 | module type T = sig
55 | type span
56 |
57 | type 'a located = {span : span; value : 'a}
58 |
59 | val pp_located :
60 | (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a located -> unit
61 |
62 | val make_located : span:span_concrete -> value:'a -> unit -> 'a located
63 |
64 | val equal_located : ('a -> 'a -> bool) -> 'a located -> 'a located -> bool
65 |
66 | val hash_fold_located :
67 | (Base_internalhash_types.state -> 'a -> Base_internalhash_types.state) ->
68 | Base_internalhash_types.state ->
69 | 'a located ->
70 | Base_internalhash_types.state
71 |
72 | val compare_located : ('a -> 'a -> int) -> 'a located -> 'a located -> int
73 |
74 | val sexp_of_located : ('a -> Sexplib0.Sexp.t) -> 'a located -> Sexplib0.Sexp.t
75 |
76 | val value : 'a located -> 'a
77 |
78 | val span : 'a located -> span
79 |
80 | val pp_span : Format.formatter -> span -> unit
81 |
82 | val make_loc : 'a 'b. f:('a -> 'b) -> 'a located -> 'b located
83 |
84 | val builtin_located : 'a -> 'a located
85 |
86 | val merge_spans : span -> span -> span
87 |
88 | val merge_spans_list : span list -> span
89 |
90 | val span_of_concrete : span_concrete -> span
91 |
92 | val span_to_concrete : span -> span_concrete
93 |
94 | val map_located : 'a 'b. f:('a -> 'b) -> 'a located -> 'b located
95 | end
96 |
97 | module Enabled : T = struct
98 | type span = span_concrete
99 |
100 | type 'a located = {span : span_concrete; [@hash.ignore] value : 'a}
101 | [@@deriving show {with_path = false}, make, sexp_of, hash]
102 |
103 | let equal_located f {value = value1; _} {value = value2; _} = f value1 value2
104 |
105 | let compare_located f {value = value1; _} {value = value2; _} =
106 | f value1 value2
107 |
108 | let value l = l.value
109 |
110 | let span l = l.span
111 |
112 | let pp_span = pp_span_concrete
113 |
114 | let make_loc ~f l = {span = l.span; value = f l.value}
115 |
116 | let builtin_located a =
117 | {span = (Lexing'.dummy_pos, Lexing'.dummy_pos); value = a}
118 |
119 | let merge_spans = merge_spans_concrete
120 |
121 | let merge_spans_list = merge_spans_concrete_list
122 |
123 | let span_of_concrete s = s
124 |
125 | let span_to_concrete s = s
126 |
127 | let map_located ~f {span; value} = {span; value = f value}
128 | end
129 |
130 | module Disabled : T = struct
131 | open Base.Hash.Builtin
132 |
133 | type span = unit
134 |
135 | type 'a located = {span : unit; value : 'a} [@@deriving show, hash]
136 |
137 | let sexp_of_located f l = f l.value
138 |
139 | let make_located ~span:_span ~value () = {span = (); value}
140 |
141 | let equal_located f v1 v2 = f v1.value v2.value
142 |
143 | let compare_located f v1 v2 = f v1.value v2.value
144 |
145 | let value v = v.value
146 |
147 | let span _ = ()
148 |
149 | let pp_span _ _ = ()
150 |
151 | let make_loc ~f l = {span = l.span; value = f l.value}
152 |
153 | let builtin_located a = {span = (); value = a}
154 |
155 | let merge_spans _ _ = ()
156 |
157 | let merge_spans_list _ = ()
158 |
159 | let span_of_concrete _ = ()
160 |
161 | let span_to_concrete _ = (Lexing'.dummy_pos, Lexing'.dummy_pos)
162 |
163 | let map_located ~f {span; value; _} = {span; value = f value}
164 | end
165 |
--------------------------------------------------------------------------------
/lib/partial_evaluator.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Errors
3 |
4 | module Make =
5 | functor
6 | (Config : Config.T)
7 | ->
8 | struct
9 | open Config
10 |
11 | open Interpreter.Make (Config)
12 |
13 | open Lang_types.Make (Config)
14 |
15 | class ['s] partial_evaluator (ctx : ctx) (errors : _) =
16 | object (self : 's)
17 | inherit [_] map as super
18 |
19 | (* Make a deep copy of the ctx. Local values should not flow into
20 | code that call partial_evaluator. *)
21 | val mutable ctx = {ctx with program = ctx.program}
22 |
23 | method! visit_InvalidType _ _ = unreachable ()
24 |
25 | method! visit_Reference env (ref, ty) =
26 | match find_in_scope ref.value !(ctx.scope) with
27 | | Some (Comptime ex) ->
28 | Value
29 | (self#with_interpreter env ref.span (fun inter ->
30 | inter#interpret_expr ex ) )
31 | | Some (Runtime _) ->
32 | Reference (ref, self#visit_type_ env ty)
33 | | None ->
34 | ice "Resolver bug"
35 |
36 | method! visit_type_ env ty =
37 | let ty = super#visit_type_ env ty in
38 | if
39 | is_immediate_expr !(ctx.scope) ctx.program
40 | (builtin_located @@ Value (Type ty))
41 | then
42 | self#with_interpreter env (builtin_located ()).span (fun inter ->
43 | inter#interpret_type ty )
44 | else self#unwrap_expr_types ty
45 |
46 | method private unwrap_expr_types =
47 | function
48 | | ExprType {value = Value (Type t); _}
49 | | ExprType
50 | {value = ResolvedReference (_, {value = Value (Type t); _}); _} ->
51 | self#unwrap_expr_types t
52 | | t ->
53 | t
54 |
55 | method! visit_Block env b =
56 | self#with_vars [] (fun _ -> super#visit_Block env b)
57 |
58 | method! visit_Let env vars =
59 | (* TODO: this won't work if `vars` will be actually a list. *)
60 | let vars' =
61 | self#visit_list
62 | (fun env (name, ex) -> (name, self#visit_expr env ex))
63 | env vars
64 | in
65 | let vars_scope =
66 | List.map vars' ~f:(fun (name, ex) ->
67 | (name, Runtime (type_of ctx.program ex)) )
68 | in
69 | ctx.scope := vars_scope :: !(ctx.scope) ;
70 | Let vars'
71 |
72 | method! visit_DestructuringLet env let_ =
73 | let expr = self#visit_expr env let_.destructuring_let_expr in
74 | match type_of ctx.program expr with
75 | | StructType id ->
76 | let struct_ = Program.get_struct ctx.program id in
77 | let vars =
78 | List.map let_.destructuring_let ~f:(fun (name, new_name) ->
79 | List.Assoc.find struct_.struct_fields
80 | ~equal:(equal_located String.equal)
81 | name
82 | |> Option.value_exn
83 | |> fun {field_type} -> (new_name, Runtime field_type) )
84 | in
85 | ctx.scope := vars :: !(ctx.scope) ;
86 | DestructuringLet {let_ with destructuring_let_expr = expr}
87 | | other_ty -> (
88 | match type_of_type ctx.program other_ty with
89 | | StructSig sign ->
90 | let struct_sign = Arena.get ctx.program.struct_signs sign in
91 | let vars =
92 | List.map let_.destructuring_let ~f:(fun (name, new_name) ->
93 | List.Assoc.find struct_sign.st_sig_fields
94 | ~equal:(equal_located String.equal)
95 | name
96 | |> Option.value_exn
97 | |> fun field_type ->
98 | (new_name, Runtime (expr_to_type ctx.program field_type)) )
99 | in
100 | ctx.scope := vars :: !(ctx.scope) ;
101 | DestructuringLet {let_ with destructuring_let_expr = expr}
102 | | _ ->
103 | ice "Type-check bug 1" )
104 |
105 | method! visit_switch env switch =
106 | let cond = self#visit_expr env switch.switch_condition in
107 | let branches =
108 | List.map switch.branches
109 | ~f:(fun {value = {branch_var; branch_ty; branch_stmt}; span} ->
110 | let stmt =
111 | self#with_vars
112 | [(branch_var, Runtime branch_ty)]
113 | (fun _ -> self#visit_stmt env branch_stmt)
114 | in
115 | {value = {branch_var; branch_ty; branch_stmt = stmt}; span} )
116 | in
117 | {switch_condition = cond; branches}
118 |
119 | method! visit_function_signature env
120 | { value =
121 | { function_attributes;
122 | function_params;
123 | function_returns;
124 | function_is_type };
125 | span } =
126 | let function_params =
127 | self#visit_list
128 | (fun env (n, e) -> (n, self#visit_type_ env e))
129 | env function_params
130 | in
131 | let vars = List.map function_params ~f:make_runtime in
132 | let function_returns =
133 | self#with_vars vars (fun _ -> self#visit_type_ env function_returns)
134 | in
135 | { value =
136 | { function_attributes;
137 | function_params;
138 | function_returns;
139 | function_is_type };
140 | span }
141 |
142 | method! visit_function_ env f =
143 | let sign =
144 | self#visit_function_signature env f.value.function_signature
145 | in
146 | ctx.functions <- ctx.functions + 1 ;
147 | let args =
148 | List.map sign.value.function_params ~f:(fun (name, ty) ->
149 | (name, Runtime ty) )
150 | in
151 | let out =
152 | self#with_vars args (fun _ ->
153 | let body = self#visit_function_impl env f.value.function_impl in
154 | {function_signature = sign; function_impl = body} )
155 | in
156 | ctx.functions <- ctx.functions - 1 ;
157 | {value = out; span = f.span}
158 |
159 | method! visit_IntfMethodCall env call =
160 | let intf_instance = self#visit_expr env call.intf_instance in
161 | let args = self#visit_list self#visit_expr env call.intf_args in
162 | match is_immediate_expr !(ctx.scope) ctx.program intf_instance with
163 | | true -> (
164 | let intf_ty =
165 | match
166 | self#with_interpreter env call.intf_loc (fun inter ->
167 | inter#interpret_expr intf_instance )
168 | with
169 | | Type t ->
170 | t
171 | | _ ->
172 | ice "Type-check bug 2"
173 | in
174 | match
175 | Program.find_impl_intf ctx.program call.intf_def intf_ty
176 | with
177 | | Some impl ->
178 | let method_ =
179 | List.find_map_exn impl.impl_methods ~f:(fun (name, impl) ->
180 | let method_name, _ = call.intf_method in
181 | if equal_string name.value method_name then Some impl
182 | else None )
183 | in
184 | FunctionCall
185 | ( {value = Value (Function method_); span = call.intf_loc},
186 | args,
187 | method_.value.function_signature.value.function_is_type )
188 | | None ->
189 | ice "Type-check bug 3" )
190 | | false ->
191 | IntfMethodCall {call with intf_instance; intf_args = args}
192 |
193 | val mutable visited_signs : (int * int) list = []
194 |
195 | method! visit_StructSig _ sign_id =
196 | match List.Assoc.find ctx.updated_items sign_id ~equal:equal_int with
197 | | Some new_id ->
198 | StructType new_id
199 | | None ->
200 | StructSig sign_id
201 |
202 | method! visit_mk_struct env mk =
203 | let mk =
204 | self#with_vars
205 | [ make_runtime
206 | ( {value = "Self"; span = mk.mk_struct_details.mk_span},
207 | StructSig mk.mk_struct_details.mk_sig ) ]
208 | (fun _ -> super#visit_mk_struct env mk)
209 | in
210 | mk
211 |
212 | method! visit_UnionSig _ sign_id =
213 | match List.Assoc.find ctx.updated_unions sign_id ~equal:equal_int with
214 | | Some new_id ->
215 | UnionType new_id
216 | | None ->
217 | UnionSig sign_id
218 |
219 | method! visit_mk_union env mk =
220 | let mk =
221 | self#with_vars
222 | [ make_runtime
223 | ( {value = "Self"; span = mk.mk_union_details.mk_span},
224 | UnionSig mk.mk_union_details.mk_sig ) ]
225 | (fun _ -> super#visit_mk_union env mk)
226 | in
227 | mk
228 |
229 | method! visit_MakeUnionVariant env (expr, uid) =
230 | let expr = self#visit_expr env expr in
231 | let new_id =
232 | match List.Assoc.find ctx.updated_items uid ~equal:equal_int with
233 | | Some new_id ->
234 | new_id
235 | | None ->
236 | uid
237 | in
238 | MakeUnionVariant (expr, new_id)
239 |
240 | method! visit_UnionVariant env (expr, uid) =
241 | let expr = self#visit_value env expr in
242 | let new_id =
243 | match List.Assoc.find ctx.updated_items uid ~equal:equal_int with
244 | | Some new_id ->
245 | new_id
246 | | None ->
247 | uid
248 | in
249 | UnionVariant (expr, new_id)
250 |
251 | method! visit_FunctionCall env (f, args, is_ty) =
252 | let f = self#visit_expr env f in
253 | let args = self#visit_list self#visit_expr env args in
254 | if
255 | is_immediate_expr !(ctx.scope) ctx.program
256 | {value = FunctionCall (f, args, is_ty); span = f.span}
257 | then
258 | Value
259 | (self#with_interpreter env f.span (fun inter ->
260 | inter#interpret_fc (f, args, is_ty) ) )
261 | else FunctionCall (f, args, is_ty)
262 |
263 | method! visit_StructSigMethodCall env call =
264 | let st_sig_instance = self#visit_expr env call.st_sig_call_instance in
265 | let args =
266 | self#visit_list self#visit_expr env call.st_sig_call_args
267 | in
268 | let method_name, m_temp = call.st_sig_call_method in
269 | let visited_method_ = self#visit_function_signature env m_temp in
270 | match is_immediate_expr !(ctx.scope) ctx.program st_sig_instance with
271 | | true ->
272 | let st_sig_ty =
273 | match
274 | self#with_interpreter env call.st_sig_call_span (fun inter ->
275 | inter#interpret_expr st_sig_instance )
276 | with
277 | | Type t ->
278 | t
279 | | _ ->
280 | ice "Type-check bug 4"
281 | in
282 | let methods = Program.methods_of ctx.program st_sig_ty in
283 | let method_ =
284 | List.find_map_exn methods ~f:(fun (name, fn) ->
285 | if equal_string name.value method_name then Some fn
286 | else None )
287 | in
288 | FunctionCall
289 | ( { value = Value (Function method_);
290 | span = call.st_sig_call_span },
291 | args,
292 | method_.value.function_signature.value.function_is_type )
293 | | false ->
294 | StructSigMethodCall
295 | { call with
296 | st_sig_call_instance = st_sig_instance;
297 | st_sig_call_args = args;
298 | st_sig_call_method = (method_name, visited_method_) }
299 |
300 | method private with_vars : 'a. _ -> (unit -> 'a) -> 'a =
301 | fun vars f ->
302 | let prev_vars = !(ctx.scope) in
303 | ctx.scope := vars :: prev_vars ;
304 | let out = f () in
305 | ctx.scope := prev_vars ;
306 | out
307 |
308 | (* FIXME: This function should create new instance of the partial_evaluator
309 | and call new_instance#visit_function_ but there is some problems with
310 | generics I can not solve yet. *)
311 | method private with_interpreter
312 | : 'a. 'env -> span -> (interpreter -> 'a) -> 'a =
313 | fun env span f ->
314 | let inter =
315 | new interpreter ctx errors span (fun ctx f ->
316 | self#with_new_ctx ctx (fun _ -> self#visit_function_ env f) )
317 | in
318 | f inter
319 |
320 | method private with_new_ctx new_ctx f =
321 | let old_ctx = ctx in
322 | ctx <- new_ctx ;
323 | let out = f () in
324 | ctx <- old_ctx ;
325 | out
326 | end
327 | end
328 |
--------------------------------------------------------------------------------
/lib/show.ml:
--------------------------------------------------------------------------------
1 | module Make =
2 | functor
3 | (Config : Config.T)
4 | ->
5 | struct
6 | open Caml.Format
7 | module Lang = Lang.Make (Config)
8 | module Interpreter = Interpreter.Make (Config)
9 |
10 | let format_to_string : 'a. 'a -> (formatter -> 'a -> unit) -> string =
11 | fun x show ->
12 | let buffer = Buffer.create 0 in
13 | let f = Format.formatter_of_buffer buffer in
14 | show f x ; pp_print_flush f () ; Buffer.contents buffer
15 |
16 | type error = [Lang.error | Interpreter.error]
17 |
18 | let list_iter ~f ~flast l =
19 | let open Base in
20 | match (List.drop_last l, List.last l) with
21 | | Some rest, Some last ->
22 | List.iter rest ~f ; flast last
23 | | _ ->
24 | ()
25 |
26 | let rec pp_expr : _ -> Lang.expr -> _ =
27 | fun f ex ->
28 | match ex.value with
29 | | Value v ->
30 | pp_value f v
31 | | FunctionCall (fname, args, is_ty) ->
32 | pp_expr f fname ;
33 | pp_print_string f (if is_ty then "[" else "(") ;
34 | list_iter args
35 | ~f:(fun e -> pp_expr f e ; pp_print_string f ", ")
36 | ~flast:(fun e -> pp_expr f e) ;
37 | pp_print_string f (if is_ty then "]" else ")")
38 | | Reference (name, _) | ResolvedReference (name, _) ->
39 | pp_print_string f name.value
40 | | StructField (s, field, _) ->
41 | pp_expr f s ;
42 | pp_print_string f "." ;
43 | pp_print_string f field.value
44 | | _ ->
45 | pp_print_string f ""
46 |
47 | and pp_value f = function
48 | | Integer i ->
49 | Z.pp_print f i
50 | | Builtin b ->
51 | pp_print_string f b
52 | | Type t ->
53 | pp_type f t
54 | | Bool true ->
55 | pp_print_string f "true"
56 | | Bool false ->
57 | pp_print_string f "false"
58 | | String s ->
59 | pp_print_string f {|"|} ;
60 | pp_print_string f s ;
61 | pp_print_string f {|"|}
62 | | _ ->
63 | pp_print_string f ""
64 |
65 | and pp_type f = function
66 | | TypeN 0 ->
67 | pp_print_string f "Type"
68 | | TypeN n ->
69 | pp_print_string f "Type" ; pp_print_int f n
70 | | IntegerType ->
71 | pp_print_string f "Integer"
72 | | BoolType ->
73 | pp_print_string f "Bool"
74 | | VoidType ->
75 | pp_print_string f "VoidType"
76 | | BuiltinType t ->
77 | pp_print_string f t
78 | | StructType s ->
79 | pp_print_string f ""
82 | | _ ->
83 | pp_print_string f ""
84 |
85 | module DiagnosticMsg = struct
86 | open Config
87 | open Located.Lexing'
88 |
89 | type t =
90 | { severity : [`Error | `Warn];
91 | diagnostic_id : int;
92 | diagnostic_msg : string;
93 | spans : (span * string) list;
94 | additional_msg : (string * string) list }
95 |
96 | open struct
97 | (*
98 | Notes about position type.
99 | pos_cnum - offset from the start of the file.
100 | pos_lnum - сurrent line.
101 | pos_bol - offset from the start of the file to the start of the line.
102 |
103 | *)
104 | let string_repeat s n =
105 | let s = Bytes.of_string s in
106 | let len = Bytes.length s in
107 | let res = Bytes.create (n * len) in
108 | for i = 0 to Int.pred n do
109 | Bytes.blit s 0 res (i * len) len
110 | done ;
111 | Bytes.to_string res
112 |
113 | let show_start_line f e =
114 | pp_print_string f
115 | (match e.severity with `Error -> "Error" | `Warn -> "Warn") ;
116 | pp_print_string f "[" ;
117 | pp_print_int f e.diagnostic_id ;
118 | pp_print_string f "]: " ;
119 | pp_print_string f e.diagnostic_msg
120 |
121 | let show_empty_line_no_newline f line_num_size spaces_count =
122 | pp_print_string f (string_repeat " " line_num_size) ;
123 | pp_print_string f "|" ;
124 | pp_print_string f (string_repeat " " spaces_count)
125 |
126 | let is_dummy (pos1, pos2) =
127 | equal_position pos1 dummy_pos || equal_position pos2 dummy_pos
128 |
129 | let range ((pos1, _) as range) =
130 | if is_dummy range then sprintf "File: \n"
131 | else
132 | let file = pos1.pos_fname in
133 | let line = pos1.pos_lnum in
134 | let char1 = pos1.pos_cnum - pos1.pos_bol in
135 | (* yes, [pos1.pos_bol] *)
136 | sprintf "File: \"%s\":%d:%d" file line char1
137 |
138 | let extract_code_line text (pos1, pos2) =
139 | let end_pos =
140 | let quit_loop = ref false in
141 | let pos_newline = ref pos1.pos_cnum in
142 | while not !quit_loop do
143 | try
144 | if
145 | Char.equal (String.get text !pos_newline) '\n'
146 | || Char.equal (String.get text !pos_newline) '\r'
147 | then quit_loop := true
148 | else pos_newline := !pos_newline + 1
149 | with _ -> quit_loop := true
150 | done ;
151 | !pos_newline
152 | in
153 | let hint_len =
154 | match Int.equal pos1.pos_lnum pos2.pos_lnum with
155 | | true ->
156 | pos2.pos_cnum - pos1.pos_cnum
157 | | false ->
158 | end_pos - pos1.pos_cnum + 3
159 | in
160 | (String.sub text pos1.pos_bol (end_pos - pos1.pos_bol), hint_len)
161 |
162 | let show_place_one_span f ((pos1, pos2) as span) span_str code
163 | line_num_size =
164 | (* Line 1 *)
165 | show_empty_line_no_newline f line_num_size 0 ;
166 | pp_print_newline f () ;
167 | (* Line 2 *)
168 | pp_print_int f pos1.pos_lnum ;
169 | pp_print_string f " | " ;
170 | let code_line, len = extract_code_line code span in
171 | let code_line =
172 | match pos1.pos_lnum == pos2.pos_lnum with
173 | | true ->
174 | code_line
175 | | false ->
176 | code_line ^ "..."
177 | in
178 | pp_print_string f code_line ;
179 | pp_print_newline f () ;
180 | let offset = pos1.pos_cnum - pos1.pos_bol in
181 | (* Line 3 *)
182 | show_empty_line_no_newline f line_num_size (offset + 1) ;
183 | pp_print_string f (string_repeat "^" len) ;
184 | pp_print_string f " " ;
185 | pp_print_string f span_str ;
186 | pp_print_newline f () ;
187 | ()
188 |
189 | (* This functions works correctly only with numbers bigger than 0 *)
190 | let int_digits_count i =
191 | let rec int_digits_count_inner c = function
192 | | 0 ->
193 | c
194 | | n ->
195 | int_digits_count_inner (c + 1) (n / 10)
196 | in
197 | int_digits_count_inner 0 i
198 | end
199 |
200 | let show f e code =
201 | let open Base in
202 | let ((pos1, _) as span1), _ =
203 | List.hd_exn e.spans |> fun (s, e) -> (span_to_concrete s, e)
204 | in
205 | show_start_line f e ;
206 | pp_print_newline f () ;
207 | pp_print_string f (range span1) ;
208 | pp_print_newline f () ;
209 | if equal_position pos1 dummy_pos then ()
210 | else
211 | match e.spans with
212 | | (s, sm) :: [] ->
213 | let line_num_size = int_digits_count pos1.pos_lnum + 1 in
214 | show_place_one_span f (span_to_concrete s) sm code line_num_size
215 | | _ ->
216 | Errors.ice "There are should be only one span in the list"
217 | end
218 |
219 | let show_error : string -> error -> string =
220 | fun code e ->
221 | format_to_string ()
222 | @@ fun f _ ->
223 | match e with
224 | | `DuplicateField (field, _) ->
225 | DiagnosticMsg.show f
226 | { severity = `Error;
227 | diagnostic_id = 1;
228 | diagnostic_msg = "Duplicate struct field " ^ field.value;
229 | spans = [(field.span, "Duplicated")];
230 | additional_msg = [] }
231 | code
232 | | `DuplicateVariant (ty, span) ->
233 | DiagnosticMsg.show f
234 | { severity = `Error;
235 | diagnostic_id = 1;
236 | diagnostic_msg =
237 | "Duplicate variant with type " ^ format_to_string ty pp_type;
238 | spans = [(span, "Duplicated variant in this union")];
239 | additional_msg = [] }
240 | code
241 | | `UnresolvedIdentifier id ->
242 | DiagnosticMsg.show f
243 | { severity = `Error;
244 | diagnostic_id = 1;
245 | diagnostic_msg = "Unresolved identifier " ^ id.value;
246 | spans = [(id.span, "Cannot resolve this identifier")];
247 | additional_msg = [] }
248 | code
249 | | `MethodNotFound (e, m) ->
250 | DiagnosticMsg.show f
251 | { severity = `Error;
252 | diagnostic_id = 1;
253 | diagnostic_msg =
254 | "Method " ^ m.value ^ " not found in "
255 | ^ format_to_string e pp_expr;
256 | spans = [(m.span, "Method not found")];
257 | additional_msg = [] }
258 | code
259 | | `TypeError (expected, actual, span) ->
260 | let expected = format_to_string expected pp_type in
261 | let actual = format_to_string actual pp_type in
262 | DiagnosticMsg.show f
263 | { severity = `Error;
264 | diagnostic_id = 1;
265 | diagnostic_msg =
266 | "Expected type `" ^ expected ^ "` but found `" ^ actual ^ "`";
267 | spans = [(span, "This has type `" ^ actual ^ "`")];
268 | additional_msg = [] }
269 | code
270 | | `ExpectedFunction (got, span) ->
271 | DiagnosticMsg.show f
272 | { severity = `Error;
273 | diagnostic_id = 1;
274 | diagnostic_msg =
275 | "Expected function but got value with `"
276 | ^ format_to_string got pp_type
277 | ^ "` type.";
278 | spans = [(span, "This cannot be called")];
279 | additional_msg = [] }
280 | code
281 | | `OnlyFunctionIsAllowed span ->
282 | DiagnosticMsg.show f
283 | { severity = `Error;
284 | diagnostic_id = 1;
285 | diagnostic_msg = "Only function is allowed ";
286 | spans = [(span, "Only function is allowed here")];
287 | additional_msg = [] }
288 | code
289 | | `ArgumentNumberMismatch (expected, actual, span) ->
290 | DiagnosticMsg.show f
291 | { severity = `Error;
292 | diagnostic_id = 1;
293 | diagnostic_msg =
294 | "Expected " ^ string_of_int expected ^ " arguments but found "
295 | ^ string_of_int actual ^ ".";
296 | spans = [(span, "When calling this function")];
297 | additional_msg = [] }
298 | code
299 | | `UninterpretableStatement (_, span) ->
300 | DiagnosticMsg.show f
301 | { severity = `Error;
302 | diagnostic_id = 1;
303 | diagnostic_msg = "Uninterpretable statement.";
304 | spans = [(span, "This statement cannot be interpreted")];
305 | additional_msg = [] }
306 | code
307 | | `FieldNotFoundF field | `FieldNotFound (_, field) ->
308 | DiagnosticMsg.show f
309 | { severity = `Error;
310 | diagnostic_id = 1;
311 | diagnostic_msg = "Field `" ^ field.value ^ "` not found.";
312 | spans = [(field.span, "This field not found")];
313 | additional_msg = [] }
314 | code
315 | | `MissingField (_, field, span) ->
316 | DiagnosticMsg.show f
317 | { severity = `Error;
318 | diagnostic_id = 1;
319 | diagnostic_msg =
320 | "Field `" ^ field.value
321 | ^ "` missing in destructuring statement.";
322 | spans = [(span, "In this binding")];
323 | additional_msg = [] }
324 | code
325 | | `CannotHaveMethods (expr, ty) ->
326 | DiagnosticMsg.show f
327 | { severity = `Error;
328 | diagnostic_id = 1;
329 | diagnostic_msg =
330 | "Type `"
331 | ^ format_to_string ty pp_type
332 | ^ "` cannot have methods.";
333 | spans = [(expr.span, "This cannot have methods")];
334 | additional_msg = [] }
335 | code
336 | | `IsNotStruct expr ->
337 | DiagnosticMsg.show f
338 | { severity = `Error;
339 | diagnostic_id = 1;
340 | diagnostic_msg =
341 | "Expression is not struct type, so it cannot be used in such \
342 | context.";
343 | spans = [(expr.span, "This is not struct type")];
344 | additional_msg = [] }
345 | code
346 | | `IsNotUnion expr ->
347 | DiagnosticMsg.show f
348 | { severity = `Error;
349 | diagnostic_id = 1;
350 | diagnostic_msg =
351 | "Expression is not union type, so it cannot be used in such \
352 | context.";
353 | spans = [(expr.span, "This is not union type")];
354 | additional_msg = [] }
355 | code
356 | | `CaseNotFound span ->
357 | DiagnosticMsg.show f
358 | { severity = `Error;
359 | diagnostic_id = 1;
360 | diagnostic_msg = "Case type not found in union.";
361 | spans =
362 | [ ( span,
363 | "Type of this variable is not found in the condition union"
364 | ) ];
365 | additional_msg = [] }
366 | code
367 | | `ExpectedTypeFunction (is_type_fn, span) ->
368 | let diagnostic_msg =
369 | if is_type_fn then
370 | "Function should be called using `[]` brackets but called with \
371 | `()` parens."
372 | else
373 | "Function should be called using `()` brackets but called with \
374 | `[]` parens."
375 | in
376 | DiagnosticMsg.show f
377 | { severity = `Error;
378 | diagnostic_id = 1;
379 | diagnostic_msg;
380 | spans = [(span, "When calling this function")];
381 | additional_msg = [] }
382 | code
383 | end
384 |
--------------------------------------------------------------------------------
/lib/std/std.tact:
--------------------------------------------------------------------------------
1 | struct Cell {
2 | val c: builtin_Cell
3 | }
4 |
5 | // Do not change place of builder struct - for internal reasons
6 | // it should be second struct in the file.
7 | struct Builder {
8 | val inner: builtin_Builder
9 |
10 | fn new() -> Self {
11 | Self { inner: builtin_begin_cell() }
12 | }
13 | fn build(self: Self) -> Cell {
14 | Cell { c: builtin_end_cell(self.inner) }
15 | }
16 | fn serialize_int(self: Self, int: Integer, bits: Integer) -> Self {
17 | Self { inner: builtin_store_int(self.inner, int, bits) }
18 | }
19 | fn serialize_uint(self: Self, uint: Integer, bits: Integer) -> Self {
20 | Self { inner: builtin_store_uint(self.inner, uint, bits) }
21 | }
22 | fn serialize_coins(self: Self, c: Integer) -> Self {
23 | Self { inner: builtin_store_grams(self.inner, c) }
24 | }
25 | fn serialize_ref(self: Self, cell: Cell) -> Self {
26 | Self { inner: builtin_store_ref(self.inner, cell.c) }
27 | }
28 | fn serialize_maybe_ref(self: Self, maybe_cell: Cell) -> Self {
29 | Self { inner: builtin_store_maybe_ref(self.inner, maybe_cell.c) }
30 | }
31 | fn bits(self: Self) -> Integer {
32 | builtin_builder_bits(self.inner)
33 | }
34 | fn refs(self: Self) -> Integer {
35 | builtin_builder_refs(self.inner)
36 | }
37 | fn depth(self: Self) -> Integer {
38 | builtin_builder_depth(self.inner)
39 | }
40 | }
41 |
42 | struct Slice {
43 | val s: builtin_Slice
44 |
45 | fn parse(cell: Cell) -> Self {
46 | Self { s: builtin_begin_parse(cell.c) }
47 | }
48 |
49 | fn load_int(self: Self, bits: Integer) -> LoadResult[Integer] {
50 | let output = builtin_load_int(self.s, bits);
51 | let slice = Self { s: output.value1 };
52 | let int = output.value2;
53 | LoadResult[Integer] { slice: believe_me(slice), value: int }
54 | }
55 |
56 | fn load_uint(self: Self, bits: Integer) -> LoadResult[Integer] {
57 | let output = builtin_load_uint(self.s, bits);
58 | let slice = Self { s: output.value1 };
59 | let int = output.value2;
60 | LoadResult[Integer] { slice: believe_me(slice), value: int }
61 | }
62 |
63 | fn load_coins(self: Self) -> LoadResult[Integer] {
64 | let output = builtin_load_grams(self.s);
65 | let slice = Self { s: output.value1 };
66 | let coins = output.value2 ;
67 | LoadResult[Integer] { slice: believe_me(slice), value: coins }
68 | }
69 |
70 | fn load_ref(self: Self) -> LoadResult[Cell] {
71 | let output = builtin_load_ref(self.s);
72 | let slice = Self { s: output.value1 };
73 | let ref = Cell { c: output.value2 };
74 | LoadResult[Cell] { slice: believe_me(slice), value: ref }
75 | }
76 |
77 | fn load_bits(self: Self, bits: Integer) -> LoadResult[Self] {
78 | let output = builtin_load_bits(self.s, bits);
79 | let slice = Self { s: output.value1 };
80 | let slice2 = Self { s: output.value2 };
81 | LoadResult[Self] { slice: believe_me(slice), value: believe_me(slice2) }
82 | }
83 |
84 | /* Developer notes: if you add `LoadResult[...]` type, don't forget to
85 | increment Slice id in the `builtins.ml` */
86 |
87 | fn refs_count(self: Self) -> Integer {
88 | builtin_slice_refs(self.s)
89 | }
90 | fn bits(self: Self) -> Integer {
91 | builtin_slice_bits(self.s)
92 | }
93 |
94 | impl Serialize {
95 | fn serialize(self: Self, b: Builder) -> Builder {
96 | Builder { inner: builtin_store_slice(b.inner, self.s) }
97 | }
98 | }
99 | }
100 |
101 | struct RefCell {
102 | val inner: Cell
103 |
104 | impl Serialize {
105 | fn serialize(self: Self, b: Builder) -> Builder {
106 | b.serialize_ref(self.inner)
107 | }
108 | }
109 | impl Deserialize {
110 | fn deserialize(s: Slice) -> LoadResult[Self] {
111 | let {slice, value} = s.load_ref();
112 | LoadResult[Self].new(Self { inner: value }, slice)
113 | }
114 | }
115 | }
116 |
117 | struct TypedSerializeCell[X: Serialize] {
118 | val value: X
119 |
120 | impl Serialize {
121 | fn serialize(self: Self, b: Builder) -> Builder {
122 | let cell = self.value.serialize(Builder.new()).build();
123 | b.serialize_ref(cell)
124 | }
125 | }
126 | }
127 |
128 | struct TypedDeserializeCell[X: Deserialize] {
129 | val value: X
130 |
131 | impl Deserialize {
132 | fn deserialize(s: Slice) -> LoadResult[Self] {
133 | let {slice, value} = s.load_ref();
134 | let {slice as _, value} = X.deserialize(Slice.parse(value));
135 | LoadResult[Self].new(Self { value: believe_me(value) }, slice)
136 | }
137 | }
138 | }
139 |
140 | struct SliceBits[N: Integer] {
141 | val inner: Slice
142 |
143 | @derive impl Serialize {}
144 |
145 | impl Deserialize {
146 | fn deserialize(s: Slice) -> LoadResult[Self] {
147 | let {value, slice} = s.load_bits(N);
148 | LoadResult[Self].new(Self { inner: value }, slice)
149 | }
150 | }
151 | }
152 |
153 | fn thrown(n: Integer) {
154 | builtin_throw(n);
155 | }
156 |
157 | struct Coins {
158 | val value: Integer
159 |
160 | fn new(c: Integer) -> Self {
161 | Self { value: c }
162 | }
163 |
164 | impl Serialize {
165 | fn serialize(self: Self, builder: Builder) -> Builder {
166 | builder.serialize_coins(self.value)
167 | }
168 | }
169 |
170 | impl Deserialize {
171 | fn deserialize(s: Slice) -> LoadResult[Self] {
172 | let {slice, value} = s.load_coins();
173 | LoadResult[Self].new(Self { value: value }, slice)
174 | }
175 | }
176 | }
177 |
178 | struct Int[bits: Integer] {
179 | val value: Integer
180 |
181 | fn new(i: Integer) -> Self {
182 | Self { value: i }
183 | }
184 |
185 | fn add(self: Self, other: Self) { Self { value: self.value + other.value } }
186 | fn sub(self: Self, other: Self) { Self { value: self.value - other.value } }
187 | fn mul(self: Self, other: Self) { Self { value: self.value * other.value } }
188 | fn div(self: Self, other: Self) { Self { value: self.value / other.value } }
189 | fn bit_and(self: Self, other: Self) { Self { value: self.value & other.value } }
190 | fn bit_or(self: Self, other: Self) { Self { value: self.value | other.value } }
191 | fn eq(self: Self, other: Self) { self.value == other.value }
192 | fn neq(self: Self, other: Self) { self.value != other.value }
193 | fn leq(self: Self, other: Self) { self.value <= other.value }
194 | fn lt(self: Self, other: Self) { self.value < other.value }
195 | fn lt(self: Self, other: Self) { self.value < other.value }
196 | fn geq(self: Self, other: Self) { self.value >= other.value }
197 | fn gt(self: Self, other: Self) { self.value > other.value }
198 |
199 | impl Serialize {
200 | fn serialize(self: Self, builder: Builder) -> Builder {
201 | builder.serialize_int(self.value, bits)
202 | }
203 | }
204 |
205 | impl Deserialize {
206 | fn deserialize(s: Slice) -> LoadResult[Self] {
207 | let res = s.load_int(bits);
208 | let {slice, value} = res;
209 |
210 | LoadResult[Self] {
211 | slice: slice,
212 | value: Self { value: value }
213 | }
214 | }
215 | }
216 |
217 | impl From[Integer] {
218 | fn from(i: Integer) -> Self {
219 | Self { value: i }
220 | }
221 | }
222 | }
223 |
224 | struct Uint[bits: Integer] {
225 | val value: Integer
226 |
227 | fn new(i: Integer) -> Self {
228 | Self { value: i }
229 | }
230 |
231 | fn add(self: Self, other: Self) { Self { value: self.value + other.value } }
232 | fn sub(self: Self, other: Self) { Self { value: self.value - other.value } }
233 | fn mul(self: Self, other: Self) { Self { value: self.value * other.value } }
234 | fn div(self: Self, other: Self) { Self { value: self.value / other.value } }
235 | fn bit_and(self: Self, other: Self) { Self { value: self.value & other.value } }
236 | fn bit_or(self: Self, other: Self) { Self { value: self.value | other.value } }
237 | fn eq(self: Self, other: Self) { self.value == other.value }
238 | fn neq(self: Self, other: Self) { self.value != other.value }
239 | fn leq(self: Self, other: Self) { self.value <= other.value }
240 | fn lt(self: Self, other: Self) { self.value < other.value }
241 | fn lt(self: Self, other: Self) { self.value < other.value }
242 | fn geq(self: Self, other: Self) { self.value >= other.value }
243 | fn gt(self: Self, other: Self) { self.value > other.value }
244 |
245 | impl Serialize {
246 | fn serialize(self: Self, builder: Builder) -> Builder {
247 | builder.serialize_uint(self.value, bits)
248 | }
249 | }
250 |
251 | impl Deserialize {
252 | fn deserialize(s: Slice) -> LoadResult[Self] {
253 | let res = s.load_uint(bits);
254 |
255 | LoadResult[Self] {
256 | slice: res.slice,
257 | value: Self { value: res.value }
258 | }
259 | }
260 | }
261 |
262 | impl From[Integer] {
263 | fn from(i: Integer) -> Self {
264 | Self { value: i }
265 | }
266 | }
267 | }
268 |
269 | struct AddrNone {
270 | impl Serialize {
271 | fn serialize(self: Self, b: Builder) -> Builder {
272 | return b;
273 | }
274 | }
275 |
276 | impl Deserialize {
277 | fn deserialize(s: Slice) -> LoadResult[Self] {
278 | return LoadResult[Self].new(s, Self{});
279 | }
280 | }
281 | }
282 |
283 | // Interesting situation with this declaration: `bits` field should have count of bits
284 | // deriving from the `len` field which requires more powerful dependent types than we have for now.
285 | // Do we want to make such declarations possible?
286 | struct AddrExtern {
287 | val len: Int9
288 | val bits: Integer
289 |
290 | impl Serialize {
291 | fn serialize(self: Self, b: Builder) -> Builder {
292 | let b = self.len.serialize(b);
293 | let b = b.serialize_int(self.bits, self.len.value);
294 | return b;
295 | }
296 | }
297 |
298 | impl Deserialize {
299 | fn deserialize(slice: Slice) -> LoadResult[Self] {
300 | let {value as len, slice} = Int9.deserialize(slice);
301 | let {value as bits, slice} = slice.load_int(len.value);
302 |
303 | LoadResult[Self] {
304 | slice: slice,
305 | value: Self {
306 | len: len,
307 | bits: bits,
308 | }
309 | }
310 | }
311 | }
312 | }
313 |
314 | union MsgAddressExt {
315 | @discriminator(0, 2) // 0b00
316 | case AddrNone
317 | @discriminator(1, 2) // 0b01
318 | case AddrExtern
319 |
320 | @derive
321 | impl Serialize {}
322 |
323 | @derive
324 | impl Deserialize {}
325 | }
326 |
327 | struct AddressStd {
328 | val workchain_id: Int8
329 | val address: Int256
330 |
331 | fn new(workchain_id: Int8, address: Int256) -> Self {
332 | Self {
333 | workchain_id: workchain_id,
334 | address: address,
335 | }
336 | }
337 |
338 | impl Serialize {
339 | fn serialize(self: Self, b: Builder) -> Builder {
340 | let b = b.serialize_int(0, 0); // AnyCast
341 | serializer[Self](self, b)
342 | }
343 | }
344 |
345 | impl Deserialize {
346 | fn deserialize(s: Slice) -> LoadResult[Self] {
347 | let res_anycast = s.load_int(1);
348 | if (res_anycast.value == 0) {
349 | return deserializer[Self](s);
350 | } else {
351 | thrown(0); // unreachable
352 | }
353 | }
354 | }
355 | }
356 |
357 | struct AddressVar {
358 | val len: Int9
359 | val workchain_id: Int32
360 | val address: Integer
361 |
362 | impl Serialize {
363 | fn serialize(self: Self, b: Builder) -> Builder {
364 | let b = b.serialize_int(0, 0); // AnyCast
365 | let b = serializer[Self](self, b);
366 | return b;
367 | }
368 | }
369 |
370 | impl Deserialize {
371 | fn deserialize(s: Slice) -> LoadResult[Self] {
372 | let {value as anycast, slice} = s.load_int(1);
373 | if (anycast == 0) {
374 | let {value as len, slice} = Int9.deserialize(slice);
375 | let {value as workchain_id, slice} = Int32.deserialize(slice);
376 | let {value as address, slice} = slice.load_int(len.value);
377 | return LoadResult[Self]
378 | .new(slice, Self {
379 | len: len,
380 | workchain_id: workchain_id,
381 | address: address,
382 | });
383 | } else {
384 | thrown(0); // unreachable
385 | }
386 | }
387 | }
388 | }
389 |
390 | union MsgAddressInt {
391 | @discriminator(2, 2) // 0b10
392 | case AddressStd
393 | @discriminator(3, 2) // 0b11
394 | case AddressVar
395 |
396 | @derive
397 | impl Serialize {}
398 |
399 | @derive
400 | impl Deserialize {}
401 | }
402 |
403 | union MsgAddress {
404 | case AddrNone
405 | case AddrExtern
406 | case AddressStd
407 | case AddressVar
408 |
409 | @derive
410 | impl Serialize {}
411 |
412 | @derive
413 | impl Deserialize {}
414 | }
415 |
416 | struct ExtOutMsgInfoRelaxed {
417 | val src: MsgAddress
418 | val dest: MsgAddressExt
419 | val created_lt: Uint64
420 | val created_at: Uint32
421 |
422 | @derive
423 | impl Serialize {}
424 | }
425 |
426 | struct Timestamps {
427 | val created_lt: Uint64
428 | val created_at: Uint32
429 |
430 | fn zeros() -> Self {
431 | Self {
432 | created_lt: 0,
433 | created_at: 0,
434 | }
435 | }
436 |
437 | @derive
438 | impl Serialize {}
439 |
440 | @derive
441 | impl Deserialize {}
442 | }
443 |
444 | struct IntMsgInfoFlags {
445 | val ihr_disabled: Uint1
446 | val bounce: Uint1
447 | val bounced: Uint1
448 |
449 | fn new(ihr_disabled: Bool, bounce: Bool) -> Self {
450 | fn bool_to_int(x: Bool) -> Uint1 {
451 | if (x) {
452 | return 1;
453 | } else {
454 | return 0;
455 | }
456 | }
457 | Self {
458 | ihr_disabled: bool_to_int(ihr_disabled),
459 | bounce: bool_to_int(bounce),
460 | bounced: 0, // will be rewrited by the validator
461 | }
462 | }
463 |
464 | @derive
465 | impl Serialize {}
466 |
467 | @derive
468 | impl Deserialize {}
469 | }
470 |
471 | struct IntMsgInfoAddresses {
472 | val src: MsgAddressInt
473 | val dst: MsgAddressInt
474 |
475 | @derive
476 | impl Serialize {}
477 |
478 | @derive
479 | impl Deserialize {}
480 | }
481 |
482 | struct IntMsgInfoCoins {
483 | val amount: Coins
484 | val _extra_currencies: Uint1 // we ignore extra_currencies for now
485 | val ihr_fee: Coins
486 | val fwd_fee: Coins
487 |
488 | fn new(amount: Coins, ihr_fee: Coins, fwd_fee: Coins) -> Self {
489 | Self {
490 | amount: amount,
491 | _extra_currencies: 0,
492 | ihr_fee: ihr_fee,
493 | fwd_fee: fwd_fee,
494 | }
495 | }
496 |
497 | @derive
498 | impl Serialize {}
499 |
500 | @derive
501 | impl Deserialize {}
502 | }
503 |
504 | struct IntMsgInfo {
505 | val flags: IntMsgInfoFlags
506 | val addresses: IntMsgInfoAddresses
507 | val coins: IntMsgInfoCoins
508 | val timestamps: Timestamps
509 |
510 | fn new(
511 | flags: IntMsgInfoFlags,
512 | dst: MsgAddressInt,
513 | coins: IntMsgInfoCoins,
514 | ) -> Self {
515 | Self {
516 | flags: flags,
517 | addresses: IntMsgInfoAddresses {
518 | src: AddressStd.new(0, 0), // it will be replaced by smartcontract address by TVM
519 | dst: dst,
520 | },
521 | coins: coins,
522 | timestamps: Timestamps.zeros(), // it will be replaced by current timestamps by TVM
523 | }
524 | }
525 |
526 | @derive
527 | impl Serialize {}
528 |
529 | @derive
530 | impl Deserialize {}
531 | }
532 |
533 | struct ExtInMsgInfo {
534 | val src: MsgAddressExt
535 | val dest: MsgAddressInt
536 | val import_fee: Coins
537 |
538 | @derive
539 | impl Deserialize {}
540 | }
541 |
542 | union CommonMsgInfo {
543 | case IntMsgInfo
544 | @discriminator(2, 2) // 0b10
545 | case ExtInMsgInfo
546 |
547 | @derive
548 | impl Serialize {}
549 |
550 | @derive
551 | impl Deserialize {}
552 | }
553 |
554 | union CommonMsgInfoRelaxed {
555 | case ExtOutMsgInfoRelaxed
556 | case IntMsgInfo
557 |
558 | impl Serialize {
559 | fn serialize(self: Self, b: Builder) -> Builder {
560 | switch(self) {
561 | case IntMsgInfo info => {
562 | let b = b.serialize_int(0, 1);
563 | return info.serialize(b);
564 | }
565 | case ExtOutMsgInfoRelaxed info => {
566 | let b = b.serialize_int(3, 2); // 0b11
567 | return info.serialize(b);
568 | }
569 | }
570 | }
571 | }
572 | }
573 |
574 | struct MessageRelaxed[X: Serialize] {
575 | val info: CommonMsgInfoRelaxed
576 | val body: X
577 |
578 | impl Serialize {
579 | fn serialize(self: Self, b: Builder) -> Builder {
580 | let b = self.info.serialize(b);
581 | let b = b.serialize_int(0, 1); // init
582 | let b = b.serialize_int(0, 1); // body discriminant
583 | let b = self.body.serialize(b);
584 | return b;
585 | }
586 | }
587 | }
588 |
589 | struct Message[X: Deserialize] {
590 | val info: CommonMsgInfo
591 | val body: X
592 |
593 | impl Deserialize {
594 | fn deserialize(s: Slice) -> LoadResult[Self] {
595 | let {slice, value as info} = CommonMsgInfo.deserialize(s);
596 | let {slice, value as init} = slice.load_int(1);
597 |
598 | if (init == 0) {
599 | let {slice, value as discr} = slice.load_int(1);
600 | if (discr == 0) {
601 | let {slice, value as body} = X.deserialize(slice);
602 | // FIXME: weird bug
603 | let mes = Self { info: info, body: believe_me(body) };
604 | return LoadResult[Self].new(mes, slice);
605 | } else {
606 | /* TODO: cells */
607 | thrown(0);
608 | }
609 | } else {
610 | thrown(0);
611 | }
612 | }
613 | }
614 | }
615 |
616 | struct SendRawMsgFlags {
617 | val value: Int8
618 |
619 | fn default() -> Self {
620 | Self { value: 0 }
621 | }
622 |
623 | fn carry_remaining_balance(self: Self) -> Self {
624 | Self { value: self.value.value + 128 }
625 | }
626 |
627 | fn carry_remaining_input(self: Self) -> Self {
628 | Self { value: self.value.value + 64 }
629 | }
630 |
631 | @derive
632 | impl Deserialize {}
633 |
634 | @derive
635 | impl Serialize {}
636 | }
637 |
638 | fn send_raw_msg(msg: Cell, flags: SendRawMsgFlags) -> VoidType {
639 | builtin_send_raw_message(msg.c, flags.value.value);
640 | }
641 |
642 | fn send_internal[X: Serialize](header: IntMsgInfo, body: X, flags: SendRawMsgFlags) -> VoidType {
643 | let info: CommonMsgInfoRelaxed = header;
644 | let msg = MessageRelaxed[X] { info: info, body: body };
645 | let ce = msg.serialize(Builder.new()).build();
646 | send_raw_msg(ce, flags);
647 | }
648 |
649 | fn send_external[X: Serialize](header: ExtOutMsgInfoRelaxed, body: X, flags: SendRawMsgFlags) -> VoidType {
650 | let info: CommonMsgInfoRelaxed = header;
651 | let msg = MessageRelaxed[X] { info: info, body: body };
652 | let ce = msg.serialize(Builder.new()).build();
653 | send_raw_msg(ce, flags);
654 | }
655 |
656 | fn hash_of_slice(s: Slice) -> Uint256 {
657 | return Uint256.new(builtin_slice_hash(s.s));
658 | }
659 |
660 | fn is_signature_valid(hash: Uint256, sign: SliceBits[512], pubkey: Uint256) -> Bool {
661 | return builtin_check_signature(hash.value, sign.inner.s, pubkey.value);
662 | }
663 |
664 | struct Signature {
665 | val _sig: SliceBits[512]
666 | val _rest: Slice
667 |
668 | fn is_valid(self: Self, public_key: Uint256) -> Bool {
669 | return is_signature_valid(hash_of_slice(self._rest), self._sig, public_key);
670 | }
671 |
672 | impl Deserialize {
673 | fn deserialize(s: Slice) -> LoadResult[Self] {
674 | let {value as sig, slice} = SliceBits[512].deserialize(s);
675 | LoadResult[Self].new(Self{_sig: sig, _rest: slice}, slice)
676 | }
677 | }
678 | }
679 |
680 | /* Deserialize rest of the slice into this struct */
681 | struct RestSlice {
682 | val inner: Slice
683 |
684 | impl Deserialize {
685 | fn deserialize(s: Slice) -> LoadResult[Self] {
686 | let empty_slice = builtin_slice_last(s.s, 0);
687 | LoadResult[Self].new(Self{inner: s}, empty_slice)
688 | }
689 | }
690 | }
691 |
692 | struct SignedBody[X: Deserialize] {
693 | val _sign: Signature
694 | val _rest: X
695 |
696 | fn verify_body(self: Self, pubkey: Uint256) -> X {
697 | if (self._sign.is_valid(pubkey)) {
698 | return self._rest;
699 | } else {
700 | thrown(0);
701 | // This will never be called but without this return, FunC does not compile
702 | // this code.
703 | return self._rest;
704 | }
705 | }
706 |
707 | impl Deserialize {
708 | fn deserialize(slice: Slice) -> LoadResult[Self] {
709 | let {value as sign, slice} = Signature.deserialize(slice);
710 | let {value as rest, slice} = X.deserialize(slice);
711 | LoadResult[Self].new(
712 | Self { _sign: sign, _rest: believe_me(rest) },
713 | slice
714 | )
715 | }
716 | }
717 | }
718 |
719 | struct Globals {
720 | fn load_state() -> Cell {
721 | Cell { c: builtin_get_data() }
722 | }
723 | fn save_state(state: Cell) {
724 | builtin_set_data(state.c);
725 | }
726 | fn get_now() -> Uint[32] {
727 | builtin_now()
728 | }
729 | fn get_my_address() -> AddressStd {
730 | AddressStd.deserialize(Slice { s: builtin_my_address() }).value
731 | }
732 | fn get_balance() -> Coins {
733 | struct Balance {
734 | val tons: Coins
735 | val extra_currencies: Cell
736 | }
737 | let balance: Balance = believe_me(builtin_get_balance());
738 | return balance.tons;
739 | }
740 | fn get_logical_time() -> Int[64] {
741 | builtin_cur_lt()
742 | }
743 | fn get_block_logical_time() -> Int[64] {
744 | builtin_block_lt()
745 | }
746 | }
747 |
748 | struct RemainingBalance{}
749 | struct RemainingInput{}
750 | union Money {
751 | case Coins
752 | case RemainingBalance
753 | case RemainingInput
754 | }
755 |
756 | struct RequestBuilder[X: Serialize] {
757 | val msg_flags: IntMsgInfoFlags
758 | val send_flags: SendRawMsgFlags
759 | val coins: IntMsgInfoCoins
760 | val is_body_setup: Bool // TODO: make `Option[T]` type.
761 | val body: X
762 |
763 | fn new() -> Self {
764 | Self {
765 | msg_flags: IntMsgInfoFlags.new(false, false),
766 | send_flags: SendRawMsgFlags.default(),
767 | coins: IntMsgInfoCoins.new(Coins.new(0), Coins.new(0), Coins.new(0)),
768 | is_body_setup: false,
769 | body: believe_me(0),
770 | }
771 | }
772 |
773 | fn can_be_bounced(self: Self) -> Self {
774 | Self {
775 | msg_flags: IntMsgInfoFlags { ihr_disabled: self.msg_flags.ihr_disabled, bounce: 1, bounced: 0 },
776 | send_flags: self.send_flags,
777 | body: self.body,
778 | is_body_setup: self.is_body_setup,
779 | coins: self.coins,
780 | }
781 | }
782 | fn ihr_disabled(self: Self) -> Self {
783 | Self {
784 | msg_flags: IntMsgInfoFlags { ihr_disabled: 1, bounce: self.msg_flags.bounce, bounced: 0 },
785 | send_flags: self.send_flags,
786 | body: self.body,
787 | is_body_setup: self.is_body_setup,
788 | coins: self.coins,
789 | }
790 | }
791 | fn money(self: Self, money: Money) -> Self {
792 | switch (money) {
793 | case Coins coins => {
794 | return Self {
795 | msg_flags: self.msg_flags,
796 | send_flags: self.send_flags,
797 | body: self.body,
798 | is_body_setup: self.is_body_setup,
799 | coins: IntMsgInfoCoins.new(coins, self.coins.ihr_fee, self.coins.fwd_fee),
800 | };
801 | }
802 | case RemainingBalance _ => {
803 | return Self {
804 | msg_flags: self.msg_flags,
805 | send_flags: self.send_flags.carry_remaining_balance(),
806 | body: self.body,
807 | is_body_setup: self.is_body_setup,
808 | coins: self.coins,
809 | };
810 | }
811 | case RemainingInput _ => {
812 | return Self {
813 | msg_flags: self.msg_flags,
814 | send_flags: self.send_flags.carry_remaining_input(),
815 | body: self.body,
816 | is_body_setup: self.is_body_setup,
817 | coins: self.coins,
818 | };
819 | }
820 | }
821 | }
822 | fn body(self: Self, body: X) -> Self {
823 | Self {
824 | msg_flags: self.msg_flags,
825 | send_flags: self.send_flags,
826 | body: body,
827 | is_body_setup: true,
828 | coins: self.coins,
829 | }
830 | }
831 | fn send_to(self: Self, dst: MsgAddressInt) {
832 | if (self.is_body_setup) {
833 | // TODO: I don't know why MessageRelaxed[X] type does not work here.
834 | let info = IntMsgInfo.new(self.msg_flags, dst, self.coins);
835 | let b = CommonMsgInfo.serialize(info, Builder.new());
836 | let b = b.serialize_int(0, 1); // init
837 | let b = b.serialize_int(0, 1); // body discriminant
838 | let b = self.body.serialize(b);
839 | send_raw_msg(b.build(), self.send_flags);
840 | } else {
841 | thrown(87);
842 | }
843 | }
844 | }
845 |
--------------------------------------------------------------------------------
/lib/syntax.ml:
--------------------------------------------------------------------------------
1 | open Sexplib.Std
2 |
3 | module Make =
4 | functor
5 | (Config : Config.T)
6 | ->
7 | struct
8 | include Config
9 |
10 | class ['s] base_visitor =
11 | object (_ : 's)
12 | inherit ['s] VisitorsRuntime.map
13 |
14 | inherit ['s] Zint.map
15 |
16 | method visit_located
17 | : 'a 'b. ('env -> 'a -> 'b) -> 'env -> 'a located -> 'b located =
18 | fun f env l -> {value = f env l.value; span = l.span}
19 |
20 | method visit_span _ span = span
21 | end
22 |
23 | type ident = Ident of string
24 |
25 | and struct_definition =
26 | { struct_attributes : attribute list; [@sexp.list]
27 | fields : struct_field located list; [@sexp.list]
28 | struct_bindings : binding located list; [@sexp.list]
29 | impls : impl list; [@sexp.list]
30 | struct_span : (span[@sexp.opaque]) }
31 |
32 | and impl =
33 | { impl_attributes : attribute list; [@sexp.list]
34 | interface : expr located;
35 | methods : binding located list }
36 |
37 | and interface_definition =
38 | { interface_attributes : attribute list; [@sexp.list]
39 | interface_members : binding located list [@sexp.list] }
40 |
41 | and function_call =
42 | { fn : expr located;
43 | arguments : expr located list; [@sexp.list]
44 | is_type_func_call : bool [@sexp.bool] }
45 |
46 | and method_call =
47 | { receiver : expr located;
48 | receiver_fn : ident located;
49 | receiver_arguments : expr located list }
50 |
51 | (* TODO: union impls *)
52 | and union_definition =
53 | { union_attributes : attribute list; [@sexp.list]
54 | union_members : (expr located * attribute list) list; [@sexp.list]
55 | union_bindings : binding located list; [@sexp.list]
56 | union_impls : impl list; [@sexp.list]
57 | union_span : (span[@sexp.opaque]) }
58 |
59 | and attribute =
60 | { attribute_ident : ident located;
61 | attribute_exprs : expr located list [@sexp.list] }
62 |
63 | and expr =
64 | | Struct of struct_definition
65 | | StructConstructor of struct_constructor
66 | | Interface of interface_definition
67 | | Union of union_definition
68 | | Reference of ident located
69 | | FieldAccess of field_access
70 | | FunctionCall of function_call
71 | | MethodCall of method_call
72 | | Function of function_definition
73 | | Int of (Zint.t[@visitors.name "z"])
74 | | Bool of bool
75 | | String of string
76 |
77 | and stmt =
78 | | CodeBlock of (stmt located list[@sexp.list])
79 | | Let of binding located
80 | | DestructuringLet of destructuring_binding located
81 | | Assignment of assignment
82 | | If of if_
83 | | Return of expr located
84 | | Break of stmt located
85 | | Expr of expr located
86 | | Switch of switch
87 | | WhileLoop of while_loop
88 |
89 | and while_loop = {while_cond : expr located; while_body : stmt located}
90 |
91 | and switch =
92 | { switch_condition : expr located;
93 | branches : switch_branch located list;
94 | default : stmt option [@sexp.option] }
95 |
96 | and switch_branch =
97 | {ty : expr located; var : ident located; stmt : stmt located}
98 |
99 | and struct_constructor =
100 | { constructor_id : expr located;
101 | fields_construction : (ident located * expr located) list [@sexp.list]
102 | }
103 |
104 | and struct_field =
105 | { field_attributes : attribute list; [@sexp.list]
106 | field_name : ident located;
107 | field_type : expr located }
108 |
109 | and function_param = ident located * expr located
110 |
111 | and function_definition =
112 | { function_attributes : attribute list; [@sexp.list]
113 | name : ident located option; [@sexp.option]
114 | is_type_function : bool; [@sexp.bool]
115 | params : function_param located list; [@sexp.list]
116 | returns : expr located option; [@sexp.option]
117 | function_body : function_body option; [@sexp.option]
118 | function_def_span : (span[@sexp.opaque]) }
119 |
120 | and function_body = {function_stmt : stmt located}
121 |
122 | and binding = {binding_name : ident located; binding_expr : expr located}
123 |
124 | and destructuring_binding =
125 | { destructuring_binding : (ident located * ident located) list located;
126 | destructuring_binding_expr : expr located;
127 | destructuring_binding_rest : bool }
128 |
129 | and assignment =
130 | { assignment_lvalue : assignment_lvalue located;
131 | assignment_expr : expr located }
132 |
133 | and assignment_lvalue =
134 | | ReferenceLvalue of ident located
135 | | FieldAccessLvalue of ident located * ident located list
136 |
137 | and if_ =
138 | { condition : expr located;
139 | body : stmt located;
140 | else_ : stmt located option [@sexp.option] }
141 |
142 | and field_access = {from_expr : expr located; to_field : ident located}
143 |
144 | and program = {stmts : stmt located list [@sexp.list]}
145 | [@@deriving
146 | show {with_path = false},
147 | make,
148 | sexp_of,
149 | visitors
150 | {variety = "fold"; name = "visitor"; ancestors = ["base_visitor"]}]
151 |
152 | let ident_to_string = function Ident s -> s
153 | end
154 |
--------------------------------------------------------------------------------
/lib/type_check.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | module Make =
4 | functor
5 | (Config : Config.T)
6 | ->
7 | struct
8 | open Lang_types.Make (Config)
9 |
10 | open Interpreter.Make (Config)
11 |
12 | open Builtin.Make (Config)
13 |
14 | open Errors
15 |
16 | type type_check_error = TypeError of type_ | NeedFromCall of expr
17 |
18 | class ['s] remover_of_resolved_reference =
19 | object (self : 's)
20 | inherit ['s] map as super
21 |
22 | method! visit_ResolvedReference env (_, ex) =
23 | (self#visit_expr env ex).value
24 |
25 | method! visit_type_ env =
26 | function
27 | | ExprType {value = Value (Type t); _} ->
28 | self#visit_type_ env t
29 | | ty ->
30 | super#visit_type_ env ty
31 | end
32 |
33 | let is_sig_part_of sign1 sign2 ~equal_ty =
34 | let remover = new remover_of_resolved_reference in
35 | let sign1 = remover#visit_struct_sig () sign1 in
36 | let sign2 = remover#visit_struct_sig () sign2 in
37 | let is_part = ref true in
38 | List.iter sign2.st_sig_fields ~f:(fun (name2, ty2) ->
39 | if
40 | not
41 | (List.exists sign1.st_sig_fields ~f:(fun (name1, ty1) ->
42 | equal_string name1.value name2.value && equal_ty ty1 ty2 ) )
43 | then is_part := false ) ;
44 | !is_part
45 |
46 | let is_union_sig_part_of sign1 sign2 =
47 | let remover = new remover_of_resolved_reference in
48 | let sign1 = remover#visit_union_sig () sign1 in
49 | let sign2 = remover#visit_union_sig () sign2 in
50 | let is_part = ref true in
51 | List.iter sign2.un_sig_cases ~f:(fun ty2 ->
52 | if not (List.exists sign1.un_sig_cases ~f:(equal_type_ ty2)) then
53 | is_part := false ) ;
54 | !is_part
55 |
56 | class type_checker (errors : _) (functions : _) =
57 | object (self)
58 | val mutable fn_returns : type_ option = None
59 |
60 | method check_return_type ~program ~current_bindings actual =
61 | match fn_returns with
62 | | Some fn_returns' -> (
63 | match
64 | self#check_type (type_of program actual) ~program
65 | ~current_bindings ~expected:fn_returns'
66 | with
67 | | Ok ty ->
68 | fn_returns <- Some ty ;
69 | Ok ty
70 | | v ->
71 | v )
72 | | None ->
73 | ice "Caller should guarantee this"
74 |
75 | method get_fn_returns =
76 | match fn_returns with
77 | | Some x ->
78 | x
79 | | None ->
80 | ice "Caller should guarantee this"
81 |
82 | method with_fn_returns
83 | : 'env 'a. 'env -> type_ -> ('env -> 'a) -> 'a * type_ =
84 | fun env ty f ->
85 | let prev = fn_returns in
86 | fn_returns <- Some ty ;
87 | let result = f env in
88 | let new_fn_returns = self#get_fn_returns in
89 | fn_returns <- prev ;
90 | (result, new_fn_returns)
91 |
92 | method check_type ~program ~current_bindings ~expected actual_ty =
93 | let actual = actual_ty in
94 | let remover = new remover_of_resolved_reference in
95 | let actual' = remover#visit_type_ () actual in
96 | let expected' = remover#visit_type_ () expected in
97 | let is_sig_part_of_call sign_actual sign_expected =
98 | is_sig_part_of sign_actual sign_expected ~equal_ty:(fun ty1 ty2 ->
99 | equal_expr ty1 ty2 )
100 | in
101 | match expected with
102 | | HoleType ->
103 | Ok actual
104 | | _ when equal_type_ HoleType actual' ->
105 | Ok expected
106 | | _ when equal_type_ expected' actual' ->
107 | Ok actual
108 | | StructSig sign_expected -> (
109 | let sign_expected =
110 | Arena.get program.struct_signs sign_expected
111 | in
112 | match actual' with
113 | | StructType sid ->
114 | let s = Program.get_struct program sid in
115 | let sign_actual = sig_of_struct s 0 in
116 | if is_sig_part_of_call sign_actual sign_expected then
117 | Ok (StructType sid)
118 | else Error (TypeError expected)
119 | | ExprType {value = Reference (_, StructSig sid2); _} ->
120 | let sign_actual = Arena.get program.struct_signs sid2 in
121 | if is_sig_part_of_call sign_actual sign_expected then
122 | Ok actual'
123 | else Error (TypeError expected)
124 | | ExprType ({value = FunctionCall _; _} as ex_ty) -> (
125 | let ex_ty = type_of program ex_ty in
126 | match ex_ty with
127 | | StructSig sid2 ->
128 | let sign_actual = Arena.get program.struct_signs sid2 in
129 | if is_sig_part_of_call sign_actual sign_expected then
130 | Ok actual'
131 | else Error (TypeError expected)
132 | | _ ->
133 | Error (TypeError expected) )
134 | | _ ->
135 | Error (TypeError expected) )
136 | | UnionSig sign_expected -> (
137 | let sign_expected = Arena.get program.union_signs sign_expected in
138 | match actual' with
139 | | UnionType sid ->
140 | let s = Program.get_union program sid in
141 | let sign_actual = sig_of_union s in
142 | if is_union_sig_part_of sign_actual sign_expected then
143 | Ok (UnionType sid)
144 | else Error (TypeError expected)
145 | | ExprType {value = Reference (_, UnionSig sid2); _}
146 | | UnionSig sid2 ->
147 | let sign_actual = Arena.get program.union_signs sid2 in
148 | if is_union_sig_part_of sign_actual sign_expected then
149 | Ok (UnionSig sid2)
150 | else Error (TypeError expected)
151 | | _ ->
152 | Error (TypeError expected) )
153 | | TypeN 0 -> (
154 | match actual with
155 | | StructSig _ | Type0 _ ->
156 | Ok actual
157 | | _ -> (
158 | match actual_ty with
159 | | (StructSig _ as ty) | (UnionSig _ as ty) ->
160 | Ok ty
161 | | _ ->
162 | Error (TypeError expected) ) )
163 | | (StructType _ as ty) | (UnionType _ as ty) -> (
164 | let from_intf_ =
165 | let inter =
166 | new interpreter (make_ctx program current_bindings functions)
167 | errors (Config.builtin_located ()).span (fun _ f -> f)
168 | in
169 | Value
170 | (inter#interpret_fc
171 | ( from_intf,
172 | [ { value = Value (Type actual);
173 | span = (Config.builtin_located ()).span } ],
174 | true ) )
175 | in
176 | let impl =
177 | Program.get_uty_details program ty
178 | |> Option.value_exn
179 | |> fun f ->
180 | f.uty_impls
181 | |> List.find_map ~f:(fun i ->
182 | if
183 | equal_expr_kind
184 | (Value (Type (InterfaceType i.impl_interface)))
185 | from_intf_
186 | then Some i.impl_methods
187 | else None )
188 | |> Option.bind ~f:List.hd
189 | in
190 | match impl with
191 | | Some (_, m) ->
192 | Error
193 | (NeedFromCall {value = Value (Function m); span = m.span})
194 | | _ ->
195 | Error (TypeError expected) )
196 | | InterfaceType v -> (
197 | match actual_ty with
198 | | Type0 ty -> (
199 | match Program.find_impl_intf program v ty with
200 | | Some _ ->
201 | Ok actual_ty
202 | | _ ->
203 | Error (TypeError expected) )
204 | | _ ->
205 | Error (TypeError expected) )
206 | | ExprType ex ->
207 | self#check_type ~expected:(type_of program ex) ~program
208 | ~current_bindings actual_ty
209 | | _otherwise ->
210 | Error (TypeError expected)
211 | end
212 | end
213 |
--------------------------------------------------------------------------------
/lib/zint.ml:
--------------------------------------------------------------------------------
1 | include Z
2 |
3 | let pp = Z.pp_print
4 |
5 | let sexp_of_t z = Sexplib.Sexp.of_string (Z.to_string z)
6 |
7 | class ['s] map =
8 | object (_ : 's)
9 | method visit_z : 'env. 'env -> t -> t = fun _env z -> z
10 | end
11 |
12 | let equal = Z.equal
13 |
14 | let hash_fold_t h v = Ppx_hash_lib.Std.Hash.fold_string h @@ Z.to_string v
15 |
--------------------------------------------------------------------------------
/tact.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "TON Tact Language"
4 | maintainer: ["cmxog@protonmail.com" "dmytro.polunin@gmail.com"]
5 | authors: ["cmxog@protonmail.com" "dmytro.polunin@gmail.com"]
6 | license: "Apache-2.0"
7 | homepage: "https://github.com/tact-lang/tact"
8 | bug-reports: "https://github.com/tact-lang/tact/issues"
9 | depends: [
10 | "ocaml" {>= "4.14.0"}
11 | "ppx_show" {>= "0.2.1"}
12 | "ppx_make" {>= "0.3.0"}
13 | "ppx_blob" {>= "0.7.2"}
14 | "js_of_ocaml-ppx" {>= "4.0.0"}
15 | "zarith_stubs_js" {>= "0.15.0"}
16 | "dune" {>= "2.9" & build & >= "3.1.1"}
17 | "js_of_ocaml" {>= "4.0.0"}
18 | "mparser" {>= "1.3"}
19 | "zarith" {>= "1.12"}
20 | "base" {>= "0.15.0"}
21 | "ppx_jane" {>= "0.15.0"}
22 | "visitors" {= "20210608"}
23 | "containers" {>= "3.8.0"}
24 | "linenoise" {>= "1.3.1"}
25 | "bos" {>= "0.2.1"}
26 | "cmdliner" {>= "1.1.1"}
27 | "core" {with-test & >= "0.15.0"}
28 | "ppx_expect" {with-test & >= "0.15.0"}
29 | "ppx_matches" {with-test & >= "0.1"}
30 | "alcotest" {with-test & >= "1.5.0"}
31 | "ppx_inline_alcotest" {with-test & >= "1.0.0"}
32 | "utop" {build & >= "2.9.1"}
33 | "ocamlformat" {build & = "0.23.0"}
34 | "odoc" {with-doc}
35 | ]
36 | build: [
37 | ["dune" "subst"] {dev}
38 | [
39 | "dune"
40 | "build"
41 | "-p"
42 | name
43 | "-j"
44 | jobs
45 | "--promote-install-files=false"
46 | "@install"
47 | "@runtest" {with-test}
48 | "@doc" {with-doc}
49 | ]
50 | ["dune" "install" "-p" name "--create-install-files" name]
51 | ]
52 | dev-repo: "git+https://github.com/tact-lang/tact.git"
53 |
--------------------------------------------------------------------------------
/test/builtin_basics.ml:
--------------------------------------------------------------------------------
1 | open Shared.Disabled
2 | module Config = Shared.DisabledConfig
3 |
4 | let find scope name =
5 | let open Config in
6 | List.find_map scope ~f:(fun (n, ex) ->
7 | if equal_string n.value name then Some ex else None )
8 |
9 | let%test "int type equality" =
10 | let source =
11 | {|
12 | let T = Int[257];
13 | let T1 = Int[257];
14 | let T2 = Int[256];
15 | |}
16 | in
17 | Alcotest.(check bool)
18 | "types with same bits are equal" true
19 | (let scope = (compile source).bindings in
20 | let t = find scope "T" |> Option.value_exn
21 | and t1 = find scope "T1" |> Option.value_exn in
22 | pp_sexp (Lang.sexp_of_expr t) ;
23 | pp_sexp (Lang.sexp_of_expr t1) ;
24 | Lang.equal_expr t t1 ) ;
25 | Alcotest.(check bool)
26 | "types with different bits are not equal" false
27 | (let scope = (compile source).bindings in
28 | let t = find scope "T" |> Option.value_exn
29 | and t2 = find scope "T2" |> Option.value_exn in
30 | pp_sexp (Lang.sexp_of_expr t) ;
31 | pp_sexp (Lang.sexp_of_expr t2) ;
32 | Lang.equal_expr t t2 )
33 |
--------------------------------------------------------------------------------
/test/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (package tact)
3 | (preprocess
4 | (staged_pps ppx_matches ppx_expect ppx_sexp_conv))
5 | (name shared)
6 | (inline_tests)
7 | (modules shared)
8 | (libraries tact core sexplib))
9 |
10 | (library
11 | (package tact)
12 | (preprocess
13 | (staged_pps ppx_matches ppx_expect ppx_sexp_conv))
14 | (name tact_tests)
15 | (inline_tests)
16 | (modules syntax immediacy_check builtin lang codegen_func errors)
17 | (libraries tact core sexplib shared))
18 |
19 | (library
20 | (package tact)
21 | (preprocess
22 | (staged_pps ppx_matches ppx_sexp_conv ppx_inline_alcotest))
23 | (name tact_tests_)
24 | (inline_tests)
25 | (modules builtin_basics lang_types std)
26 | (libraries tact core sexplib shared alcotest))
27 |
--------------------------------------------------------------------------------
/test/errors.ml:
--------------------------------------------------------------------------------
1 | module Config = Shared.EnabledConfig
2 | module Show = Tact.Show.Make (Shared.EnabledConfig)
3 | open Config
4 | open Base
5 |
6 | let fmt = Caml.Format.std_formatter
7 |
8 | let%expect_test "error showing one line" =
9 | let source = {|
10 | fn test() {
11 |
12 | }
13 | |} in
14 | let open Show.DiagnosticMsg in
15 | let pos1 : Tact.Located.pos =
16 | {pos_fname = "f"; pos_cnum = 3 + 1; pos_lnum = 1; pos_bol = 1}
17 | in
18 | let pos2 : Tact.Located.pos =
19 | {pos_fname = "f"; pos_cnum = 7 + 1; pos_lnum = 1; pos_bol = 1}
20 | in
21 | let msg =
22 | { severity = `Error;
23 | diagnostic_id = 0;
24 | diagnostic_msg = "msg";
25 | spans = [(span_of_concrete (pos1, pos2), "message")];
26 | additional_msg = [] }
27 | in
28 | Show.DiagnosticMsg.show fmt msg source ;
29 | [%expect
30 | {|
31 | Error[0]: msg
32 | File: "f":1:3
33 | |
34 | 1 | fn test() {
35 | | ^^^^ message |}]
36 |
37 | let%expect_test "error showing two lines" =
38 | let source = {|
39 | let a = test(
40 | arg1, arg2);
41 | |} in
42 | let open Show.DiagnosticMsg in
43 | let pos1 : Tact.Located.pos =
44 | {pos_fname = "f"; pos_cnum = 8 + 1; pos_lnum = 1; pos_bol = 1}
45 | in
46 | let pos2 : Tact.Located.pos =
47 | {pos_fname = "f"; pos_cnum = 13 + 13 + 1; pos_lnum = 2; pos_bol = 13 + 1}
48 | in
49 | let msg =
50 | { severity = `Error;
51 | diagnostic_id = 0;
52 | diagnostic_msg = "msg";
53 | spans = [(span_of_concrete (pos1, pos2), "message")];
54 | additional_msg = [] }
55 | in
56 | Show.DiagnosticMsg.show fmt msg source ;
57 | [%expect
58 | {|
59 | Error[0]: msg
60 | File: "f":1:8
61 | |
62 | 1 | let a = test(...
63 | | ^^^^^^^^ message |}]
64 |
65 | let pp =
66 | let open Base in
67 | Shared.Enabled.pp_compile ~show_errors:(fun (elist, _) source ->
68 | List.iter elist ~f:(fun x ->
69 | let s = Show.show_error source x in
70 | Caml.Format.print_string s ) )
71 |
72 | let handle_parse_error (f : unit -> unit) =
73 | match f () with
74 | | value ->
75 | value
76 | | exception Shared.Enabled.Parser.Error (msg, _) ->
77 | Caml.print_string msg
78 |
79 | let%expect_test "failed scope resolution" =
80 | let source = {|
81 | let T = IntN256;
82 | |} in
83 | pp source ;
84 | [%expect
85 | {|
86 | Error[1]: Unresolved identifier IntN256
87 | File: "":2:12
88 | |
89 | 2 | let T = IntN256;
90 | | ^^^^^^^ Cannot resolve this identifier |}]
91 |
92 | let%expect_test "method not found" =
93 | let source = {|
94 | struct St { }
95 |
96 | let _ = St{}.method();
97 | |} in
98 | pp source ;
99 | [%expect
100 | {|
101 | Error[1]: Method method not found in
102 | File: "":4:19
103 | |
104 | 4 | let _ = St{}.method();
105 | | ^^^^^^ Method not found |}]
106 |
107 | let%expect_test "duplicate field" =
108 | let source =
109 | {|
110 | struct Foo {
111 | val field: Integer
112 | val field: Integer
113 | }
114 | |}
115 | in
116 | pp source ;
117 | [%expect
118 | {|
119 | Error[1]: Duplicate struct field field
120 | File: "":3:12
121 | |
122 | 3 | val field: Integer
123 | | ^^^^^ Duplicated |}]
124 |
125 | let%expect_test "duplicate variant" =
126 | let source =
127 | {|
128 | union Test1 {
129 | case Integer
130 | case Integer
131 | }
132 |
133 | union Test2[T: Type] {
134 | case Integer
135 | case T
136 | }
137 | let _ = Test2[Integer];
138 | |}
139 | in
140 | pp source ;
141 | [%expect
142 | {|
143 | Error[1]: Duplicate variant with type Integer
144 | File: "":2:6
145 | |
146 | 2 | union Test1 {...
147 | | ^^^^^^^^^^^^^^^^ Duplicated variant in this union
148 | Error[1]: Duplicate variant with type Integer
149 | File: "":11:14
150 | |
151 | 11 | let _ = Test2[Integer];
152 | | ^^^^^^^^^^^^^ Duplicated variant in this union |}]
153 |
154 | let%expect_test "type errors" =
155 | let source =
156 | {|
157 | struct Test {}
158 |
159 | fn test1() -> Test {
160 | 123
161 | }
162 |
163 | fn test2() -> Test {
164 | return 123;
165 | }
166 |
167 | fn expect_test(t: Test) {}
168 | expect_test(123);
169 | |}
170 | in
171 | pp source ~include_std:false ;
172 | [%expect
173 | {|
174 | Error[1]: Expected type `` but found `Integer`
175 | File: "":5:8
176 | |
177 | 5 | 123...
178 | | ^^^^^^ This has type `Integer`
179 | Error[1]: Expected type `` but found `Integer`
180 | File: "":9:15
181 | |
182 | 9 | return 123;
183 | | ^^^ This has type `Integer`
184 | Error[1]: Expected type `` but found `Integer`
185 | File: "":13:18
186 | |
187 | 13 | expect_test(123);
188 | | ^^^ This has type `Integer` |}]
189 |
190 | let%expect_test "is not a struct error" =
191 | let source =
192 | {|
193 | fn test() { 123 }
194 | let a = test() { field: 123 };
195 | |}
196 | in
197 | pp source ~include_std:false ;
198 | [%expect
199 | {|
200 | Error[1]: Expression is not struct type, so it cannot be used in such context.
201 | File: "":3:14
202 | |
203 | 3 | let a = test() { field: 123 };
204 | | ^^^^ This is not struct type |}]
205 |
206 | let%expect_test "cannot have methods error" =
207 | let source = {|
208 | 123.test();
209 | |} in
210 | pp source ~include_std:false ;
211 | [%expect
212 | {|
213 | Error[1]: Method test not found in 123
214 | File: "":2:10
215 | |
216 | 2 | 123.test();
217 | | ^^^^ Method not found |}]
218 |
219 | let%expect_test "this cannot be called error" =
220 | let source = {|
221 | 123();
222 | |} in
223 | pp source ~include_std:false ;
224 | [%expect
225 | {|
226 | Error[1]: Expected function but got value with `Integer` type.
227 | File: "":2:6
228 | |
229 | 2 | 123();
230 | | ^^^ This cannot be called |}]
231 |
232 | let%expect_test "argument number mismatch" =
233 | let source = {|
234 | fn test(x: Integer) {}
235 | test(10, 20, 30);
236 | |} in
237 | pp source ~include_std:false ;
238 | [%expect
239 | {|
240 | Error[1]: Expected 1 arguments but found 3.
241 | File: "":3:6
242 | |
243 | 3 | test(10, 20, 30);
244 | | ^^^^ When calling this function |}]
245 |
246 | (* FIXME: this should print error. *)
247 | let%expect_test "uninterpretable statement" =
248 | let source =
249 | {|
250 | fn test() { builtin_begin_cell(); }
251 | test();
252 | |}
253 | in
254 | pp source ~include_std:false ;
255 | [%expect
256 | {|
257 | (Ok
258 | ((bindings
259 | ((((span (pos pos)) (value test))
260 | ((span (pos pos))
261 | (value
262 | (Value
263 | (Function
264 | ((span (pos pos))
265 | (value
266 | ((function_signature
267 | ((span (pos pos))
268 | (value ((function_params ()) (function_returns HoleType)))))
269 | (function_impl
270 | (Fn
271 | ((span (pos pos))
272 | (value
273 | (Return
274 | ((span (pos pos))
275 | (value
276 | (FunctionCall
277 | (((span (pos pos))
278 | (value
279 | (ResolvedReference
280 | (((span (pos pos)) (value builtin_begin_cell))
281 | ))))
282 | () false)))))))))))))))))))
283 | (structs ()) (type_counter ) (memoized_fcalls )
284 | (struct_signs (0 ())) (union_signs (0 ())) (attr_executors ))) |}]
285 |
286 | let%expect_test "field not found" =
287 | let source =
288 | {|
289 | struct Empty {}
290 | let _ = Empty{}.field;
291 | let {field} = Empty{};
292 | |}
293 | in
294 | pp source ~include_std:false ;
295 | [%expect
296 | {|
297 | Error[1]: Field `field` not found.
298 | File: "":3:22
299 | |
300 | 3 | let _ = Empty{}.field;
301 | | ^^^^^ This field not found
302 | Error[1]: Field `field` not found.
303 | File: "":4:11
304 | |
305 | 4 | let {field} = Empty{};
306 | | ^^^^^ This field not found |}]
307 |
308 | let%expect_test "missing field error" =
309 | let source =
310 | {|
311 | struct Test { val field: Integer }
312 | let {} = Test{field: 10};
313 | |}
314 | in
315 | pp source ~include_std:false ;
316 | [%expect
317 | {|
318 | Error[1]: Field `field` missing in destructuring statement.
319 | File: "":3:6
320 | |
321 | 3 | let {} = Test{field: 10};
322 | | ^^^^^^^^^^^^^^^^^^^^^^^^ In this binding |}]
323 |
324 | let%expect_test "Case Not Found Error" =
325 | let source =
326 | {|
327 | union Test {
328 | case Int[10]
329 | case Int[20]
330 | }
331 | fn test(value: Test) {
332 | switch (value) {
333 | case Int[10] _ => { return 10; }
334 | case Int[123] _ => { return 123; }
335 | }
336 | }
337 | |}
338 | in
339 | pp source ;
340 | [%expect
341 | {|
342 | Error[1]: Case type not found in union.
343 | File: "":9:24
344 | |
345 | 9 | case Int[123] _ => { return 123; }
346 | | ^ Type of this variable is not found in the condition union |}]
347 |
348 | let%expect_test "Expected Type Function" =
349 | let source =
350 | {|
351 | union Test[X: Type] {}
352 |
353 | let _ = Test(Integer);
354 | |}
355 | in
356 | pp source ;
357 | [%expect
358 | {|
359 | Error[1]: Function should be called using `[]` brackets but called with `()` parens.
360 | File: "":4:14
361 | |
362 | 4 | let _ = Test(Integer);
363 | | ^^^^^^^^^^^^ When calling this function |}]
364 |
365 | let%expect_test "Proper error positioning for ambigiously defined data types" =
366 | let sources =
367 | [ {|
368 | struct T {v}
369 | |};
370 | {|
371 | interface I {i}
372 | |};
373 | {|
374 | union U {u}
375 | |} ]
376 | in
377 | List.iter sources ~f:(fun s -> handle_parse_error (fun () -> pp s)) ;
378 | [%expect
379 | {|
380 | Error in line 2, column 17:
381 | struct T {v}
382 | ^
383 | Expecting "//", "fn", "impl", "val", '@', '}', block comment or whitespace
384 | Error in line 2, column 20:
385 | interface I {i}
386 | ^
387 | Expecting "//", "fn", '@', '}', block comment or whitespace
388 | Error in line 2, column 16:
389 | union U {u}
390 | ^
391 | Expecting "//", "case", "fn", "impl", '@', '}', block comment or whitespace |}]
392 |
--------------------------------------------------------------------------------
/test/immediacy_check.ml:
--------------------------------------------------------------------------------
1 | module Config = struct
2 | include Tact.Located.Disabled
3 | end
4 |
5 | module Syntax = Tact.Syntax.Make (Config)
6 | module Parser = Tact.Parser.Make (Config)
7 | module Lang = Tact.Lang.Make (Config)
8 | module Show = Tact.Show.Make (Config)
9 | module Interpreter = Tact.Interpreter.Make (Config)
10 | module Errors = Tact.Errors
11 | module Zint = Tact.Zint
12 | module C = Tact.Compiler
13 | module Codegen = Tact.Codegen_func
14 | module Func = Tact.Func
15 | include Core
16 |
17 | type error = [Lang.error | Interpreter.error] [@@deriving sexp_of]
18 |
19 | let make_errors e = new Errors.errors e
20 |
21 | let parse_program s = Parser.parse s
22 |
23 | let strip_if_exists_in_other o1 o2 ~equal =
24 | List.filter o1 ~f:(fun o1_item -> not @@ List.exists o2 ~f:(equal o1_item))
25 |
26 | let strip : program:Lang.program -> previous:Lang.program -> Lang.program =
27 | fun ~program ~previous ->
28 | { program with
29 | bindings =
30 | strip_if_exists_in_other program.bindings previous.bindings
31 | ~equal:(fun (x1, _) (y1, _) -> Config.equal_located equal_string x1 y1);
32 | structs =
33 | strip_if_exists_in_other program.structs previous.structs
34 | ~equal:(fun (id1, _) (id2, _) -> equal_int id1 id2);
35 | unions =
36 | strip_if_exists_in_other program.unions previous.unions
37 | ~equal:(fun (id1, _) (id2, _) -> equal_int id1 id2);
38 | interfaces =
39 | strip_if_exists_in_other program.interfaces previous.interfaces
40 | ~equal:(fun (id1, _) (id2, _) -> equal_int id1 id2);
41 | struct_signs =
42 | Lang.Arena.strip_if_exists program.struct_signs previous.struct_signs;
43 | union_signs =
44 | Lang.Arena.strip_if_exists program.union_signs previous.struct_signs }
45 |
46 | let compile_pass p prev_program errors =
47 | let c = new Lang.constructor ~program:prev_program errors in
48 | let p' = c#visit_program Lang.default_ctx p in
49 | p'
50 |
51 | let build_program ?(errors = make_errors Show.show_error)
52 | ?(strip_defaults = true) ~codegen p =
53 | let p' = compile_pass p (Lang.default_program ()) errors in
54 | let p'' =
55 | if strip_defaults then strip ~program:p' ~previous:(Lang.default_program ())
56 | else p'
57 | in
58 | errors#to_result p''
59 | |> Result.map_error ~f:(fun errors ->
60 | let errs = List.map errors ~f:(fun (_, err, _) -> err) in
61 | (errs, p'') )
62 | |> Result.map ~f:codegen
63 |
64 | let rec pp_sexp = Sexplib.Sexp.pp_hum Caml.Format.std_formatter
65 |
66 | and sexp_of_errors =
67 | sexp_of_pair (List.sexp_of_t sexp_of_error) Lang.sexp_of_program
68 |
69 | and print_sexp e =
70 | pp_sexp (Result.sexp_of_t Lang.sexp_of_program sexp_of_errors e)
71 |
72 | let compile ?(strip_defaults = true) s =
73 | parse_program s |> build_program ~strip_defaults ~codegen:(fun x -> x)
74 |
75 | let pp_compile ?(strip_defaults = true) s =
76 | compile ~strip_defaults s |> print_sexp
77 |
78 | open Lang
79 |
80 | let%expect_test "Immediacy Checks Comptime Reference" =
81 | let scope = [[make_comptime (bl "Test", bl @@ Value Void)]] in
82 | let expr = bl @@ Reference (bl "Test", VoidType) in
83 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
84 | [%expect {| true |}]
85 |
86 | let%expect_test "Immediacy Checks Runtime Reference" =
87 | let scope = [[make_runtime (bl "Test", VoidType)]] in
88 | let expr = bl @@ Reference (bl "Test", VoidType) in
89 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
90 | [%expect {| false |}]
91 |
92 | let%expect_test "Immediacy Checks Primitive" =
93 | let scope = [] in
94 | let expr =
95 | bl @@ Primitive (Prim {name = ""; exprs = []; out_ty = VoidType})
96 | in
97 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
98 | [%expect {| false |}]
99 |
100 | let%expect_test "Immediacy Checks Empty Function" =
101 | let scope = [] in
102 | let expr =
103 | bl
104 | @@ Value
105 | (Function
106 | ( bl
107 | @@ { function_signature =
108 | bl
109 | @@ { function_attributes = [];
110 | function_is_type = false;
111 | function_params = [];
112 | function_returns = VoidType };
113 | function_impl = Fn (bl @@ Block []) } ) )
114 | in
115 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
116 | [%expect {| true |}]
117 |
118 | let%expect_test "Immediacy Checks Function Argument" =
119 | let scope = [] in
120 | let expr =
121 | bl
122 | @@ Value
123 | (Function
124 | ( bl
125 | @@ { function_signature =
126 | bl
127 | @@ { function_attributes = [];
128 | function_params = [(bl "arg", VoidType)];
129 | function_is_type = false;
130 | function_returns = VoidType };
131 | function_impl =
132 | Fn
133 | ( bl
134 | @@ Block [bl @@ Expr (bl @@ Reference (bl "arg", VoidType))]
135 | ) } ) )
136 | in
137 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
138 | [%expect {| true |}]
139 |
140 | let%expect_test "Immediacy Checks Let Argument" =
141 | let scope = [] in
142 | let expr =
143 | bl
144 | @@ Value
145 | (Function
146 | ( bl
147 | @@ { function_signature =
148 | bl
149 | @@ { function_attributes = [];
150 | function_params = [];
151 | function_is_type = false;
152 | function_returns = VoidType };
153 | function_impl =
154 | Fn
155 | ( bl
156 | @@ Block
157 | [ bl @@ Let [(bl "arg", bl @@ Value Void)];
158 | bl @@ Expr (bl @@ Reference (bl "arg", VoidType)) ]
159 | ) } ) )
160 | in
161 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
162 | [%expect {| true |}]
163 |
164 | let%expect_test "Immediacy Checks Destructuring Let" =
165 | let scope = [] in
166 | let expr =
167 | bl
168 | @@ Value
169 | (Function
170 | ( bl
171 | @@ { function_signature =
172 | bl
173 | @@ { function_attributes = [];
174 | function_params = [];
175 | function_is_type = false;
176 | function_returns = VoidType };
177 | function_impl =
178 | Fn
179 | ( bl
180 | @@ Block
181 | [ bl
182 | @@ DestructuringLet
183 | { destructuring_let =
184 | [(bl "a", bl "b"); (bl "c", bl "c")];
185 | destructuring_let_rest = false;
186 | destructuring_let_expr = bl @@ Value Void };
187 | bl @@ Expr (bl @@ Reference (bl "b", VoidType));
188 | bl @@ Expr (bl @@ Reference (bl "c", VoidType)) ] )
189 | } ) )
190 | in
191 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
192 | [%expect {| true |}]
193 |
194 | let%expect_test "Immediacy Checks Function Call WITHOUT Primitive" =
195 | let scope = [] in
196 | let expr =
197 | bl
198 | @@ FunctionCall
199 | ( bl
200 | @@ Value
201 | (Function
202 | ( bl
203 | @@ { function_signature =
204 | bl
205 | @@ { function_attributes = [];
206 | function_params = [];
207 | function_is_type = false;
208 | function_returns = VoidType };
209 | function_impl =
210 | Fn
211 | ( bl
212 | @@ Block
213 | [ bl @@ Let [(bl "arg", bl @@ Value Void)];
214 | bl
215 | @@ Expr (bl @@ Reference (bl "arg", VoidType))
216 | ] ) } ) ),
217 | [],
218 | false )
219 | in
220 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
221 | [%expect {| true |}]
222 |
223 | let%expect_test "Immediacy Checks Function Call WITH Primitive" =
224 | let scope = [] in
225 | let expr =
226 | bl
227 | @@ FunctionCall
228 | ( bl
229 | @@ Value
230 | (Function
231 | ( bl
232 | @@ { function_signature =
233 | bl
234 | @@ { function_attributes = [];
235 | function_params = [];
236 | function_is_type = false;
237 | function_returns = VoidType };
238 | function_impl =
239 | Fn
240 | ( bl
241 | @@ Block
242 | [ bl
243 | @@ Expr
244 | ( bl
245 | @@ Primitive
246 | (Prim
247 | { name = "";
248 | exprs = [];
249 | out_ty = VoidType } ) ) ] ) }
250 | ) ),
251 | [],
252 | false )
253 | in
254 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
255 | [%expect {| false |}]
256 |
257 | let f_with_primitive =
258 | bl
259 | @@ Value
260 | (Function
261 | ( bl
262 | @@ { function_signature =
263 | bl
264 | @@ { function_attributes = [];
265 | function_params = [];
266 | function_is_type = false;
267 | function_returns = VoidType };
268 | function_impl =
269 | Fn
270 | ( bl
271 | @@ Block
272 | [ bl
273 | @@ Expr
274 | ( bl
275 | @@ Primitive
276 | (Prim
277 | {name = ""; exprs = []; out_ty = VoidType}
278 | ) ) ] ) } ) )
279 |
280 | let%expect_test "Immediacy Checks Function Call that contains function with \
281 | primitive" =
282 | let scope = [] in
283 | let expr =
284 | bl
285 | @@ FunctionCall
286 | ( bl
287 | @@ Value
288 | (Function
289 | ( bl
290 | @@ { function_signature =
291 | bl
292 | @@ { function_attributes = [];
293 | function_is_type = false;
294 | function_params = [];
295 | function_returns = VoidType };
296 | function_impl =
297 | Fn
298 | ( bl
299 | @@ Block [bl @@ Let [(bl "_", f_with_primitive)]] )
300 | } ) ),
301 | [],
302 | false )
303 | in
304 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
305 | [%expect {| true |}]
306 |
307 | let%expect_test "Immediacy Checks Function Call that Call function with \
308 | primitive" =
309 | let scope = [] in
310 | let expr =
311 | bl
312 | @@ FunctionCall
313 | ( bl
314 | @@ Value
315 | (Function
316 | ( bl
317 | @@ { function_signature =
318 | bl
319 | @@ { function_attributes = [];
320 | function_is_type = false;
321 | function_params = [];
322 | function_returns = VoidType };
323 | function_impl =
324 | Fn
325 | ( bl
326 | @@ Block
327 | [ bl
328 | @@ Expr
329 | ( bl
330 | @@ FunctionCall
331 | (f_with_primitive, [], false) ) ]
332 | ) } ) ),
333 | [],
334 | false )
335 | in
336 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
337 | [%expect {| false |}]
338 |
339 | let%expect_test "Immediacy Checks Top Level Fn With Sign" =
340 | let scope = [] in
341 | let expr =
342 | bl
343 | @@ Value
344 | (Function
345 | ( bl
346 | @@ { function_signature =
347 | bl
348 | @@ { function_attributes = [];
349 | function_is_type = false;
350 | function_params = [];
351 | function_returns = StructSig 0 };
352 | function_impl = Fn (bl @@ Block []) } ) )
353 | in
354 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
355 | [%expect {| true |}]
356 |
357 | let%expect_test "Immediacy Checks Struct Sig" =
358 | let scope = [] in
359 | let expr = bl @@ Value (Type (StructSig 0)) in
360 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
361 | [%expect {| false |}]
362 |
363 | let%expect_test "Immediacy Checks Self Type" =
364 | let scope = [] in
365 | let expr =
366 | bl @@ FunctionCall (bl @@ Value Void, [bl @@ Value (Type SelfType)], false)
367 | in
368 | pp_sexp @@ sexp_of_bool @@ is_immediate_expr scope (default_program ()) expr ;
369 | [%expect {| false |}]
370 |
371 | (* let%expect_test "Immediacy Checks MyInt Type" =
372 | let source =
373 | {|
374 | struct Cell {
375 | val c: builtin_Cell
376 | }
377 | struct Builder {
378 | val b: builtin_Builder
379 | fn serialize_int(self: Self, int: Integer, bits: Integer) -> Self {
380 | let b = builtin_store_int(self.b, int, bits);
381 | Self { b: b }
382 | }
383 | }
384 |
385 | struct Slice {
386 | val s: builtin_Slice
387 | fn load_int(self: Self, bits: Integer) -> LoadResult(Integer) {
388 | let output = builtin_load_int(self.s, bits);
389 | let slice = Self { s: output.value1 };
390 | let int = output.value2;
391 | LoadResult(Integer) { slice: slice, value: int }
392 | }
393 | }
394 | struct MyInt[bits: Integer] {
395 | val value: Integer
396 | impl Deserialize {
397 | fn deserialize(s: Slice) -> LoadResult(Self) {
398 | let res = s.load_int(bits);
399 |
400 | LoadResult(Self) {
401 | slice: res.slice,
402 | value: Self { value: res.value }
403 | }
404 | }
405 | }
406 | }
407 | |}
408 | in
409 | let p = Option.value_exn @@ Result.ok @@ compile source in
410 | let res =
411 | is_immediate_expr
412 | [List.map p.bindings ~f:make_comptime]
413 | p
414 | ( bl
415 | @@ FunctionCall
416 | ( List.Assoc.find_exn p.bindings (bl @@ "MyInt")
417 | ~equal:(Config.equal_located equal_string),
418 | [bl @@ Value (Integer (Z.of_int 123))] ) )
419 | in
420 | pp_sexp @@ sexp_of_bool res ;
421 | [%expect {| true |}] *)
422 |
423 | let%expect_test "Immediacy Checks Unions Functions" =
424 | let source =
425 | {|
426 | union MsgAddressExt {
427 | case Integer
428 | fn serialize(self: Self) { }
429 | }
430 | |}
431 | in
432 | let _ = Option.value_exn @@ Result.ok @@ compile source in
433 | pp_sexp @@ sexp_of_bool true ;
434 | [%expect {| true |}]
435 |
436 | let%expect_test "Immediacy Checks Unions Functions" =
437 | let source =
438 | {|
439 | union MsgAddressExt {
440 | case Integer
441 | fn serialize(self: Self) { }
442 | }
443 | |}
444 | in
445 | let _ = Option.value_exn @@ Result.ok @@ compile source in
446 | pp_sexp @@ sexp_of_bool true ;
447 | [%expect {| true |}]
448 |
--------------------------------------------------------------------------------
/test/lang_types.ml:
--------------------------------------------------------------------------------
1 | open Shared.Disabled
2 | open Shared.Disabled.Lang
3 | module Config = Shared.DisabledConfig
4 |
5 | let find scope name =
6 | let open Config in
7 | List.find_map scope ~f:(fun (n, ex) ->
8 | if equal_string n.value name then Some ex else None )
9 |
10 | let%test "aliased structures equality" =
11 | let source = {|
12 | struct T { val a: Int[257] }
13 | let T1 = T;
14 | |} in
15 | Alcotest.(check bool)
16 | "aliased types are the same" true
17 | (let scope = (compile source).bindings in
18 | let t = find scope "T" |> Option.value_exn
19 | and t1 = find scope "T1" |> Option.value_exn in
20 | pp_sexp (Lang.sexp_of_expr t) ;
21 | pp_sexp (Lang.sexp_of_expr t1) ;
22 | Lang.equal_expr t t1 )
23 |
24 | let%test "carbon copy structure equality" =
25 | let source =
26 | {|
27 | struct T { val a: Int[257] }
28 | struct T1 { val a: Int[257] }
29 | |}
30 | in
31 | Alcotest.(check bool)
32 | "carbon copy types are not the same" false
33 | (let scope = (compile source).bindings in
34 | let t = find scope "T" |> Option.value_exn
35 | and t1 = find scope "T1" |> Option.value_exn in
36 | pp_sexp (Lang.sexp_of_expr t) ;
37 | pp_sexp (Lang.sexp_of_expr t1) ;
38 | Lang.equal_expr t t1 )
39 |
40 | let%test "parameterized structure equality" =
41 | let source =
42 | {|
43 | struct T[X: Type] { val a: X }
44 | let T1 = T[Int[257]];
45 | let T2 = T[Bool];
46 | let T3 = T[Int[257]];
47 | |}
48 | in
49 | Alcotest.(check bool)
50 | "differently parameterized types are not the same" false
51 | (let scope = (compile source).bindings in
52 | let t1 = find scope "T1" |> Option.value_exn
53 | and t2 = find scope "T2" |> Option.value_exn in
54 | pp_sexp (Lang.sexp_of_expr t1) ;
55 | pp_sexp (Lang.sexp_of_expr t2) ;
56 | Lang.equal_expr t1 t2 ) ;
57 | Alcotest.(check bool)
58 | "equally parameterized types are the same" true
59 | (let scope = (compile source).bindings in
60 | let t1 = find scope "T1" |> Option.value_exn
61 | and t3 = find scope "T3" |> Option.value_exn in
62 | pp_sexp (Lang.sexp_of_expr t1) ;
63 | pp_sexp (Lang.sexp_of_expr t3) ;
64 | Lang.equal_expr t1 t3 )
65 |
66 | let%test "builtin function equality" =
67 | let bl = Config.builtin_located in
68 | let f1 =
69 | { function_signature =
70 | bl
71 | { function_attributes = [];
72 | function_is_type = false;
73 | function_params = [];
74 | function_returns = VoidType };
75 | function_impl = BuiltinFn (builtin_fun (fun _ _ -> Void)) }
76 | and f2 =
77 | { function_signature =
78 | bl
79 | { function_attributes = [];
80 | function_is_type = false;
81 | function_params = [];
82 | function_returns = VoidType };
83 | function_impl = BuiltinFn (builtin_fun (fun _ _ -> Void)) }
84 | in
85 | Alcotest.(check bool)
86 | "different instances of the same builtin function are not equal" false
87 | (equal_function_kind f1 f2) ;
88 | Alcotest.(check bool)
89 | "same instances of the same builtin function are equal" true
90 | (equal_function_kind f1 f1)
91 |
--------------------------------------------------------------------------------
/test/shared.ml:
--------------------------------------------------------------------------------
1 | module Make =
2 | functor
3 | (Config : Tact.Config.T)
4 | ->
5 | struct
6 | module Syntax = Tact.Syntax.Make (Config)
7 | module Parser = Tact.Parser.Make (Config)
8 | module Lang = Tact.Lang.Make (Config)
9 | module Show = Tact.Show.Make (Config)
10 | module Interpreter = Tact.Interpreter.Make (Config)
11 | module Builtin = Tact.Builtin.Make (Config)
12 | module Codegen = Tact.Codegen_func.Make (Config)
13 | module Errors = Tact.Errors
14 | module Zint = Tact.Zint
15 | module Func = Tact.Func
16 | include Core
17 |
18 | type error = [Lang.error | Interpreter.error] [@@deriving sexp_of]
19 |
20 | let make_errors e = new Errors.errors e
21 |
22 | let parse_program s = Parser.parse s
23 |
24 | let strip_if_exists_in_other o1 o2 ~equal =
25 | List.filter o1 ~f:(fun o1_item ->
26 | not @@ List.exists o2 ~f:(equal o1_item) )
27 |
28 | let strip : program:Lang.program -> previous:Lang.program -> Lang.program =
29 | fun ~program ~previous ->
30 | { program with
31 | bindings =
32 | strip_if_exists_in_other program.bindings previous.bindings
33 | ~equal:(fun (x1, _) (y1, _) ->
34 | Config.equal_located equal_string x1 y1 );
35 | structs =
36 | strip_if_exists_in_other program.structs previous.structs
37 | ~equal:(fun (id1, _) (id2, _) -> equal_int id1 id2);
38 | unions =
39 | strip_if_exists_in_other program.unions previous.unions
40 | ~equal:(fun (id1, _) (id2, _) -> equal_int id1 id2);
41 | interfaces =
42 | strip_if_exists_in_other program.interfaces previous.interfaces
43 | ~equal:(fun (id1, _) (id2, _) -> equal_int id1 id2);
44 | type_functions =
45 | strip_if_exists_in_other program.type_functions
46 | previous.type_functions ~equal:(fun (id1, _) (id2, _) ->
47 | Lang.equal_type_ id1 id2 );
48 | struct_signs =
49 | Lang.Arena.strip_if_exists program.struct_signs previous.struct_signs;
50 | union_signs =
51 | Lang.Arena.strip_if_exists program.union_signs previous.union_signs }
52 |
53 | let compile_pass p prev_program errors =
54 | let c = new Lang.constructor ~program:prev_program errors in
55 | let p' = c#visit_program Lang.default_ctx p in
56 | p'
57 |
58 | let build_program ?(errors = make_errors Show.show_error)
59 | ?(prev_program = Lang.default_program ()) ?(strip_defaults = true)
60 | ~include_std ~codegen p =
61 | let prev_prog =
62 | match include_std with
63 | | true ->
64 | let c = new Lang.constructor ~program:prev_program errors in
65 | let p' =
66 | c#visit_program Lang.default_ctx (parse_program Builtin.std)
67 | in
68 | p'
69 | | false ->
70 | prev_program
71 | in
72 | (* This will make a deep copy. Lang.constructor mutates input program,
73 | so we need deep copy if we want to strip bindings later. *)
74 | let prev_prog_copy =
75 | { prev_prog with
76 | bindings = prev_prog.bindings;
77 | struct_signs = Lang.Arena.deep_copy prev_prog.struct_signs;
78 | union_signs = Lang.Arena.deep_copy prev_prog.union_signs }
79 | in
80 | let p' = compile_pass p prev_prog_copy errors in
81 | let p'' =
82 | if strip_defaults then strip ~program:p' ~previous:prev_prog else p'
83 | in
84 | errors#to_result p''
85 | |> Result.map_error ~f:(fun errors ->
86 | let errs = List.map errors ~f:(fun (_, err, _) -> err) in
87 | (errs, p'') )
88 | |> Result.map ~f:codegen
89 |
90 | let rec pp_sexp = Sexplib.Sexp.pp_hum Caml.Format.std_formatter
91 |
92 | and sexp_of_errors =
93 | sexp_of_pair (List.sexp_of_t sexp_of_error) Lang.sexp_of_program
94 |
95 | and print_sexp e =
96 | pp_sexp (Result.sexp_of_t Lang.sexp_of_program sexp_of_errors e)
97 |
98 | let pp_compile ?(prev_program = Lang.default_program ())
99 | ?(strip_defaults = true) ?(include_std = true)
100 | ?(show_errors = fun x _ -> pp_sexp (sexp_of_errors x)) s =
101 | parse_program s
102 | |> build_program ~prev_program ~strip_defaults ~include_std
103 | ~codegen:(fun x -> x)
104 | |> fun res ->
105 | ( match res with
106 | | Ok t ->
107 | pp_sexp @@ Result.sexp_of_t Lang.sexp_of_program sexp_of_errors (Ok t)
108 | | Error e ->
109 | show_errors e s ) ;
110 | Caml.print_newline ()
111 |
112 | let pp_codegen ?(prev_program = Lang.default_program ())
113 | ?(strip_defaults = false) ?(include_std = true) s =
114 | let _ =
115 | parse_program s
116 | |> build_program ~prev_program ~strip_defaults ~include_std
117 | ~codegen:Codegen.codegen
118 | |> Result.map ~f:(Func.pp_program Caml.Format.std_formatter)
119 | |> Result.map_error ~f:(fun e -> pp_sexp (sexp_of_errors e))
120 | in
121 | ()
122 |
123 | exception Exn of error list * Lang.program
124 |
125 | let compile s =
126 | parse_program s
127 | |> build_program ~codegen:(fun x -> x) ~include_std:true
128 | |> Result.map_error ~f:(fun (errs, p) -> Exn (errs, p))
129 | |> Result.ok_exn
130 | end
131 |
132 | module EnabledConfig = struct
133 | include Tact.Located.Enabled
134 | end
135 |
136 | module DisabledConfig = struct
137 | include Tact.Located.Disabled
138 | end
139 |
140 | module Enabled = Make (EnabledConfig)
141 | module Disabled = Make (DisabledConfig)
142 |
--------------------------------------------------------------------------------
/test/std.ml:
--------------------------------------------------------------------------------
1 | module Config = struct
2 | include Tact.Located.Disabled
3 | end
4 |
5 | module Syntax = Tact.Syntax.Make (Config)
6 | module Parser = Tact.Parser.Make (Config)
7 | module Lang = Tact.Lang.Make (Config)
8 | module Show = Tact.Show.Make (Config)
9 | module Interpreter = Tact.Interpreter.Make (Config)
10 | module Builtin = Tact.Builtin.Make (Config)
11 | module Errors = Tact.Errors
12 | module Zint = Tact.Zint
13 | module C = Tact.Compiler
14 | include Core
15 |
16 | type error = [Lang.error | Interpreter.error] * Lang.program
17 | [@@deriving sexp_of]
18 |
19 | let make_errors e = new Errors.errors e
20 |
21 | let parse_program s = Parser.parse s
22 |
23 | let pp_sexp = Sexplib.Sexp.pp_hum Caml.Format.std_formatter
24 |
25 | let compile_std ?(errors = make_errors Show.show_error)
26 | ?(prev_program = Lang.default_program ()) () =
27 | let std =
28 | let c = new Lang.constructor ~program:prev_program errors in
29 | let p' = c#visit_program Lang.default_ctx (parse_program Builtin.std) in
30 | p'
31 | in
32 | (errors, std)
33 |
34 | let%test "test std build" =
35 | Alcotest.(check bool)
36 | "std build" true
37 | (let errors, std = compile_std () in
38 | pp_sexp (sexp_of_string (errors#show_errors Builtin.std)) ;
39 | pp_sexp (Lang.sexp_of_program std) ;
40 | Result.is_ok (errors#to_result ()) )
41 |
--------------------------------------------------------------------------------
/tonvm.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "TON Virtual Machine implementation"
4 | maintainer: ["cmxog@protonmail.com" "dmytro.polunin@gmail.com"]
5 | authors: ["cmxog@protonmail.com" "dmytro.polunin@gmail.com"]
6 | license: "Apache-2.0"
7 | homepage: "https://github.com/tact-lang/tact"
8 | bug-reports: "https://github.com/tact-lang/tact/issues"
9 | depends: [
10 | "ocaml" {>= "4.14.0"}
11 | "base" {>= "0.15.0"}
12 | "bitstring" {>= "4.1.0"}
13 | "ppx_bitstring" {>= "4.1.0"}
14 | "zarith" {>= "1.12"}
15 | "stdint" {>= "0.7.0"}
16 | "ppx_show" {>= "0.2.1"}
17 | "sexplib" {>= "0.15.0"}
18 | "ppx_sexp_conv" {>= "0.15.1"}
19 | "dune" {>= "2.9" & build & >= "3.1.1"}
20 | "ppx_expect" {with-test & >= "0.15.0"}
21 | "odoc" {with-doc}
22 | ]
23 | build: [
24 | ["dune" "subst"] {dev}
25 | [
26 | "dune"
27 | "build"
28 | "-p"
29 | name
30 | "-j"
31 | jobs
32 | "--promote-install-files=false"
33 | "@install"
34 | "@runtest" {with-test}
35 | "@doc" {with-doc}
36 | ]
37 | ["dune" "install" "-p" name "--create-install-files" name]
38 | ]
39 | dev-repo: "git+https://github.com/tact-lang/tact.git"
40 |
--------------------------------------------------------------------------------
/tonvm/bitstr.ml:
--------------------------------------------------------------------------------
1 | (* Workaround for raise Exit in ppx_bitstring + Base *)
2 | exception Exit = Caml.Exit
3 |
4 | type t = Bitstring.t
5 |
6 | let sexp_of_t _ = Sexplib.Sexp.Atom ""
7 |
--------------------------------------------------------------------------------
/tonvm/cp.ml:
--------------------------------------------------------------------------------
1 | type codepage = Codepage of int
2 |
--------------------------------------------------------------------------------
/tonvm/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (libraries zarith stdint base sexplib bitstring)
3 | (package tonvm)
4 | (name tonvm)
5 | (inline_tests)
6 | (preprocess
7 | (pps ppx_sexp_conv ppx_expect ppx_bitstring)))
8 |
--------------------------------------------------------------------------------
/tonvm/instr.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | module Stack = Instr_stack
3 | module Null = Instr_null
4 |
5 | type t = [Stack.t | Null.t] [@@deriving sexp_of, sexp]
6 |
7 | type error = [Stack.error | Null.error]
8 |
9 | let[@warning "-11"] sexp_of_error = function
10 | | #Stack.error as error ->
11 | Stack.sexp_of_error error
12 | | #Null.error as error ->
13 | Null.sexp_of_error error
14 |
15 | let execute vm = function
16 | | #Stack.t as instr ->
17 | Stack.execute vm instr
18 | | #Null.t as instr ->
19 | Null.execute vm instr
20 |
21 | let encode ?(cp = Cp.Codepage 0) = function
22 | | #Stack.t as instr ->
23 | Stack.encode cp instr
24 | | #Null.t as instr ->
25 | Null.encode cp instr
26 |
27 | let decode ?(cp = Cp.Codepage 0) bits =
28 | let decoders = [Stack.decode; Null.decode] in
29 | let init : (t * Bitstring.t, error) Result.t = Ok (`NOP, bits) in
30 | List.fold_until ~init
31 | ~f:(fun _acc decoder ->
32 | match decoder cp bits with
33 | | Ok v ->
34 | Stop (Ok v)
35 | | Error (`UnsupportedBitcode _bits) as err ->
36 | Continue err
37 | | Error _ as err ->
38 | Stop err )
39 | ~finish:(fun acc -> acc)
40 | decoders
41 |
42 | let rec decode_all ?(cp = Cp.Codepage 0) ?(init : t list = []) bits =
43 | match Bitstring.bitstring_length bits with
44 | | 0 ->
45 | Ok (List.rev init, bits)
46 | | _ -> (
47 | match decode ~cp bits with
48 | | Ok (v, bits) ->
49 | decode_all ~cp bits ~init:(v :: init)
50 | | Error _ as err ->
51 | err )
52 |
--------------------------------------------------------------------------------
/tonvm/instr_null.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Cp
3 | open Bitstr
4 |
5 | type t = [`NULL | `ISNULL] [@@deriving sexp_of, sexp]
6 |
7 | type error = [`UnsupportedCodepage of Int.t | `UnsupportedBitcode of Bitstr.t]
8 | [@@deriving sexp_of]
9 |
10 | let execute vm = function
11 | | `NULL ->
12 | Vm.push Null vm
13 | | `ISNULL -> (
14 | let value, vm = Vm.pop vm in
15 | match value with
16 | | Null ->
17 | Vm.push Vm.const_true vm
18 | | _ ->
19 | Vm.push Vm.const_false vm )
20 |
21 | let encode cp instr =
22 | match cp with
23 | | Codepage 0 -> (
24 | match instr with
25 | | `NULL ->
26 | let%bitstring bits = {| 0x6D: 8 |} in
27 | Ok bits
28 | | `ISNULL ->
29 | let%bitstring bits = {| 0x6E: 8 |} in
30 | Ok bits )
31 | | Codepage cp ->
32 | Error (`UnsupportedCodepage cp)
33 |
34 | let decode cp bits =
35 | match cp with
36 | | Codepage 0 -> (
37 | match%bitstring bits with
38 | | {| 0x6D : 8 ; rest : -1 :bitstring |} ->
39 | Ok (`NULL, rest)
40 | | {| 0x6E : 8 ; rest : -1 :bitstring |} ->
41 | Ok (`ISNULL, rest)
42 | | {|_|} as bits ->
43 | Error (`UnsupportedBitcode bits) )
44 | | Codepage cp ->
45 | Error (`UnsupportedCodepage cp)
46 |
--------------------------------------------------------------------------------
/tonvm/instr_null_test.ml:
--------------------------------------------------------------------------------
1 | let%expect_test "NULL" =
2 | Tests.(execute "(NULL)" |> print_stack) ;
3 | [%expect {| (Null) |}]
4 |
5 | let%expect_test "NULL encoding" =
6 | Tests.(encode_decode `NULL |> print) ;
7 | [%expect {| (Ok NULL) |}]
8 |
9 | let%expect_test "ISNULL" =
10 | Tests.(execute "(NULL ISNULL)" |> print_stack) ;
11 | [%expect {| ((Integer -1)) |}]
12 |
13 | let%expect_test "negative ISNULL" =
14 | Tests.(execute "(NULL ISNULL ISNULL)" |> print_stack) ;
15 | [%expect {| ((Integer 0)) |}]
16 |
17 | let%expect_test "ISNULL encoding" =
18 | Tests.(encode_decode `ISNULL |> print) ;
19 | [%expect {| (Ok ISNULL) |}]
20 |
--------------------------------------------------------------------------------
/tonvm/instr_stack.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Cp
3 | open Bitstr
4 |
5 | type core = [`NOP | `XCHG of int * int | `PUSH of int | `POP of int]
6 | [@@deriving sexp_of, sexp]
7 |
8 | type ext = [`SWAP | `XCHG0 of int | `DUP | `OVER | `DROP | `NIP]
9 | [@@deriving sexp_of, sexp]
10 |
11 | type t = [core | ext] [@@deriving sexp_of, sexp]
12 |
13 | type error =
14 | [ `InvalidInstruction of core
15 | | `UnsupportedBitcode of Bitstr.t
16 | | `UnsupportedCodepage of Int.t ]
17 | [@@deriving sexp_of]
18 |
19 | let rec core_of_ext : ext -> core = function
20 | | `SWAP ->
21 | core_of_ext (`XCHG0 1)
22 | | `XCHG0 i ->
23 | `XCHG (0, i)
24 | | `DUP ->
25 | `PUSH 0
26 | | `OVER ->
27 | `PUSH 1
28 | | `DROP ->
29 | `POP 0
30 | | `NIP ->
31 | `POP 1
32 |
33 | let execute vm =
34 | let execute_core vm = function
35 | | `NOP ->
36 | vm
37 | | `XCHG (i, j) ->
38 | Vm.interchange i j vm
39 | | `PUSH i ->
40 | Vm.push (Vm.at vm i) vm
41 | | `POP i ->
42 | let _, vm = Vm.interchange 0 i vm |> Vm.pop in
43 | vm
44 | in
45 | function
46 | | #ext as instr ->
47 | execute_core vm @@ core_of_ext instr
48 | | #core as instr ->
49 | execute_core vm instr
50 |
51 | let encode cp =
52 | let encode_core instr =
53 | match cp with
54 | | Codepage 0 -> (
55 | match instr with
56 | | `NOP ->
57 | let%bitstring bits = {| 0x00: 8 |} in
58 | Ok bits
59 | | `XCHG (0, i) when i >= 1 && i <= 15 ->
60 | let%bitstring bits = {| 0x0: 4; i : 4 |} in
61 | Ok bits
62 | | `XCHG (i, j) when i >= 1 && j > i && j <= 15 ->
63 | let%bitstring bits = {| 0x10: 8; i : 4; j: 4 |} in
64 | Ok bits
65 | | `XCHG (0, ii) when ii >= 0 && ii <= 255 ->
66 | let%bitstring bits = {| 0x11: 8; ii : 8 |} in
67 | Ok bits
68 | | `PUSH i when i >= 0 && i <= 15 ->
69 | let%bitstring bits = {| 0x2: 4; i : 4 |} in
70 | Ok bits
71 | | `POP i when i >= 0 && i <= 15 ->
72 | let%bitstring bits = {| 0x3: 4; i : 4 |} in
73 | Ok bits
74 | | instr ->
75 | Error (`InvalidInstruction instr) )
76 | | Codepage cp ->
77 | Error (`UnsupportedCodepage cp)
78 | in
79 | function
80 | | #ext as instr ->
81 | encode_core @@ core_of_ext instr
82 | | #core as instr ->
83 | encode_core instr
84 |
85 | let decode cp bits =
86 | match cp with
87 | | Codepage 0 -> (
88 | match%bitstring bits with
89 | | {| 0x00 : 8 ; rest : -1 : bitstring |} ->
90 | Ok (`NOP, rest)
91 | | {| 0x0 : 4 ; i : 4; rest : -1 : bitstring |} ->
92 | Ok (`XCHG (0, i), rest)
93 | | {| 0x10 : 8 ; i : 4; j : 4; rest : -1 : bitstring |} ->
94 | Ok (`XCHG (i, j), rest)
95 | | {| 0x11 : 8 ; ii : 8; rest : -1 : bitstring |} ->
96 | Ok (`XCHG (0, ii), rest)
97 | | {| 0x2 : 4 ; i : 4; rest : -1 : bitstring |} ->
98 | Ok (`PUSH i, rest)
99 | | {| 0x3 : 4 ; i : 4; rest : -1 : bitstring |} ->
100 | Ok (`POP i, rest)
101 | | {|_|} as bits ->
102 | Error (`UnsupportedBitcode bits) )
103 | | Codepage cp ->
104 | Error (`UnsupportedCodepage cp)
105 |
--------------------------------------------------------------------------------
/tonvm/instr_stack_test.ml:
--------------------------------------------------------------------------------
1 | let%expect_test "NOP" =
2 | Tests.(execute "(NOP)" |> print_stack) ;
3 | [%expect {| () |}]
4 |
5 | let%expect_test "NOP encoding" =
6 | Tests.(encode_decode `NOP |> print) ;
7 | [%expect {| (Ok NOP) |}]
8 |
9 | let%expect_test "SWAP" =
10 | Tests.(execute "(NULL DUP ISNULL SWAP)" |> print_stack) ;
11 | [%expect {| (Null (Integer -1)) |}]
12 |
13 | let%expect_test "XCHG0" =
14 | Tests.(execute "(NULL DUP ISNULL (XCHG0 1))" |> print_stack) ;
15 | [%expect {| (Null (Integer -1)) |}]
16 |
17 | let%expect_test "XCHG" =
18 | Tests.(execute "(NULL DUP DUP ISNULL (XCHG (0 2)))" |> print_stack) ;
19 | [%expect {| (Null Null (Integer -1)) |}]
20 |
21 | let%expect_test "XCHG encoding" =
22 | Tests.(encode_decode (`XCHG (0, 1)) |> print) ;
23 | Tests.(encode_decode (`XCHG (1, 2)) |> print) ;
24 | Tests.(encode_decode (`XCHG (0, 100)) |> print) ;
25 | [%expect
26 | {|
27 | (Ok (XCHG (0 1)))
28 | (Ok (XCHG (1 2)))
29 | (Ok (XCHG (0 100))) |}]
30 |
31 | let%expect_test "DUP" =
32 | Tests.(execute "(NULL DUP)" |> print_stack) ;
33 | [%expect {| (Null Null) |}]
34 |
35 | let%expect_test "OVER" =
36 | Tests.(execute "(NULL DUP ISNULL OVER)" |> print_stack) ;
37 | [%expect {| (Null (Integer -1) Null) |}]
38 |
39 | let%expect_test "POP" =
40 | Tests.(execute "(NULL DUP ISNULL (POP 1))" |> print_stack) ;
41 | [%expect {| ((Integer -1)) |}]
42 |
43 | let%expect_test "POP encoding" =
44 | Tests.(encode_decode (`POP 1) |> print) ;
45 | [%expect {|
46 | (Ok (POP 1)) |}]
47 |
48 | let%expect_test "DROP" =
49 | Tests.(execute "(NULL DUP ISNULL DROP)" |> print_stack) ;
50 | [%expect {| (Null) |}]
51 |
52 | let%expect_test "NIP" =
53 | Tests.(execute "(NULL DUP ISNULL NIP)" |> print_stack) ;
54 | [%expect {| ((Integer -1)) |}]
55 |
56 | let%expect_test "PUSH" =
57 | Tests.(execute "(NULL (PUSH 0))" |> print_stack) ;
58 | [%expect {| (Null Null) |}]
59 |
60 | let%expect_test "PUSH encoding" =
61 | Tests.(encode_decode (`PUSH 1) |> print) ;
62 | [%expect {|
63 | (Ok (PUSH 1)) |}]
64 |
--------------------------------------------------------------------------------
/tonvm/tests.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Sexplib
3 |
4 | let execute program =
5 | let instrs = List.t_of_sexp Instr.t_of_sexp @@ Sexp.of_string program in
6 | Vm.execute (Vm.make ()) Instr.execute instrs
7 |
8 | let print_stack (vm : Vm.t) =
9 | List.sexp_of_t Vm.sexp_of_value vm.stack
10 | |> Sexplib.Sexp.pp_hum Caml.Format.std_formatter
11 |
12 | let encode_decode ?(cp = Cp.Codepage 0) instr =
13 | Instr.encode ~cp instr
14 | |> Result.bind ~f:(Instr.decode ~cp)
15 | |> Result.map ~f:fst
16 |
17 | let print instr =
18 | let f = Caml.Format.std_formatter in
19 | Sexplib.Sexp.pp_hum f
20 | @@ Result.sexp_of_t Instr.sexp_of_t Instr.sexp_of_error instr ;
21 | Caml.Format.pp_print_newline f ()
22 |
--------------------------------------------------------------------------------
/tonvm/tonvm.ml:
--------------------------------------------------------------------------------
1 | module Instr = Instr
2 | module VM = Vm
3 | module Codepage = Cp
4 |
--------------------------------------------------------------------------------
/tonvm/vm.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | exception StackUnderflow of int
4 |
5 | type cont = int [@@deriving sexp_of, sexp]
6 |
7 | type value =
8 | | Integer of Zint.t
9 | | Cell
10 | | Tuple
11 | | Null
12 | | Slice
13 | | Builder
14 | | Cont of cont
15 | [@@deriving sexp_of, sexp]
16 |
17 | type codepage = Stdint.int16
18 |
19 | type gas_limits =
20 | { current_gas_limit : int64;
21 | maximal_gas_limit : int64;
22 | remaining_gas : int64;
23 | gas_credit : int64 }
24 |
25 | let default_gas_limits =
26 | { current_gas_limit = 0L;
27 | maximal_gas_limit = 0L;
28 | remaining_gas = 0L;
29 | gas_credit = 0L }
30 |
31 | type t =
32 | { (* Stack *)
33 | stack : value list;
34 | (* Control registers *)
35 | c0 : value;
36 | c1 : value;
37 | c2 : value;
38 | c3 : value;
39 | c4 : value;
40 | c5 : value;
41 | c6 : value;
42 | c7 : value;
43 | c8 : value;
44 | c9 : value;
45 | c10 : value;
46 | c11 : value;
47 | c12 : value;
48 | c13 : value;
49 | c14 : value;
50 | c15 : value;
51 | (* Current continuation *)
52 | cc : cont;
53 | (* Current codepage *)
54 | cp : codepage;
55 | (* Gas limits *)
56 | gas : gas_limits }
57 |
58 | let push value vm = {vm with stack = value :: vm.stack}
59 |
60 | let pop vm =
61 | match vm.stack with
62 | | v :: stack ->
63 | (v, {vm with stack})
64 | | [] ->
65 | raise (StackUnderflow 0)
66 |
67 | let at vm x =
68 | match List.nth vm.stack x with
69 | | Some x' ->
70 | x'
71 | | None ->
72 | raise (StackUnderflow x)
73 |
74 | let interchange x y vm =
75 | let replace pos a l = List.mapi ~f:(fun i x -> if i = pos then a else x) l in
76 | match (List.nth vm.stack x, List.nth vm.stack y) with
77 | | Some x', Some y' ->
78 | {vm with stack = replace x y' vm.stack |> replace y x'}
79 | | None, _ ->
80 | raise (StackUnderflow x)
81 | | _, None ->
82 | raise (StackUnderflow y)
83 |
84 | let const_true = Integer (Z.of_int (-1))
85 |
86 | and const_false = Integer Z.zero
87 |
88 | let make ?(gas = default_gas_limits) () =
89 | { stack = [];
90 | c0 = Null;
91 | c1 = Null;
92 | c2 = Null;
93 | c3 = Null;
94 | c4 = Null;
95 | c5 = Null;
96 | c6 = Null;
97 | c7 = Null;
98 | c8 = Null;
99 | c9 = Null;
100 | c10 = Null;
101 | c11 = Null;
102 | c12 = Null;
103 | c13 = Null;
104 | c14 = Null;
105 | c15 = Null;
106 | cc = 0;
107 | cp = Stdint.Int16.zero;
108 | gas }
109 |
110 | let execute (vm : t) f instrs = List.fold instrs ~init:vm ~f
111 |
--------------------------------------------------------------------------------
/tonvm/zint.ml:
--------------------------------------------------------------------------------
1 | include Z
2 |
3 | let sexp_of_t z = Sexplib.Sexp.of_string @@ Z.to_string z
4 |
5 | let t_of_sexp s = Z.of_string @@ Sexplib.Sexp.to_string s
6 |
--------------------------------------------------------------------------------